Files
Agent-System/internal/server/lineage_access.go
T
WeiCHEN 381b56dafc refactor(docgov): converge shared lifecycle vocab into contracts
Introduce internal/contracts/vocabulary for state/role/error sentinels,
alias through docgov, and retarget hot production consumers so
coordinator no longer imports docgov for vocabulary-only use.
2026-09-22 21:44:57 +08:00

110 lines
3.1 KiB
Go

package server
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"chat-agent-system/internal/projectworkflow"
)
// filterLineageEdges 丢掉不属于该项目,或任一端文档对当前主体不可见的边。
// 不可见端整行省略,避免通过 ID、标题或数量泄漏。
func filterLineageEdges(rows []*projectworkflow.DocumentLineage, projectID int64, visible func(docID string) bool) []*projectworkflow.DocumentLineage {
out := make([]*projectworkflow.DocumentLineage, 0, len(rows))
for _, row := range rows {
if row == nil || row.ProjectID != projectID {
continue
}
if visible == nil || !visible(row.DownstreamDocID) || !visible(row.UpstreamDocID) {
continue
}
out = append(out, row)
}
return out
}
func writeEmptyLineage(w http.ResponseWriter) {
writeJSONResponse(w, http.StatusOK, []*projectworkflow.DocumentLineage{})
}
// lineageDocVisible 判断 docID 是否属于 projectID,且当前用户可查看其任务。
// 无法证明归属时返回 false,调用方应给出与“没有血缘”相同的空结果。
func (s *Server) lineageDocVisible(ctx context.Context, projectID, userID int64, docID string) bool {
docID = strings.TrimSpace(docID)
if s == nil || projectID <= 0 || docID == "" {
return false
}
taskKey, placed, ok := s.lineageDocPlacement(ctx, projectID, docID)
if !ok || !placed {
return false
}
if taskKey == "" {
return true
}
return s.allowWorkflowDocTaskAccess(ctx, projectID, userID, taskKey)
}
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 {
if status, mapped := domainHTTPStatus(err); !(mapped && status == http.StatusNotFound) {
return "", false, false
}
}
if err == nil && doc != nil {
if doc.ProjectID != projectID {
return "", false, true
}
return doc.TaskKey, true, true
}
}
if pid, task, parsed := parseCanonicalWorkflowDocID(docID); parsed {
if pid != projectID {
return "", false, true
}
return task, true, true
}
if pid, parsed := parseHyphenProjectDocID(docID); parsed {
if pid != projectID {
return "", false, true
}
return "", true, true
}
return "", false, true
}
func parseHyphenProjectDocID(docID string) (int64, bool) {
const prefix = "doc-proj"
if !strings.HasPrefix(docID, prefix) {
return 0, false
}
rest := strings.TrimPrefix(docID, prefix)
idx := strings.IndexByte(rest, '-')
if idx <= 0 || idx >= len(rest)-1 {
return 0, false
}
projectID, err := strconv.ParseInt(rest[:idx], 10, 64)
if err != nil || projectID <= 0 {
return 0, false
}
return projectID, true
}
// lineageTestManagerAlias 仅在两种 test_manager 文档 ID 写法之间互换,不把任意空查询改写成别的文档。
func lineageTestManagerAlias(projectID int64, docID string) (string, bool) {
canonical := fmt.Sprintf("doc_proj%d_test_manager", projectID)
hyphen := fmt.Sprintf("doc-proj%d-test_manager", projectID)
switch docID {
case canonical:
return hyphen, true
case hyphen:
return canonical, true
default:
return "", false
}
}