Published manifests now use bd_/pm_/tm_/sw_ prefixes so workflows cannot share a doc id. Frozen instances keep the old keys; lookup, templates, and duty checks treat the two names as the same task. Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package bizdev
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"chat-agent-system/internal/docgov"
|
|
"chat-agent-system/internal/workflowdef"
|
|
)
|
|
|
|
var (
|
|
legacyDocPPattern = regexp.MustCompile(`^doc-p(\d+)-(.+)$`)
|
|
legacyDocProjHyphenPattern = regexp.MustCompile(`^doc-proj(\d+)-(.+)$`)
|
|
)
|
|
|
|
// normalizeDocID 将 legacy 本地 ID 映射为服务端权威 doc_id。
|
|
func normalizeDocID(docID string, projectID int64, taskKey string) string {
|
|
docID = strings.TrimSpace(docID)
|
|
if docID == "" {
|
|
return canonicalDocID(projectID, taskKey)
|
|
}
|
|
if m := legacyDocPPattern.FindStringSubmatch(docID); len(m) == 3 {
|
|
return fmt.Sprintf("doc_proj%s_%s", m[1], m[2])
|
|
}
|
|
if m := legacyDocProjHyphenPattern.FindStringSubmatch(docID); len(m) == 3 {
|
|
return fmt.Sprintf("doc_proj%s_%s", m[1], m[2])
|
|
}
|
|
return docID
|
|
}
|
|
|
|
func canonicalDocID(projectID int64, taskKey string) string {
|
|
if projectID <= 0 || taskKey == "" {
|
|
return ""
|
|
}
|
|
return fmt.Sprintf("doc_proj%d_%s", projectID, taskKey)
|
|
}
|
|
|
|
// ResolveAndValidateDocID 解析权威 doc_id;若客户端传入则校验归属。
|
|
func (s *Service) ResolveAndValidateDocID(ctx context.Context, req TaskRunRequest) (string, error) {
|
|
rawDocID := strings.TrimSpace(req.DocID)
|
|
docID := normalizeDocID(rawDocID, req.ProjectID, req.TaskKey)
|
|
if docID == "" {
|
|
return "", nil
|
|
}
|
|
|
|
if s.docGov == nil {
|
|
return docID, nil
|
|
}
|
|
|
|
doc, err := lookupDocument(ctx, s, docID)
|
|
if err != nil {
|
|
// 尚未建档:与空 doc_id 行为一致,交由 CreateOrGetDocument 创建
|
|
if fallback := canonicalDocID(req.ProjectID, req.TaskKey); fallback != "" {
|
|
return fallback, nil
|
|
}
|
|
lookupID := rawDocID
|
|
if lookupID == "" {
|
|
lookupID = docID
|
|
}
|
|
return "", fmt.Errorf("doc_id not found: %s", lookupID)
|
|
}
|
|
if req.ProjectID > 0 && doc.ProjectID != req.ProjectID {
|
|
return "", fmt.Errorf("doc_id %s does not belong to project %d", docID, req.ProjectID)
|
|
}
|
|
// 已注册文档的 task_key 以账本为准。旧键与新键是同一任务,不算改写。
|
|
if req.TaskKey != "" && doc.TaskKey != "" && !workflowdef.SameTaskKey(doc.TaskKey, req.TaskKey) {
|
|
return "", fmt.Errorf("doc_id %s task_key mismatch: expected %s got %s", docID, req.TaskKey, doc.TaskKey)
|
|
}
|
|
return doc.DocID, nil
|
|
}
|
|
|
|
func lookupDocument(ctx context.Context, s *Service, docID string) (*docgov.DocumentRecord, error) {
|
|
doc, err := s.docGov.GetDocument(ctx, docID)
|
|
if err == nil {
|
|
return doc, nil
|
|
}
|
|
for _, alt := range workflowdef.AlternateDocIDs(docID) {
|
|
altDoc, altErr := s.docGov.GetDocument(ctx, alt)
|
|
if altErr == nil {
|
|
return altDoc, nil
|
|
}
|
|
}
|
|
return nil, err
|
|
}
|