v0.1.0 7deb448

recovery.go RawHistoryFolder

1package domain
2
3import (
4	"encoding/hex"
5	"errors"
6	"fmt"
7	"io"
8	"slices"
9	"strings"
10)
11
12// IncarnationID permanently distinguishes uses of the same session name.
13type IncarnationID [16]byte
14
15// CheckpointRef identifies one immutable snapshot manifest.
16type CheckpointRef struct {
17	Generation     uint64
18	ManifestDigest [32]byte
19}
20
21// CatalogueRecord is the durable source of truth for one named session.
22type CatalogueRecord struct {
23	Name           string
24	IncarnationID  IncarnationID
25	Cwd            string
26	CreatedAt      int64
27	UpdatedAt      int64
28	LastUsedSeq    uint64
29	TabNames       []string
30	Committed      *CheckpointRef
31	DegradedReason string
32}
33
34// Equal reports canonical value equality between checkpoint references.
35func (r *CheckpointRef) Equal(other *CheckpointRef) bool {
36	if r == nil || other == nil {
37		return r == nil && other == nil
38	}
39	return *r == *other
40}
41
42// Equal reports canonical value equality, including ordered tab names and
43// pointed-to checkpoint values rather than pointer identity.
44func (r CatalogueRecord) Equal(other CatalogueRecord) bool {
45	return r.Name == other.Name && r.IncarnationID == other.IncarnationID && r.Cwd == other.Cwd &&
46		r.CreatedAt == other.CreatedAt && r.UpdatedAt == other.UpdatedAt && r.LastUsedSeq == other.LastUsedSeq &&
47		slices.Equal(r.TabNames, other.TabNames) && r.Committed.Equal(other.Committed) &&
48		r.DegradedReason == other.DegradedReason
49}
50
51// CatalogueMetadataUpdate changes mutable runtime metadata for one catalogue
52// incarnation without carrying authority-owned recovery or checkpoint fields.
53type CatalogueMetadataUpdate struct {
54	Name          string
55	IncarnationID IncarnationID
56	Cwd           *string
57	UpdatedAt     *int64
58	LastUsedSeq   *uint64
59	TabNames      *[]string
60}
61
62// MetadataUpdate returns a complete mutable-metadata update for r. Catalogue
63// implementations retain all recovery and checkpoint fields they own.
64func (r CatalogueRecord) MetadataUpdate() CatalogueMetadataUpdate {
65	cwd, updatedAt, lastUsedSeq := r.Cwd, r.UpdatedAt, r.LastUsedSeq
66	tabNames := append([]string(nil), r.TabNames...)
67	return CatalogueMetadataUpdate{
68		Name: r.Name, IncarnationID: r.IncarnationID,
69		Cwd: &cwd, UpdatedAt: &updatedAt, LastUsedSeq: &lastUsedSeq, TabNames: &tabNames,
70	}
71}
72
73// NewIncarnationID reads a new non-zero identity from r.
74func NewIncarnationID(r io.Reader) (IncarnationID, error) {
75	var id IncarnationID
76	if _, err := io.ReadFull(r, id[:]); err != nil {
77		return id, err
78	}
79	if id == (IncarnationID{}) {
80		return id, errors.New("zero incarnation ID")
81	}
82	return id, nil
83}
84
85func (id IncarnationID) String() string {
86	return hex.EncodeToString(id[:])
87}
88
89// MarshalText emits the canonical lowercase hexadecimal identity.
90func (id IncarnationID) MarshalText() ([]byte, error) {
91	if id == (IncarnationID{}) {
92		return nil, errors.New("zero incarnation ID")
93	}
94	return []byte(id.String()), nil
95}
96
97// UnmarshalText accepts only a canonical lowercase non-zero identity.
98func (id *IncarnationID) UnmarshalText(text []byte) error {
99	if len(text) != 32 || strings.ToLower(string(text)) != string(text) {
100		return errors.New("invalid incarnation ID")
101	}
102	var decoded IncarnationID
103	if _, err := hex.Decode(decoded[:], text); err != nil {
104		return fmt.Errorf("invalid incarnation ID: %w", err)
105	}
106	if decoded == (IncarnationID{}) {
107		return errors.New("zero incarnation ID")
108	}
109	*id = decoded
110	return nil
111}
112
113// Validate rejects catalogue states that cannot be restored deterministically.
114func (r CatalogueRecord) Validate() error {
115	if err := ValidateSessionName(r.Name); err != nil {
116		return err
117	}
118	if r.IncarnationID == (IncarnationID{}) {
119		return errors.New("zero catalogue incarnation ID")
120	}
121
122	if r.DegradedReason != "" && r.Committed == nil {
123		return errors.New("broken session has no committed checkpoint")
124	}
125	if r.Committed != nil {
126		if r.Committed.Generation == 0 {
127			return errors.New("zero checkpoint generation")
128		}
129		if r.Committed.ManifestDigest == ([32]byte{}) {
130			return errors.New("zero checkpoint manifest digest")
131		}
132	}
133	return nil
134}

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 12

Tags 11

↑↓ moveenter openesc close