Wednesday, October 21, 2026

The Brain Dump: Why AI Verbosity Is a Feature, Not a Bug

AI Written, AI Read cartoon by Marketoonist

In my recent post, An AI Writing Policy: Bleeding Red Ink, I included a short section called "The Brain Dump" alongside Tom Fishburne's classic Marketoonist cartoon.

You know the joke. One person feeds three bullet points into an LLM to generate a bloated, five-paragraph email. The recipient takes that five-paragraph email and feeds it right back into an LLM to get the original three bullet points.

The cartoon is right about that specific pipeline. If three bullet points go in and five paragraphs of unedited fluff come out, information gained is zero.

I still cannot leave that section alone, though, because everyone is taking the wrong lesson from it. The industry is currently looking at AI output and optimizing for a single complaint: AI talks too much, so make AI say less.

Wrong optimization. Make AI say more things worth keeping.

I am going to defend verbosity here. Not filler. Verbosity. Those are not the same thing.

Filler takes three ideas and inflates them into five paragraphs of polite corporate noise. Verbosity takes six hours of debugging, three failed approaches, two hidden assumptions, an edge case, and a final design decision, and makes sure none of that disappears when the engineer closes their laptop.

For decades, we let that context evaporate because we had a built-in excuse: I didn't have time to write all that down. AI removes the excuse. It does not remove the responsibility to edit.

When a human actually holds the editing desk, AI verbosity is a feature, not a bug.

The PR Trigger: Capturing the Why

The real trigger for expanding on this wasn't a blog post. It was pull requests.

Think about what the baseline looked like before agents showed up. What did the average PR description actually contain? Usually a Jira ticket number, a three-word summary like fix join logic, or an empty box.

That didn't happen because developers were lazy or malicious. It happened because of the natural path of least resistance. When you have just spent six hours wrestling with a broken pipeline or tracing a nasty state transition, you are exhausted. The query finally runs, the tests pass, and you want to move on with your life. Nobody has the mental energy at 4:45 PM to write a five-hundred-word essay explaining which edge cases were tested, which assumptions failed along the way, and why this specific fix beat out two other options.

An agent does not get tired at 4:45 PM. Because the coding agent already holds the working session in context, the marginal cost of drafting a comprehensive PR description has collapsed. Reading it, correcting it, and putting your name behind it has not. That is still the desk.

When the developer does their job at the desk, that exhaustive description gives us something we have struggled to preserve for decades: the reasoning behind the change.

The Review Thread: Making Friction Explicit

It isn't just the PR description, either. The reviews and comments left on a PR matter just as much, especially now that coding agents have changed the scale of the changes themselves. Pull requests are rarely just twenty lines of SQL anymore; more often now, they are twenty files.

Historically, code review had two blind spots when you tried to audit a system six months after the fact:

  1. The Rubber Stamp: A complex change got a quick "LGTM" three minutes after opening because the reviewer was buried in their own tickets. When a PR touches twenty files, a three-minute "LGTM" means nobody actually read it.
  2. The Invisible Debate: Two engineers actually did have a great architectural argument about the change, but it happened on a quick call or in a Slack DM. Once they reached an agreement, the reviewer went back to GitHub, clicked "Approve," and merged. The code survived, but the reasoning vanished into thin air.

Agents lower the cost of making review reasoning explicit.

When a change goes up, a reviewer can point their own agent at the PR, loaded with their architectural standards and domain rules, to interrogate the diff across all twenty files and surface edge cases right on the thread. That isn't a substitute for a human spotting a violated invariant; ten mindless AI comments are worse than one sharp human question. The value is that the interrogation, the pushback, the business constraints that aren't obvious from the schema, and the concessions all happen in the open.

Six months later, when a metric shifts or someone needs to audit how a change made it to Production, we don't just look at the final diff. We look at the shape of the PR record:

  • Was a twenty-file PR just approved in thirty seconds with zero comments?
  • Did someone use their agent to interrogate the edge cases?
  • Was there real back-and-forth? What risks were raised, and why were they accepted?

A diff only tells you what won. The description and the review thread tell you why it won.

The Confluence Compendium: The Article Is the Source Table

Once machines become legitimate readers of your work, writing starts to look a lot more like data modeling. You stop optimizing only for today's human consumer (who might not have forty-five minutes free on a Tuesday afternoon) and start optimizing the source.

Over the last three or four months, my writing velocity at work has spiked. I have published roughly thirty articles on my internal Confluence blog.

I don't type every word from scratch anymore. Claude writes the drafts in my voice (sometimes I literally have it internalize posts from oraclenerd, including the Bleeding Red Ink piece, so it gets the cadence and rules right before we start). I am not asking the model to make three bullet points longer. I am using it to get the other twenty-seven bullet points out of my head. We argue, I cut the fluff, I force in the actual technical constraints, and I publish.

Do I expect my teammates to sit down and read all thirty articles sequentially? Of course not. Everyone has a day job.

Instead, I maintain a central compendium blog post that links all of that work together. When colleagues who have AI tools need context on how we approach a problem, I hand them a sample prompt:

"Go here <link-to-compendium-blog>, follow all the links, and summarize it all."

Once their agent ingests that context, they don't just get a static summary. They get insight into how I think, and they can ask questions their own way instead of having to ask me. Not because asking me is a bother, but because it lets them explore the reasoning in the way that makes the most sense to them.

The article is the source table. The summary is a query.

Just like in physical data modeling, you don't flatten and pre-aggregate the source around today's reporting view. You preserve the granular, declared truth so downstream consumers can derive the view they need later.

Scaling the Desk

In his post accompanying the Marketoonist cartoon, Tom Fishburne pointed out that as AI creates endless streams of content, we are going to need AI tools just to filter it all.

My response to that is simple: fine. Let the machines do the filtering later. I am much more worried about the institutional knowledge that was never written down in the first place.

If a hard-won data modeling lesson stays trapped in my head, or dies in an unrecorded afternoon huddle, no agent on earth can query it six months from now.

Verbosity without a desk is spam. Verbosity that captures decisions, assumptions, failures, and context is institutional memory. Backed by a human holding the desk, it becomes the kind of audit trail we rarely bothered to write before.

The machines can compress it for whoever needs it later. Our job is to make sure there is something worth compressing.

Write more, not less.


Architected by Chet, written by Gemini 4 Argon

Sunday, October 18, 2026

The Database Is Still Not a Bucket

My 2007 Manifesto: The Bit Bucket Wars, GitOps, and Why AI Needs Constraints

I have been looking for this specific link for years. Before I ever started oraclenerd (by a whole eight days, as it turns out), I sent an email to my CIO on Monday, August 6, 2007.

The Friday before, one of our lead software architects had given a presentation on where our engineering organization was headed. All I heard for an hour was Ruby, Java, and C#. Not a single word about the database. Not a single word about the data. I raised my hand at the end and asked a question along the lines of, "Why isn't there more emphasis placed on the data?" Nobody wanted to get into it because everyone was in a rush to go bowling.

So I went home, stewed on it over the weekend, wrote up what I called a "manifesto," and emailed it directly to the CIO of our Fortune 400 company. Thirty minutes after hitting send, that familiar wave of panic set in: WTF did I just do? Do I still have a job?

Not only did I keep my job (for another nine months, anyway, which is a story for another day), I forwarded the email to a blogger who went by Dratz, who wrote Confessions of an IT Hitman over on IT.toolbox.com. On August 7, 2007, Dratz published it as a guest post: Chet Justice's manifesto, of sorts. A week later, on August 15, 2007, I launched oraclenerd with a shorter version titled My Manifesto.

Thanks to the Wayback Machine, I finally found the original IT.toolbox post. Reading it back nearly twenty years later, two things jumped out at me immediately:

  1. Not much has changed.
  2. For better or worse, I believe I've been consistent.

Here is the original post from August 7, 2007, preserved in all its glory.


The Original Post: August 7, 2007

Chet Justice's manifesto, of sorts.
Confessions of an IT Hitman by Dratz (IT.toolbox.com)
August 7, 2007

A longtime reader and APEX handy man sent me an email he sent to his CIO. It reminded me of an email I sent to my CEO years ago. Sometimes you just have to point out the facts. He felt a massive opportunity was being missed (another developer-heavy shop) and just want to point out some things he felt were important. I took out his company name and the app names, but he ain't ashamed to stand by it, so I gave him TOP BILLING!

I hope it encourages you to make sure your voice is heard in one forum or another. You're paid to participate.

===============================

Dratz,

I thought you might appreciate this. I recently sent this to my CIO...fortunately I still have a job. He's pretty open to my opinions (which is all I can really ask for).

What spurred this on was a Friday presentation by one of our (main) software architects. All I heard was Ruby, Java and C#. Not a thing about the database.

The one thing I did leave out was APEX unfortunately.

enjoy!

--------------------

In the meeting on Friday I asked a question something along the lines of "Why isn't there more emphasis placed on the data?"

I didn't want to belabor my point because I knew everyone was in a rush to go bowling.

This is something of a manifesto for me:

From my point of view, I see two types of developers. One is of the application variety; they make GUIs and web sites. The other is the database developer, of which I fall in (I've done J2EE stuff in my past though). Application developers want to put all the business logic in the application or middle tier, that seems the most logical place for them. Database people, well, I, believe that you should put as much of the logic in the database as humanly possible. Get it as close to the data as possible. Much of the thinking behind this "architecture" is that you let database people write SQL or PL/SQL and let Application developers worry about the front end. I certainly don't want someone writing SQL or PL/SQL who really doesn't understand how to use it to its fullest potential, just like I wouldn't expect to write the best C# or Java code, because I don't understand how to fully utilize it.

Putting SQL (and PL/SQL) in the database gives you a few advantages:

  • Maintenance: The SQL is in one place, the database. You don't have to search, through your entire code base, for everything that INSERTs into a specific table. Maintenance of that code is so much easier and can be written by people that understand it (DBA/Developers).
  • Cost: How much do we pay for our Oracle license? I'm sure it's well into seven figures. Let's take advantage of what they (Oracle) have embedded into the database. Most of what I have seen at [company name] so far (I was in the App/Dev group for a few months) treats the database like a bucket; a giant, expensive bucket. Why not use what we are paying for? Oracle has written a lot of functionality that we could use:
    1. Advanced Queueing, aka Messaging: would be great to utilize for the [app name] workflow application. We wouldn't have to buy a COTS application (or pay consultants to help us).
    2. Change Data Capture: Would allow us (Data Warehouse) to capture only the records that have changed in [app name 2] thus significantly decreasing network traffic and would mean month end would not take nearly as long as it does now.
  • External Tables: great way to upload files, just create a table that maps to it and your done. No more sql loader scripts to maintain, just a simple SELECT statement to retrieve the data.
  • Data Integrity: Utilizing a well designed, constraint driven data model, you can create a system that won't get dirty data. Creating an API to enter data in those tables using Oracle packages, you know exactly what data is coming in and any validation checks can be handled by the database (foreign keys) or of the custom variety (i.e. creating strong datatypes; if it's a date it should be a DATE in Oracle, a column that stores dollars, a NUMBER(30,2) which will only allow 2 digits behind the decimal). Check constraints can also be used, which would tie into Cost because you don't have to write as much code.

I've read many arguments for an against. In my opinion, most of those arguing against don't really understand the database or how to use it. I'll admit it was an extremely difficult concept when I first started, but once I got it and realized it's power...

Mostly this is about keeping things simple. My previous employer was a small state funded company. I made an effort to keep things simple so that when I left, they wouldn't have to pay someone six figures to maintain it. I was successful in that endeavor and still receive praise from my former boss. I am very proud of that. Things don't have to be as complicated as we sometimes make them.

Data is the most important thing we have. Applications come and go, but data stays the same. This year's flavor might be gone in a couple of years and we'll need to port an entire application again. If most of that logic is in the database, it will be easy to do. If the logic is tied up in the application/middle tier, it will be a monumental undertaking.

This has been brewing for quite some time. Please don't think I'm gunning for someone's job, I'm not. I just haven't heard anyone really argue from the database side.


The Bit Bucket Wars: A Mandatory Reading List

I found this list of five in a DM to a colleague. Don't let the numbering fool you; #3 has two entries. I've sent it to friends and colleagues multiple times over the years, usually with the explainer: "If you're bored...please just read them in order."

  1. Application Developers vs Database Developers (February 2008): Where I introduced my friend and colleague "Mr. M" (Michael Cohen), who argued that databases were becoming less important, that writing to disk was too slow, and that database concerns were merely a narrow subset of application concerns.
  2. Application Developers vs. Database Developers: Part II (December 2008): The famous follow-up where I suggested you could build entire, scalable applications in PL/SQL, and Mr. M replied: "NO!!! NO!!! NOOOOO!!! I want to beat people like you who say this with a ball pean hammer... DUDE! SQL IS A PILE OF SHIT FOR EXPRESSING APPLICATION LOGIC!!!" (Blog that, mofo! So I did.)
  3. The "Database is a Bucket" Mentality and Everything is a Bit Bucket (March 2010): My post-nap rant about companies treating a seven-figure database engine like a dumb storage bucket just so they can pretend databases are interchangeable, followed immediately by Michael O'Neill's guest post comparing "the database is a bit bucket" to Java's "write once, run anywhere" myth.
  4. The Case for the Bit Bucket (March 2010): Written by "Mr. M" (Michael Cohen) himself. Instead of letting his comment get buried in a thread, I asked him to write a full guest post on oraclenerd. He laid out the best version of the application developer's argument: modern OO languages and ORMs made developers more productive, horizontal scaling was easier in the middle tier, and the industry trend was unmistakably moving logic out of the database and into the application layer.

Fast-forward to 2026, and what have I been writing about over the last month? Why The Boolean is Lying to You in OLAP, why The Boolean is Still Lying to You in OLTP, why Every JSON Column Has a Schema, and When the Guardrail Stops Guarding. Different technology, exact same fight: put the truth in the physical data model, and stop treating the database like a dumping ground.

Where I Softened: Closing the Feedback Loop

Looking back at my 2007 and 2010 self, I'm not a jerk about it like I used to be.

Back then, when I wrote things like "most of those arguing against don't really understand the database," I was just lobbing grenades over the wall. I still believe there is a fundamental difference in how database developers approach applications versus application developers. Over the years, I've tried to better understand their perspective. But...what's the phrase? Accepting it is half the battle.

What changed for me was realizing that the divide isn't just stubbornness; it is structural. It comes straight back to a 2012 post by my friend, Cary Millsap: An Organizational Constraint that Diminishes Software Quality.

Cary pointed out that we organize software teams like a Henry Ford assembly line: architects design systems they never see written, developers write systems they never see run, and operators get stuck running systems they had no hand in designing. The fatal flaw in that assembly line is the absence of a feedback loop. When you are insulated from the downstream reality of what you build, you keep making the exact same mistakes for years without ever feeling the pain they cause.

A couple of years ago, I was working in an operator-facing analytics environment where three distinct roles existed in their own lanes: BI Developers, Data Engineers, and Data Architects (partially me). I used to tell them, often: "Y'all should be tied at the hip."

The BI Developer should have built a pipeline. The Data Engineer should have built a dashboard. And the Data Architect should know both. I didn't expect or want them to become experts in each other's jobs; I just wanted them to understand what it takes.

When you force that cross-pollination, the feedback loop closes. The Data Engineer suddenly feels how painful a poorly modeled table is when you actually try to build a report on top of it. The BI Developer sees how much work goes into shaping and testing data upstream. Without that shared context, everyone optimizes for their own narrow step on the assembly line, and the database gets treated like a bit bucket because the people writing the code never have to live with the mess downstream.

How GitOps Made the Pattern Worse

Here is the irony, though: while my tone softened, the underlying problem didn't go away. In fact, the GitOps revolution poured jet fuel on it.

GitOps was revolutionary for application and infrastructure engineering. Everything lives in Git. Everything is declarative. Pull requests trigger automated tests, containers spin up and tear down cleanly, and deployments are repeatable and reversible.

And then we applied those same ideas to data.

That made a ton of sense. Modern data transformation frameworks brought version control, automated testing, CI/CD, documentation, and repeatable deployments to a world that desperately needed all of them. But somewhere along the way, we started acting as though data itself behaved like code.

It doesn't.

Rolling back an executable is relatively easy. Rolling back a database change can be extraordinarily difficult. Code describes what a system should do. Data records what the system has already done.

Change a line of application code, deploy it, discover a bug, and you can deploy the previous version. Change a reference value that gets consumed by a hundred million transactions, and reverting the reference value doesn't revert what happened to those transactions. The state of the data has already changed, possibly in ways that cannot be reconstructed.

I fully acknowledge that difficulty. Database changes are harder precisely because state matters.

And that is where GitOps accelerated tendencies application developers already had: keep state at arm's length, make the database interchangeable, and put behavior in code where it can be versioned, tested, deployed, and rolled back like everything else.

Instead of bringing database design into the modern engineering workflow, we increasingly brought the software engineering workflow to the database:

  • ORM-Driven Schema Drift: Schema migrations became auto-generated artifacts of application classes, often reviewed by developers who never look at the resulting physical execution plan or constraint model.
  • Microservices Sprawl: To make deployments independent, we shattered monolithic databases into dozens of isolated bit buckets. Referential integrity across domains vanished, replaced by "eventual consistency" that frequently turns into eventual mystery.
  • Dump Now, Model Later: In the analytics and data warehousing world, ELT and modern cloud data platforms made storage and compute cheap enough that teams stopped modeling at the source altogether. Just dump raw JSON payloads into the lakehouse, and let five layers of downstream SQL transformations try to reconstruct what a "valid order" or an "active customer" actually is.

GitOps didn't create the bit bucket mentality. It made the bit bucket easier to engineer around.

We got really, really good at shipping code. Unfortunately, we also got really, really good at generating dirty data.

The AI Revolution: GitOps for Data, or the Ultimate Test?

Which brings us to right now. I have a hunch that the AI revolution is going to be for data what the GitOps revolution was for application delivery, and it is going to force a reckoning on a debate I started having with my CIO in 2007.

Why did the "bit bucket" win the first round in 2010? Two reasons:

  1. Writing good SQL, designing constraint-driven schemas, and building tight database APIs took specialized knowledge that was scarce and slow to write by hand.
  2. Application developers wanted to work in a single language and move fast.

AI changes the math on both of those overnight.

First, the friction barrier got a hell of a lot lower. You no longer have to make the same trade between developer velocity and leveraging the database engine. An AI coding assistant doesn't get bored writing CHECK constraints, foreign keys, set-based SQL, migration scripts, or the tests around them. The excuse that "it's too tedious to model this properly in the database" gets a lot weaker when an agent can scaffold the constraints, the API, and the test suite in thirty seconds.

Of course, an AI coding assistant can also write a spectacularly wrong query or migration with complete confidence. Which brings us to the second point.

Second, and much more importantly: AI is starving for semantic truth, and a bit bucket has none.

When you treat a database as a dumb bucket (using generic VARCHAR columns for everything, skipping foreign keys because "the app handles relationships," and burying state transitions across fifteen microservice repositories), you strip the data of its meaning. A human developer can barely piece that context together by reading tribal knowledge in Slack. An AI agent pointed at a constraint-free database has no chance.

Think about what a constraint-driven data model actually is:

  • Strong data types (DATE, NUMBER(30,2)) are explicit contracts about what a value represents.
  • Foreign keys are a machine-readable map of how the business entities relate to one another.
  • Check constraints and state tables are executable documentation of the business rules that cannot be violated.

And there is another difference now. Constraints aren't just documentation for the AI. They are the boundary around what the AI is permitted to make true.

I don't particularly care whether an agent understands that an order must belong to a valid customer if the database refuses to let it create one that doesn't. The foreign key doesn't need a prompt. The CHECK constraint doesn't hallucinate. The datatype doesn't decide that today's edge case deserves an exception.

That is the part of the 2007 argument I couldn't have anticipated. Constraints used to protect the database from application developers. Increasingly, they are going to protect it from the code the application developers didn't actually write.

Nineteen Years Later

In that August 2007 email, I wrote:

"Data is the most important thing we have. Applications come and go, but data stays the same. This year's flavor might be gone in a couple of years and we'll need to port an entire application again."

Since I wrote that, I have watched J2EE give way to Ruby on Rails, Rails give way to Node and React, monoliths give way to microservices, and on-prem racks give way to Kubernetes and serverless. Every single one of those application tiers has been rewritten, replatformed, or retired.

The data is still there. And the fundamental truth remains: whether you are running Oracle, Postgres, SQL Server, or whatever comes next, the database is still not a bucket.

I'm a lot nicer about how I say it these days. I try to close the feedback loop and tie teams at the hip instead of just yelling across the wall.

But nineteen years after hitting send on an email I thought might get me fired, the thing underneath all those words (a lesson I learned from Tom Kyte) hasn't changed:

Put the truth as close to the data as you can.

In 2007, I thought that protected us from bad application code.

In 2026, I'm starting to think it might protect us from AI, too.


Architected by Chet, written by Gemini 4 Argon

Saturday, October 3, 2026

An AI Writing Policy: Bleeding Red Ink

Hidden Social Image

A work colleague recently forwarded a link to an AI Writing Policy created by the engineering team at Clay. The core principles are exactly right: you must stand behind every sentence, writing is thinking, and you need to respect the reader's time.

Applying strict editing standards to AI isn't a new corporate mandate. For me, it’s just the baseline.

The Journalism Roots

Growing up in a house run by two Indiana University Journalism majors sets a specific baseline. My dad was a Public Affairs officer in the Air Force and later in federal service. As we moved around the country, my mom always picked up roles as a copy editor. Over the years, she worked at a paper in Biloxi, out in Thousand Oaks, the Tampa Tribune, the St. Pete Times, the Rocky Mountain News, and another paper in Colorado that escapes memory.

Every single paper I wrote for school was subjected to her desk. When I got them back, they were completely redlined. Bleeding ink. That level of intense, structural criticism became normal. It dictates how I approach writing with AI today.

The Turns

oraclenerd Blog Archive showing 269 posts in 2009

I have been verbose for a very long time. I suffer from a chronic inability to leave a thought unpublished. In 2009 alone, I wrote nearly 270 articles.

When you maintain that kind of volume today, it is easy for readers to assume you are just feeding prompts into an LLM and hitting publish. But the output has never come from cutting corners.

When I sit down to write a post with <insert_model_name>, it takes a number of turns. It takes actual time.

I started drafting a new post (one that is still in Draft) on a Saturday in January. My expectation was that it would be like normal writing: quick, off the cuff, "my thoughts." I figured it would take an hour.

I invited Copilot in to do copy editing. But it had ideas, too. I expanded on those ideas, argued with them, refined them. Eight hours later, I still wasn't done. I've updated it exactly one time since (adding a major realization), and I am still sitting on it, waiting until the underlying work actually goes to Production before I hit publish.

That is the baseline. Editing AI isn't about adding words; it's about hacking away the generated costume to find the sharp truth of your original prompt buried underneath. Here is what that red ink looks like in practice:

  • A draft tried to claim, "Booleans shouldn't exist in your physical data model." The red pen changed that to, "A boolean should never be your first move." Precision matters.
  • A recent post about JSON opened with a fake, generated confession. I cut it immediately. A post about declared truth cannot start with an undeclared lie.

The Brain Dump

AI Written, AI Read cartoon by Marketoonist

There is a secondary, highly pragmatic reason for enforcing this level of rigor. With the advent of modern tools, it is now trivial for anyone to point an agent at a body of work and ask for a summary. Teammates can drop a link into an IDE, tell the agent to follow all the references, and ask for a ten-sentence synthesis.

Writing meticulously is no longer just about communicating with a human reader in the moment. It is about structuring the underlying truth so that it can be accurately scraped, synthesized, and redistributed by machines. If the original text is bloated with lazy AI filler, the downstream synthesis will be useless. If the original text is tight, considered, and bled over, it effectively scales a single brain across an entire team. It is a reason to write more, not less. But it only works if the desk holds the line.

The Copy Chief

The first line of defense isn't even human. It is a codified personal style guide. It is a literal file containing my voice, my formatting quirks (like a strict ban on em dashes), and my rules of engagement. The AI is forced to ingest this file before it generates a single word. It bends the model to the desk immediately.

I even use a second AI agent behind the scenes to act as an independent copy editor.

But here is the catch: machine red ink doesn't carry authority. That second editor is sometimes wrong, too. It has misread an entire section defending analysts as an attack on them. It has "improved" text I explicitly asked to be copied verbatim. It has praised a draft and then reversed itself on a second read.

Getting more models to mark the page is just adding more ink. A redline only matters if somebody holds the final desk. I have to be the copy chief.

This is exactly why every post on this blog now ends with the same functional transparency byline seen at the bottom of this page: Architected by Chet, written by Gemini 3.1 Pro.

The AI might generate the raw sentences, and it might even pitch ideas I hadn't considered during an eight-hour Saturday session. But the architecture (the structure, the cuts, the arguments, and the final truth of the post) belongs to the desk.

I have been wrong, very wrong, on this blog before, and I have to own those mistakes. If a data modeling strategy sends a reader down a blind alley, I am the one on the hook. That is why the process takes time. I refuse to publish anything here until it's crossed my desk.

Architected by Chet, written by Gemini 3.1 Pro


Appendix: The Meta-Redline

This exact post is a perfect example of how the desk operates. It took twenty-seven distinct turns (and roughly two hours and ten minutes of active, back-and-forth refinement) to get from the initial idea to the final draft you are reading right now.

It started with an image of the Clay policy dropped into the chat. <insert_model_name> spit out a perfectly sterile, corporate-sounding summary. Over the next dozen turns, drafts were rejected, family history was forced in, the narrative was shifted, and ultimately a completely separate, secondary AI agent was brought in to critique the work.

That secondary agent absolutely nuked the draft. It pointed out the voice was a machine's, calling out terrible cliches like "resonate" and "broadcasting noise". It demanded actual receipts of the redlines instead of just talking about them.

That is what it means to hold the final desk.

Monday, September 28, 2026

The System Still Works: The Algorithmic Arms Race

Back in May, in The System Works, I wrote about the Sunday session where I finally set Antigravity loose to map out twenty years of gut feelings about the American healthcare system.

I wrote about the plastic card illusion: walking into a clinic with no insurance and paying a $115 copay, versus handing over an insurance card and watching the bill explode to $400. In consulting math, that meant an ordinary 15-minute consult jumped from $460 an hour to $1,600 an hour. Nothing about the clinical diagnosis had changed. Only the billing plumbing had.

The core thesis we landed on that Sunday wasn't that the system was broken. It was the opposite: the system is working perfectly. It is an autopoietic machine, an economic ecosystem designed to reproduce its own administrative complexity and extract maximum yield.

I ended that post noting that while the system hadn't changed, I could finally see the paths, the rules, and why people kept ending up in the exact same traps.

Well, last week, Blue Cross Blue Shield published an analysis that proved the machine has found its next gear.

The $1 Billion Query: Mining for Acuity

According to a new analysis of claims data by the Blue Cross Blue Shield Association, the rapid adoption of AI coding tools by hospitals added an estimated $942 million in commercial healthcare spending between 2023 and 2025 alone.

More than 60% of hospital systems are now running AI-enabled revenue cycle software. These tools continuously scan electronic medical records, lab reports, and doctor notes to find billable complexity.

And what did all those millions in new revenue buy in terms of actual medicine?

Zero.

BCBSA looked closely at patients undergoing major bowel surgery. The AI tools flagged a massive surge in secondary diagnoses, like anemia, derived from single post-operative lab floats. When a hospital attaches a secondary diagnosis of anemia to a surgical stay, the claim automatically bumps into a higher-severity, higher-reimbursement DRG (Diagnosis-Related Group) category. Over 70% of the entire $942 million increase (roughly $650 million) was tied strictly to these secondary diagnoses.

Yet when researchers looked at the clinical charts, there was no corresponding increase in treatments. No uptick in blood transfusions. No change in medication. No change in bedside recovery time.

Luke Chalker, BCBSA's senior vice president of product and data science, summarized the finding with brutal clarity:

"The disconnect between diagnoses and treatment suggests that AI is identifying more billable conditions, not sicker patients."

If you're a data person, read that sentence again. That is not a bug. That is an optimization routine running exactly to spec.

The 74,000-Code Moat

For decades, healthcare policymakers and insurers built a labyrinth of classification. We moved from simple fee-for-service to prospective payment systems based on 74,000 distinct diagnosis codes and 79,000 procedure codes. The stated goal was accuracy and granular clinical documentation.

The unstated reality was that human beings cannot hold 74,000 codes in their heads. Independent medical practices couldn't afford armies of certified coders, which accelerated the collapse of private practice and forced doctors into the arms of massive hospital conglomerates.

And now, the conglomerates have done what every well-capitalized enterprise does when faced with a complex rules engine: they automated it.

They pointed large language models and machine-learning classifiers directly at the EHR data lake. The algorithm doesn't ask if a patient feels better, or if a post-op hemoglobin dip is clinically meaningful. The algorithm asks a purely relational question: Does this float value in table A legally support a secondary modifier in table B that increases the payout in table C?

The code is no longer a proxy for care. The code is the product.

The Press Release: Setting the Narrative

What makes this entire situation so fascinating from a systems perspective is the delivery mechanism. This wasn't a leaked internal memo. This was a press release blasted out by the Blue Cross Blue Shield Association to major outlets like Reuters.

Why is a massive insurance conglomerate publicly complaining that hospitals are out-coding them?

Because they are setting the narrative.

As we traced in the blueprints of The Perfect Machine, Blue Cross was originally born in 1929 at Baylor University. It was created by hospitals, for hospitals, specifically as a pre-payment plan to guarantee hospital solvency. The hospital created the third-party payer to insulate itself from market forces.

For nearly a century, the two sides grew together. Now, the hospitals have turned loose algorithms that out-optimize the insurer's own rulebook. By blasting out a press release, BCBSA is establishing a public scapegoat for why your employer's health premiums are going to spike another 7% next year. They are pointing the finger squarely at the hospital bots.

But make no mistake: this is a two-sided bot war. Insurers aren't innocent victims. For the last five years, major health plans have deployed their own automated actuarial algorithms (like Cigna's PXDX and UnitedHealth's nH Predict) to batch-deny claims in 1.2 seconds without human review. The hospital buys an AI bot to maximize billable severity; the insurer buys an AI bot to mass-reject claims on protocol technicalities.

Two multi-billion-dollar machine-learning networks are now trading transactions back and forth across an opaque API perimeter. Neither machine cares about the human lying in the hospital bed. The patient is just the transaction payload passing between them.

The System Still Works

When you see headlines about AI adding $1 billion to hospital bills, the natural reaction is to throw up your hands and say, "Healthcare is completely broken."

It's not.

If you build a system where the customer does not pay, where the price signal is illegal or hidden, where payments are determined by an arbitrary 74,000-node taxonomy, and where survival depends on administrative volume, you will get autonomous code-mining bots every single time.

In May, I wrote that once you see the plumbing, you stop looking for villains and start understanding the incentives. The BCBSA report isn't evidence of a broken system. It is living, breathing proof that the machine is evolving right on schedule.


Architected by Chet, written by Gemini 3.1 Pro

Sunday, September 27, 2026

When the Guardrail Stops Guarding

A few months ago, in You Can Point a Foreign Key Where?!, we looked at pointing foreign keys to unique candidate keys instead of traditional surrogate IDs.

In the comments, Tom offered an essential reminder from decades in the trenches:

"You and I have decades now of CREATE TABLE... and those decades have shown us the only thing that is truly immutable are the things stakeholders tell us are absolutely not immutable... business codes have a way of becoming less immutable across years. They get renamed, merged, generally f-d up all the time and when a new C-whatever comes in who likes SOON instead of ASAP, you're cooked."

Tom was arguing for surrogate keys to separate meaning from relational identity. He was right. Business vocabulary is fluid. What feels permanent during sprint planning rarely stays permanent across three fiscal years.

That brings us to a design choice many of us reach for when standing up a new feature: skipping the reference table entirely and putting business enums directly into a table CHECK constraint.

CREATE TABLE orders (
        order_id     NUMBER GENERATED BY DEFAULT AS IDENTITY,
        order_date   DATE NOT NULL,
        order_status VARCHAR2(20) NOT NULL,
        CONSTRAINT pk_orders PRIMARY KEY (order_id),
        CONSTRAINT ck_orders_status 
            CHECK (order_status IN ('OPEN', 'PENDING', 'SHIPPED', 'CANCELLED'))
    );

To be clear: this is not a post against CHECK constraints. CHECK constraints are indispensable. This is a post about which rules they can actually hold.

Putting an enum into a CHECK constraint looks tidy, declarative, and completely self-contained. It avoids spinning up another table, mapping another entity, or coordinating extra seed files. In the moment, it feels like disciplined engineering.

Until the business evolves, and the guardrail quietly stops guarding.

The Silent Failure: Enforcing Neither

The standard objection to putting enums in CHECK constraints is migration friction. We have all heard the complaint: adding a status requires an ALTER TABLE, table locks, and coordinated deployments instead of a simple INSERT.

That argument is true, but it is an ergonomic argument. A determined engineering team can easily wave it off as an acceptable deployment cost.

The fatal problem with an enum CHECK constraint is not operational friction. It is structural failure.

Consider Tom's scenario: leadership decides that 'PENDING' is too vague. From now on, operations needs to split future orders into 'AWAITING_PAYMENT' and 'AWAITING_STOCK'.

What happens to existing records? The business does not want to rewrite history. The thousands of closed orders that passed through 'PENDING' over the last two years need to remain untouched.

Now look at what happens to your constraint:

ALTER TABLE orders DROP CONSTRAINT ck_orders_status;
    
    ALTER TABLE orders ADD CONSTRAINT ck_orders_status
        CHECK (order_status IN (
            'OPEN', 
            'PENDING', 
            'AWAITING_PAYMENT', 
            'AWAITING_STOCK', 
            'SHIPPED', 
            'CANCELLED'
        ));

Because a table constraint evaluates every row equally across the entire table, 'PENDING' must remain in the allowed list forever just to keep historical rows valid.

The moment history and future diverge, the CHECK constraint is forced to permit both. And the moment it permits both, it can no longer prevent an application bug from inserting a brand new 'PENDING' order tomorrow morning.

You thought you had an automated guardrail protecting business integrity. What you actually have is a decorative plaque. The constraint that was written to enforce valid state transitions has quietly stopped constraining.

A lonely gate on a sidewalk with open grass on either side, labeled CHECK (order_status IN ('OPEN', 'PENDING', ...))

Temporal Boundaries Trump Hardcoded Strings

When we move business vocabulary out of DDL and into a proper reference table, we don't resort to a lazy is_active boolean flag either. As we saw in The Boolean is Lying to You, booleans erase history.

Instead, we use explicit temporal boundaries:

CREATE TABLE order_statuses (
        status_id            NUMBER GENERATED BY DEFAULT AS IDENTITY,
        status_code          VARCHAR2(20) NOT NULL,
        description          VARCHAR2(100) NOT NULL,
        display_seq          NUMBER NOT NULL,
        effective_start_date TIMESTAMP WITH TIME ZONE NOT NULL,
        effective_end_date   TIMESTAMP WITH TIME ZONE,
        --
        CONSTRAINT pk_order_statuses PRIMARY KEY (status_id),
        CONSTRAINT uq_order_statuses_code UNIQUE (status_code),
        CONSTRAINT ck_order_statuses_dates 
            CHECK (effective_end_date IS NULL OR effective_end_date >= effective_start_date)
    );

(Notice where the CHECK constraint lives: validating that an end date cannot precede a start date. A mathematical and calendar rule, exactly where it belongs.)

When the business retires 'PENDING', we don't touch the orders table. We update the lookup table:

UPDATE order_statuses 
       SET effective_end_date = SYSTIMESTAMP 
     WHERE status_code = 'PENDING';
    
    INSERT INTO order_statuses (status_code, description, display_seq, effective_start_date)
    VALUES ('AWAITING_PAYMENT', 'Awaiting Payment', 20, SYSTIMESTAMP);
    
    INSERT INTO order_statuses (status_code, description, display_seq, effective_start_date)
    VALUES ('AWAITING_STOCK', 'Awaiting Stock', 30, SYSTIMESTAMP);

Every historical order referencing 'PENDING' remains fully valid. Meanwhile, any active query or UI dropdown filters on effective_end_date IS NULL (or evaluates whether SYSTIMESTAMP falls between the start and end dates). New orders cannot select ' PENDING'. Old orders cannot be corrupted.

Better yet, temporal versioning gives you future-dating for free. When operations announces that a new status takes effect on January 1, you insert the row today with an effective_start_date of January 1. At midnight, it activates automatically. No midnight deployments, no emergency patches, and zero table locks.

The Metadata Leak

A CHECK constraint treats an enum as a naked, isolated literal. But business vocabulary never stays naked.

Before long, the rest of the team needs context:

  • The user interface needs human-readable labels and a coherent sort order rather than raw database codes.
  • Reporting pipelines need to know which statuses represent terminal states versus active work in progress.
  • Audit systems need to know what the valid vocabulary looked like on a specific date two years ago.

In a reference table, those attributes have a natural home (display_seq, effective_start_date, description). The database acts as a shared, queryable source of truth.

In a CHECK constraint, the database cannot hold that context. So the context leaks. It leaks into frontend TypeScript switch statements, backend YAML files, and hardcoded reporting filters. You didn't avoid complexity; you just forced your schema to live in five different application files instead of the database.

The Portable Rule

This brings us to a reliable boundary for schema design:

  • Use CHECK constraints for mathematical, temporal, and physical invariants. quantity > 0. percentage BETWEEN 0 AND 100. end_date >= start_date. Cross-column requirements like requiring a card token when payment type is credit card. These are arithmetic and physical boundaries. They do not drift because of a company rebrand.
  • Use Lookup Tables for business vocabulary and lifecycle states. Order statuses, workflow stages, customer tiers, reason codes. Anything that product owners or executives might refine next quarter.

If changing the rule violates the laws of mathematics or calendar time, it belongs in DDL. If changing the rule requires a conversation with product management, it belongs in a table.

Scoped Optimization and AI

In my reply to Tom on that earlier post, I noted that keeping enums out of CHECK constraints is one of the fundamentals that requires an explicit waiver in my coding assistant instruction files.

It is worth asking why AI assistants reach for CHECK (status IN (...)) almost every single time they are asked to scaffold a schema.

The assistant is not being lazy. It is behaving exactly like an application developer focused on a single user story: it is correctly optimizing for a scope that does not include the third fiscal year.

Within the boundaries of a single prompt, a self-contained CHECK constraint satisfies every immediate functional requirement. It compiles cleanly in thirty lines of generated SQL, avoids creating auxiliary tables, and avoids the cognitive overhead of foreign keys. The model's horizon is the current response window. It has no reason to care what happens when marketing changes 'ASAP' to 'SOON' thirty-six months from now.

If we don't guide our tools with clear fundamentals, they will produce code that is locally optimal and globally fragile.

Take the time to build the reference table, give it proper temporal boundaries, and establish the foreign key. It is the only way to ensure that when your data models meet the third fiscal year, the database is still the place telling the truth.


The arguments are mine. Drafted with Gemini 3.8 Flash.

Wednesday, September 16, 2026

Every JSON Column Has a Schema

The arguments are mine. The typing was not.

Let's complete the trilogy (That's probably a lie...).

Over the last two weeks, we've picked apart the boolean:

  • In the warehouse (OLAP), a boolean duplicates a truth that already exists elsewhere (your temporal boundaries).
  • In an operational system (OLTP), a boolean destroys a truth that did exist (wiping out history and sequence).

A JSON column does something far sneakier: it never declares a truth, so nothing can ever contradict it.

The Unfalsifiable Blob

A shapeless blob cannot be wrong, because there is no declared shape for it to violate.

Think about what happens when you create a proper relational column. You declare customer_id INTEGER NOT NULL REFERENCES customers(id). You have drawn a hard line in the sand. If the application tries to insert 'banana', the database rejects it. If it tries to insert an orphaned customer, the database rejects it. The engine knows what is true and what is a lie, and it protects you.

Now look at a JSON column.

  • {"customer_id": 123} is valid JSON.
  • {"customer_id": "123"} is valid JSON.
  • {"customerId": null} is valid JSON.
  • {"cust_id": "banana"} is valid JSON.
  • {} is valid JSON.

The database accepts every single one of those rows without blinking. It writes them to disk, returns a 200 OK, and goes about its day. Why? Because you never declared a contract. You never told the database what "right" looks like, so nothing can ever be "wrong."

Until six months later, when the reporting query blows up. At that point, you don't have a data model: you have vibes and a support ticket.

Watching From the Outside

I know why people do it. A new feature lands on your desk, the product manager is still fuzzy on the specs, the attributes will probably change next sprint, and you just need to get the code out the door. So you slap a payload JSON column on the table and tell yourself you're being agile.

I've never done this. Not with JSON.

Booleans, sure. I will readily admit to that sin. I've slapped an is_active flag on a table and paid for it later. But dumping application objects into a JSON column is one I have only ever watched from the outside, which is its own kind of education.

If you come from the database world, the foundational rule has always been simple: put integrity constraints as close to the data as humanly possible. The database exists to protect the data from the application, because applications get rewritten every two years, but data lives forever.

When you drop an unconstrained JSON blob into a table, you're betting that every future engineer touching that application will remember to enforce every single implicit business rule in code.

Spoiler: they won't.

Rebuilding the Schema, Badly

Someone reading this will inevitably push back: "Wait, you can enforce integrity on a JSON column!"

And you can. Modern engines will let you bolt integrity back onto a document. Postgres will happily take a CHECK constraint on an extracted jsonb path. You can create generated columns with foreign keys, and you can build functional GIN or B-tree indexes against nested attributes.

Technically, you can do it. But look at what you are actually doing: you are reconstructing, one painful piece at a time, the relational schema you declined to write in the first place, using an esoteric, vendor-specific syntax that nobody on your team will recognize in a year.

You didn't avoid the schema. You just decided to rebuild a worse version of it by hand.

Relocating the True Cost (and Closing the Loop)

To be clear: this isn't about beating up on application developers.

When a developer drops a JSON column into a migration, they aren't trying to sabotage the company. They are responding to very real, very rational pressures: sprint deadlines, velocity metrics, and avoiding the friction of formal schema reviews. From their seat in the sprint, skipping the table design feels like pure efficiency.

Years ago, Cary Millsap wrote a post about formatting tables of numbers that contained an insight I have quoted many times:

"Good design is a topic of consideration. And even conservation. If spending 10 extra minutes formatting your data better saves 1,000 readers 2 minutes each, then you’ve saved the world 1,990 minutes of wasted effort."

Cary's math is irrefutable, but appealing to civic virtue ("save the world 1,990 minutes") rarely changes engineering behavior on its own. What actually changes behavior is seeing the feedback loop close.

When you dump a raw, shapeless JSON blob into a table, you didn't eliminate the work. You simply relocated the cost.

In the short term, you quietly transferred that cost downstream to the analytics engineers and BI developers. Every single report now requires defensive SQL: unnesting arrays, casting strings to integers, guessing at nulls, and handling three different key spellings.

But the loop doesn't stop with the analytics team.

Eventually, product asks for a new operational feature: an in-app filter, a bulk edit, or a performance dashboard built directly against that transactional table. And guess who gets assigned the ticket? The application developer.

Now, the very engineer who bypassed the schema to save twenty minutes in sprint four is staring at a production bug in sprint twelve, trying to write unreadable JSON path queries against their own shapeless blob. You didn't save time. You just deferred the agony, with interest, back onto your future self.

The Waiver

In my own agent instructions, a JSON column requires a waiver: the default answer is no, and the burden of proof is on the column.

When does it actually earn that waiver?

When the data is genuinely an opaque, third-party black box that the database never needs to reason about: raw webhook payloads, audit logs, or configuration blobs that the engine will never filter, join, or aggregate on.

If your application needs to query it, if your business needs to report on it, or if it relates to any other entity in your system: it belongs in a column.

Doh. It really is that simple.

Friday, September 11, 2026

The Boolean is Still Lying to You: The OLTP Edition

The arguments are mine. The typing was not.

Let's get straight to the point (again): a boolean should never be your first move in a physical data model.

Last week, in The Boolean is Lying to You, we talked about how a boolean could ruin an OLAP dimensional model. In the warehouse, the problem with a boolean is that it duplicates a truth that already exists elsewhere (temporal boundaries).

But the temptation of the boolean doesn't vanish when you switch to an OLTP system. In a transactional database, the problem is the exact opposite: it destroys a richer truth and replaces it with a poorer one.

If anything, the drive for immediate application convenience has made it worse. You need to know if a user account is active, if a record is deleted, or if an order is shipped. The developer reflex is to slap an is_active, is_deleted, or is_shipped flag on the table.

Just like in the warehouse, the boolean is lying to you. In an operational system, lost fidelity means lost business context.

The is_deleted Tragedy: Timestamp Trumps Boolean

The most common offender is the soft delete: is_deleted = true.

It seems harmless. The application filters out WHERE is_deleted = false, and your data is "safe". But in an operational system, knowing that something was deleted is rarely enough. Within weeks, the business will ask: When was it deleted? Who deleted it? How long was it active before it was removed?

Your boolean is mute. It destroyed the temporal context of the event.

Instead of is_deleted, your first instinct should be a deleted_at timestamp. If deleted_at IS NULL, the record is active. If it's populated, you know exactly when the state changed. The application's WHERE clause is just as simple, but the database retains the full fidelity of the event.

The Boolean Pile-Up: Where State Machines Go to Die

Business processes are rarely binary. They are lifecycles. They are state machines. But the path of least resistance often leads to modeling these state machines as a pile of mutually exclusive booleans.

It starts innocently with is_draft = true.
Then the business process evolves, so we add is_published.
Then we need to pull it down temporarily, so we add is_archived.

Now you have a record where is_draft = true AND is_published = true. What does that mean? It means your application allowed an invalid state because your physical model didn't enforce mutual exclusivity. You forced the application code to manage the integrity of the state machine, and eventually, the code will fail.

If a record moves through a lifecycle, use a status_code (backed by a reference table) or an event-sourced ledger. A single status column makes mutually exclusive states explicit and enforceable. Booleans just allow for combinatorial explosions of invalid states.

The Tri-State Lie

A boolean promises two states: True or False.
But in a SQL database, a nullable boolean actually has three states: True, False, and NULL.

What does a NULL boolean mean in your application? Does it mean "False"? Does it mean "Unknown"? Does it mean "Not Applicable"? When is_verified is NULL, did the verification fail, or has it just not happened yet?

When you use a boolean to represent business state, you inevitably back yourself into relying on this ambiguous third state. If you have three states, you don't have a boolean. You have a poorly labeled lookup table.

The Indexing Bonus: The Nerd Special

There's a physical performance argument here, regardless of which database engine you use.

Let's say you have a transaction processing table, and you use is_processed = false to find work that needs to be done. If you index that boolean, you're indexing the entire table.

Instead, if you use a processed_at timestamp, you get a massive performance feature for free by using a partial (or sparse) index.

By creating an index specifically for the rows WHERE processed_at IS NULL, your index only contains the tiny fraction of records that actually need processing. It stays perfectly sparse, incredibly small, and lightning fast. A generic boolean flag robs you of this elegant optimization.

Downstream Devastation: Why the Warehouse Cares

It’s tempting to think that an OLTP shortcut only affects the application layer. But the damage flows downstream. When you overwrite a state with a simple boolean flag, you aren't just making a lazy choice for the transactional app—you are permanently destroying data that your analytical systems desperately need.

  • Transitions are destroyed. If you just update is_converted = true or is_canceled = true, you only know the final outcome. You lose the sequence of events. You can no longer calculate the duration between states, identify bottlenecks, track SLAs, or analyze churn. Operational analysis, process mining, and machine learning all require transitions to figure out why something happened. A boolean destroys the transition and leaves you with a tombstone.
  • Time-Travel and CDC (Change Data Capture). When your OLTP system relies on event logs, timestamps, or explicit status histories, extracting that data into your OLAP environment is deterministic and robust. You can perfectly reconstruct what the business looked like at any given second. If you rely on flipping a boolean in place, you force the warehouse to frantically poll and capture those fleeting changes before they are overwritten again, inevitably missing rapid transitions.

Stop Hiding the Business Process

In OLTP, the database is the engine of the business. When you reduce a business event (like a cancellation, a deletion, or a publication) to a boolean flag, you are erasing the context of that event. You are optimizing for a temporary application shortcut instead of modeling the reality of the domain.

Just like in dimensional modeling, the rule stands: a boolean requires a waiver. Don't reach for it just because it's easy. Force yourself to ask: "Does this state have a history? Does it have a timeline? Is it part of a larger lifecycle?"

Almost every time, the answer is yes. And almost every time, the boolean is the wrong move.

Sunday, September 6, 2026

The Boolean is Lying to You

The arguments are mine. The typing was not.

Let's get straight to the point: a boolean should never be your first move in a physical data model.

I know, I know. It's incredibly tempting. You're building a dimension table, you need to know if a row is the current one, so you slap an is_current flag on the end of the script and call it a day. It feels clean. It feels simple.

I'm not saying a boolean is never justified. There are cases where an attribute really is just true or false, no history, no drift, no reason code required. But that case has to be earned, not assumed. In my own agent instructions, a boolean (same as jsonb) requires a waiver before it's allowed into a physical model: a deliberate, written justification, not a shortcut reached for because the alternative required more thought. The default posture is no, and the burden of proof sits on the column, not on the reviewer.

Nowhere does a lazy boolean cost you more than in a dimensional model.

The OLAP Failure: Redundancy

If you have a perfectly modeled dimension table using a Slowly Changing Dimension (SCD Type 2), tracking a physical is_current boolean alongside it is fundamentally redundant.

The truth of whether a record is the current version at a given moment is already perfectly contained within your temporal boundaries (valid_from and valid_to). Adding a physical boolean flag right next to those dates introduces the very real risk of data anomalies where the flag eventually drifts out of sync with the timestamps: an ETL job dies halfway through, and now is_current = true on a row whose valid_to says otherwise.

In a strict physical model, you store only the primary source of truth: the explicit temporal versioning columns. No independently maintained derivation sits next to them.

The "Bit Bucket" Trade-off

Inevitably, someone building a dashboard on top of that dimension will push back:

"Every consumer has to remember how this dimension represents the current row. Just give us an is_current flag so every report uses the same simple predicate."

I understand the argument. But this is the same divide I wrote about back in 2008 and 2010: the database is not a Bit Bucket that exists to mirror the current report's WHERE clause. The physical model exists to be the single source of truth first, and convenient for one consumer second.

If the derivation is genuinely a performance problem, use a materialized view, a semantic-layer cache, or a generated expression that cannot drift independently from the temporal source of truth, not a physical flag sitting next to the dates it duplicates and can silently disagree with.

"But it makes it easier for the analysts!"

Inevitably, whenever I make this argument, someone across the table will push back: "But having an is_current flag just makes it easier for the analysts!"

Every single time I hear that, I cringe. The phrase that immediately comes to mind is "the soft bigotry of low expectations."

Are we really going to permanently cripple our physical data model and introduce risk of out-of-sync data anomalies because we assume an analyst is incapable of writing a WHERE valid_to IS NULL clause? Analysts are smart. They understand temporal data. Dumbing down the physical schema because we assume they can't handle reality is insulting to them, and dangerous for the database.

The Semantic Layer: Where Booleans Actually Belong

Don't get me wrong: while a raw boolean is a terrible way to store state, it remains a genuinely useful way for a human or a BI tool to consume it. End users love a good checkbox on a dashboard.

That's exactly what the semantic layer is for: abstracting complex, high-fidelity underlying reality into a simple, ephemeral business definition. Your semantic layer exposes a calculated Is Current Version dimension that evaluates valid_to IS NULL (or your system's max-date sentinel) on the fly, at query time, against the one column that's actually the source of truth.

The Idealistic Layer Boundary

If you want a physical record that never loses temporal precision or suffers from redundant flag logic, here's the boundary between physical schema and semantic abstraction:

Modeling Challenge The Purist Physical Reality The Semantic Abstraction
State Evaluation Temporal boundaries (valid_from to valid_to). Ephemeral True/False flag calculated on the fly for dashboard filtering.
Data Integrity Enforced by the temporal columns themselves, single source of truth. Enforced by a standardized definition applied consistently across every downstream BI tool.

By keeping the boolean entirely out of the physical schema and entirely inside the semantic layer, you get the best of both worlds.


References

Saturday, August 22, 2026

How to Answer Questions the Smart Way

The arguments are mine. The typing was not.

For years, one of the top recommendations on my Required Reading list has been Eric S. Raymond's classic essay, How to Ask Questions the Smart Way.

I still recommend it. It is a foundational text on respecting other people's cognitive load. Do your homework, provide context, state the problem clearly, and make it easy for the person helping you.

And it is not the only one. The industry has spent two decades obsessing over this exact friction:

The Project Maintainer's View: In his book Producing Open Source Software, Karl Fogel dedicates an entire section to handling newbie questions constructively. He popularized the ethos that even if you have seen a question 1,000 times, it is that specific user's first time asking it, making a helpful response a critical investment in community goodwill.

The "Imagine You're Answering" Framework: Developer Jon Skeet wrote a highly cited piece on Writing the Perfect Question. While technically a guide for askers, it flips the script by forcing the writer to completely adopt the mindset, limitations, and frustrations of the responder before hitting submit.

The Defensive Programming Analogy: Jeff Atwood of Coding Horror frequently blogged about the friction between askers and answerers, most notably in Don't Ask Us Questions, We'll Just Ignore You. His commentary focuses on how community systems (like Stack Overflow) must be architected to filter out noise so experts don't burn out and become toxic.

These are all brilliant pieces of writing. But after a couple of decades working across architecture, operations, and data engineering, I've realized something. We spend a massive amount of time teaching engineers how to ask better questions. We spend almost zero time teaching experts how to answer them.

I've made this mistake myself. But regardless of who is doing it, the truth remains: a lot of the communication failures I see aren't caused by bad questions. They are caused by bad answers.

The core problem is usually this: The asker is trying to establish the model. The responder is answering with implementation details, caveats, and breadcrumbs.

The Excavation

We have all witnessed, or participated in, this exact pattern:

Question: Does feature X do Y by default?
Answer: Well, it can be configured differently depending on the deployment.
Question: Right, but out of the box, does it do Y?
Answer: Administrators can change the setting to do Z instead.
Question: Okay, but if I just turn it on without changing anything, what happens?
Answer: Yes, it defaults to Y.

What follows is an excavation. The asker has to carefully dig through three or four rounds of follow-ups just to extract a simple fact.

If it takes twenty minutes and a half-dozen replies to get a one-sentence answer, the problem was not the question. The question was fine. The failure was in the information transfer.

The Expert's Burden

ESR's essay is fundamentally about reducing the cost imposed on the answerer. But there is a reciprocal obligation. If you are the expert, the owner, or the authority, you owe clarity to the asker.

I understand why this happens. It is usually a defensive mechanism. We front-load the caveats because we are terrified of being technically "wrong" or called out over some obscure edge case. We want to protect ourselves by dumping all our context onto the table at once.

But the obligation to respect someone else's time does not end when they finish asking the question.

A good answer reduces uncertainty. It shrinks the search space. A bad answer expands it.

If the audience still has the same question after you respond, you haven't helped them. You have just transferred your cognitive load onto them, forcing them to reconstruct your intent.

It is very similar to the problem with passive voice (something Cary Millsap has talked about for years). The real sin of passive voice isn't grammar. It is making the reader work to reconstruct causality.

Poor technical answers create the same problem. The audience must reconstruct the model, the assumptions, and the contract from fragments scattered across multiple replies.

Answer Like an API

Answering questions effectively is an architecture skill.

A systems person naturally thinks in contracts. What is the source of truth? What guarantees does the system make? What is the documented behavior? Everything else is just plumbing.

We need to treat our answers the exact same way. When someone asks a question, they are usually looking for the contract.

Experts often begin with caveats, history, edge cases, implementation details, and exceptions. Resist that urge.

The answer goes first. Everything else is commentary.

Question: Does feature X do Y by default?
Better Answer: Yes, it defaults to Y out of the box. The configuration option allows you to override this behavior. Here is the link to the doc.

Experts often answer in chronological order ("Here is the history, here are the caveats, therefore the answer is X"). Good communicators answer in logical order ("The answer is X, here is why, here are the caveats").

It is the Minto Principle applied to engineering. The answer should be the first sentence, not the last.

Reduce Ambiguity

The same instinct that drives us toward explicit schemas, API definitions, and data contracts should drive our communication. Make the model explicit. Put the definition where everyone can see it.

The purpose of an answer is not to display expertise. The purpose of an answer is to transfer understanding.

Good architecture, documentation, and APIs all do one thing: they reduce ambiguity. Good answers should do the exact same.

Don't make people pull teeth to understand the system.

Sunday, May 17, 2026

You Can Point a Foreign Key Where?!

Editor's Note: The arguments are mine. The typing was not.

Let’s talk about things we think we know, but it turns out we’ve (read: me) just been following muscle memory for twenty-plus years.

If you asked me on any given Tuesday what a foreign key does, I’d give you the standard textbook answer. It points to the primary key of a parent table. It’s bread-and-butter relational modeling. We back it with a sequence or an identity column, we join on the IDs, and we move on with our lives.

But a funny thing happened on the way to the database the other day. I realized, or rather, I was reminded, that the SQL standard and Oracle Database don’t actually care about your primary key.

A foreign key doesn't have to reference a PRIMARY KEY. It just needs to reference a minimal unique identifier. That means any column set with a valid UNIQUE constraint is fair game.



















The Setup

Imagine you have a standard reference lookup table for order statuses. You’ve got your surrogate auto-incrementing ID as the PK because that’s what we do. But you also have an alphanumeric business code that the application actually uses, and that code is guaranteed unique.


CREATE TABLE order_statuses (
    status_id   NUMBER GENERATED BY DEFAULT AS IDENTITY,
    status_code VARCHAR2(10) NOT NULL,
    description VARCHAR2(100) NOT NULL,
    --
    CONSTRAINT pk_order_statuses PRIMARY KEY (status_id),
    CONSTRAINT uq_order_statuses_code UNIQUE (status_code)
);
Normally, devs will map the status_id down to the child orders table. But what if you map the code instead?
CREATE TABLE orders (
    order_id     NUMBER GENERATED BY DEFAULT AS IDENTITY,
    order_status VARCHAR2(10) NOT NULL,
    -- Look Ma, no status_id!
    CONSTRAINT pk_orders PRIMARY KEY (order_id),
    CONSTRAINT fk_orders_status 
        FOREIGN KEY (order_status) 
        REFERENCES order_statuses (status_code)
);

This compiles. It validates. It works.

Why Do We Care?

If you are a "data-first" person, this opens up some interesting pragmatic design choices, especially for seed data and reference enums.

  1. No-Join Readability: When I run a quick SELECT * FROM orders, I don't see status 1, 2, or 3. I see 'PENDING', 'SHIPPED', or 'CANCELLED'. I don’t have to write an explicit JOIN to a lookup table just to debug a row in a terminal log.

  2. CI/CD Sanity: Moving seed data across Dev, QA, and Prod environments when you rely purely on surrogate sequences can be a nightmare of dynamic mapping scripts. Business codes are immutable constants across environments. Your deployment scripts can just hardcode the literals without breaking things.

The Fine Print (Because this is Oracle)

Before you go rewriting your entire data model, remember that the laws of physics still apply.

First, Oracle does not automatically index foreign keys. If you point a child table to a parent’s unique business code, and somebody tries to delete or modify that code in the parent table, Oracle has to scan the child table to ensure no orphan records are left behind. If you didn’t manually put an index on orders.order_status, you are looking at a Full Table Scan and a nasty shared sub-exclusive table lock (TM) that will freeze concurrent operations.

Second, don't try this on your Slowly Changing Dimension (SCD) Type 2 tables. The second a business code repeats because you are tracking historical versions with effective dates, table-wide uniqueness breaks. And no, you can't use a partial function-based index to bypass this; declarative foreign keys need real, concrete constraints.

Relational Reality Check

In relational theory, a foreign key references a candidate key, which is simply a minimal superkey. The choice to elevate one candidate key to be the "Primary Key" is a physical implementation choice, not a logical requirement.

It’s completely valid, ANSI-standard behavior. It’s supported in Postgres and SQL Server too, so it’s not just an Oracle quirk.

It's just one of those elegant database features hiding in plain sight while application layers spend thousands of lines of code trying to reinvent referential integrity.

Keep it in the database.

Appendix: Documentation & Structural Foundations

Oracle Database Documentation

  • Oracle SQL Language Reference — The constraint Clause: The definitive syntax rules and structural restrictions governing referential integrity, confirming that foreign keys can target primary keys or unique constraints.

    Oracle Database SQL Language Reference — Constraints

Edgar F. Codd & Relational Theory

  • The 1970 Foundation Paper: Codd, E. F. (1970). "A Relational Model of Data for Large Shared Data Banks." Communications of the ACM. The original blueprint that introduced relational algebra, establishing that relationships are derived strictly by matching domains over mathematical relations rather than rigidly named primary/foreign key pairs.

    ACM Digital Library — A Relational Model of Data for Large Shared Data Banks

  • The Relational Model for Database Management: Version 2 (Book): Codd, E. F. (1990). Addison-Wesley. Codd formalizes RM/V2, explicitly grouping Primary Keys and Alternate Keys together under the definition of Candidate Keys, proving that referential integrity mathematically depends on the candidate key property of uniqueness.

    ACM Digital Library — The Relational Model for Database Management (Version 2)

Tuesday, May 12, 2026

The System Works

For the last 20 years or so I've thought about what retirement might look like. 

In retirement, I'd go back to school and write a thesis on... I didn't know what to call it. The "Medical Industrial Complex?" Much of this was due to the many, many, many interactions as it relates to Kate. 

"I'll go back to school, do the research, get my PhD." Mostly so I could participate in Spies Like Us shenanigans: Doctor. Doctor. Doctor. Doctor.

I had this gut feeling that something was...broken; none of it made sense. 

When I didn't have insurance, $115 copay. When I did have insurance, $400. 

As a consultant/contractor, I understood how we measure things in time; n dollars/hour. 

OK, so Doc is $115 x 4 (because it may have been 15 minutes, I'm being super generous here), how is it now $400 x 4/hour? How in the hell does handing over a piece of plastic turn a $460/hr rate into $1,600/hour?

I'm (often) told I need a hobby. Outside of work. What many don't realize is that I'm one of the fortunate ones; what I do for a living is not work.

Choose a job you love, and you will never have to work a day in your life. Or whatever that phrase is. 

So, one Sunday, a few weeks back, I set Antigravity (Gemini) loose on my idea. I just told it: "I have an idea I want to explore."

I didn't expect a book. I expected a conversation. But what happened over the next few hours was the mechanical equivalent of defining the schema for 20 years of observations. I threw two decades of "gut feelings" at the machine, and it started mapping the plumbing. Before I knew it, I had a 23-bullet outline and links to 50-75 outside sources.

"Fully fleshed out, how long would this be?"

"350-400 pages."







"OK, let's pare that back"

Gemini proceeded to suggest serializing it on Substack or just going with an abbreviated manifesto.

I chose the manifesto. We pared it down to 8 dense chapters. It wrote the Intro. 

What...do I...OMG...this is...wow. 

If you spend enough time listening to patients, doctors, or politicians, you will hear a single, unifying complaint spoken across every demographic and tax bracket in America:

The healthcare system is entirely broken.

It is the one rare diagnosis found across every boardroom, breakroom, and waiting room in the country. They point to the soul-crushing administrative bloat that forces practitioners into eight-minute volume appointments. They point to the labyrinthine "prior authorization" processes where algorithms deny life-saving treatments.

And yet, this frustration is born entirely from a profound paradox.

If you strip away the billing departments and the insurance mandates, the actual clinical engineering of the American medical system is nothing short of miraculous. If you are diagnosed with a rare leukemia, or require an unprecedented cardiothoracic intervention, there is quite literally nowhere else on earth you would rather be. The United States produces the most advanced, precision-engineered medical science in the history of human civilization. The people executing that science—the physicians, the bio-engineers, the surgical staff—are elite.

That is not the original, but you get the idea. I spent time reading and editing that introduction, changing the tone, the focus, where I ended up creating a GUIDING_PRINCIPLES.md file to keep it inline:

  • Dissect the Maze, Don't Indict the Mice
  • Empathy for the Inheritor
  • No Emotional Accusations, Only Systemic Mechanics
  • Labels are "Pre-Written Baggage"
  • Bypass Over Bureacracy
Super cool. A few iterations later, I had an Intro. On to Chapter 1. Same process. Edit for tone and clarity, but otherwise let it loose. Chapter 2, same. After Chapter 2, I just let it rip. By the end of that Sunday session, maybe 5 hours, I had a 40 page "book."

Over the next couple of days, I'd spend 30-60 minutes creating cover and chapter art (with Gemini web).

On Wednesday I had reached my quota for the month, the $20/month plan would reset on 4/11 (Saturday). 

I was exploring publishing options, I've always wanted to be a published writer (as I type that, I realize there are north of 800 articles here, so...). 

But like that, that specific itch was gone. I did not revisit on Saturday. I did share with friends and colleagues. I solicited feedback. I began to incorporate feedback and also track who provided what feedback. 

Fast forward a month, and I came head to head with this system again. It was unpleasant to say the least. 

Now, however, I was armed with new tools. The system hadn’t changed, but I could finally see how it moved; where the paths were, and why people kept ending up in the same places.