Context
Continuation of the block-no-scale effort (#34 / PR #230). ShotSequencer already emits ShotDecision/ShotDecisionReason on its decisions stream (groundwork landed with #34); this design persists the final stop reason onto the ShotRecord.
This clusters with two related items — all three should be designed together:
- Stop reason on ShotRecord — persist the final stop reason onto the record
/ws/v1/shotState WebSocket topic — stream ShotSequencer decisions to clients
- Mid-shot scale loss → volume fallback — fall back to volume-based stopping when scale drops
Intended shape
A WebSocket that streams ShotSequencer's decision info to clients — why the sequencer advanced the profile to the next step, why it skipped a step, why it stopped the shot (target weight, target volume, scale loss, no-scale block, manual, ...), so GHC-/machine-initiated outcomes are explainable to skins the same way REST callers already get a typed error.
The persisted ShotRecord stop reason is the durable tail of that same decision vocabulary.
Current state
ShotDecision / ShotDecisionReason types exist
decisions stream on ShotSequencer currently emits only noScale
- Reason vocab intentionally aligns with the REST
block_no_scale type
- No wire serialization yet
- No design/handoff note exists yet (checked 2026年05月26日)
Decision vocabulary (proposed)
| Reason |
Trigger |
targetWeight |
Stop-at-weight reached |
targetVolume |
Stop-at-volume reached |
scaleLost |
Scale disconnected mid-shot |
noScaleBlock |
Shot blocked by no-scale setting |
manual |
User-initiated stop (GHC / REST) |
profileAdvance |
Profile advanced to next step |
profileSkip |
Step skipped by "move on if" |
Open questions
- What's the WS wire format? JSON over WebSocket at
/ws/v1/shotState?
- Should decisions be emitted per-shot-step or only for the final stop?
- Volume-fallback: does the existing
_accumulatedVolume / targetVolume path work cleanly when scale drops mid-pour?
- Should the WS topic be unauthenticated (same trust model as other /ws/v1 endpoints)?
See also
|