Your Metric Is Not Your State

What a refractometer taught me about observability

In July, I wrote about a farmer walking a vineyard row at dawn, crushing a grape onto a refractometer prism, and typing “13.5 Brix” into a chat window. The agent on the other end didn’t care how the number arrived. To the Digital Scribe, I wrote, a number is just a number.

In the vineyard, that’s a defensible position. Fresh grape juice is exactly what a refractometer is built to read.

This fall, I’ve been pointing the same kind of instrument at juice that’s fermenting. It turns out the agent should have cared.

Three batches and a prism

Over the last several weeks I’ve started three small batches: a blackberry wine that’s now bulk aging, a pear wine that began fermenting on September 24, and a fireweed honey mead that got its yeast two days later. They’re one-gallon batches, which matters for this story, because at that size every sample you pull is wine you don’t get to drink and oxygen you’ve let in.

So I track them with a refractometer. A few drops of liquid on a glass prism, close the cover, hold it up to the light, read a number off the scale. The number is Brix, roughly the percentage of dissolved sugar. It’s fast, cheap, and costs a few drops per reading. During the blackberry’s primary fermentation I took readings twice a day, every time I punched down the cap of fruit.

And once fermentation starts, the refractometer is wrong.

The instrument isn’t lying, exactly

A refractometer measures how much light bends as it passes through a liquid. Dissolved sugar bends light, so in fresh juice the amount of bending is a good proxy for the amount of sugar. The scale on the instrument is calibrated on that assumption.

Fermentation breaks the assumption. Yeast converts sugar into alcohol, and alcohol bends light too. A few days into primary, the refractometer is reporting the combined effect of the sugar that remains and the alcohol that’s been produced, and presenting all of it as sugar. The reading comes out higher than the real sugar content. Taken at face value, it says the fermentation is further behind than it actually is.

To get something usable, you run each reading through a correction formula. Most home winemakers use calculators built on formulas originally developed for brewing, and every one of them needs the same extra input: the original reading, taken before the yeast went in.

Here’s what that looked like on the blackberry:

Day Raw refractometer (Brix) Corrected value
08/29/2026 20.7 20.7
08/30/2026 18.4 16.5
08/30/2026 17.0 14.3
08/31/2026 13.2 8.1
08/31/2026 10.8 4.1
09/01/2026 8.7 0.7
09/01/2026 8.3 0.1

Nothing is malfunctioning. The instrument is doing exactly what it was built to do. What changed is the system it’s pointed at, and a number that was a reliable proxy in one state became a misleading one in the next.

Neither instrument measures sugar

The traditional alternative is a hydrometer, a weighted glass float. The deeper it sinks, the less dense the liquid. Sugar makes liquid denser, so falling specific gravity means sugar is being consumed.

Except the hydrometer doesn’t measure sugar either. It measures density, and alcohol is less dense than water, so as fermentation proceeds the alcohol pulls the reading down on its own. A finished dry wine routinely reads below 1.000, lower than plain water, which would be impossible if the hydrometer were really a sugar meter.

So the refractometer measures how light bends and the hydrometer measures how heavy the liquid is. Neither measures what I actually care about: how much sugar is left, whether the yeast is still working, whether this wine is done. I infer those. The instruments give me evidence.

Even a steady reading is ambiguous. The traditional sign that fermentation has finished is the same reading several days running. But a fermentation that has stalled also produces the same reading several days running. Mead is notorious for this: honey has very little natural buffering, the pH drifts down as fermentation proceeds, and the yeast can slow or stop well short of dry. A flat line can mean done or stuck, and the instrument can’t tell you which. You tell them apart with other evidence: where the line flattened, what the pH is doing, what you expected to see.

That distinction between evidence and state sounds pedantic until you notice it’s one we get wrong in software constantly.

Your dashboard has the same problem

CPU utilization isn’t health. A service at 15% CPU can be deadlocked, and one at 90% can be doing exactly what you want. An HTTP 200 isn’t correctness; it tells you the server returned a response, not that the response was right. A green health check tells you the health check endpoint answered. Model confidence isn’t truth; it’s a number the model produced about its own output.

Each of these started as a reasonable proxy under a particular set of conditions. Then the system changed, a new failure mode appeared, or someone started optimizing the number itself, and the proxy drifted away from the state it was meant to represent. The dashboard kept rendering it with exactly the same authority.

That’s the refractometer problem. The metric was calibrated for one state of the system and is now being read in another, and nothing on the display tells you so. And like the flat fermentation line, a steady metric can mean two opposite things: a quiet error rate might mean a healthy service, or one that stopped receiving traffic an hour ago.

The failure isn’t collecting the metric. It’s treating the metric as the state instead of as evidence about the state.

A reading needs its history

Back to that correction formula. To interpret today’s reading, you need the reading from before fermentation began. Without it, the current number is close to uninterpretable. The same Brix value could describe a fermentation that has barely started or one that’s nearly finished, depending entirely on where it began.

The meaning of a measurement depends on its lineage.

This is where I think observability practice most often falls short. We store the value and the timestamp and call it a record. What we usually drop is everything needed to interpret it later: which instrument produced it, under what conditions, what calibration assumptions it carried, what baseline it should be read against, and what state the system was believed to be in at the time.

My fermentation log keeps the raw reading and the corrected value side by side. The corrected value is what I act on. The raw value is what I re-derive from if I later learn the correction was off, find a better formula, or discover I misread the original. A corrected value on its own is a conclusion with its evidence thrown away.

That’s a pattern worth stealing. Store the observation separately from the interpretation. Keep the provenance that makes the observation meaningful. Let state be something you derive, with its evidence attached, rather than something you overwrite. It’s the same idea behind the forensic receipt work I’ve been doing: a claim should carry what it was based on.

Watching isn’t free

One more thing the one-gallon batches have taught me. I use a refractometer because a hydrometer needs a much larger sample, and in a small carboy that sample is a real fraction of the batch, and pulling it lets oxygen in. The act of measuring changes the system being measured.

It also means deciding how often to look. The blackberry had to be punched down twice a day anyway, so twice-daily readings were free. The pear and the mead don’t need that kind of handling, so I’ve settled on a reading every couple of days: often enough to catch a stall, rare enough to leave the batch alone. That’s a measurement policy, and it means my log has deliberate gaps in it. Those gaps are their own story, and I’ll come back to them.

Software has the same trade-off: tracing overhead, probes that add latency, sampling that shifts timing. Usually the effect is small. Sometimes it isn’t, and how often to look is a design decision with costs on both sides.

Evidence, then state

Here’s what I’ve taken from a few weeks of squinting at a prism.

Separate what you observed from what you concluded. “The refractometer read 9 Brix” and “fermentation is about two-thirds done” are different kinds of statement. They belong in different places, and they should be allowed to disagree.

Keep the raw reading. Corrections, normalizations, and aggregations are interpretations. If you keep only the interpreted value, you can never revisit the interpretation.

Record the conditions that make a reading meaningful. Instrument, baseline, calibration assumptions, the believed state of the system. A metric without its context is a number waiting to be misread.

Treat state as a derived claim. “Fermentation is complete” isn’t a reading. It’s a conclusion drawn from several readings over time, plus whatever evidence rules out “stuck,” and it should be traceable back to all of it.

Notice when your proxy stops being a proxy. Every metric was calibrated for some range of system behavior. When the system leaves that range, the metric keeps reporting with exactly the same confidence.

The agent in July was right about one thing: it shouldn’t matter whether a number comes from a clipboard or a five-thousand-dollar probe. Where it was wrong was in believing the number could travel without its history.

The pear wine is still in primary as I write this. The raw refractometer reading says it has a long way to go. The corrected number says otherwise, and the corrected number is only trustworthy because I wrote down what the juice read before the yeast went in.

The number on the instrument isn’t the system. It never was.

Facebooktwitterredditlinkedinmail

Your Hiring Process Needs HTTP Status Codes

Because “we’ll be in touch” is not an observable state.

I submitted a job application recently.

It was one of those applications where you actually put in the effort. I read the job description carefully, tailored the resume, wrote a cover letter specifically for the organization, and answered the optional questions rather than pretending they were optional.

Then I clicked Submit and got… nothing.

No confirmation email. No “we received your application.” No indication that a candidate record had been created. Just a browser page telling me the submission had succeeded and the vague hope that somewhere, deep inside an applicant tracking system, my carefully assembled application had not been written directly to /dev/null.

As a developer, I find this troubling. Not because I expect an interview, or because I think every recruiter owes me personalized feedback. I just want a status code.

And I think that’s a smaller ask than it sounds like, for a reason I’ll come back to at the end: the states already exist. Somewhere in that ATS, my application is in one. The decision not to show me which one is a product decision, not a privacy constraint.

The Web Solved This Problem Decades Ago

When software sends a request to another system, we generally consider it useful for the receiving system to say what happened. Did it work? Is it still processing? Was the request malformed? Is the resource gone? Did something catch fire on the server?

HTTP gives us an entire vocabulary for this.

Hiring systems, meanwhile, have developed their own protocol:

POST /careers/applications

[no observable response]

This is sometimes followed, anywhere from three minutes to six months later, by:

After careful consideration...

Instead of inventing vague candidate statuses like “Under Consideration,” perhaps hiring systems could adopt something developers already understand.

The 2xx Family: Success, Allegedly

200 OK

A human being has reviewed your application.

Nothing else is implied.

Honestly, this alone would be revolutionary.

202 Accepted

Your application has been received and accepted for processing.

This is the most important status code in the entire hiring protocol. The candidate does not need the hiring manager’s notes or a walkthrough of the internal workflow. They need:

202 APPLICATION_ACCEPTED

Now we know the thing exists.

204 No Content

Your application was successfully received.

We have chosen not to acknowledge this in any way.

The 3xx Family: You Have Been Redirected

302 Found

We found an internal candidate.

Thanks for playing.

304 Not Modified

Your application has been “Under Review” for 47 days.

There has been no change since the last time you checked, or the 23 times before that. There will be no change the next time either. Please consider caching this response locally.

410 Gone

The position existed when you applied. It no longer exists. No additional information is available.

At least 410 would be useful. The current discovery mechanism is noticing that your bookmarked job posting has started redirecting to the careers homepage.

The 4xx Family: This One’s On You

401 Unauthorized

You must create an account before applying.

Your password must contain at least 14 characters, uppercase and lowercase letters, a number, a symbol, and one ancient Sumerian glyph. It cannot match any password you have used since 1997.

You will then manually enter every field from the resume you just uploaded.

You will never use this account again.

403 Forbidden

You have the required experience and qualifications.

Unfortunately, you live 37 miles outside the geographic boundary within which we have determined this Zoom meeting can occur.

404 Not Found

Hiring manager not found. Recruiter not found. Job posting not found.

Nobody knows who owns this requisition.

408 Request Timeout

After seven weeks without communication, the candidate has accepted another position.

The hiring team will contact them tomorrow to schedule the second interview.

409 Conflict

The position requires 12 years of production experience with a technology introduced six years ago.

The conflict cannot be resolved.

417 Expectation Failed

You expected the phrase “we’ll be in touch” to imply future communication.

It did not.

422 Unprocessable Content

Your experience is relevant, but your career path does not fit cleanly into the predefined boxes in our applicant tracking system.

Human intervention may be required.

Human intervention is unavailable.

425 Too Early

You applied 14 minutes after the job was posted.

LinkedIn reports 612 applicants.

We have questions too.

451 Unavailable For Legal Reasons

Your qualifications are not the issue.

The issue is a background check vendor, a non-compete of uncertain enforceability, or a sponsorship question nobody wants to be the one to ask about.

The 5xx Family: It Was Never You

500 Internal Server Error

Something has gone wrong internally. Your recruiter left, the headcount was frozen, the hiring manager changed, Finance reconsidered the budget, the organization restructured, or the position was posted by accident.

We will communicate all of these possibilities using the same message:

We have decided to move forward with other candidates.

502 Bad Gateway

The recruiter says they are waiting for the hiring manager.

The hiring manager says recruiting owns the next step.

Your request cannot be routed.

503 Service Unavailable

Your recruiter is no longer with the company.

Their calendar link remains active.

504 Gateway Timeout

“We expect to have a decision by Friday.”

Friday has passed. The specification does not define which Friday was intended.

Candidates Don’t Need Distributed Tracing

There’s a serious point hiding under all of this. Hiring has an observability problem, and it’s usually mistaken for a communication problem.

Companies understandably cannot expose everything happening inside a recruiting process. Candidate evaluations are private. Interview feedback may be sensitive. Hiring managers need room to compare people and make decisions.

That’s fine. Nobody is asking for:

GET /hiring-manager/private-thoughts/about-me

But there is an enormous amount of territory between exposing confidential deliberations and providing no information at all.

A hiring pipeline already has states. An application was received. It entered review. Someone reviewed it. An interview was requested. The requisition was paused. The position was filled. The position was canceled. The candidate was declined.

Those state transitions already exist in the system. They are already recorded, timestamped, and reportable, because that’s how recruiting teams measure their own funnel. The candidate is the one participant in the process who cannot see the record they’re in.

Give Us the Boring Version

Imagine an applicant portal that reported nothing more than this:

202  APPLICATION_RECEIVED     Mar 04
200  REVIEWED                 Mar 19
304  NO_CHANGE                Apr 02
304  NO_CHANGE                Apr 30
410  POSITION_CLOSED          May 12

That’s not radical transparency. It’s barely transparency at all. But it tells you the system is functioning.

It removes the need to wonder whether an application disappeared. It reduces pointless portal refreshing. And it prevents candidates from reading silence as information, when silence might mean anything from “the hiring manager is on vacation” to “the requisition was canceled two weeks ago.”

Mostly, it treats applicants like participants in a process rather than packets transmitted into an undocumented endpoint.

200 OK

I don’t expect every application to result in an interview. I’ve submitted enough of them to have considerable empirical evidence supporting that position.

I don’t expect detailed rejection feedback either. I understand why companies are reluctant to provide it. I don’t even particularly mind 403 REQUIREMENTS_NOT_MET or 410 POSITION_FILLED. Those are answers.

What makes modern hiring uniquely frustrating isn’t rejection. It’s uncertainty about whether anything is happening at all.

Software engineers have spent decades building systems around a simple principle: when one system asks another to do something, the response should carry enough information to understand what happened.

Candidates aren’t asking for distributed tracing of your recruiting infrastructure.

We’d settle for a status code.

Facebooktwitterredditlinkedinmail