返回 DeepSeek-Reasonix
types.go
根目录 / internal / checkpoint / types.go
1 package checkpoint
2
3 import (
4 "time"
5
6 fileenc "reasonix/internal/fileutil/encoding"
7 )
8
9 // Schema versions for on-disk checkpoint JSON.
10 const (
11 SchemaV1 = 1
12 SchemaV2 = 2
13 SchemaV3 = 3
14 )
15
16 // Coverage describes how completely a checkpoint captured workspace mutations.
17 type Coverage string
18
19 const (
20 CoverageComplete Coverage = "complete"
21 CoveragePartial Coverage = "partial"
22 CoverageNone Coverage = "none"
23 CoverageLegacy Coverage = "legacy"
24 )
25
26 // CoverageGap records why a checkpoint cannot guarantee full file restore.
27 type CoverageGap struct {
28 Reason string `json:"reason"`
29 Detail string `json:"detail,omitempty"`
30 Tool string `json:"tool,omitempty"`
31 Path string `json:"path,omitempty"`
32 }
33
34 // Common coverage-gap reasons.
35 const (
36 GapBashSideEffect = "bash_side_effect"
37 GapHookWrite = "hook_write"
38 GapMCPExternal = "mcp_external"
39 GapScratch = "scratch"
40 GapOutsideWorkspace = "outside_workspace"
41 GapSymlink = "symlink"
42 GapHardlink = "hardlink"
43 GapUnreadable = "unreadable"
44 GapOversized = "oversized"
45 GapBackgroundWriter = "background_writer_cross_turn"
46 GapLegacyUnverified = "legacy_unverified"
47 GapCaptureFailed = "capture_failed"
48 GapExpiredPayload = "expired_file_payload"
49 )
50
51 // HasProjectCoverageGap reports a gap that can prevent restoring workspace
52 // files. Scratch-only gaps do not.
53 func HasProjectCoverageGap(gaps []CoverageGap) bool {
54 for _, gap := range gaps {
55 if gap.Reason != "" && gap.Reason != GapScratch {
56 return true
57 }
58 }
59 return false
60 }
61
62 // CaptureSource identifies how a preimage was obtained.
63 type CaptureSource string
64
65 const (
66 CapturePreviewer CaptureSource = "previewer"
67 CaptureBeforeMutation CaptureSource = "before_mutation"
68 CaptureAfterMutation CaptureSource = "after_mutation"
69 CaptureLegacy CaptureSource = "legacy"
70 CaptureManual CaptureSource = "manual"
71 )
72
73 // FileRevision is the v2 per-file preimage plus last Reasonix-owned after fingerprint.
74 type FileRevision struct {
75 Path string `json:"path"`
76 Existed bool `json:"existed"`
77 Mode uint32 `json:"mode,omitempty"`
78 Encoding *fileenc.Kind `json:"encoding,omitempty"`
79 SHA256 string `json:"sha256,omitempty"`
80 BlobRef string `json:"blobRef,omitempty"`
81 CaptureSource CaptureSource `json:"captureSource,omitempty"`
82 // AfterSHA256 is the fingerprint of the file after the last Reasonix-owned write.
83 // Empty means "no after fingerprint" (legacy or never observed).
84 AfterSHA256 string `json:"afterSha256,omitempty"`
85 AfterExisted *bool `json:"afterExisted,omitempty"`
86 AfterMode uint32 `json:"afterMode,omitempty"`
87 // Inline content is only used for in-memory stores without a blob dir, and
88 // for legacy v1 migration paths. Persisted v2 checkpoints prefer BlobRef.
89 Content *string `json:"content,omitempty"`
90 }
91
92 // MutationRecord tracks one observed mutation for ownership and conflict detection.
93 type MutationRecord struct {
94 Seq int64 `json:"seq"`
95 Path string `json:"path"`
96 Tool string `json:"tool,omitempty"`
97 Source CaptureSource `json:"source,omitempty"`
98 WriterID string `json:"writerId,omitempty"`
99 Turn int `json:"turn"`
100 BeforeSHA string `json:"beforeSha,omitempty"`
101 AfterSHA string `json:"afterSha,omitempty"`
102 Time time.Time `json:"time,omitempty"`
103 }
104
105 // ActiveWriter describes a background writer that still owns open mutations.
106 type ActiveWriter struct {
107 ID string `json:"id"`
108 Turn int `json:"turn"`
109 StartedAt time.Time `json:"startedAt,omitempty"`
110 Kind string `json:"kind,omitempty"` // "background_subagent", ...
111 }
112
113 // RewindScope selects what a rewind restores. Mirrors control.RewindScope without
114 // importing control (checkpoint is a lower layer).
115 type RewindScope int
116
117 const (
118 RewindCode RewindScope = iota // files only
119 RewindConversation // message log only
120 RewindBoth // both
121 )
122
123 // RewindConflict describes a file that cannot be safely restored.
124 type RewindConflict struct {
125 Path string `json:"path"`
126 Reason string `json:"reason"`
127 CheckpointSHA string `json:"checkpointSha,omitempty"`
128 LastOwnedSHA string `json:"lastOwnedSha,omitempty"`
129 CurrentSHA string `json:"currentSha,omitempty"`
130 CheckpointMode uint32 `json:"checkpointMode,omitempty"`
131 CurrentMode uint32 `json:"currentMode,omitempty"`
132 CurrentExisted bool `json:"currentExisted"`
133 CheckpointExist bool `json:"checkpointExisted"`
134 }
135
136 // Conflict reason constants.
137 const (
138 ConflictManualEdit = "manual_edit"
139 ConflictExternalChange = "external_change"
140 ConflictDeletedRecreate = "deleted_and_recreated"
141 ConflictTypeChange = "type_change"
142 ConflictModeChange = "mode_change"
143 ConflictMissingPayload = "missing_payload"
144 ConflictPathUnsafe = "path_unsafe"
145 ConflictBusyWriter = "active_writer"
146 ConflictStalePlan = "stale_plan"
147 ConflictBoundaryInvalid = "boundary_invalid"
148 ConflictCoverageLegacy = "legacy_unverified"
149 ConflictExpired = "expired_payload"
150 )
151
152 // FileStage records per-file progress through a rewind transaction.
153 type FileStage struct {
154 Path string `json:"path"`
155 Phase string `json:"phase"` // precheck|prepare|commit|compensate|done|skipped
156 Action string `json:"action,omitempty"` // write|delete|restore
157 Error string `json:"error,omitempty"`
158 Compensated bool `json:"compensated,omitempty"`
159 CompError string `json:"compensateError,omitempty"`
160 }
161
162 // RewindPlan is the structured precheck result returned to the controller/UI.
163 type RewindPlan struct {
164 PlanID string `json:"planId"`
165 Turn int `json:"turn"`
166 Scope RewindScope `json:"scope"`
167 Coverage Coverage `json:"coverage"`
168 CoverageGaps []CoverageGap `json:"coverageGaps,omitempty"`
169 Legacy bool `json:"legacy,omitempty"`
170 ExpiredFilePayload bool `json:"expiredFilePayload,omitempty"`
171 CanFiles bool `json:"canFiles"`
172 CanConversation bool `json:"canConversation"`
173 DisabledReason string `json:"disabledReason,omitempty"`
174 Conflicts []RewindConflict `json:"conflicts,omitempty"`
175 Files []string `json:"files,omitempty"`
176 FileCount int `json:"fileCount"`
177 ActiveWriters []ActiveWriter `json:"activeWriters,omitempty"`
178 SessionRevision int64 `json:"sessionRevision"`
179 WorkspaceToken string `json:"workspaceToken,omitempty"`
180 BoundaryIndex int `json:"boundaryIndex,omitempty"`
181 HasBoundary bool `json:"hasBoundary"`
182 CreatedAt time.Time `json:"createdAt"`
183 ConversationAction string `json:"conversationAction,omitempty"`
184 // Single-file revert extras.
185 Path string `json:"path,omitempty"`
186 ConflictResolution string `json:"conflictResolution,omitempty"`
187 }
188
189 // RewindResult is returned after commit or undo.
190 type RewindResult struct {
191 OK bool `json:"ok"`
192 TransactionID string `json:"transactionId,omitempty"`
193 UndoAvailable bool `json:"undoAvailable"`
194 Written []string `json:"written,omitempty"`
195 Deleted []string `json:"deleted,omitempty"`
196 Files []FileStage `json:"files,omitempty"`
197 ConversationOK bool `json:"conversationOk,omitempty"`
198 ConversationForked bool `json:"conversationForked,omitempty"`
199 OperationID string `json:"operationId,omitempty"`
200 Branch string `json:"branch,omitempty"`
201 Partial bool `json:"partial,omitempty"`
202 Error string `json:"error,omitempty"`
203 Conflicts []RewindConflict `json:"conflicts,omitempty"`
204 Coverage Coverage `json:"coverage,omitempty"`
205 CoverageGaps []CoverageGap `json:"coverageGaps,omitempty"`
206 }
207
208 // ConflictResolution chooses how to handle a single-file conflict on commit.
209 type ConflictResolution string
210
211 const (
212 // ResolveKeepCurrent leaves the on-disk file alone.
213 ResolveKeepCurrent ConflictResolution = "keep_current"
214 // ResolveOverwriteCheckpoint force-writes the checkpoint preimage after
215 // the user explicitly confirmed in the single-file UI.
216 ResolveOverwriteCheckpoint ConflictResolution = "overwrite_checkpoint"
217 )
218
219 // TransactionState is the durable lifecycle of a rewind transaction.
220 type TransactionState string
221
222 const (
223 TxPrepared TransactionState = "prepared"
224 TxCommitting TransactionState = "committing"
225 TxCommitted TransactionState = "committed"
226 TxAborted TransactionState = "aborted"
227 TxUndone TransactionState = "undone"
228 )
229
230 // TransactionTarget is one file's forward/restore payload inside a transaction.
231 type TransactionTarget struct {
232 Path string `json:"path"`
233 AbsPath string `json:"absPath"`
234 // Restore: what to write (or delete) to reach checkpoint state.
235 RestoreExisted bool `json:"restoreExisted"`
236 RestoreMode uint32 `json:"restoreMode,omitempty"`
237 RestoreSHA string `json:"restoreSha,omitempty"`
238 RestoreBlob string `json:"restoreBlob,omitempty"`
239 RestoreInline []byte `json:"restoreInline,omitempty"`
240 RestoreEncoding *fileenc.Kind `json:"restoreEncoding,omitempty"`
241 // Forward: current on-disk state at prepare time (for compensate / undo).
242 ForwardExisted bool `json:"forwardExisted"`
243 ForwardMode uint32 `json:"forwardMode,omitempty"`
244 ForwardSHA string `json:"forwardSha,omitempty"`
245 ForwardBlob string `json:"forwardBlob,omitempty"`
246 ForwardInline []byte `json:"forwardInline,omitempty"`
247 // Staging paths are transaction-unique siblings of AbsPath so publish and
248 // compensation never cross filesystems.
249 PublishTmp string `json:"publishTmp,omitempty"`
250 BackupPath string `json:"backupPath,omitempty"`
251 // Published is a durable "may have published" intent. It is persisted before
252 // the first rename so crash recovery conservatively inspects this target.
253 Published bool `json:"published"`
254 // Action describes the intended commit action.
255 Action string `json:"action"` // write|delete
256 }
257
258 // TransactionManifest is the durable description of a rewind/undo transaction.
259 type TransactionManifest struct {
260 SchemaVersion int `json:"schemaVersion"`
261 ID string `json:"id"`
262 SessionID string `json:"sessionId,omitempty"`
263 WorkspaceRoot string `json:"workspaceRoot"`
264 State TransactionState `json:"state"`
265 Kind string `json:"kind"` // rewind|undo|file_revert
266 Turn int `json:"turn"`
267 Scope RewindScope `json:"scope"`
268 Path string `json:"path,omitempty"` // single-file
269 CreatedAt time.Time `json:"createdAt"`
270 UpdatedAt time.Time `json:"updatedAt"`
271 SessionRevision int64 `json:"sessionRevision"`
272 WorkspaceToken string `json:"workspaceToken,omitempty"`
273 Coverage Coverage `json:"coverage,omitempty"`
274 CoverageGaps []CoverageGap `json:"coverageGaps,omitempty"`
275 Targets []TransactionTarget `json:"targets,omitempty"`
276 // ConversationForward holds a JSON-encoded message snapshot when conversation
277 // is part of the transaction. Opaque to this package so it can stay free of
278 // provider imports; the controller supplies and applies it.
279 ConversationForward []byte `json:"conversationForward,omitempty"`
280 BoundaryIndex int `json:"boundaryIndex,omitempty"`
281 HasBoundary bool `json:"hasBoundary"`
282 ConversationAction string `json:"conversationAction,omitempty"`
283 // TruncateFrom is the checkpoint turn to drop after a successful conversation rewind.
284 TruncateFrom int `json:"truncateFrom,omitempty"`
285 // CheckpointTurns holds serialized future checkpoints for undo.
286 CheckpointBackup []byte `json:"checkpointBackup,omitempty"`
287 // ParentTransaction is set for undo transactions that reverse a committed rewind.
288 ParentTransaction string `json:"parentTransaction,omitempty"`
289 Error string `json:"error,omitempty"`
290 }
291
292 // Default retention and soft byte budget for file payloads. Both v3 raw
293 // preimages and legacy blobs use the same budget value in their own stores.
294 const (
295 DefaultRetainCheckpoints = 100
296 DefaultBlobQuotaBytes = 1 << 30 // 1 GiB
297 DefaultMaxFileBytes = 32 << 20 // 32 MiB per file capture
298 )
299
299 lines GO