Files
weichenandCursor 432884911f feat(docrevision): attach coverage status to formal human revisions
Unknown, partial, and failed coverage stay distinct from no known dependencies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-20 15:35:07 +00:00

362 lines
14 KiB
Go

// Package docrevision implements controlled/freeform document Version + Revision ledger.
package docrevision
import (
"errors"
"time"
)
const (
SchemaVersion = 1
VersionOpen = "open"
VersionSealed = "sealed"
OriginTemplate = "template"
OriginAgent = "agent"
OriginAgentSquad = "agent_squad"
OriginMigration = "migration"
SourceTemplate = "template"
SourceHuman = "human"
SourceAgent = "agent"
SourceAgentSquad = "agent_squad"
SourceMigration = "migration"
SourceMigrationRepair = "migration_repair"
SourceSystem = "system"
ChangeHumanRevision = "human_revision"
ChangeAgentCommit = "agent_commit"
ChangeAgentNoop = "agent_noop"
ChangeImport = "import"
ChangeRestoreAsNew = "restore_as_new"
ChangeMigrationRepair = "migration_repair"
ActorHuman = "human"
ActorAgent = "agent"
ActorAgentSquad = "agent_squad"
ActorTemplate = "template"
ActorMigration = "migration"
ActorSystem = "system"
CandidateReady = "ready"
CandidateNeedsConfirmation = "needs_confirmation"
CandidateCommitted = "committed"
CandidateRejected = "rejected"
CandidateStale = "stale"
CandidateQualityFailed = "quality_failed"
RiskLow = "low"
RiskMedium = "medium"
RiskHigh = "high"
RiskCritical = "critical"
AnalysisComplete = "complete"
AnalysisFallback = "fallback"
AnalysisUnassessed = "unassessed"
AnalysisCorrected = "corrected"
// Trace coverage carried on a formal commit report. These are not PI analysis statuses.
ImpactConfirmed = "confirmed_impact"
ImpactPossible = "possible_impact"
ImpactNoKnown = "no_known_dependencies"
ImpactIndexPending = "index_pending"
ImpactPartial = "partial"
ImpactFailed = "failed"
// Report authority separates a candidate estimate from the report written at formal commit.
ReportProposed = "proposed"
ReportAuthoritative = "authoritative"
// Proposed reports stay actionable only while active.
ReportActive = "active"
ReportSuperseded = "superseded"
ReportRejected = "rejected"
ReportExpired = "expired"
ReportCommitted = "committed"
ErrCodeHeadConflict = "DOCUMENT_HEAD_CONFLICT"
ErrCodeEditForbidden = "DOCUMENT_EDIT_FORBIDDEN"
ErrCodeBusy = "DOCUMENT_BUSY"
ErrCodeCandidateStale = "AGENT_CANDIDATE_STALE"
ErrCodeReportInvalid = "CHANGE_REPORT_INVALID"
ErrCodeLifecycleLocked = "DOCUMENT_LIFECYCLE_LOCKED"
ErrCodeTooLarge = "DOCUMENT_TOO_LARGE"
ErrCodeNeedsConfirmation = "AGENT_NEEDS_CONFIRMATION"
ErrCodeNotFound = "DOCUMENT_REVISION_NOT_FOUND"
ErrCodeInvalidDocID = "DOCUMENT_REVISION_INVALID_DOC_ID"
)
var (
ErrNotFound = errors.New("docrevision: not found")
ErrInvalidDocID = errors.New("docrevision: invalid doc_id")
ErrHeadConflict = errors.New("docrevision: head conflict")
ErrLifecycleLocked = errors.New("docrevision: lifecycle locked")
ErrCandidateStale = errors.New("docrevision: candidate stale")
ErrReportInvalid = errors.New("docrevision: change report invalid")
ErrNeedsConfirmation = errors.New("docrevision: high/critical needs confirmation")
ErrIdempotencyConflict = errors.New("docrevision: idempotency conflict")
ErrForbidden = errors.New("docrevision: forbidden")
ErrTooLarge = errors.New("docrevision: document too large")
ErrNoOpenVersion = errors.New("docrevision: no open version")
)
const MaxMarkdownBytes = 8 << 20 // 8 MiB
// ActorSnapshot is an immutable attribution payload frozen at write time.
type ActorSnapshot struct {
DisplayName string `json:"display_name,omitempty"`
Role string `json:"role,omitempty"`
Duty string `json:"duty,omitempty"`
AgentKey string `json:"agent_key,omitempty"`
SquadName string `json:"squad_name,omitempty"`
Model string `json:"model,omitempty"`
ConfigVer string `json:"config_version,omitempty"`
Extra map[string]string `json:"extra,omitempty"`
}
type ContentBlob struct {
ContentSHA256 string `json:"content_sha256"`
ContentMarkdown string `json:"content_markdown"`
ByteSize int64 `json:"byte_size"`
CreatedAt time.Time `json:"created_at"`
}
type BlockNode struct {
ID string `json:"id"`
Type string `json:"type"`
StartOffset int `json:"start_offset"`
EndOffset int `json:"end_offset"`
HeadingPath string `json:"heading_path,omitempty"`
BusinessKey string `json:"business_key,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
Order int `json:"order"`
}
type Snapshot struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
ContentSHA256 string `json:"content_sha256"`
ParentSnapshotID *int64 `json:"parent_snapshot_id,omitempty"`
BlockMap []BlockNode `json:"block_map"`
SourceKind string `json:"source_kind"`
ActorUserID *int64 `json:"actor_user_id,omitempty"`
ActorRef string `json:"actor_ref,omitempty"`
ActorSnapshot ActorSnapshot `json:"actor_snapshot"`
SourceRunID string `json:"source_run_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type Version struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
VersionNo int `json:"version_no"`
Status string `json:"status"`
OriginKind string `json:"origin_kind"`
BaselineSnapshotID int64 `json:"baseline_snapshot_id"`
HeadSnapshotID int64 `json:"head_snapshot_id"`
HumanRevisionCount int `json:"human_revision_count"`
SourceRunID string `json:"source_run_id,omitempty"`
ChangeReportID *int64 `json:"change_report_id,omitempty"`
OpenedAt time.Time `json:"opened_at"`
SealedAt *time.Time `json:"sealed_at,omitempty"`
}
type ChangeEvent struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
VersionID int64 `json:"version_id"`
EventNo int `json:"event_no"`
FromSnapshotID *int64 `json:"from_snapshot_id,omitempty"`
ToSnapshotID int64 `json:"to_snapshot_id"`
ChangeKind string `json:"change_kind"`
ActorKind string `json:"actor_kind"`
ActorUserID *int64 `json:"actor_user_id,omitempty"`
ActorRef string `json:"actor_ref,omitempty"`
ActorSnapshot ActorSnapshot `json:"actor_snapshot"`
ChangeSet []DiffHunk `json:"change_set,omitempty"`
EditSessionID string `json:"edit_session_id,omitempty"`
RequestID string `json:"request_id,omitempty"`
IdempotencyKey string `json:"idempotency_key"`
CreatedAt time.Time `json:"created_at"`
}
type AgentCandidate struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
InputSnapshotID int64 `json:"input_snapshot_id"`
OutputSnapshotID *int64 `json:"output_snapshot_id,omitempty"`
RunID string `json:"run_id"`
ProposalID string `json:"proposal_id,omitempty"`
Status string `json:"status"`
AuthorizedScope string `json:"authorized_scope,omitempty"`
QualityResult string `json:"quality_result,omitempty"`
ChangeReportID *int64 `json:"change_report_id,omitempty"`
ActorKind string `json:"actor_kind"`
ActorSnapshot ActorSnapshot `json:"actor_snapshot"`
CreatedAt time.Time `json:"created_at"`
DecidedAt *time.Time `json:"decided_at,omitempty"`
}
type ChangeReport struct {
ID int64 `json:"id"`
DocID string `json:"doc_id"`
FromSnapshotID int64 `json:"from_snapshot_id"`
ToSnapshotID int64 `json:"to_snapshot_id"`
SchemaVersion int `json:"schema_version"`
Summary []string `json:"summary"`
RiskLevel string `json:"risk_level"`
RiskItems []RiskItem `json:"risk_items"`
ProjectImpacts map[string]any `json:"project_impacts"`
DocumentImpacts []DocImpact `json:"document_impacts"`
OpenQuestions []string `json:"open_questions"`
AnalysisStatus string `json:"analysis_status"`
ModelSnapshot string `json:"model_snapshot,omitempty"`
PromptVersion string `json:"prompt_version,omitempty"`
DeterministicFacts map[string]any `json:"deterministic_facts"`
CorrectsReportID *int64 `json:"corrects_report_id,omitempty"`
// Authority is proposed on a candidate estimate and authoritative on the formal-commit successor.
Authority string `json:"authority,omitempty"`
// Lifecycle is active, superseded, rejected, expired, or committed.
Lifecycle string `json:"lifecycle,omitempty"`
// SuccessorReportID points from a committed proposed report to its authoritative report.
SuccessorReportID *int64 `json:"successor_report_id,omitempty"`
RawJSON string `json:"-"`
CreatedAt time.Time `json:"created_at"`
}
type RiskItem struct {
Category string `json:"category"`
Description string `json:"description"`
Mitigation string `json:"mitigation,omitempty"`
}
type DocImpact struct {
DocID string `json:"doc_id"`
ImpactType string `json:"impact_type"`
Reason string `json:"reason"`
RecommendedAction string `json:"recommended_action,omitempty"`
}
type DiffHunk struct {
ID string `json:"id"`
Kind string `json:"kind"` // insert|delete|modify|move
Section string `json:"section,omitempty"`
BeforeText string `json:"before_text,omitempty"`
AfterText string `json:"after_text,omitempty"`
BlockIDBefore string `json:"block_id_before,omitempty"`
BlockIDAfter string `json:"block_id_after,omitempty"`
ActorKind string `json:"actor_kind,omitempty"`
ActorLabel string `json:"actor_label,omitempty"`
LineStart int `json:"line_start,omitempty"`
LineEnd int `json:"line_end,omitempty"`
}
type DiffResult struct {
FromSnapshotID int64 `json:"from_snapshot_id"`
ToSnapshotID int64 `json:"to_snapshot_id"`
Summary DiffSummary `json:"summary"`
Hunks []DiffHunk `json:"hunks"`
}
type DiffSummary struct {
Inserts int `json:"inserts"`
Deletes int `json:"deletes"`
Modifies int `json:"modifies"`
Moves int `json:"moves"`
}
type DocRootPointers struct {
DocID string
Kind string // controlled|freeform
LedgerVersionID *int64
HeadSnapshotID *int64
HeadRevisionNo int
ReviewSnapshotID *int64
RevisionSchemaVer int
RowRevision int64
LifecycleState string
ContentSHA256 string
LegacyVersionID int // freeform only
WorkDir string
ProjectID int64
AuthorID *int64
}
type RevisionState struct {
DocID string `json:"doc_id"`
CurrentVersionNo int `json:"current_version_no"`
VersionStatus string `json:"version_status"`
BaselineSnapshotID int64 `json:"baseline_snapshot_id"`
HeadSnapshotID int64 `json:"head_snapshot_id"`
HeadRevisionNo int `json:"head_revision_no"`
HeadSHA256 string `json:"head_sha256"`
ManualChangeCount int `json:"manual_change_count"`
Capabilities []string `json:"capabilities"`
ETag string `json:"etag"`
HeadBlockMap []BlockNode `json:"head_block_map,omitempty"`
// Pending* expose the latest needs_confirmation agent candidate (not yet in version list).
PendingCandidateID *int64 `json:"pending_candidate_id,omitempty"`
PendingChangeReportID *int64 `json:"pending_change_report_id,omitempty"`
PendingRiskLevel string `json:"pending_risk_level,omitempty"`
PendingCandidateStatus string `json:"pending_candidate_status,omitempty"`
// CanDecidePending is set by the API layer after authz (author/admin may decide).
CanDecidePending bool `json:"can_decide_pending"`
}
type HumanRevisionRequest struct {
DocID string
Markdown string
ExpectedHeadSnapshot *int64
ExpectedRevisionNo *int
IdempotencyKey string
EditSessionID string
RequestID string
ActorUserID int64
ActorSnapshot ActorSnapshot
LifecycleState string
WorkDir string
ProjectID int64
AllowCreateBaseline bool
ClientBlockMap []BlockNode
}
type HumanRevisionResult struct {
State RevisionState `json:"state"`
Event ChangeEvent `json:"event"`
Overlay DiffResult `json:"manual_overlay"`
Conflict *ConflictInfo `json:"conflict,omitempty"`
Report *ChangeReport `json:"report,omitempty"`
}
type ConflictInfo struct {
Code string `json:"code"`
BaseSnapshotID int64 `json:"base_snapshot_id"`
CurrentSnapshotID int64 `json:"current_snapshot_id"`
CurrentRevisionNo int `json:"current_revision_no"`
CurrentSHA256 string `json:"current_sha256"`
}
type AgentCandidateRequest struct {
DocID string
RunID string
ProposalID string
InputSnapshotID int64
OutputMarkdown string
ReportJSON string
ActorKind string
ActorSnapshot ActorSnapshot
AuthorizedScope string
OnBehalfOf *int64
}
type CommitAgentDecision struct {
Decision string // commit|reject
Confirmed bool // required when risk high/critical
ExpectedInputSHA string
ExpectedOutputSHA string
Reason string
ActorUserID int64
ActorSnapshot ActorSnapshot
}