merge: feature/docgov-vocabulary-2026-09-22
Keep contracts/vocabulary with G1 uow/workspace duty ports; chat stays decoupled.
This commit is contained in:
@@ -4,13 +4,14 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/events"
|
||||
"chat-agent-system/internal/notification"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// EventConsumer applies document.approved onto workflow task bookkeeping.
|
||||
@@ -67,7 +68,7 @@ func (c *EventConsumer) HandleLease(ctx context.Context, lease events.Lease, tx
|
||||
if doc.ProjectID != lease.ProjectID {
|
||||
return events.ErrQuarantine
|
||||
}
|
||||
if doc.CurrentState != docgov.StateApproved {
|
||||
if doc.CurrentState != vocabulary.StateApproved {
|
||||
return events.ErrQuarantine
|
||||
}
|
||||
if taskKey == "" {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/app/uow"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
"chat-agent-system/internal/events"
|
||||
@@ -24,7 +25,7 @@ const (
|
||||
type Principal struct {
|
||||
UserID int64
|
||||
Name string
|
||||
Role docgov.UserRole
|
||||
Role vocabulary.UserRole
|
||||
}
|
||||
|
||||
type HumanEditCommand struct {
|
||||
@@ -42,7 +43,7 @@ type DecisionCommand struct {
|
||||
DocID string
|
||||
Comment string
|
||||
RejectReason string
|
||||
RollbackTo docgov.DocumentState
|
||||
RollbackTo vocabulary.DocumentState
|
||||
}
|
||||
|
||||
type Commands struct {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
"chat-agent-system/internal/taskrun"
|
||||
@@ -70,7 +71,7 @@ func (a *docTaskBusyCheckerAdapter) DocTaskSquadBusy(ctx context.Context, projec
|
||||
}
|
||||
doc, err := a.gov.FindByProjectTaskKey(ctx, projectID, taskKey)
|
||||
if err != nil {
|
||||
if err == docgov.ErrDocumentNotFound {
|
||||
if err == vocabulary.ErrDocumentNotFound {
|
||||
return false, "", nil
|
||||
}
|
||||
return false, "", err
|
||||
@@ -78,7 +79,7 @@ func (a *docTaskBusyCheckerAdapter) DocTaskSquadBusy(ctx context.Context, projec
|
||||
if doc == nil || !doc.HasActiveLease(time.Now()) {
|
||||
return false, "", nil
|
||||
}
|
||||
if doc.LeaseHolderType == docgov.LeaseHolderSquad && a.taskRuns != nil {
|
||||
if doc.LeaseHolderType == vocabulary.LeaseHolderSquad && a.taskRuns != nil {
|
||||
if run, runErr := a.taskRuns.GetRun(doc.LeaseHolderID); runErr == nil && run.Status != "running" {
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"chat-agent-system/internal/app/docnotify"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
@@ -49,10 +50,10 @@ func (t traceUnlock) ReopenApprovedDocs(ctx context.Context, projectID int64, do
|
||||
for _, key := range docTaskKeys {
|
||||
docID := fmt.Sprintf("doc_proj%d_%s", projectID, key)
|
||||
_, err := t.gov.ReopenForRevision(ctx, docID, actorID, "creator", reason)
|
||||
if err == nil || errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if err == nil || errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, docgov.ErrInvalidStateTransition) {
|
||||
if errors.Is(err, vocabulary.ErrInvalidStateTransition) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
|
||||
+11
-10
@@ -17,6 +17,7 @@ import (
|
||||
"chat-agent-system/internal/app/uow"
|
||||
"chat-agent-system/internal/authz"
|
||||
"chat-agent-system/internal/config"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/db"
|
||||
"chat-agent-system/internal/docduty"
|
||||
@@ -541,7 +542,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
}
|
||||
upstreamDocID := fmt.Sprintf("doc_proj%d_system_reqs", req.ProjectID)
|
||||
if s.docGov != nil && req.ProjectID > 0 {
|
||||
if doc, err := s.docGov.GetDocument(ctx, upstreamDocID); err == nil && doc != nil && doc.CurrentState == docgov.StateApproved {
|
||||
if doc, err := s.docGov.GetDocument(ctx, upstreamDocID); err == nil && doc != nil && doc.CurrentState == vocabulary.StateApproved {
|
||||
upstreamSRDoc = doc
|
||||
if doc.WorkDir != "" {
|
||||
contentPath := filepath.Join(doc.WorkDir, "output.md")
|
||||
@@ -594,7 +595,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
continue
|
||||
}
|
||||
doc, err := s.docGov.GetDocument(ctx, docID)
|
||||
if err != nil || doc == nil || doc.CurrentState != docgov.StateApproved || doc.WorkDir == "" {
|
||||
if err != nil || doc == nil || doc.CurrentState != vocabulary.StateApproved || doc.WorkDir == "" {
|
||||
continue
|
||||
}
|
||||
if b, readErr := os.ReadFile(filepath.Join(doc.WorkDir, "output.md")); readErr == nil && len(b) > 0 {
|
||||
@@ -720,7 +721,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
Success: false,
|
||||
ErrorReason: fmt.Sprintf("文档处于【%s】阶段已锁定,禁止 force_direct_edit", docRec.CurrentState),
|
||||
DocRecord: docRec,
|
||||
}, docgov.ErrDocumentLocked
|
||||
}, vocabulary.ErrDocumentLocked
|
||||
}
|
||||
// 已认领文档必须有编制人已同意的提案;未认领可直接强制覆写
|
||||
if docRec != nil && docRec.AuthorClaimed {
|
||||
@@ -733,14 +734,14 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
Success: false,
|
||||
ErrorReason: "force_direct_edit 需要编制人已同意的变更提案 (approved_by_author)",
|
||||
DocRecord: docRec,
|
||||
}, docgov.ErrForceEditNotAllowed
|
||||
}, vocabulary.ErrForceEditNotAllowed
|
||||
}
|
||||
appliedProposalID = prop.ProposalID
|
||||
}
|
||||
} else if docRec != nil {
|
||||
checkRes, checkErr := s.docGov.CheckModificationPermission(ctx, docID, docgov.RoleSystem)
|
||||
checkRes, checkErr := s.docGov.CheckModificationPermission(ctx, docID, vocabulary.RoleSystem)
|
||||
if checkErr != nil {
|
||||
if errors.Is(checkErr, docgov.ErrDocumentLocked) || errors.Is(checkErr, docgov.ErrDocumentBusy) {
|
||||
if errors.Is(checkErr, vocabulary.ErrDocumentLocked) || errors.Is(checkErr, vocabulary.ErrDocumentBusy) {
|
||||
reason := checkErr.Error()
|
||||
if checkRes != nil && checkRes.Reason != "" {
|
||||
reason = checkRes.Reason
|
||||
@@ -810,11 +811,11 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
leaseTTL = s.cfg.AgentTimeout
|
||||
}
|
||||
if leaseTTL <= 0 {
|
||||
leaseTTL = docgov.DefaultSquadLeaseTTL
|
||||
leaseTTL = vocabulary.DefaultSquadLeaseTTL
|
||||
}
|
||||
leased, leaseErr := s.docGov.AcquireLease(ctx, docgov.LeaseAcquireRequest{
|
||||
DocID: docID,
|
||||
HolderType: docgov.LeaseHolderSquad,
|
||||
HolderType: vocabulary.LeaseHolderSquad,
|
||||
HolderID: leaseHolderID,
|
||||
WorkDir: req.WorkDir,
|
||||
TTL: leaseTTL,
|
||||
@@ -860,7 +861,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
return
|
||||
case <-ticker.C:
|
||||
hbCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
_, _ = s.docGov.HeartbeatLease(hbCtx, docID, string(docgov.LeaseHolderSquad), leaseHolderID, leaseTTL)
|
||||
_, _ = s.docGov.HeartbeatLease(hbCtx, docID, string(vocabulary.LeaseHolderSquad), leaseHolderID, leaseTTL)
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
@@ -868,7 +869,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
defer func() {
|
||||
if leaseAcquired {
|
||||
relCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
_, _ = s.docGov.ReleaseLease(relCtx, docID, string(docgov.LeaseHolderSquad), leaseHolderID)
|
||||
_, _ = s.docGov.ReleaseLease(relCtx, docID, string(vocabulary.LeaseHolderSquad), leaseHolderID)
|
||||
cancel()
|
||||
if streamer := stream.FromContext(ctx); streamer != nil {
|
||||
streamer.EmitGovernance(stream.GovernancePayload{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/coordinator"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/taskrun"
|
||||
@@ -58,7 +59,7 @@ func (s *Session) executeAction(ctx context.Context, action *coordinator.Pending
|
||||
if s.Actions.DocGov == nil {
|
||||
return nil, fmt.Errorf("docgov engine not available")
|
||||
}
|
||||
doc, err := s.Actions.DocGov.ReviewApprove(ctx, action.DocID, uid, docgov.RoleReviewer, name, action.Comment)
|
||||
doc, err := s.Actions.DocGov.ReviewApprove(ctx, action.DocID, uid, vocabulary.RoleReviewer, name, action.Comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func (s *Session) executeAction(ctx context.Context, action *coordinator.Pending
|
||||
if s.Actions.DocGov == nil {
|
||||
return nil, fmt.Errorf("docgov engine not available")
|
||||
}
|
||||
doc, err := s.Actions.DocGov.ApproverApprove(ctx, action.DocID, uid, docgov.RoleApprover, name, action.Comment)
|
||||
doc, err := s.Actions.DocGov.ApproverApprove(ctx, action.DocID, uid, vocabulary.RoleApprover, name, action.Comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,13 +83,13 @@ func (s *Session) executeAction(ctx context.Context, action *coordinator.Pending
|
||||
if reason == "" {
|
||||
reason = "用户确认驳回"
|
||||
}
|
||||
doc, err := s.Actions.DocGov.ReviewReject(ctx, action.DocID, uid, docgov.RoleReviewer, name, reason)
|
||||
doc, err := s.Actions.DocGov.ReviewReject(ctx, action.DocID, uid, vocabulary.RoleReviewer, name, reason)
|
||||
if err != nil {
|
||||
rollback := action.Rollback
|
||||
if rollback == "" {
|
||||
rollback = docgov.StateDrafting
|
||||
rollback = vocabulary.StateDrafting
|
||||
}
|
||||
doc, err = s.Actions.DocGov.ApproverReject(ctx, action.DocID, uid, docgov.RoleApprover, name, reason, rollback)
|
||||
doc, err = s.Actions.DocGov.ApproverReject(ctx, action.DocID, uid, vocabulary.RoleApprover, name, reason, rollback)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Package vocabulary holds stable, behavior-free shared enums and sentinel values.
|
||||
// Domain packages keep invariants and typed error wrappers; consumers import this
|
||||
// leaf instead of pulling docgov solely for state/role/error strings.
|
||||
package vocabulary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DocumentState is the controlled-document lifecycle vocabulary.
|
||||
type DocumentState string
|
||||
|
||||
const (
|
||||
StateDrafting DocumentState = "drafting" // 编制中 (草稿)
|
||||
StateReviewing DocumentState = "reviewing" // 审核中 (待审核, 锁定)
|
||||
StateApproving DocumentState = "approving" // 批准中 (待批准, 锁定)
|
||||
StateApproved DocumentState = "approved" // 已批准 (归档生效, 终态锁定)
|
||||
StateRejected DocumentState = "rejected" // 已驳回 (中间状态或流转标记)
|
||||
)
|
||||
|
||||
// UserRole is the document-governance duty role vocabulary.
|
||||
type UserRole string
|
||||
|
||||
const (
|
||||
RoleAuthor UserRole = "author" // 编制人
|
||||
RoleReviewer UserRole = "reviewer" // 审核人
|
||||
RoleApprover UserRole = "approver" // 批准人
|
||||
RoleSystem UserRole = "system" // 系统/智能体小队
|
||||
)
|
||||
|
||||
// LeaseHolderType is the edit-lease holder vocabulary (orthogonal to IsLocked).
|
||||
type LeaseHolderType string
|
||||
|
||||
const (
|
||||
LeaseHolderNone LeaseHolderType = ""
|
||||
LeaseHolderSquad LeaseHolderType = "squad"
|
||||
LeaseHolderHuman LeaseHolderType = "human"
|
||||
)
|
||||
|
||||
// Default lease TTLs shared by squad/human writers.
|
||||
const (
|
||||
DefaultSquadLeaseTTL = 30 * time.Minute
|
||||
DefaultHumanLeaseTTL = 15 * time.Minute
|
||||
)
|
||||
|
||||
// CommentType is the document comment-kind vocabulary.
|
||||
type CommentType string
|
||||
|
||||
const (
|
||||
CommentGeneral CommentType = "comment" // 普通批注
|
||||
CommentSuggestion CommentType = "suggestion" // 修改建议
|
||||
CommentRejection CommentType = "reject_reason" // 驳回原因
|
||||
)
|
||||
|
||||
// Cross-module sentinel errors. Wire/HTTP mapping stays in server; wrappers with
|
||||
// extra fields (e.g. DocumentBusyError) remain in the owning domain package.
|
||||
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")
|
||||
ErrStaleFencing = errors.New("docgov: stale lease fencing token")
|
||||
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")
|
||||
ErrContentProjectionMissing = errors.New("docgov: document content projection missing")
|
||||
ErrContentProjectionEmpty = errors.New("docgov: document content projection empty")
|
||||
ErrContentIntegrityMismatch = errors.New("docgov: document content integrity mismatch")
|
||||
)
|
||||
|
||||
// Stable machine-readable content-error codes (HTTP status mapping stays in server).
|
||||
const (
|
||||
ErrCodeDocumentNotFound = "DOCUMENT_NOT_FOUND"
|
||||
ErrCodeProjectionMissing = "DOCUMENT_CONTENT_PROJECTION_MISSING"
|
||||
ErrCodeProjectionEmpty = "DOCUMENT_CONTENT_PROJECTION_EMPTY"
|
||||
ErrCodeIntegrityMismatch = "DOCUMENT_CONTENT_INTEGRITY_MISMATCH"
|
||||
ErrCodeProjectionDrift = "DOCUMENT_CONTENT_PROJECTION_DRIFT"
|
||||
)
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
"chat-agent-system/internal/agent"
|
||||
"chat-agent-system/internal/app/stageflow"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/intent"
|
||||
"chat-agent-system/internal/kb"
|
||||
"chat-agent-system/internal/maturity"
|
||||
@@ -42,7 +42,7 @@ type PendingAction struct {
|
||||
WorkDir string
|
||||
Reason string
|
||||
Comment string
|
||||
Rollback docgov.DocumentState
|
||||
Rollback vocabulary.DocumentState
|
||||
TaskKey string
|
||||
}
|
||||
|
||||
@@ -1013,7 +1013,7 @@ func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.
|
||||
UserID: userID,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, docgov.ErrDocumentBusy) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentBusy) {
|
||||
msg := "该文档正在由项目助手生成或他人编辑中,请稍后再发起更新。您可以先继续对话或处理其他文档。"
|
||||
if taskRes != nil && taskRes.ErrorReason != "" {
|
||||
msg = taskRes.ErrorReason + "。您可以先继续对话或处理其他文档。"
|
||||
@@ -1027,7 +1027,7 @@ func (e *CoordinatorEngine) handleTriggerTask(ctx context.Context, cand *intent.
|
||||
UpdatedContext: userCtx,
|
||||
}, nil
|
||||
}
|
||||
if errors.Is(err, docgov.ErrDocumentLocked) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentLocked) {
|
||||
return &CoordinationResult{
|
||||
Status: StatusRejected,
|
||||
Intent: cand.Intent,
|
||||
@@ -1654,9 +1654,9 @@ func (e *CoordinatorEngine) handleDocGovProposalDecide(ctx context.Context, cand
|
||||
}
|
||||
userCtx.applyPending(nil)
|
||||
|
||||
rollback := docgov.StateDrafting
|
||||
rollback := vocabulary.StateDrafting
|
||||
if userCtx.InteractiveCard != nil && strings.Contains(userCtx.InteractiveCard.ImpactRisk, "reviewing") {
|
||||
rollback = docgov.StateDrafting
|
||||
rollback = vocabulary.StateDrafting
|
||||
}
|
||||
|
||||
return &CoordinationResult{
|
||||
|
||||
+58
-71
@@ -1,38 +1,70 @@
|
||||
package docgov
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
)
|
||||
|
||||
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")
|
||||
ErrStaleFencing = errors.New("docgov: stale lease fencing token")
|
||||
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")
|
||||
ErrContentProjectionMissing = errors.New("docgov: document content projection missing")
|
||||
ErrContentProjectionEmpty = errors.New("docgov: document content projection empty")
|
||||
ErrContentIntegrityMismatch = errors.New("docgov: document content integrity mismatch")
|
||||
// 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 (
|
||||
ErrCodeDocumentNotFound = "DOCUMENT_NOT_FOUND"
|
||||
ErrCodeProjectionMissing = "DOCUMENT_CONTENT_PROJECTION_MISSING"
|
||||
ErrCodeProjectionEmpty = "DOCUMENT_CONTENT_PROJECTION_EMPTY"
|
||||
ErrCodeIntegrityMismatch = "DOCUMENT_CONTENT_INTEGRITY_MISMATCH"
|
||||
ErrCodeProjectionDrift = "DOCUMENT_CONTENT_PROJECTION_DRIFT"
|
||||
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.
|
||||
@@ -73,21 +105,6 @@ func NewContentError(code string, cause error, retryable, needsAdmin bool) *Cont
|
||||
return &ContentError{Code: code, Message: msg, Retryable: retryable, NeedsAdmin: needsAdmin, cause: cause}
|
||||
}
|
||||
|
||||
// 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"`
|
||||
@@ -107,27 +124,6 @@ func (e *DocumentBusyError) Error() string {
|
||||
|
||||
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
|
||||
@@ -221,15 +217,6 @@ func (d *DocumentRecord) ClearLease() {
|
||||
d.LeaseFencingToken = ""
|
||||
}
|
||||
|
||||
// CommentType 批注类型
|
||||
type CommentType string
|
||||
|
||||
const (
|
||||
CommentGeneral CommentType = "comment" // 普通批注
|
||||
CommentSuggestion CommentType = "suggestion" // 修改建议
|
||||
CommentRejection CommentType = "reject_reason" // 驳回原因
|
||||
)
|
||||
|
||||
// DocumentComment 批注与修改意见实体
|
||||
type DocumentComment struct {
|
||||
ID int64 `json:"id"`
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"chat-agent-system/internal/auth"
|
||||
"chat-agent-system/internal/authz"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
)
|
||||
|
||||
// failClosedAuthz 生产/DB 模式在授权依赖异常时默认拒绝。
|
||||
@@ -180,21 +180,21 @@ func (s *Server) requireDocGovDocAccess(w http.ResponseWriter, r *http.Request,
|
||||
}
|
||||
|
||||
// resolveDocGovRole 服务端按文档职责分配解析 docgov 角色,不信任客户端自报角色。
|
||||
func (s *Server) resolveDocGovRole(ctx context.Context, docID string, user *auth.UserAccount) docgov.UserRole {
|
||||
func (s *Server) resolveDocGovRole(ctx context.Context, docID string, user *auth.UserAccount) vocabulary.UserRole {
|
||||
if user == nil || s.gov == nil {
|
||||
return docgov.UserRole("")
|
||||
return vocabulary.UserRole("")
|
||||
}
|
||||
doc, err := s.gov.GetDocument(ctx, docID)
|
||||
if err != nil || doc == nil {
|
||||
return docgov.UserRole("")
|
||||
return vocabulary.UserRole("")
|
||||
}
|
||||
switch user.ID {
|
||||
case doc.AuthorID:
|
||||
return docgov.RoleAuthor
|
||||
return vocabulary.RoleAuthor
|
||||
case doc.ReviewerID:
|
||||
return docgov.RoleReviewer
|
||||
return vocabulary.RoleReviewer
|
||||
case doc.ApproverID:
|
||||
return docgov.RoleApprover
|
||||
return vocabulary.RoleApprover
|
||||
}
|
||||
return docgov.UserRole("")
|
||||
return vocabulary.UserRole("")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
"chat-agent-system/internal/app/docflow"
|
||||
"chat-agent-system/internal/app/uow"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
"chat-agent-system/internal/freedoc"
|
||||
@@ -213,7 +214,7 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
// This UI has no edit-session heartbeat: release the request-scoped human lease on every exit
|
||||
// (success, 409, projection failure) so a 15m residual cannot block a later squad run.
|
||||
if kind == "controlled" && s.gov != nil {
|
||||
if err := s.gov.AssertHumanCanEdit(r.Context(), docID, user.ID, docgov.RoleAuthor); err != nil {
|
||||
if err := s.gov.AssertHumanCanEdit(r.Context(), docID, user.ID, vocabulary.RoleAuthor); err != nil {
|
||||
writeDocGovError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -222,7 +223,7 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
holderID = "human_anon"
|
||||
}
|
||||
defer func() {
|
||||
_, _ = s.gov.ReleaseLease(context.Background(), docID, string(docgov.LeaseHolderHuman), holderID)
|
||||
_, _ = s.gov.ReleaseLease(context.Background(), docID, string(vocabulary.LeaseHolderHuman), holderID)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
writeJSONResponse(w, http.StatusServiceUnavailable, map[string]any{"error": "docrevision unavailable"})
|
||||
return
|
||||
}
|
||||
res, err := cmds.HumanEdit(r.Context(), docflow.Principal{UserID: user.ID, Name: user.Name, Role: docgov.RoleAuthor}, docflow.HumanEditCommand{
|
||||
res, err := cmds.HumanEdit(r.Context(), docflow.Principal{UserID: user.ID, Name: user.Name, Role: vocabulary.RoleAuthor}, docflow.HumanEditCommand{
|
||||
DocID: docID,
|
||||
Request: docrevision.HumanRevisionRequest{
|
||||
DocID: docID, Markdown: body.Markdown, ExpectedHeadSnapshot: expectedHead, ExpectedRevisionNo: expectedRev,
|
||||
@@ -279,7 +280,7 @@ func (s *Server) handleDocRevisionPatchContent(w http.ResponseWriter, r *http.Re
|
||||
if _, err := s.gov.SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
DocID: docID,
|
||||
ActorID: user.ID,
|
||||
ActorRole: docgov.RoleAuthor,
|
||||
ActorRole: vocabulary.RoleAuthor,
|
||||
ActorName: user.Name,
|
||||
MarkdownBody: body.Markdown,
|
||||
}); err != nil {
|
||||
@@ -372,7 +373,7 @@ func (s *Server) handleDocRevisionCandidateDecision(w http.ResponseWriter, r *ht
|
||||
writeJSONResponse(w, http.StatusServiceUnavailable, map[string]any{"error": "docrevision unavailable"})
|
||||
return
|
||||
}
|
||||
st, err := cmds.AgentCommit(r.Context(), docflow.Principal{UserID: user.ID, Name: user.Name, Role: docgov.RoleAuthor}, docflow.AgentCommitCommand{
|
||||
st, err := cmds.AgentCommit(r.Context(), docflow.Principal{UserID: user.ID, Name: user.Name, Role: vocabulary.RoleAuthor}, docflow.AgentCommitCommand{
|
||||
DocID: docID, CandidateID: cid,
|
||||
Decision: docrevision.CommitAgentDecision{
|
||||
Decision: body.Decision, Confirmed: body.Confirmed,
|
||||
|
||||
+21
-20
@@ -16,6 +16,7 @@ import (
|
||||
"chat-agent-system/internal/app/uow"
|
||||
"chat-agent-system/internal/bizdev"
|
||||
"chat-agent-system/internal/chat"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/httpx"
|
||||
@@ -431,9 +432,9 @@ func (s *Server) handleRunTask(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func writeTaskError(w http.ResponseWriter, err error, payload any) {
|
||||
status := http.StatusInternalServerError
|
||||
if errors.Is(err, docgov.ErrDocumentBusy) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentBusy) {
|
||||
status = http.StatusConflict
|
||||
} else if errors.Is(err, docgov.ErrDocumentLocked) || errors.Is(err, docgov.ErrForceEditNotAllowed) {
|
||||
} else if errors.Is(err, vocabulary.ErrDocumentLocked) || errors.Is(err, vocabulary.ErrForceEditNotAllowed) {
|
||||
status = http.StatusConflict
|
||||
} else if errors.Is(err, projectworkflow.ErrPrerequisiteNotMet) {
|
||||
status = http.StatusBadRequest
|
||||
@@ -454,19 +455,19 @@ func writeDocGovError(w http.ResponseWriter, err error) {
|
||||
})
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrDocumentBusy) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentBusy) {
|
||||
writeSafeError(w, nil, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrDocumentLocked) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentLocked) {
|
||||
writeSafeError(w, nil, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
writeSafeError(w, nil, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrLeaseNotHeld) || errors.Is(err, docgov.ErrLeaseExpired) {
|
||||
if errors.Is(err, vocabulary.ErrLeaseNotHeld) || errors.Is(err, vocabulary.ErrLeaseExpired) {
|
||||
writeSafeError(w, nil, http.StatusConflict, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -713,7 +714,7 @@ func (s *Server) handleDocGovSubmitReview(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
|
||||
doc, err := s.gov.SubmitForReview(r.Context(), req.DocID, user.ID, docgov.RoleAuthor, user.Name)
|
||||
doc, err := s.gov.SubmitForReview(r.Context(), req.DocID, user.ID, vocabulary.RoleAuthor, user.Name)
|
||||
if err != nil {
|
||||
writeSafeError(w, r, http.StatusBadRequest, "submit review failed: "+err.Error())
|
||||
return
|
||||
@@ -759,13 +760,13 @@ func (s *Server) handleDocGovReviewDecision(w http.ResponseWriter, r *http.Reque
|
||||
var err error
|
||||
|
||||
if action == "approve" {
|
||||
doc, err = s.gov.ReviewApprove(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, req.Comment)
|
||||
doc, err = s.gov.ReviewApprove(r.Context(), req.DocID, user.ID, vocabulary.RoleReviewer, user.Name, req.Comment)
|
||||
} else if action == "reject" {
|
||||
reason := req.Reason
|
||||
if reason == "" {
|
||||
reason = req.Comment
|
||||
}
|
||||
doc, err = s.gov.ReviewReject(r.Context(), req.DocID, user.ID, docgov.RoleReviewer, user.Name, reason)
|
||||
doc, err = s.gov.ReviewReject(r.Context(), req.DocID, user.ID, vocabulary.RoleReviewer, user.Name, reason)
|
||||
} else {
|
||||
writeSafeError(w, r, http.StatusBadRequest, "invalid action: must be approve or reject")
|
||||
return
|
||||
@@ -806,7 +807,7 @@ func (s *Server) handleDocGovApproverDecision(w http.ResponseWriter, r *http.Req
|
||||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||||
return
|
||||
}
|
||||
p := docflow.Principal{UserID: user.ID, Name: user.Name, Role: docgov.RoleApprover}
|
||||
p := docflow.Principal{UserID: user.ID, Name: user.Name, Role: vocabulary.RoleApprover}
|
||||
var doc *docgov.DocumentRecord
|
||||
var err error
|
||||
|
||||
@@ -818,7 +819,7 @@ func (s *Server) handleDocGovApproverDecision(w http.ResponseWriter, r *http.Req
|
||||
reason = req.Comment
|
||||
}
|
||||
doc, err = cmds.Reject(r.Context(), p, docflow.DecisionCommand{
|
||||
DocID: req.DocID, RejectReason: reason, RollbackTo: docgov.DocumentState(req.RollbackTo),
|
||||
DocID: req.DocID, RejectReason: reason, RollbackTo: vocabulary.DocumentState(req.RollbackTo),
|
||||
})
|
||||
} else {
|
||||
writeSafeError(w, r, http.StatusBadRequest, "invalid action: must be approve or reject")
|
||||
@@ -890,7 +891,7 @@ func (s *Server) handleDocGovProposeModification(w http.ResponseWriter, r *http.
|
||||
req.RiskAssessment,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
writeSafeError(w, r, http.StatusNotFound, "document not found")
|
||||
return
|
||||
}
|
||||
@@ -933,11 +934,11 @@ func (s *Server) handleDocGovDecideProposal(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
proposal, err := s.gov.DecideModificationProposal(r.Context(), req.ProposalID, user.ID, req.Approve, req.Reason)
|
||||
if err != nil {
|
||||
if errors.Is(err, docgov.ErrProposalNotFound) {
|
||||
if errors.Is(err, vocabulary.ErrProposalNotFound) {
|
||||
writeSafeError(w, r, http.StatusNotFound, "proposal not found")
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrUnauthorizedActor) {
|
||||
if errors.Is(err, vocabulary.ErrUnauthorizedActor) {
|
||||
writeSafeError(w, r, http.StatusForbidden, "decide proposal failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
@@ -1004,9 +1005,9 @@ func (s *Server) handleDocGovAddComment(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
cType := docgov.CommentType(req.CommentType)
|
||||
cType := vocabulary.CommentType(req.CommentType)
|
||||
if cType == "" {
|
||||
cType = docgov.CommentGeneral
|
||||
cType = vocabulary.CommentGeneral
|
||||
}
|
||||
|
||||
comment, err := s.gov.AddComment(
|
||||
@@ -1302,7 +1303,7 @@ func (s *Server) handleDocGovSaveContent(w http.ResponseWriter, r *http.Request)
|
||||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||||
return
|
||||
}
|
||||
role := docgov.RoleAuthor
|
||||
role := vocabulary.RoleAuthor
|
||||
res, err := s.gov.SaveDocumentContent(r.Context(), docgov.ContentSaveRequest{
|
||||
DocID: req.DocID,
|
||||
ActorID: user.ID,
|
||||
@@ -1339,8 +1340,8 @@ func (s *Server) handleDocGovAcquireLease(w http.ResponseWriter, r *http.Request
|
||||
writeSafeError(w, r, http.StatusServiceUnavailable, "docgov engine not initialized")
|
||||
return
|
||||
}
|
||||
if docgov.LeaseHolderType(req.HolderType) == docgov.LeaseHolderHuman {
|
||||
if err := s.gov.CheckHumanCanEdit(r.Context(), req.DocID, user.ID, docgov.RoleAuthor); err != nil {
|
||||
if vocabulary.LeaseHolderType(req.HolderType) == vocabulary.LeaseHolderHuman {
|
||||
if err := s.gov.CheckHumanCanEdit(r.Context(), req.DocID, user.ID, vocabulary.RoleAuthor); err != nil {
|
||||
writeDocGovError(w, err)
|
||||
return
|
||||
}
|
||||
@@ -1353,7 +1354,7 @@ func (s *Server) handleDocGovAcquireLease(w http.ResponseWriter, r *http.Request
|
||||
ttl := time.Duration(req.TTLSeconds) * time.Second
|
||||
doc, err := s.gov.AcquireLease(r.Context(), docgov.LeaseAcquireRequest{
|
||||
DocID: req.DocID,
|
||||
HolderType: docgov.LeaseHolderType(req.HolderType),
|
||||
HolderType: vocabulary.LeaseHolderType(req.HolderType),
|
||||
HolderID: req.HolderID,
|
||||
WorkDir: workDir,
|
||||
TTL: ttl,
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/projectworkflow"
|
||||
)
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *Server) lineageDocVisible(ctx context.Context, projectID, userID int64,
|
||||
func (s *Server) lineageDocPlacement(ctx context.Context, projectID int64, docID string) (taskKey string, placed bool, ok bool) {
|
||||
if s.gov != nil {
|
||||
doc, err := s.gov.GetDocument(ctx, docID)
|
||||
if err != nil && !errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if err != nil && !errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
return "", false, false
|
||||
}
|
||||
if err == nil && doc != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"time"
|
||||
|
||||
"chat-agent-system/internal/app/docflow"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/docquery"
|
||||
"chat-agent-system/internal/docrevision"
|
||||
@@ -132,7 +133,7 @@ func (s *Server) handleDocGovGetDocumentContent(w http.ResponseWriter, r *http.R
|
||||
}
|
||||
if errors.Is(err, docquery.ErrClosed) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{
|
||||
"error_code": docgov.ErrCodeDocumentNotFound, "message": "document not found",
|
||||
"error_code": vocabulary.ErrCodeDocumentNotFound, "message": "document not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -150,7 +151,7 @@ func (s *Server) handleDocGovGetDocumentContent(w http.ResponseWriter, r *http.R
|
||||
|
||||
func (s *Server) loadControlledDocumentContent(ctx context.Context, docID string) (*docgov.DocumentContent, error) {
|
||||
if s == nil || s.gov == nil {
|
||||
return nil, docgov.ErrDocumentNotFound
|
||||
return nil, vocabulary.ErrDocumentNotFound
|
||||
}
|
||||
svc := &docquery.Service{Gov: s.gov, Rev: s.docRev(), RepoRoot: s.repoRoot()}
|
||||
view, err := svc.Get(ctx, docquery.Principal{UserID: 1}, docquery.DocRef{DocID: docID})
|
||||
@@ -191,7 +192,7 @@ func writeControlledContentError(w http.ResponseWriter, r *http.Request, err err
|
||||
var ce *docgov.ContentError
|
||||
if errors.As(err, &ce) {
|
||||
status := http.StatusConflict
|
||||
if ce.Code == docgov.ErrCodeDocumentNotFound {
|
||||
if ce.Code == vocabulary.ErrCodeDocumentNotFound {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
writeJSONResponse(w, status, map[string]any{
|
||||
@@ -207,9 +208,9 @@ func writeControlledContentError(w http.ResponseWriter, r *http.Request, err err
|
||||
})
|
||||
return
|
||||
}
|
||||
if errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
writeJSONResponse(w, http.StatusNotFound, map[string]any{
|
||||
"error_code": docgov.ErrCodeDocumentNotFound, "message": err.Error(),
|
||||
"error_code": vocabulary.ErrCodeDocumentNotFound, "message": err.Error(),
|
||||
"retryable": false, "needs_admin": false, "correlation_id": corr,
|
||||
})
|
||||
return
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"chat-agent-system/internal/agent"
|
||||
"chat-agent-system/internal/bizdev"
|
||||
"chat-agent-system/internal/contracts/vocabulary"
|
||||
"chat-agent-system/internal/ctxmgr"
|
||||
"chat-agent-system/internal/docgov"
|
||||
"chat-agent-system/internal/projectinstance"
|
||||
@@ -409,7 +410,7 @@ func (s *Server) preflightTaskDocumentBusy(ctx context.Context, req createTaskRu
|
||||
return nil, nil
|
||||
}
|
||||
doc, err := s.gov.GetDocument(ctx, docID)
|
||||
if errors.Is(err, docgov.ErrDocumentNotFound) {
|
||||
if errors.Is(err, vocabulary.ErrDocumentNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
@@ -419,7 +420,7 @@ func (s *Server) preflightTaskDocumentBusy(ctx context.Context, req createTaskRu
|
||||
return nil, nil
|
||||
}
|
||||
// 已结束的小队 run 由 AcquireLease 的孤儿回收逻辑处理,不在这里误拦截。
|
||||
if doc.LeaseHolderType == docgov.LeaseHolderSquad && s.taskRuns != nil {
|
||||
if doc.LeaseHolderType == vocabulary.LeaseHolderSquad && s.taskRuns != nil {
|
||||
if run, runErr := s.taskRuns.GetRun(doc.LeaseHolderID); runErr == nil && run.Status != "running" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user