A candidate used the same report later attached to the version, so a preview could be treated as the formal result. Proposed reports now close on replace or reject, and only the commit writes an authoritative successor.
353 lines
13 KiB
Go
353 lines
13 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"
|
|
|
|
// 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"`
|
|
}
|
|
|
|
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
|
|
}
|