Skip to content

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 example api:010:02. Use it when the memory holds specs for a single product.
  • product-rooted<product>:<module>:<feature>:<rule>[:<flow>], for example srv: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 use acme.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:

hadron spec describe -m acme.com::platform-specs

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:

hadron spec describe -m acme.com::platform-specs --declare product

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:

hadron spec ls -m acme.com::specs --prefix api
hadron spec register -m acme.com::specs

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>):

hadron node update acme.com::specs::api:010:04 \
  --abstract "..." --content-file w4.md

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:

hadron spec lint --module api -m acme.com::specs   # ✓ api — all specs OK

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:

hadron spec find "re-engage users who never finished setup" -m acme.com::specs

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:

hadron spec supersede api:010:04 -m acme.com::specs \
  --title "Some new spec" --copy-body --yes

--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.