返回 DeepSeek-Reasonix
session_display_pager_checkpoint_progress.go
根目录 / internal / agent / session_display_pager_checkpoint_progress.go
1 package agent
2
3 import (
4 "context"
5 "crypto/sha256"
6 "database/sql"
7 "encoding"
8 "encoding/json"
9 "errors"
10 "hash"
11 "os"
12 )
13
14 type checkpointPagerProgress struct {
15 Header SessionDisplayIndex `json:"header"`
16 Users int `json:"users"`
17 Hash []byte `json:"hash"`
18 }
19
20 // The rebuild owner already fenced the entire source generation and holds its
21 // cross-process lock. Entries, offset, turn state and semantic digest always
22 // commit together. Nothing in this staging database is a readable generation.
23 func saveCheckpointPagerProgress(ctx context.Context, tx *sql.Tx, idx SessionDisplayIndex, users int, digest []byte) error {
24 body, err := json.Marshal(checkpointPagerProgress{Header: idx, Users: users, Hash: digest})
25 if err != nil {
26 return err
27 }
28 _, err = tx.ExecContext(ctx, `INSERT OR REPLACE INTO metadata VALUES('checkpoint_progress',?)`, string(body))
29 return err
30 }
31
32 func restoreCheckpointPagerProgress(ctx context.Context, db *sql.DB, idx *SessionDisplayIndex, users *int, digest hash.Hash) error {
33 var raw string
34 err := db.QueryRowContext(ctx, `SELECT value FROM metadata WHERE key='checkpoint_progress'`).Scan(&raw)
35 if errors.Is(err, sql.ErrNoRows) {
36 // New staging is empty; a damaged/missing progress row cannot leave
37 // entries that a fresh zero-offset build could accidentally reuse.
38 _, err = db.ExecContext(ctx, `DELETE FROM entries; DELETE FROM metadata`)
39 return err
40 }
41 if err != nil {
42 return err
43 }
44 var progress checkpointPagerProgress
45 valid := json.Unmarshal([]byte(raw), &progress) == nil && progress.Header.SchemaVersion == SessionDisplayIndexSchemaVersion &&
46 progress.Header.Entries == nil && progress.Header.MessageCount >= 0 && progress.Header.TranscriptSize >= 0 &&
47 progress.Users >= 0 && progress.Users <= progress.Header.MessageCount && progress.Header.AuthoredTurns >= 0 &&
48 progress.Header.AuthoredTurns <= progress.Header.MessageCount
49 var count int
50 var end int64
51 if err := db.QueryRowContext(ctx, `SELECT COUNT(*),COALESCE(MAX(offset+length),0) FROM entries`).Scan(&count, &end); err != nil {
52 return err
53 }
54 valid = valid && count == progress.Header.MessageCount && end == progress.Header.TranscriptSize
55 if valid {
56 valid = digest.(encoding.BinaryUnmarshaler).UnmarshalBinary(progress.Hash) == nil
57 }
58 if !valid {
59 // Invalid disposable progress is rebuilt from the original source; it
60 // never authorizes accepting a prefix or changing the authoritative file.
61 digest.Reset()
62 _, err := db.ExecContext(ctx, `DELETE FROM entries; DELETE FROM metadata`)
63 return err
64 }
65 *idx, *users = progress.Header, progress.Users
66 return nil
67 }
68
69 func publishCheckpointDisplayPager(ctx context.Context, db *sql.DB, source string, f *os.File, fingerprint string, idx SessionDisplayIndex, hasher hash.Hash) error {
70 var digest [sha256.Size]byte
71 copy(digest[:], hasher.Sum(nil))
72 idx.ContentDigest = digestString(digest)
73 identity, known, err := SessionContentIdentity(source)
74 if err != nil {
75 return err
76 }
77 if known {
78 if identity.DigestHex != idx.ContentDigest {
79 return errors.New("checkpoint does not match authoritative identity")
80 }
81 idx.Revision, idx.RevisionKnown = identity.Revision, identity.RevisionKnown
82 }
83 if err := validateCheckpointPagerFile(source, f, fingerprint); err != nil {
84 return err
85 }
86 body, err := json.Marshal(idx)
87 if err != nil {
88 return err
89 }
90 _, err = db.ExecContext(ctx, `INSERT OR REPLACE INTO metadata VALUES('source',?),('header',?)`, fingerprint, string(body))
91 return err
92 }
93
93 lines GO