Skip to content

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.

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 citation. Not a title, not a database id: the node's loc, which is unique within its memory and permanent once minted. A node is a spec because it is marked as one, with the spec tag or the spec role, not because of the shape of its loc. Any valid loc will do:

api:010:02                            a numbered rule
onboarding:mentor:screens:settings    a named path

Many corpora, Hadron's own included, number their specs in a legal-code style, and the tooling still produces that numbering when you ask it to allocate one:

<module>:<feature>:<rule>[:<flow>]              api:010:02
<product>:<module>:<feature>:<rule>[:<flow>]    srv:api:010:02

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 numbering is a convention, not a rule. A loc implies no parent: a spec is connected to the rest of the corpus by the edges it was given, not by where its loc sits. A corpus may use the numbering, named paths, or both. No spec is refused or flagged for its shape, and a memory does not declare a numbering "scheme". Only the tooling's numbering helpers, which allocate the next number, need a numbered loc to work from.

That citation 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, and stable enough to write into a file that will outlive the conversation.

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.

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

A general-provisions contract is a spec that states what a group of sibling specs share, so each sibling does not have to repeat it. The specs it governs point at it with an inheritance edge. So "every operation in this module refuses an unauthenticated caller" is stated once, at the module's contract, and governs every spec that inherits it. A sibling overrides it only by saying so explicitly.

The legal-code numbering reserves a place for the contract at each level. When the tooling allocates a number, it wires the new spec's edge to that contract, and it creates the contract along with any new level:

A spec at this level 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

Outside that numbering, the edge is one you add yourself. Either way it is the edge, not the loc, that makes a spec inherit a contract, and nothing requires a spec to have one.

This is why a spec read alone is rarely the whole rule. When a spec seems to leave something undefined, follow its inheritance edge to the contract. It is an actual edge in the graph, so the tooling shows it to you.

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 every spec the tooling scaffolds starts from a template that has 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 heldByUserId before 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.