Essays 6 min read

One Writer Per Artifact

Typing my automation handed me most of its rules for free; the type carries the contract. One rule came free with nothing, and it was the one my system was breaking: every file gets exactly one job that writes it. On the difference between rules you remember and rules the structure carries.

One Writer Per Artifact

There is a directory in my automation that took me longer to understand than any code I have ever written, and it contained no code. It was a data directory, the one my dashboard reads from, and the reason it resisted understanding was simple to state once I finally saw it: more than a dozen different jobs wrote into it. To answer the smallest possible question about that folder, what is in here and can I trust it, I had to hold every one of those writers in my head at once, know their schedules, and reason about who wrote last and what they overwrote. One folder, a dozen-plus owners, which is to say no owner at all. That directory was the soup at the center of my system, and it was soup for one nameable reason. It violated a rule I did not know existed.

The rule is: every artifact gets exactly one writer. One file, one job responsible for it, no exceptions you did not choose on purpose.

Rules that come free, and the one that does not

I found the rule while typing my loops, and the typing is what makes its absence visible. Most of the rules a pipeline needs turn out to ride along with the type, free, the way grammar rides along with a part of speech. Call a loop an emit, meaning it pushes out into the world, and its contract arrives with the word: safe to run twice, because someday the scheduler hiccups and it runs twice, and an emit that double-fires is an incident. Call a loop a report and the opposite contract arrives: pure snapshot, no side effects, regenerate it a hundred times for free. Call a loop a watch and the strictest clause of all shows up: it reads state and returns verdicts and must never, ever write data, because a watcher that writes has become part of the watched. Call it an ingest and it inherits the duty to survive its source being down. I did not compose any of that. The types carry it, and every future loop I write will inherit its contract at the moment of naming.

Single writer per artifact is different, and the difference is why it is the rule that actually gets broken. It is not a fact about any one loop. It is a fact about the space between loops. Nothing in a transform's own contract prevents a second transform, written eight months later on a busy day, from writing to the same file. Each loop is individually correct. The collision lives in the pair, and no one is standing where the pair is visible. That is precisely how my soup directory happened. Not one bad decision. A dozen individually fine decisions, each one adding a writer, no vantage point from which the accumulation could be seen. Fog never announces itself. It accrues.

Chosen twice, or drifted into

The rule is not "two writers must never happen." I have one deliberate violation: a job that seeds a file and a second job that fills in the rest, colliding by design, and the design is written down where the next reader will find it. That violation is fine, and the reason it is fine is the distinction the whole rule turns on. A violation you chose and documented is a design decision, visible, priced, revisitable. A violation you drifted into is fog with a commit history. The rule's real text is not "one writer, always." It is "never two writers by accident." Constraints in a system you build alone are not there to stop a rogue teammate. They are there to stop the accumulation of reasonable Tuesdays.

I did not invent the rule, and finding its provenance was the same relief as finding the rest of the discipline. In data engineering it is close to load-bearing doctrine, and one tool, Dagster, is built around it wholesale: its core unit is the asset, an artifact with exactly one producing job and its lineage tracked for free. I studied Dagster and did not adopt it; at my scale the machinery costs more than it returns. But the idea rode home free. Think in artifacts with single owners, enforce it by hand, and you have taken the tool's best thought without taking on its care and feeding.

Rules you remember, rules the structure carries

A rule I have to remember is a rule I will eventually break. Not through carelessness, through arithmetic: I hold a rule in attention a hundred times and lapse once, and the system keeps the lapse. Every rule stored in my memory decays at the rate of my worst day. This is the same reason I build maps outside my head instead of trusting recall, and it took me embarrassingly long to see that it applies to laws as much as to maps. Memory is the wrong substrate for anything that has to hold.

A rule the structure carries does not decay. When the contract rides on the type, and the type is declared in a registry, and a check reads the registry, the rule outlives my attention to it. The next loop I add, months from now, in a hurry, gets named, and the name hands it its obligations, and a violation surfaces as a red line in a report instead of as a mystery in a folder. The system has begun to govern loops that do not exist yet. That is the property that separates a system from a pile. A pile is ruled by the memory of whoever built it and degrades as the memory does. A system carries its own law.

So the arc closes, and it was one move all along, run three times at three depths. Typing the loops made each one legible. Drawing the graph made the whole visible. Writing the contracts into the structure made the whole durable, so the legibility survives me getting busy, which is the only condition under which legibility is ever actually tested. None of it required a rewrite, a platform, or a better memory. It required names, a drawing, and a place outside my head for the rules to live. The territory did not change. It was charted, and now the chart enforces itself.