135 lines
3.9 KiB
Go
135 lines
3.9 KiB
Go
package docrevision
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// Service is the document revision ledger.
|
|
type Service struct {
|
|
pool *pgxpool.Pool
|
|
mu sync.Mutex
|
|
|
|
// docLocks 按 doc_id 分锁(T20 B3);hydrate/commit 只阻塞同文档。
|
|
docLocks *docLockIndex
|
|
|
|
blobs map[string]*ContentBlob
|
|
snapshots map[int64]*Snapshot
|
|
versions map[int64]*Version
|
|
events map[int64]*ChangeEvent
|
|
candidates map[int64]*AgentCandidate
|
|
reports map[int64]*ChangeReport
|
|
roots map[string]*DocRootPointers
|
|
idemp map[string]int64 // idempotency_key -> event id
|
|
projections map[string]ContentProjection
|
|
|
|
nextSnapID int64
|
|
nextVerID int64
|
|
nextEvtID int64
|
|
nextCandID int64
|
|
nextRepID int64
|
|
|
|
// Outbox hooks (Wave 5); optional.
|
|
OnOutbox func(ctx context.Context, eventType string, payload map[string]any) error
|
|
|
|
// OnFinalArtifact is invoked after a terminal ledger persist (agent_commit / version_sealed).
|
|
OnFinalArtifact func(ctx context.Context, kind, docID string, projectID, snapshotID int64, eventID string, payload []byte, sealedAt *time.Time)
|
|
|
|
// OnFinalArtifactTX writes FAV staging/outbox inside persistAgentCommitLocked.
|
|
OnFinalArtifactTX func(ctx context.Context, tx pgx.Tx, kind, docID string, projectID, snapshotID int64, eventID string, payload []byte, sealedAt *time.Time) error
|
|
|
|
// ImpactResolver returns deterministic downstream impacts (lineage); optional.
|
|
ImpactResolver func(ctx context.Context, docID string) []DocImpact
|
|
|
|
// CoverageResolver classifies trace-index coverage for a formal commit.
|
|
// When unset, reports stay index_pending and must not claim no impact.
|
|
CoverageResolver func(ctx context.Context, docID string) string
|
|
|
|
// NarrativeFetcher optionally loads typed PI narrative JSON when
|
|
// PI_CHANGE_REPORT_NARRATIVE is on and ReportJSON is empty.
|
|
NarrativeFetcher func(ctx context.Context, spec NarrativeFetchSpec) (string, error)
|
|
|
|
// Root pointer projection (controlled→docgov, freeform→freedoc); set by composition root.
|
|
rootPointers RootPointerProjector
|
|
|
|
// Deliverable I/O (path owner via workspace.DeliverableKey); set by composition root.
|
|
deliverables DeliverableStore
|
|
|
|
// DataDir is the environment data root used to resolve relative WorkDir paths.
|
|
DataDir string
|
|
|
|
// StoredDataRoot / HostDataRoot rewrite container paths (e.g. /app/data)
|
|
// to a host data root for CLI live mode. Empty means no rewrite.
|
|
StoredDataRoot string
|
|
HostDataRoot string
|
|
|
|
// Feature flags
|
|
LedgerWrite bool
|
|
LedgerRead bool
|
|
AgentCommit bool
|
|
DiffUI bool
|
|
Notify bool
|
|
}
|
|
|
|
func NewService(pool *pgxpool.Pool) *Service {
|
|
return &Service{
|
|
pool: pool,
|
|
docLocks: newDocLockIndex(),
|
|
blobs: map[string]*ContentBlob{},
|
|
snapshots: map[int64]*Snapshot{},
|
|
versions: map[int64]*Version{},
|
|
events: map[int64]*ChangeEvent{},
|
|
candidates: map[int64]*AgentCandidate{},
|
|
reports: map[int64]*ChangeReport{},
|
|
roots: map[string]*DocRootPointers{},
|
|
idemp: map[string]int64{},
|
|
projections: map[string]ContentProjection{},
|
|
LedgerWrite: true,
|
|
LedgerRead: true,
|
|
AgentCommit: true,
|
|
DiffUI: true,
|
|
Notify: true,
|
|
}
|
|
}
|
|
|
|
func (s *Service) EnsureRoot(docID, kind string, projectID int64, lifecycle string) *DocRootPointers {
|
|
g := s.lockDoc(docID)
|
|
defer g.Release()
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
if r, ok := s.roots[docID]; ok {
|
|
return r
|
|
}
|
|
r := &DocRootPointers{
|
|
DocID: docID,
|
|
Kind: kind,
|
|
LifecycleState: lifecycle,
|
|
ProjectID: projectID,
|
|
RevisionSchemaVer: SchemaVersion,
|
|
}
|
|
s.roots[docID] = r
|
|
return r
|
|
}
|
|
|
|
func requireDocID(docID string) error {
|
|
if strings.TrimSpace(docID) == "" {
|
|
return ErrInvalidDocID
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NarrativeFetchSpec is the immutable input the PI narrator may see.
|
|
type NarrativeFetchSpec struct {
|
|
FromSHA string
|
|
ToSHA string
|
|
RuleRisk string
|
|
InputDigest string
|
|
Impacts []DocImpact
|
|
Hunks []DiffHunk
|
|
}
|