返回 DeepSeek-Reasonix
save_writer_tail.go
根目录 / internal / agent / save_writer_tail.go
1 package agent
2
3 import (
4 "crypto/sha256"
5 "os"
6
7 "reasonix/internal/provider"
8 "reasonix/internal/store"
9 )
10
11 // classifySnapshotWrite decides the write shape for a save. Writer-bound
12 // sessions try the event-log-tail CAS first and only fall back to a full
13 // disk classification when that tail no longer matches this writer's baseline.
14 func (s *Session) classifySnapshotWrite(path string, next []provider.Message, nextDigest [sha256.Size]byte, nextVersion uint64, allowOwnedRewrite bool) (snapshotWriteDecision, error) {
15 if decision, ok := s.writerTailDecision(path, next, nextDigest, allowOwnedRewrite); ok {
16 return decision, nil
17 }
18 return s.checkSnapshotWrite(path, next, nextDigest, nextVersion, allowOwnedRewrite)
19 }
20
21 // writerTailDecision is the event-log-tail CAS for writer-bound saves.
22 // ok is false unless a live SessionWriter authority, a paired persist view,
23 // and an unchanged event-log tail (size + index revision/digest) all agree.
24 func (s *Session) writerTailDecision(path string, next []provider.Message, nextDigest [sha256.Size]byte, allowOwnedRewrite bool) (snapshotWriteDecision, bool) {
25 s.mu.RLock()
26 auth := s.writeAuth
27 normalizedDirty := s.normalizedDirty
28 damaged := s.eventLogDamaged
29 persisted := s.persistedMessages
30 viewPath := s.persistedViewPath
31 s.mu.RUnlock()
32 if auth == nil || auth.writer == nil {
33 return snapshotWriteDecision{}, false
34 }
35 if normalizedDirty || damaged || !auth.Valid() {
36 return snapshotWriteDecision{}, false
37 }
38 key := canonicalSessionSavePath(path)
39 if viewPath != key {
40 return snapshotWriteDecision{}, false
41 }
42 base, ok := auth.writer.Baseline(path)
43 if !ok || !base.RevisionKnown || base.LogTail == 0 {
44 return snapshotWriteDecision{}, false
45 }
46 idx, err := readSessionEventIndex(path)
47 if err != nil || idx == nil ||
48 idx.LogSize != base.LogTail || idx.Revision != base.Revision || idx.ContentDigest != base.ContentDigest {
49 return snapshotWriteDecision{}, false
50 }
51 info, err := os.Stat(store.SessionEventLog(path))
52 if err != nil || info.Size() != base.LogTail {
53 return snapshotWriteDecision{}, false
54 }
55 ledgerRevision, ledgerDigest, err := sessionContentRevision(path)
56 if err != nil || ledgerRevision != base.Revision || ledgerDigest != base.ContentDigest {
57 // A failed commit can reserve the next metadata revision before its WAL
58 // record lands. Treat that reservation as a changed tail identity so the
59 // full disk classifier adopts it as the next commit's base.
60 return snapshotWriteDecision{}, false
61 }
62
63 nextDigestHex := digestString(nextDigest)
64 if base.ContentDigest == nextDigestHex {
65 return snapshotWriteDecision{revision: base.Revision, upToDate: true}, true
66 }
67 if allowOwnedRewrite {
68 return snapshotWriteDecision{revision: base.Revision}, true
69 }
70 if messagesHavePrefix(next, persisted) {
71 if len(persisted) < len(next) {
72 return snapshotWriteDecision{revision: base.Revision, appendOnly: true, appendFrom: len(persisted)}, true
73 }
74 return snapshotWriteDecision{revision: base.Revision}, true
75 }
76 if messagesHavePrefixWithCompatibleSystem(next, persisted) {
77 return snapshotWriteDecision{revision: base.Revision}, true
78 }
79 return snapshotWriteDecision{}, false
80 }
81
81 lines GO