Files
Agent-System/internal/docgov/types.go
T
weichen 3e01f18756 merge: integrate final-artifact-vault into local_dev (authorized exception)
User authorized lifting CURRENT FAV whole-branch merge protection on
2026-09-23. Keep FAV_DEGRADED_READ/FAV_PUBLISHER_ENABLED default false.
Drop duplicate goose 00034 (local_dev already has 00036). Preserve
agentx workspace admin routes and pin-export wiring.
2026-09-23 13:57:08 +00:00

274 lines
11 KiB
Go

package docgov
import (
"time"
"chat-agent-system/internal/contracts/vocabulary"
)
// Re-export stable vocabulary so domain code keeps short names.
// Cross-package consumers should import contracts/vocabulary for State/Role/Err.
type (
DocumentState = vocabulary.DocumentState
UserRole = vocabulary.UserRole
LeaseHolderType = vocabulary.LeaseHolderType
CommentType = vocabulary.CommentType
)
const (
StateDrafting = vocabulary.StateDrafting
StateReviewing = vocabulary.StateReviewing
StateApproving = vocabulary.StateApproving
StateApproved = vocabulary.StateApproved
StateRejected = vocabulary.StateRejected
RoleAuthor = vocabulary.RoleAuthor
RoleReviewer = vocabulary.RoleReviewer
RoleApprover = vocabulary.RoleApprover
RoleSystem = vocabulary.RoleSystem
LeaseHolderNone = vocabulary.LeaseHolderNone
LeaseHolderSquad = vocabulary.LeaseHolderSquad
LeaseHolderHuman = vocabulary.LeaseHolderHuman
DefaultSquadLeaseTTL = vocabulary.DefaultSquadLeaseTTL
DefaultHumanLeaseTTL = vocabulary.DefaultHumanLeaseTTL
CommentGeneral = vocabulary.CommentGeneral
CommentSuggestion = vocabulary.CommentSuggestion
CommentRejection = vocabulary.CommentRejection
ErrCodeDocumentNotFound = vocabulary.ErrCodeDocumentNotFound
ErrCodeProjectionMissing = vocabulary.ErrCodeProjectionMissing
ErrCodeProjectionEmpty = vocabulary.ErrCodeProjectionEmpty
ErrCodeIntegrityMismatch = vocabulary.ErrCodeIntegrityMismatch
ErrCodeProjectionDrift = vocabulary.ErrCodeProjectionDrift
)
var (
ErrDocumentNotFound = vocabulary.ErrDocumentNotFound
ErrDocumentLocked = vocabulary.ErrDocumentLocked
ErrOnlyAuthorCanEdit = vocabulary.ErrOnlyAuthorCanEdit
ErrReviewerCannotEdit = vocabulary.ErrReviewerCannotEdit
ErrApproverCannotEdit = vocabulary.ErrApproverCannotEdit
ErrInvalidStateTransition = vocabulary.ErrInvalidStateTransition
ErrRejectReasonRequired = vocabulary.ErrRejectReasonRequired
ErrUnauthorizedActor = vocabulary.ErrUnauthorizedActor
ErrProposalNotFound = vocabulary.ErrProposalNotFound
ErrProposalAlreadyDecided = vocabulary.ErrProposalAlreadyDecided
ErrChangeProposalPending = vocabulary.ErrChangeProposalPending
ErrDocumentBusy = vocabulary.ErrDocumentBusy
ErrStaleFencing = vocabulary.ErrStaleFencing
ErrLeaseExpired = vocabulary.ErrLeaseExpired
ErrLeaseNotHeld = vocabulary.ErrLeaseNotHeld
ErrForceEditNotAllowed = vocabulary.ErrForceEditNotAllowed
ErrContentProjectionMissing = vocabulary.ErrContentProjectionMissing
ErrContentProjectionEmpty = vocabulary.ErrContentProjectionEmpty
ErrContentIntegrityMismatch = vocabulary.ErrContentIntegrityMismatch
)
// ContentError is a structured content-read failure. 404 is only for a missing record.
type ContentError struct {
Code string
Message string
Retryable bool
NeedsAdmin bool
RecordSHA string
FileSHA string
HeadSHA string
Class string
cause error
}
func (e *ContentError) Error() string {
if e == nil {
return ""
}
if e.Message != "" {
return e.Message
}
return e.Code
}
func (e *ContentError) Unwrap() error {
if e == nil {
return nil
}
return e.cause
}
func NewContentError(code string, cause error, retryable, needsAdmin bool) *ContentError {
msg := code
if cause != nil {
msg = cause.Error()
}
return &ContentError{Code: code, Message: msg, Retryable: retryable, NeedsAdmin: needsAdmin, cause: cause}
}
// 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 }
// 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"`
// DutyLocked 为 true 时,职责权威来自工作流实例规划,heal/revoke 不得覆盖。
DutyLocked bool `json:"duty_locked"`
DutyPolicy string `json:"duty_policy,omitempty"`
// 认领状态 (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"`
ReviewSnapshotID int64 `json:"review_snapshot_id,omitempty"`
// 编辑租约 (运行时互斥,正交于 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"`
LeaseFencingToken string `json:"lease_fencing_token,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
d.LeaseFencingToken = ""
}
// 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"`
}