Product specs and citations¶
Every product accumulates rules that are true regardless of how they are built. A trial lasts fourteen days. An invite expires after one use. A worker's name is permanent within its team. Those rules outlive the ticket that introduced them, the sprint that shipped them, and usually the person who decided them.
Hadron stores them as a spec corpus: a memory run like a legal code, where each rule is a node whose address is its citation number.
This page is the model. For finding and reading one, see Read and cite product specs; for writing them, Maintain product specs.
The problem it solves¶
Ask where a product rule lives today and you get four bad answers.
In the code — precise, and it tells you what the software does, not what it promises. A reader cannot tell an intentional guarantee from an accident of implementation, and neither can the next engineer changing it.
In the ticket — accurate on the day it was written and frozen there. Tickets describe changes, so reconstructing the current rule means replaying every ticket that ever touched it, in order, hoping none is missing.
In a design doc — written to persuade, at a moment when the decision was not yet made. It records the argument, not the outcome.
In a wiki page — the closest thing to right, and it has no address. A wiki page can be renamed, reorganised, split or merged, so a reference to it from a code comment or another team's repo rots quietly.
The spec corpus is the fifth answer: a rule stated implementation-agnostically, at a permanent address, in a form other artifacts can point at.
The citation is the identity¶
A spec's address is its number. Not a title, not a slug, not a database id:
flat <module>:<feature>:<rule>[:<flow>] api:010:02
product-rooted <product>:<module>:<feature>:<rule>[:<flow>] srv:api:010:02
Each colon level is a real parent node, so the corpus is a tree you can walk
rather than a naming convention people agree to follow. Features are numbered in
tens (010, 020) and rules and flows by one — and allocation is monotonic,
strictly above the current maximum. So a retired number is never recycled, and
the gaps are not backfilled either: a new feature goes after the last one, not
into the space between two old ones.
That number is the whole point. It is what a code comment cites, what a commit message references, what another team writes into their own spec. A citation is short enough to say out loud, stable enough to write into a file that will outlive the conversation, and self-describing enough that a reader can tell which tier they are looking at before resolving it.
The consequence runs deeper than convenience: once a citation is minted it can never move. Not renamed, not renumbered, not reused. Every improvement to the corpus has to be expressible without changing an address, which is a real constraint on how specs are written — and the reason the rest of this model looks the way it does.
Why a legal code, and not just a folder of documents¶
Legal codes solved this problem first, and Hadron borrows two of their mechanisms because both fall out of the permanent-number constraint.
General provisions, so a rule is not repeated in fifty places¶
Every tier reserves a general-provisions contract its siblings inherit:
| A spec at this tier | inherits |
|---|---|
a rule (srv:api:010:04) |
its feature's :00 |
a feature (srv:api:010) |
its module's :000 |
a module (srv:api) |
its product's :gen |
So "every operation in this module refuses an unauthenticated caller" is stated once, at the module's contract, and governs every rule beneath it. A sibling overrides it only by saying so explicitly.
This is why a rule read alone is rarely the whole rule. When a spec seems to leave something undefined, the contract one tier up is the next place to look — and the inheritance is an actual edge in the graph, so the tooling shows it rather than expecting you to know.
Supersede, never renumber¶
A spec is never deleted and never renumbered. When it is replaced it is
superseded: it keeps its citation, gains a superseded tag, and gets an
edge pointing at whatever replaced it.
So a citation written into a commit message two years ago still resolves. It does not resolve to a lie, either — it resolves to a tombstone that says "this was the rule, and here is what governs now." A reader arriving from an old reference lands somewhere true rather than on a 404 or, far worse, on a different rule that happens to have inherited the number.
Occasionally a rule is withdrawn rather than superseded: the mechanism it described is gone and nothing replaces it. The number stays permanently occupied by the tombstone. Minting a replacement citation for a rule that no longer exists would be worse than the gap.
Durable vs tunable — the split the model turns on¶
Every spec separates what it guarantees from what merely happens to be true today. A trial period being time-limited is durable; its being fourteen days is tunable.
That split looks like documentation hygiene and is actually the load-bearing part, because it decides the only question that matters when a spec changes:
Does this change what the spec guarantees?
- No → amend in place. Wording, a clearer example, a newly-found edge case, a value the spec itself labels tunable. The citation keeps meaning what its readers think it means.
- Yes → supersede. A guarantee narrowing or widening, an enum gaining a member, the set of people a rule applies to changing. A new number, because everyone who cited the old one was promised something different.
Notice what the test is not: how large the diff is. A one-word change from "may" to "must" is a supersede. Rewriting three paragraphs for clarity is an amend. Size is not the signal; the promise is.
A spec with no durable-vs-tunable section cannot answer that question, which is why the linter keeps asking for one.
What a spec is not¶
- Not a design document. A spec records the outcome, not the argument. The discussion that produced it belongs where discussions live.
- Not a ticket. A ticket describes a change; a spec describes the state after it. A spec that reads like a changelog has become a ticket with a number.
- Not implementation. "The resolver checks
heldByUserIdbefore the transaction" is how something is built, and it will be false after the next refactor. "A name belongs to the person who claimed it until they release it" is the rule. Specs that name files and functions rot at the speed of the code they describe, which is exactly the coupling the corpus exists to break. - Not everything. The test is reach: would a reader in another repo, another team, or another year need this to be correct? A local ergonomics choice fails that test and is better recorded as a convention.
Who writes them¶
Whoever owns the rule — which is usually not whoever owns the code. The corpus is written in product language on purpose, so a product owner can read and change it without learning the tooling; asking a coding agent to do the mechanics is the normal path.
One decision stays with the human either way, and it is the amend-or-supersede call above. Everything else about a spec can be revised later. A citation cannot, so minting one is the one step worth being slow about.
Related¶
- Read and cite product specs — finding a rule and citing it.
- Maintain product specs — the write side.
- Amend a spec — the amend/supersede decision in practice.
- Manage product specs with a coding agent — owning the rules without driving the tooling.