package docgov import ( "errors" "time" ) var ( ErrDocumentNotFound = errors.New("docgov: document not found") ErrDocumentLocked = errors.New("docgov: document is locked, direct modification prohibited") ErrOnlyAuthorCanEdit = errors.New("docgov: only the designated author is allowed to edit or modify document content") ErrReviewerCannotEdit = errors.New("docgov: reviewers are not allowed to edit document content, only comments and review decisions are permitted") ErrApproverCannotEdit = errors.New("docgov: approvers are not allowed to edit document content, only comments and approval decisions are permitted") ErrInvalidStateTransition = errors.New("docgov: invalid document lifecycle state transition") ErrRejectReasonRequired = errors.New("docgov: rejection reason must not be empty") ErrUnauthorizedActor = errors.New("docgov: actor is not authorized for this role or action") ErrProposalNotFound = errors.New("docgov: change proposal not found") ErrProposalAlreadyDecided = errors.New("docgov: change proposal has already been decided") ErrChangeProposalPending = errors.New("docgov: document is claimed and has pending change proposal waiting for author approval") ErrDocumentBusy = errors.New("docgov: document edit lease is held by another writer") ErrLeaseExpired = errors.New("docgov: document edit lease has expired") ErrLeaseNotHeld = errors.New("docgov: caller does not hold the document edit lease") ErrForceEditNotAllowed = errors.New("docgov: force_direct_edit requires an approved_by_author proposal and unlocked drafting document") ) // LeaseHolderType 编辑租约持有者类型 type LeaseHolderType string const ( LeaseHolderNone LeaseHolderType = "" LeaseHolderSquad LeaseHolderType = "squad" LeaseHolderHuman LeaseHolderType = "human" ) // DefaultSquadLeaseTTL 小队任务默认租约时长 const DefaultSquadLeaseTTL = 30 * time.Minute // DefaultHumanLeaseTTL 人工编辑会话默认租约时长 const DefaultHumanLeaseTTL = 15 * time.Minute // DocumentBusyError 携带租约占用详情,便于 HTTP 409 与 UI 提示 type DocumentBusyError struct { DocID string `json:"doc_id"` HolderType LeaseHolderType `json:"holder_type"` HolderID string `json:"holder_id"` WorkDir string `json:"work_dir,omitempty"` ExpiresAt time.Time `json:"expires_at"` Reason string `json:"reason"` } func (e *DocumentBusyError) Error() string { if e.Reason != "" { return e.Reason } return ErrDocumentBusy.Error() } func (e *DocumentBusyError) Unwrap() error { return ErrDocumentBusy } // DocumentState 文档生命周期流转状态 type DocumentState string const ( StateDrafting DocumentState = "drafting" // 编制中 (草稿) StateReviewing DocumentState = "reviewing" // 审核中 (待审核, 锁定) StateApproving DocumentState = "approving" // 批准中 (待批准, 锁定) StateApproved DocumentState = "approved" // 已批准 (归档生效, 终态锁定) StateRejected DocumentState = "rejected" // 已驳回 (中间状态或流转标记) ) // UserRole 治理责任人角色 (各有且仅有一个责任人) type UserRole string const ( RoleAuthor UserRole = "author" // 编制人 RoleReviewer UserRole = "reviewer" // 审核人 RoleApprover UserRole = "approver" // 批准人 RoleSystem UserRole = "system" // 系统/智能体小队 ) // DocumentConfig 受控文档流转与退回配置 type DocumentConfig struct { RejectRollbackTo DocumentState `json:"reject_rollback_to"` // 驳回退回目标: drafting 或 reviewing AllowApproverToReviewer bool `json:"allow_approver_to_reviewer"` // 是否允许批准人退回至审核阶段 RequireAuthorConfirmOnReject bool `json:"require_author_confirm_on_reject"` // 驳回退回编制时是否需编制人确认已获悉 } func DefaultDocumentConfig() DocumentConfig { return DocumentConfig{ RejectRollbackTo: StateDrafting, AllowApproverToReviewer: true, RequireAuthorConfirmOnReject: true, } } // DocumentRecord 受控文档治理台账实体 type DocumentRecord struct { ID int64 `json:"id"` DocID string `json:"doc_id"` // 业务唯一ID,如 doc_proj1_feasibility_reg_v1 ProjectID int64 `json:"project_id"` // 项目ID TaskKey string `json:"task_key"` // 任务标识: feasibility_reg, intake, cdo_quote Title string `json:"title"` // 标题 Stage string `json:"stage"` // 阶段: V0.5_feasibility Version int `json:"version"` // 版本号 CurrentState DocumentState `json:"current_state"` // drafting, reviewing, approving, approved IsLocked bool `json:"is_locked"` // reviewing/approving/approved 期间锁定 IsTemplateOnly bool `json:"is_template_only"` // 是否仅为骨架模板 (模板状态即使查看也不算认领) ContentSHA256 string `json:"content_sha256"` // 当前版本正文哈希 WorkDir string `json:"work_dir"` // 关联工作区相对路径 // 三级法定责任人 (各有且仅有一个) AuthorID int64 `json:"author_id"` AuthorName string `json:"author_name"` ReviewerID int64 `json:"reviewer_id"` ReviewerName string `json:"reviewer_name"` ApproverID int64 `json:"approver_id"` ApproverName string `json:"approver_name"` // 认领状态 (Claiming State) AuthorClaimed bool `json:"author_claimed"` // 编制人是否已认领 (非模板状态查看/编辑后为 true) AuthorClaimedAt *time.Time `json:"author_claimed_at"` AuthorLastViewedAt *time.Time `json:"author_last_viewed_at"` AuthorLastEditedAt *time.Time `json:"author_last_edited_at"` ReviewerClaimed bool `json:"reviewer_claimed"` // 审核人是否已认领审核 ReviewerClaimedAt *time.Time `json:"reviewer_claimed_at"` ApproverClaimed bool `json:"approver_claimed"` // 批准人是否已认领批准 ApproverClaimedAt *time.Time `json:"approver_claimed_at"` // 驳回与决议记录 LastRejectedReason string `json:"last_rejected_reason"` LastRejectedByRole string `json:"last_rejected_by_role"` LastRejectedByName string `json:"last_rejected_by_name"` LastRejectedAt *time.Time `json:"last_rejected_at"` ApprovedAt *time.Time `json:"approved_at"` // 编辑租约 (运行时互斥,正交于 IsLocked) LeaseHolderType LeaseHolderType `json:"lease_holder_type,omitempty"` LeaseHolderID string `json:"lease_holder_id,omitempty"` LeaseWorkDir string `json:"lease_work_dir,omitempty"` LeaseExpiresAt *time.Time `json:"lease_expires_at,omitempty"` LeaseHeartbeatAt *time.Time `json:"lease_heartbeat_at,omitempty"` Config DocumentConfig `json:"config"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } // HasActiveLease 判断文档是否持有未过期的编辑租约 func (d *DocumentRecord) HasActiveLease(now time.Time) bool { if d == nil || d.LeaseHolderType == LeaseHolderNone || d.LeaseHolderType == "" { return false } if d.LeaseExpiresAt == nil { return false } return d.LeaseExpiresAt.After(now) } // ClearLease 清空租约字段(内存) func (d *DocumentRecord) ClearLease() { d.LeaseHolderType = LeaseHolderNone d.LeaseHolderID = "" d.LeaseWorkDir = "" d.LeaseExpiresAt = nil d.LeaseHeartbeatAt = nil } // CommentType 批注类型 type CommentType string const ( CommentGeneral CommentType = "comment" // 普通批注 CommentSuggestion CommentType = "suggestion" // 修改建议 CommentRejection CommentType = "reject_reason" // 驳回原因 ) // DocumentComment 批注与修改意见实体 type DocumentComment struct { ID int64 `json:"id"` DocID string `json:"doc_id"` UserID int64 `json:"user_id"` UserName string `json:"user_name"` UserRole UserRole `json:"user_role"` // author, reviewer, approver TargetSection string `json:"target_section"` // 目标章节锚点,如 "## 结论" CommentType CommentType `json:"comment_type"` // comment, suggestion, reject_reason Content string `json:"content"` // 意见内容 Status string `json:"status"` // open, resolved, accepted CreatedAt time.Time `json:"created_at"` } // ProposalStatus 变更提案状态 type ProposalStatus string const ( ProposalPendingAuthorApproval ProposalStatus = "pending_author_approval" // 待编制人拍板 ProposalApprovedByAuthor ProposalStatus = "approved_by_author" // 编制人已同意修改 ProposalRejectedByAuthor ProposalStatus = "rejected_by_author" // 编制人已拒绝修改 ProposalApplied ProposalStatus = "applied" // 已应用执行 ) // ChangeProposal 针对已认领文档的智能体修改变更提案实体 type ChangeProposal struct { ID int64 `json:"id"` ProposalID string `json:"proposal_id"` DocID string `json:"doc_id"` ProjectID int64 `json:"project_id"` TriggeredBy string `json:"triggered_by"` // agent_squad, stage_sync, upstream_change ProposedChanges string `json:"proposed_changes"` // 有哪些地方要改 (变更内容与对比摘要) ImpactAnalysis string `json:"impact_analysis"` // 影响会是什么 (范围、上下游依赖) RiskAssessment string `json:"risk_assessment"` // 可能产生的风险 (合规、工时、预算) Status ProposalStatus `json:"status"` // pending_author_approval... AuthorDecisionReason string `json:"author_decision_reason"` // 编制人批准/拒绝原因 DecidedAt *time.Time `json:"decided_at"` CreatedAt time.Time `json:"created_at"` } // AuditLog 文档治理生命周期审计跟踪 type AuditLog struct { ID int64 `json:"id"` DocID string `json:"doc_id"` Action string `json:"action"` // view, edit, claim, submit_review, review_approve, review_reject... ActorID int64 `json:"actor_id"` ActorName string `json:"actor_name"` ActorRole UserRole `json:"actor_role"` FromState DocumentState `json:"from_state"` ToState DocumentState `json:"to_state"` Details map[string]any `json:"details"` CreatedAt time.Time `json:"created_at"` }