| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "crypto/sha256" |
| 5 | "os" |
| 6 | |
| 7 | "reasonix/internal/provider" |
| 8 | "reasonix/internal/store" |
| 9 | ) |
| 10 | |
| 11 | func (s *Session) markPersisted(path string, digest [sha256.Size]byte, version uint64, revision int64, rewriteVersion int) { |
| 12 | // Recovery-lane writes stay unpaired so writer-tail CAS stays disarmed. |
| 13 | s.setPersistedBaseline(path, digest, version, revision, true, true, rewriteVersion, nil) |
| 14 | } |
| 15 | |
| 16 | // markPersistedFromLoad anchors a loaded ledger. It is not write-verified, so |
| 17 | // it never arms the snapshot no-op. view is the on-disk transcript, or the |
| 18 | // pre-repair raw view when load-time normalization changed it. |
| 19 | func (s *Session) markPersistedFromLoad(path string, digest [sha256.Size]byte, version uint64, revision int64, rewriteVersion int, view []provider.Message) { |
| 20 | s.setPersistedBaseline(path, digest, version, revision, true, false, rewriteVersion, view) |
| 21 | } |
| 22 | |
| 23 | // markPersistedRevisionUnknown records digest+version when the meta sidecar |
| 24 | // is unreadable. Revision CAS stays disarmed until a successful save. |
| 25 | func (s *Session) markPersistedRevisionUnknown(path string, digest [sha256.Size]byte, version uint64, rewriteVersion int, view []provider.Message) { |
| 26 | s.setPersistedBaseline(path, digest, version, 0, false, false, rewriteVersion, view) |
| 27 | } |
| 28 | |
| 29 | func (s *Session) setPersistedBaseline(path string, digest [sha256.Size]byte, version uint64, revision int64, revisionKnown, saveVerified bool, rewriteVersion int, persistedView []provider.Message) { |
| 30 | s.mu.Lock() |
| 31 | s.persisted = sessionPersistState{ |
| 32 | path: canonicalSessionSavePath(path), |
| 33 | digest: digest, |
| 34 | version: version, |
| 35 | revision: revision, |
| 36 | revisionKnown: revisionKnown, |
| 37 | saveVerified: saveVerified, |
| 38 | ok: true, |
| 39 | } |
| 40 | if rewriteVersion > s.persistedRewriteVersion { |
| 41 | s.persistedRewriteVersion = rewriteVersion |
| 42 | } |
| 43 | if persistedView != nil { |
| 44 | s.persistedMessages = append([]provider.Message(nil), persistedView...) |
| 45 | s.persistedViewPath = canonicalSessionSavePath(path) |
| 46 | } else { |
| 47 | s.persistedMessages = nil |
| 48 | s.persistedViewPath = "" |
| 49 | } |
| 50 | if saveVerified { |
| 51 | s.normalizedDirty = false |
| 52 | s.rawMessages = nil |
| 53 | s.eventLogDamaged = false |
| 54 | } |
| 55 | writer := s.writeWriterLocked() |
| 56 | logTail := int64(0) |
| 57 | if info, err := os.Stat(store.SessionEventLog(path)); err == nil { |
| 58 | logTail = info.Size() |
| 59 | } |
| 60 | s.mu.Unlock() |
| 61 | if writer != nil { |
| 62 | writer.RecordBaseline(path, revision, digestString(digest), revisionKnown, logTail) |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | // writeWriterLocked returns the SessionWriter behind the bound authority. |
| 67 | // Callers hold s.mu. |
| 68 | func (s *Session) writeWriterLocked() *SessionWriter { |
| 69 | if s.writeAuth == nil { |
| 70 | return nil |
| 71 | } |
| 72 | return s.writeAuth.writer |
| 73 | } |
| 74 | |
| 75 | // syncWriterBaseline copies this session's persist ledger onto the bound |
| 76 | // writer so the first save after Bind can CAS against the event-log tail. |
| 77 | func (s *Session) syncWriterBaseline(path string) { |
| 78 | if s == nil { |
| 79 | return |
| 80 | } |
| 81 | st := s.persistState(path) |
| 82 | s.mu.RLock() |
| 83 | writer := s.writeWriterLocked() |
| 84 | s.mu.RUnlock() |
| 85 | if writer == nil || !st.ok || !st.revisionKnown { |
| 86 | return |
| 87 | } |
| 88 | logTail := int64(0) |
| 89 | if info, err := os.Stat(store.SessionEventLog(path)); err == nil { |
| 90 | logTail = info.Size() |
| 91 | } |
| 92 | writer.RecordBaseline(path, st.revision, digestString(st.digest), true, logTail) |
| 93 | } |
| 94 |