The link model & the committed store

Shipmoor Team
July 31, 2026
7 min read

Not yet generally available. Blast Radius ships in Shipmoor CLI 0.9.0, which is still in the release process. This page describes shipped behavior on the release candidate; the command is not on the stable channel yet.

Every link carries a kind:

KindRelatesStored?
realizesan obligation → the code paths that realize itYes, in the committed store
documentsa doc section → the code paths it documentsYes, in the committed store
verifiesa check → the obligation it verifiesNever. Derived every run

A verifies entry appearing in a committed store is a loader error, not a tolerated extension. The reason is in the next section.

Today documents links are compared at whole-file granularity. Section-level anchors arrive with docs verification, and the documented-surface gaps list is disclosed as not-checked rather than rendered as an empty list.

Pins and what identity means

Every stored link is pinned with the content identity of both endpoints. At run time:

  • both endpoints match their pins → silent
  • exactly one moved → suspect, with the stale side named
  • both moved in the same change → in sync, and the run re-pins automatically

Identity is content, not meaning. A comment-only or whitespace-only edit changes the bytes and therefore the identity, so it suspects.

That is not a rough edge to be smoothed out later. It is the direct consequence of the no-lexical-inference law: the moment a comparator starts deciding which edits “really” changed the promise, it is making a semantic judgment, and there is no honest way to explain to a reader why one edit counted and another did not. The relief valve is an explicit re-pin, which is a human decision recorded in the committed diff, never a smarter comparator.

An obligation’s identity is its frozen statement hash, taken from .shipmoor/acceptance.json and computed by the same API Claim Check uses. There is one hashing truth, never a reimplementation.

The committed store

The durable store is a committed file, .shipmoor/links.yaml, sitting beside the acceptance set and governed by the same approval mechanic: committed means approved.

The graph is repo state, reviewed in pull requests like everything else. Two consequences follow, and both are deliberate:

  • Blast never scaffolds the store. An engine-created store would be an approval nobody made. A repo with no store gets one honest line naming where links come from.
  • Blast never writes the acceptance file. The obligation side of every link is read-only to this feature.

links.yaml is the only durable state Blast Radius owns. Store writes are minimal-diff so the change you review is the change that happened, and any change to the links.v1 shape is a declared migration, not drift.

You write the endpoints; the run writes the pins. Add an entry with no pin fields:

# shipmoor links.v1 store. Committed means approved; edits are reviewed in the PR.
links:
  - id: ac-01.realizes.login
    kind: realizes
    item: AC-01
    code:
      - app/login.py
    provenance: authored

The next run resolves both endpoints and writes the pins back, disclosed as a first pinning rather than a re-pin. Nothing drifted; the endpoints simply had no recorded identity yet, so the entry gains no re-pin history. From then on the link is classified against those pins on every run.

A declaration the run cannot honor is disclosed, never dropped

ReasonWhat it means
unknown item idThe item is not in the frozen acceptance set.
absent pathA code path is not in the repo.
missing acceptance fileThe repo has no frozen set. Write it with shipmoor claim-check freeze.

In every case the entry keeps its committed place and lands in the not-checkable bucket carrying that reason. It is never quietly pinned and never silently discarded. Fix the id or the path and the next run pins it. See Troubleshooting.

You do not have to author every link by hand.

When a Claim Check run graduates an obligation to the deterministic floor — decided satisfied through a bound check — that round’s change surface is causal evidence for which code realizes the obligation. The run mints or refreshes a realizes pin from it, labeled minted (claim-check graduation).

The rules are narrow on purpose:

  • Post-verdict and floor-only. Only obligations the floor decided satisfied through a bound check mint. A judge outcome never mints, and neither does an underspecified obligation.
  • Authored outranks minted. A mint never overwrites an authored entry; the collision is a disclosed no-op.
  • Minting requires blast_radius alongside claim_check. For a caller holding only claim_check, its absence is a disclosed skip. For a caller holding neither, the run is silent, because a free run must never advertise a paid feature the caller has shown no sign of owning.
  • The verdict is unaffected. Minting on, off, or failing yields byte-identical verdicts, exit codes, and outputs, apart from the disclosed notice.

This is why a repo’s graph thickens with every round it runs, without anyone doing traceability work as a separate task.

Verifies edges are derived, never authored

An obligation’s expected check tag comes from the frozen acceptance set — it looks like ac_01_7c9d21aa — and a check claims that obligation by carrying the tag in its test id or file name.

That is an exact string join and nothing else. A check whose name merely reads like the obligation earns no edge. This is the law applied to the check side of the graph.

The reason these edges are never stored is that they are cheap to recompute and expensive to be wrong about. A stored verifies edge could outlive the check it names, or the tag it joined on, and there would be no way to tell from the file. Deriving them every run means the graph cannot carry a stale claim about what verifies what.

Freshness: three states, never invented

Each derived edge carries exactly one freshness state, read from evidence a Claim Check run already recorded. Blast never runs a check and never invents freshness.

StateWhat it means
freshEvidence was recorded for this change.
predates this changeEvidence exists, but against a different change; a re-run is advised.
no recorded evidenceNothing has been recorded, stated as such.

Rerun advised is advisory and is never an exit-code input.

Freshness and staleness are two axes, never merged

When a change edits an obligation’s statement, the whole acceptance set is re-frozen and every expected tag moves with it. Checks tagged against the previous statement are still shown, annotated as pinned against that statement, rather than vanishing from the graph.

Freshness and that annotation are independent and are shown as two clauses. A check can have run for this change and still be tagged against a statement the change superseded, because running a check does not re-tag it. Merging the two would produce a line that is false in one of the two directions. Re-tagging the check resolves the annotation.

One check per obligation, disclosed

Each obligation shows one verifying check. The join returns a single binding per obligation, so when several checks carry the same expected tag the graph names one of them — and the run discloses that, rather than letting one edge imply there is only one check.

Configuration the join depends on

The verifies join reads claim_check.runners.test, because the rung it calls binds only a check something can run. Without a runner configured, no edges are derived and the run says so rather than presenting an empty graph as a clean one.

Next

Last updated on July 31, 2026

Was this article helpful?

Your response is saved on this device.

Related Articles