Maintain product specs¶
This guide uses hadron spec to capture a durable product rule as a spec
node in a Hadron memory, then keep it correct over time. A spec is
addressed by a citation number that is the node's loc — a legal-code-style
address. A memory uses one of two citation schemes:
- flat —
<module>:<feature>:<rule>[:<flow>], for exampleapi:010:02. Use it when the memory holds specs for a single product. - product-rooted —
<product>:<module>:<feature>:<rule>[:<flow>], for examplesrv:cha:010:01. Use it when one memory spans several products, so the product is part of every citation.
hadron spec is the opinionated layer over node/edge: it allocates the
next citation, scaffolds the required rubric, wires the edges, and enforces
the stability rules so a citation is a permanent handle. The full command
surface is in the
hadron CLI reference.
Prerequisites¶
- The hadron CLI installed and signed in — see
Install the hadron CLI. Check with
hadron auth status. - Write access to a memory that follows the loc-as-citation convention. The
flat examples below use
acme.com::specs; the product-rooted ones useacme.com::platform-specs.
Check (or declare) the scheme¶
Most single-product memories are flat and need no setup — skip ahead. If a memory spans several products, use the product-rooted scheme so each citation names its product.
spec describe reports which scheme a memory uses, the products and modules
present, and the per-tier counts:
The scheme is derived from the live specs, and a memory can also declare
it in its data — handy for an empty memory that wants to state its arity up
front, after which describe flags any drift. Declare it once, before the
first spec:
A memory should be all-flat or all-product; spec lint warns if it mixes the
two.
Decide whether it belongs¶
Write a spec when a change introduces a product-level rule a product manager would need to read — a threshold, a time window, a categorical rule, or a lifecycle / eligibility / delivery contract. Pure implementation details (file paths, query shapes, caching) do not belong here.
Pick the module (a frozen three-letter code, e.g. api for messaging) —
and, in a product-rooted corpus, the product (the shippable artifact,
e.g. srv) — then the feature the rule belongs to. See what already
exists and which numbers are free:
spec register prints the ledger derived from the live nodes, including the
next free feature per module and next free rule per feature.
Scaffold the spec¶
Preview the allocation first — --dry-run writes nothing:
hadron spec new -m acme.com::specs --module api --feature 010 \
--title "W4 — 7-day check-in" --dry-run
The output shows the citation it would allocate (the next free rule under
api:010 — here api:010:04), the node name and tags, and the
table-of-contents and inheritance edges it would create. When it looks
right, create it — supplying the abstract and body up front so the spec is
complete:
hadron spec new -m acme.com::specs --module api --feature 010 \
--title "W4 — 7-day check-in" \
--abstract "Re-engage users who signed up but never finished setup, 7 days in." \
--content-file w4.md
Without --abstract / --content-file, the spec is created from a rubric
template — a placeholder abstract and the four mandatory section
headings (Definition, Rule & examples, Durable vs tunable, What invalidates
this spec) — which you fill in afterward. To edit a spec later, address it as
a node URN (<org>::<memory>::<citation>):
Scaffold a product-rooted corpus¶
A product-rooted corpus is built top-down — each level must exist before its
children. --product qualifies every command; --new-product and
--new-module mint the (frozen) roots:
M=acme.com::platform-specs
hadron spec new -m $M --new-product --product srv --title "Server"
hadron spec new -m $M --product srv --new-module --module api --title "API"
hadron spec new -m $M --product srv --module api --new-feature --title "Ordering"
hadron spec new -m $M --product srv --module api --feature 010 --title "Place new order"
Everything else — ls --prefix srv, lint, find, supersede — works the
same; the citations simply carry the product (srv:api:010:01).
Share provisions across siblings¶
When a provision is shared by every sibling at a tier — every rule of a
feature, every feature of a module, or every module of a product — put it in
that tier's reserved general-provisions contract instead of repeating it.
Siblings inherit it automatically. Create one with --contract at the
deepest level you name:
| Shared across | Contract citation | Create with |
|---|---|---|
| every rule of a feature | api:010:00 |
spec new --module api --feature 010 --contract |
| every feature of a module | api:000 |
spec new --module api --contract |
| every module of a product | srv:gen |
spec new --product srv --contract |
The numeric tiers use their reserved zero (00, 000); the alpha product
tier uses the reserved code gen.
Create the contract before its siblings¶
Authoring order matters. spec new auto-wires the inheritance edge from a new
sibling to its contract — but only when the contract already exists:
hadron spec new -m acme.com::specs --module api --feature 050 \
--title "Geographic proximity"
# ✓ created api:050 — Geographic proximity
# edge: inherits the shared contract (general provisions) → api:000
So scaffold the :00 / :000 / :gen contract first, then add the rules,
features, or modules that defer to it. They wire themselves up, and lint
stays clean.
Add a contract to a tier that already has siblings¶
Introducing a contract into a tier that already has siblings is a
retroactive constraint: the moment api:000 exists, every sibling at that
tier must inherit it — and the pre-existing ones carry no such edge. spec
lint fails for each:
CITATION SEVERITY RULE MESSAGE
api:010 error inheritance-edge no inheritance edge to general-provisions contract api:000
api:020 error inheritance-edge no inheritance edge to general-provisions contract api:000
api:030 error inheritance-edge no inheritance edge to general-provisions contract api:000
The remedy isn't in the message: back-wire each sibling to the contract with
the inheritance edge spec new would have added. Use hadron edge add (or the
h-add-edge MCP tool), with the label inherits the shared contract (general
provisions):
for f in api:010 api:020 api:030; do
hadron edge add --from "acme.com::specs::$f" --to acme.com::specs::api:000 \
--label "inherits the shared contract (general provisions)"
done
Re-lint to confirm the tier is clean:
The same retrofit applies at all three tiers — feature siblings back-wired to a
module's :000, or module contracts to a product's :gen — not just the module
tier shown here.
An outlier sibling still inherits¶
The rule is all-or-nothing per tier, which can feel forced when a tier is heterogeneous — say a module whose contract states a scoring model, but one of its features is an A/B assignment mechanism rather than a scoring rule. That feature still needs the edge. Read the inheritance edge as "governed by the tier's general provisions," not "is another instance of the same kind." Keep each contract broad enough to genuinely cover every sibling; an edge on a true outlier means only that it is bound by the shared provisions.
Lint it¶
spec lint checks a spec against the rubric and the stability rules. A
freshly scaffolded spec fails until you replace the placeholder abstract and
state what invalidates it:
hadron spec lint api:010:04 -m acme.com::specs # one spec
hadron spec lint --module api -m acme.com::specs # a whole module
hadron spec lint --product srv -m acme.com::platform-specs # a whole product
hadron spec lint --all -m acme.com::specs # the corpus
Errors — a missing abstract, no "what invalidates" statement, a malformed
citation, a name that doesn't lead with the citation, or a missing inheritance
edge to a tier's
general-provisions contract
— exit with code 5. Warnings (a missing data.version, an absent edge, a
mixed-arity corpus) do not, unless you pass --strict. Fix the findings and
re-run until it is clean.
Find it by meaning¶
Because the citation is opaque, discovery rides on the abstract, which the memory's vector index embeds. Search by intent, not by guessing the number:
spec find is semantic by default. Pass --match-exactly for a literal
keyword search — for example, to list every spec mentioning api:010.
Replace a spec without renumbering¶
A citation is a permanent handle, so you never change a number in place. To
replace a rule, supersede it: spec mints the next free number, links
the old spec to the new one with a superseded-by edge, and tags the old
spec superseded (its loc is untouched). Like the other destructive
commands, it requires --yes when run non-interactively:
--copy-body seeds the replacement from the old spec's content and abstract.
Afterward, mark the old number retired in the register ledger — the command
prints a reminder, and never edits the register itself.
Related¶
- hadron CLI reference → Product specs — the full command surface, numbering policy, and semantics.
- Use Obsidian to view/edit memory — open the synced or exported repo as an Obsidian vault, and make citation-numbered nodes readable.