← writing

The kernel rule (and why most CMSes break it)

One hard rule keeps the Squilla kernel honest. If removing an extension would leave dead code in core, that code was in the wrong place. Here is how it plays out in practice.

I have one rule I keep taping to the inside of my skull whenever I work on Squilla, and it goes like this.

If removing an extension would leave dead code in the kernel, that code was in the wrong place to begin with.

That is the whole rule. It fits on a sticky note. It sounds almost too obvious to be worth writing down. But once you start applying it honestly, you discover that most CMSes have been quietly breaking it for years, and the consequences show up in every codebase I have ever opened with more than five years on it.

How cores accrete

The way it usually happens is innocent. Someone needs image optimisation. There is no image extension yet, no plugin system mature enough to host it, and the feature is needed by Friday. So the helper goes into core. Just for now. We will move it later.

Then someone needs email template management. Same story. There is no email extension yet, and the templates need a home, so the templates table and the rendering helper land in core. Just for now.

Then SEO defaults. Then a comment moderator. Then a tiny shortcode parser someone wrote in an afternoon to ship a campaign. Each of these decisions, on its own, is defensible. Five years later, core is two hundred thousand lines that smell like twelve different features, all tangled together, none of them removable without a refactor nobody has time for.

I have been inside that codebase more than once. I do not want to build another one.

The Squilla stance

So Squilla goes the other way. Core only contains generic infrastructure. Things that multiple extensions could plausibly use, regardless of what kind of CMS you are building on top.

The event bus is in core, because anything could publish or subscribe to events. The filter chain is in core, because anything could register a filter. The capability check is in core, because every extension call needs to be guarded the same way. The extension HTTP proxy is in core, because every extension that wants to expose an admin endpoint goes through the same machinery. Auth, sessions, RBAC, content nodes, rendering, the CoreAPI itself. All generic. All plumbing.

And then everything else lives in an extension that owns it end to end.

Image optimisation? media-manager extension. WebP conversion, resizing, thumbnails, the whole pipeline. If you uninstall media-manager, the kernel does not lose the ability to render pages. It just loses the ability to process images, which is what you asked for when you uninstalled it.

Email templates? email-manager extension. Templates, rules, logs, the dispatcher that subscribes to events and matches them against admin-defined rules. The kernel can still send transactional mail through whichever provider is active, but the template layer, the rules engine, and the audit log are all owned by email-manager. Remove the extension, the templates table is gone, and core does not have a single line of code that wonders where the templates went.

The robots.txt route? seo-extension. It registers itself, owns the response, owns the AI crawler policy. Remove the extension, the route is gone. No dangling handler in core waiting for a config flag that nobody sets.

The test

The way I apply the rule in practice is a single question I ask about any chunk of code I am about to write or move. I look at it and ask, would removing this feature leave this code orphaned?

If yes, the code is specific to a feature, and it belongs in the extension that owns the feature. Even if that means more wiring. Even if it means an event subscription instead of a direct call. Even if it means inventing a new capability so the extension can do what it needs to do through the CoreAPI.

If no, and the code would still be useful to a different extension I have not built yet, then it is generic plumbing and it belongs in core.

The test is harder than it looks because there is always a tempting third answer, which is "well, lots of extensions probably want this, so it might as well be in core." That answer is wrong almost every time. The honest version is usually "one extension wants this right now, and I am too tired to build it properly." When I notice myself reaching for the third answer, that is the cue to stop and build it as a generic mechanism that the extension consumes, or to leave it inside the extension entirely.

Why the discipline matters

This is not an aesthetic preference. I am not doing this because I want the kernel to look elegant in a diagram. I am doing it because in five years I want the kernel to still be readable.

A kernel that has held the line on this rule stays small. It stays the same size, roughly, as it was on day one, give or take the slow growth of genuinely generic infrastructure. New features do not enlarge it. They enlarge their own extensions, and those extensions live in their own folders, with their own tests, their own migrations, and their own admin UIs. The kernel does not know they exist, and it does not need to.

That is the payoff. Not architectural purity for its own sake, but a kernel I can still understand cover to cover in an afternoon, years from now, when I have forgotten most of the details of how I built it.

The rule is annoying in the moment. It is worth it every other moment after.

Want more like this?

Occasional, opinionated, no listicles.
all writing →