Classification & re-pinning

Shipmoor Team
July 31, 2026
6 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.

The truth table

Every stored link is compared endpoint by endpoint against its recorded pins.

Obligation endpoint movedCode endpoint movedResolvableClassification
nonoyessilent
noyesyessuspect: code moved, obligation unchanged
yesnoyessuspect: obligation moved, code unchanged
yesyesyesin sync, auto re-pinned, disclosed
anyanynounresolvable with reason; never suspect; never a threshold input

That is the whole classifier. There is no ranking, no severity, no confidence score. A suspect is a suspect.

Direction is a property of the diff

The two suspect rows are the same mechanism read in two directions. One code path serves both, and there is no second mechanism and nothing to configure.

Code moved, obligation unchanged. Someone changed the code that realizes a promise without touching the promise. Either the promise is now wrong, or the change did not affect it. Both are worth a human deciding.

Obligation moved, code unchanged. Someone sharpened what the system promises and nothing downstream moved. Either the code already satisfied the sharper wording, or it does not and nobody noticed.

Which direction you see is decided entirely by what the diff touched.

Reading the screen

$ shipmoor blast
Blast radius  working tree vs HEAD · 2 files changed
  suspect 1 · rerun advised 0 · ungoverned 1 · in sync 0 · silent 0

  suspect       ac-01.realizes.login · AC-01 realizes app/login.py · code moved, obligation unchanged · minted (claim-check graduation)
  ungoverned    config/limits.py · changed with no links

exit 0 · suspects at warn (set blast.suspect_threshold: fail to gate) · report .shipmoor/blast.v1.json

Headline counts first, then one line per finding, then the exit.

Findings are grouped the way the screen above groups them, and within a group they keep committed store order — the order you wrote them in, not an order the tool chose. Nothing is re-sorted by an implicit notion of importance.

Every line carries its provenance

A reader should never have to guess what kind of evidence a claim rests on.

LabelMeans
authoredA human wrote this link and committed it.
minted (claim-check graduation)A Claim Check run minted it from causal evidence, carrying the mint’s own note.
tag joinA derived verifies edge, joined on the obligation’s expected check tag.

This is the trust-through-disclosure ruling applied to every rendered line. A suspect that came from a hand-authored link and a suspect that came from a minted one are different claims, and the screen says which is which.

The counts cannot disagree

--terse condenses the same counts to one line for a CI log, and --json emits the whole report. All three read the same numbers. The renderer holds no logic: every count it prints is the length of a list it was handed.

Re-pinning: the two ways a pin ever moves

Both are visible in the committed diff. There is no third way, and nothing moves a pin silently.

Automatic, when both endpoints moved together

That is the definition of in sync: the promise and the code changed in the same change, so the run re-pins and says so.

No prompt, no flag. If you changed both sides, you have already made the decision the re-pin records. The store write is minimal-diff, touching only that entry’s pins.

Explicit, when a one-sided change did not alter the promise

A pure refactor, say. You accept that in writing:

shipmoor blast --repin ac-01.realizes.login
re-pinned  ac-01.realizes.login · code app/login.py sha256:41c7… (was sha256:12b2…)
recorded in .shipmoor/links.yaml

The re-pin edits the committed store and appends a re-pin event to the entry’s history. The judgment “this refactor did not change the promise” is itself reviewed in the pull request, with the old and new identity both visible.

That is the point of recording it. The alternative — a suspect you silence by editing a hash — would put the most consequential decision in the whole feature outside review.

--repin is repeatable: one link id per occurrence.

What --repin refuses to do

SituationBehavior
The link is not currently suspectA stated no-op. In sync or silent links have nothing to accept.
The link’s endpoint was deletedRefused, not recorded. A deleted endpoint has no identity to pin, and recording a hollow success would be a lie in the store.
The link id does not existA usage error naming the id.

Dry-running a re-pin

--no-write makes any re-pin a dry run: the intended change is computed and disclosed, and the file on disk is untouched.

shipmoor blast --repin ac-01.realizes.login --no-write

The same flag suppresses the automatic in-sync re-pin and the report file, so a --no-write run is guaranteed to leave the working tree exactly as it found it.

Unresolvable is not suspect

A link whose endpoint cannot be resolved is not classified. It lands in the disclosed not-checkable bucket carrying its reason, and it never trips the exit-code threshold under any value.

Treating an unknown as a finding would mean a repo with one bad path in its store fails its build for a reason that has nothing to do with the change under review. See Troubleshooting for each reason and its fix.

Next

Last updated on July 31, 2026

Was this article helpful?

Your response is saved on this device.

Related Articles