feat(bizdev): prefer instance-governed duties when generating docs
Inject InstanceDutySource, resolve governed bindings, lock duties after create, and skip in-memory HealBinding overwrite for governed docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -51,6 +51,22 @@ type Service struct {
|
||||
stageTaskHook func(context.Context, TaskRunRequest) (*TaskRunResult, error)
|
||||
// createDocumentHook 仅测试注入:覆盖账本创建失败路径。
|
||||
createDocumentHook func(ctx context.Context, docID string, projectID int64, taskKey, title, stage, workDir string, authorID int64, authorName string, reviewerID int64, reviewerName string, approverID int64, approverName string) (*docgov.DocumentRecord, error)
|
||||
// instanceDuty 可选:活跃工作流实例对 taskKey 的治理职责(由 app 装配,避免导入 projectinstance)。
|
||||
instanceDuty InstanceDutySource
|
||||
}
|
||||
|
||||
// InstanceDutySource 返回项目当前活跃实例对 taskKey 的治理结论。
|
||||
// governed=false 表示未受实例治理,调用方应回退项目默认职责逻辑。
|
||||
type InstanceDutySource interface {
|
||||
ResolveGovernedDuty(ctx context.Context, projectID int64, taskKey string) (author, reviewer, approver int64, dutyPolicy string, governed bool, err error)
|
||||
}
|
||||
|
||||
// SetInstanceDutySource 注入实例职责解析源(组合根可选装配)。
|
||||
func (s *Service) SetInstanceDutySource(src InstanceDutySource) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.instanceDuty = src
|
||||
}
|
||||
|
||||
// NewService 构造业务服务;生产/test 环境数据库不可用且未显式 ALLOW_IN_MEMORY 时返回 error。
|
||||
@@ -456,7 +472,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
}
|
||||
}
|
||||
|
||||
duties := resolveTaskDocumentDuties(ctx, s.pool, req)
|
||||
duties, dutyPolicy, governed := resolveTaskDocumentDuties(ctx, s.pool, req, s.instanceDuty)
|
||||
authorID, authorName := duties.Author.UserID, duties.Author.Name
|
||||
reviewerID, reviewerName := duties.Reviewer.UserID, duties.Reviewer.Name
|
||||
approverID, approverName := duties.Approver.UserID, duties.Approver.Name
|
||||
@@ -500,6 +516,21 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
DocRecord: docRec,
|
||||
}, createErr
|
||||
}
|
||||
if governed && docRec != nil {
|
||||
if lockErr := s.docGov.LockDocumentDuty(ctx, docRec.DocID, dutyPolicy); lockErr != nil {
|
||||
log.Printf("[docgov] lock document duty %s failed: %v", docRec.DocID, lockErr)
|
||||
return &TaskRunResult{
|
||||
TaskKey: req.TaskKey,
|
||||
ArtifactTitle: artifactTitle,
|
||||
WorkDir: req.WorkDir,
|
||||
Success: false,
|
||||
ErrorReason: "lock document duty failed: " + lockErr.Error(),
|
||||
DocRecord: docRec,
|
||||
}, lockErr
|
||||
}
|
||||
docRec.DutyLocked = true
|
||||
docRec.DutyPolicy = dutyPolicy
|
||||
}
|
||||
if s.pool != nil && req.ProjectID > 0 {
|
||||
assigned, _, healErr := docduty.HealProjectDocuments(ctx, s.pool, req.ProjectID)
|
||||
if healErr != nil {
|
||||
@@ -513,7 +544,8 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun
|
||||
DocRecord: docRec,
|
||||
}, healErr
|
||||
}
|
||||
if docRec != nil {
|
||||
// 受实例治理的文档不得用项目默认职责覆盖内存态返回值。
|
||||
if !governed && docRec != nil {
|
||||
members, creator, loadErr := docduty.LoadProjectMembers(ctx, s.pool, req.ProjectID)
|
||||
if loadErr != nil {
|
||||
log.Printf("[docduty] load members project %d after generate failed: %v", req.ProjectID, loadErr)
|
||||
@@ -1339,7 +1371,35 @@ func (s *Service) ResumeOrModifyWorkspace(ctx context.Context, req squad.ModifyW
|
||||
return s.squad.ResumeOrModifyWorkspace(ctx, req)
|
||||
}
|
||||
|
||||
func resolveTaskDocumentDuties(ctx context.Context, pool *pgxpool.Pool, req TaskRunRequest) docduty.Binding {
|
||||
func resolveTaskDocumentDuties(ctx context.Context, pool *pgxpool.Pool, req TaskRunRequest, instanceDuty InstanceDutySource) (docduty.Binding, string, bool) {
|
||||
if instanceDuty != nil && req.ProjectID > 0 && strings.TrimSpace(req.TaskKey) != "" {
|
||||
authorID, reviewerID, approverID, dutyPolicy, governed, err := instanceDuty.ResolveGovernedDuty(ctx, req.ProjectID, req.TaskKey)
|
||||
if err == nil && governed {
|
||||
bound := docduty.Binding{
|
||||
Author: docduty.Holder{UserID: authorID},
|
||||
Reviewer: docduty.Holder{UserID: reviewerID},
|
||||
Approver: docduty.Holder{UserID: approverID},
|
||||
}
|
||||
if pool != nil {
|
||||
if members, creator, loadErr := docduty.LoadProjectMembers(ctx, pool, req.ProjectID); loadErr == nil {
|
||||
bound.Author.Name = holderName(members, creator, authorID, req.AuthorName)
|
||||
bound.Reviewer.Name = holderName(members, creator, reviewerID, req.ReviewerName)
|
||||
bound.Approver.Name = holderName(members, creator, approverID, req.ApproverName)
|
||||
}
|
||||
}
|
||||
if bound.Author.Name == "" {
|
||||
bound.Author.Name = strings.TrimSpace(req.AuthorName)
|
||||
}
|
||||
if bound.Reviewer.Name == "" {
|
||||
bound.Reviewer.Name = strings.TrimSpace(req.ReviewerName)
|
||||
}
|
||||
if bound.Approver.Name == "" {
|
||||
bound.Approver.Name = strings.TrimSpace(req.ApproverName)
|
||||
}
|
||||
return bound, dutyPolicy, true
|
||||
}
|
||||
}
|
||||
|
||||
reqDuties := docduty.RequestDuties{
|
||||
AuthorID: req.AuthorID, AuthorName: req.AuthorName,
|
||||
ReviewerID: req.ReviewerID, ReviewerName: req.ReviewerName,
|
||||
@@ -1353,11 +1413,26 @@ func resolveTaskDocumentDuties(ctx context.Context, pool *pgxpool.Pool, req Task
|
||||
if bound.Author.UserID == 0 && req.UserID > 0 {
|
||||
bound.Author = docduty.Holder{UserID: req.UserID, Name: strings.TrimSpace(req.AuthorName)}
|
||||
}
|
||||
return bound
|
||||
return bound, "", false
|
||||
}
|
||||
}
|
||||
creator := docduty.Holder{UserID: req.UserID, Name: strings.TrimSpace(req.AuthorName)}
|
||||
return docduty.Resolve(nil, creator, reqDuties)
|
||||
return docduty.Resolve(nil, creator, reqDuties), "", false
|
||||
}
|
||||
|
||||
func holderName(members []docduty.Member, creator docduty.Holder, userID int64, fallback string) string {
|
||||
if userID <= 0 {
|
||||
return strings.TrimSpace(fallback)
|
||||
}
|
||||
if creator.UserID == userID && strings.TrimSpace(creator.Name) != "" {
|
||||
return strings.TrimSpace(creator.Name)
|
||||
}
|
||||
for _, m := range members {
|
||||
if m.UserID == userID && strings.TrimSpace(m.Name) != "" {
|
||||
return strings.TrimSpace(m.Name)
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(fallback)
|
||||
}
|
||||
|
||||
func bindingFromDoc(doc *docgov.DocumentRecord) docduty.Binding {
|
||||
|
||||
Reference in New Issue
Block a user