detached 697053d

fix(snapshot): address recovery review findings

commit
697053d9d5cdde5e055961e764df81e6fa20ab9c
author
brice · · 2026-07-27 14:36 UTC
parent
6bbbdf6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +10 -2
M internal/usecase/daemon/snapshot_capture.go +1 -1
@@ -2,7 +2,7 @@ package daemon
22
33 import snapcodec "github.com/bnema/vev/internal/usecase/snapshot"
44
5-// captureSession copies history tails and recovery transcripts while holding
5+// captureSnapshotState copies history tails and recovery transcripts while holding
66 // each pane lock. The returned capture contains only immutable state; encoding
77 // and persistence are deliberately deferred to snapshotEncodeWorker.
88 func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapshotCapture, bool) {
@@ -2,7 +2,7 @@ package daemon
2 2
3 import snapcodec "github.com/bnema/vev/internal/usecase/snapshot"3 import snapcodec "github.com/bnema/vev/internal/usecase/snapshot"
4 4
5-// captureSession copies history tails and recovery transcripts while holding5+// captureSnapshotState copies history tails and recovery transcripts while holding
6 // each pane lock. The returned capture contains only immutable state; encoding6 // each pane lock. The returned capture contains only immutable state; encoding
7 // and persistence are deliberately deferred to snapshotEncodeWorker.7 // and persistence are deliberately deferred to snapshotEncodeWorker.
8 func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapshotCapture, bool) {8 func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapshotCapture, bool) {
M internal/usecase/snapshot/manifest_test.go +4 -0
@@ -88,6 +88,10 @@ func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
8888 if got.Generation != manifest.Generation || got.Tabs[0].Panes[0].Tail != manifest.Tabs[0].Panes[0].Tail {
8989 t.Fatalf("round trip = %#v", got)
9090 }
91+ gotTranscript := got.Tabs[0].Panes[0].Transcript
92+ if gotTranscript.Kind != RecoveryTranscript || gotTranscript.Digest != transcript.Digest || gotTranscript.Size != uint32(len(transcript.Data)) {
93+ t.Fatalf("transcript round trip = %#v", gotTranscript)
94+ }
9195 for n := range len(encoded) {
9296 if _, err := UnmarshalManifest(encoded[:n]); err == nil {
9397 t.Fatalf("prefix %d accepted", n)
@@ -88,6 +88,10 @@ func TestManifestCodecUsesGenerationAndOrderedTypedReferences(t *testing.T) {
88 if got.Generation != manifest.Generation || got.Tabs[0].Panes[0].Tail != manifest.Tabs[0].Panes[0].Tail {88 if got.Generation != manifest.Generation || got.Tabs[0].Panes[0].Tail != manifest.Tabs[0].Panes[0].Tail {
89 t.Fatalf("round trip = %#v", got)89 t.Fatalf("round trip = %#v", got)
90 }90 }
91+ gotTranscript := got.Tabs[0].Panes[0].Transcript
92+ if gotTranscript.Kind != RecoveryTranscript || gotTranscript.Digest != transcript.Digest || gotTranscript.Size != uint32(len(transcript.Data)) {
93+ t.Fatalf("transcript round trip = %#v", gotTranscript)
94+ }
91 for n := range len(encoded) {95 for n := range len(encoded) {
92 if _, err := UnmarshalManifest(encoded[:n]); err == nil {96 if _, err := UnmarshalManifest(encoded[:n]); err == nil {
93 t.Fatalf("prefix %d accepted", n)97 t.Fatalf("prefix %d accepted", n)
M internal/usecase/snapshot/preflight.go +5 -1
@@ -220,9 +220,13 @@ func preflightPaneStructure(r *payloadReader, totals *vt.DecodeStats, blobs *uin
220220 if tail.Chunks > 1 {
221221 return fmt.Errorf("%w: tail blob role", ErrInvalidData)
222222 }
223- if _, err := preflightBlob(r, totals, blobs, budget); err != nil {
223+ transcript, err := preflightBlob(r, totals, blobs, budget)
224+ if err != nil {
224225 return err
225226 }
227+ // Transcripts are canonical history; they may contain multiple chunks or be
228+ // empty, so no blob-role assertion applies.
229+ _ = transcript
226230 return preflightProcess(r)
227231 }
228232
@@ -220,9 +220,13 @@ func preflightPaneStructure(r *payloadReader, totals *vt.DecodeStats, blobs *uin
220 if tail.Chunks > 1 {220 if tail.Chunks > 1 {
221 return fmt.Errorf("%w: tail blob role", ErrInvalidData)221 return fmt.Errorf("%w: tail blob role", ErrInvalidData)
222 }222 }
223- if _, err := preflightBlob(r, totals, blobs, budget); err != nil {223+ transcript, err := preflightBlob(r, totals, blobs, budget)
224+ if err != nil {
224 return err225 return err
225 }226 }
227+ // Transcripts are canonical history; they may contain multiple chunks or be
228+ // empty, so no blob-role assertion applies.
229+ _ = transcript
226 return preflightProcess(r)230 return preflightProcess(r)
227 }231 }
228 232

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 13

↑↓ moveenter openesc close