Files
Agent-System/internal/docrevision/projection_outbox.go
T

66 lines
1.8 KiB
Go

package docrevision
import "context"
// ContentProjection is the fact-source write still owed after a human ledger commit.
// The row is inserted in the same transaction as the ledger. It is removed only after
// DocGov / workspace / freeform projection succeeds.
type ContentProjection struct {
DocID string
SnapshotID int64
RevisionNo int
IdempotencyKey string
}
func (s *Service) noteContentProjectionLocked(docID string, snapshotID int64, revisionNo int, idempotencyKey string) {
if s == nil || docID == "" || snapshotID == 0 {
return
}
if s.projections == nil {
s.projections = map[string]ContentProjection{}
}
s.projections[docID] = ContentProjection{
DocID: docID,
SnapshotID: snapshotID,
RevisionNo: revisionNo,
IdempotencyKey: idempotencyKey,
}
}
// ContentProjectionPending reports an unprojected ledger head for docID.
func (s *Service) ContentProjectionPending(docID string) (ContentProjection, bool) {
if s == nil {
return ContentProjection{}, false
}
g := s.lockDoc(docID)
defer g.Release()
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.projections[docID]
return p, ok
}
// MarkContentProjected drops the in-memory intent and acks the durable row.
// A snapshot mismatch leaves a newer pending head alone.
func (s *Service) MarkContentProjected(ctx context.Context, docID string, snapshotID int64) error {
if s == nil || docID == "" || snapshotID == 0 {
return nil
}
g := s.lockDoc(docID)
s.mu.Lock()
if p, ok := s.projections[docID]; ok && p.SnapshotID == snapshotID {
delete(s.projections, docID)
}
usePG := s.usingPG()
s.mu.Unlock()
g.Release()
if !usePG {
return nil
}
_, err := s.pool.Exec(ctx, `
UPDATE document_content_projections
SET status = 'projected', projected_at = now()
WHERE doc_id = $1 AND snapshot_id = $2 AND status = 'pending'`, docID, snapshotID)
return err
}