feat(docgov): project document summary returns authoritative duties

ListWorkflowDocumentsByProject now projects author/reviewer/approver
id+name so clients cannot invent the current user as author.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 17:02:23 +00:00
co-authored by Cursor
parent 52c0292ff7
commit 79797cab7c
2 changed files with 59 additions and 1 deletions
+18 -1
View File
@@ -27,6 +27,12 @@ type WorkflowDocumentSummary struct {
LeaseHolderID string `json:"lease_holder_id,omitempty"`
LeaseExpiresAt *time.Time `json:"lease_expires_at,omitempty"`
LeaseHeartbeatAt *time.Time `json:"lease_heartbeat_at,omitempty"`
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"`
}
// DocumentContent 受控文档正文读取结果。
@@ -49,7 +55,10 @@ func (e *GovernanceEngine) ListWorkflowDocumentsByProject(ctx context.Context, p
SELECT doc_id, project_id, task_key, title, current_state, is_template_only,
COALESCE(content_sha256, ''), COALESCE(work_dir, ''), version, updated_at,
COALESCE(lease_holder_type, ''), COALESCE(lease_holder_id, ''),
lease_expires_at, lease_heartbeat_at
lease_expires_at, lease_heartbeat_at,
COALESCE(author_id, 0), COALESCE(author_name, ''),
COALESCE(reviewer_id, 0), COALESCE(reviewer_name, ''),
COALESCE(approver_id, 0), COALESCE(approver_name, '')
FROM document_records
WHERE project_id = $1 AND task_key <> ''
ORDER BY task_key ASC, doc_id ASC
@@ -67,6 +76,8 @@ func (e *GovernanceEngine) ListWorkflowDocumentsByProject(ctx context.Context, p
&item.DocID, &item.ProjectID, &item.TaskKey, &item.Title, &state,
&item.IsTemplateOnly, &item.ContentSHA256, &item.WorkDir, &item.Version, &updatedAt,
&item.LeaseHolderType, &item.LeaseHolderID, &item.LeaseExpiresAt, &item.LeaseHeartbeatAt,
&item.AuthorID, &item.AuthorName, &item.ReviewerID, &item.ReviewerName,
&item.ApproverID, &item.ApproverName,
); err != nil {
return nil, err
}
@@ -98,6 +109,12 @@ func (e *GovernanceEngine) ListWorkflowDocumentsByProject(ctx context.Context, p
LeaseHolderID: doc.LeaseHolderID,
LeaseExpiresAt: doc.LeaseExpiresAt,
LeaseHeartbeatAt: doc.LeaseHeartbeatAt,
AuthorID: doc.AuthorID,
AuthorName: doc.AuthorName,
ReviewerID: doc.ReviewerID,
ReviewerName: doc.ReviewerName,
ApproverID: doc.ApproverID,
ApproverName: doc.ApproverName,
})
}
}
+41
View File
@@ -38,3 +38,44 @@ func TestListWorkflowDocumentsIncludesActiveLease(t *testing.T) {
t.Fatalf("lease timestamps missing: %+v", docs[0])
}
}
func TestListWorkflowDocumentsIncludesAuthoritativeDuties(t *testing.T) {
engine := docgov.NewGovernanceEngine(nil)
ctx := context.Background()
const (
docID = "doc_proj143_intake"
authorID = int64(9)
reviewerID = int64(2)
approverID = int64(3)
)
if _, err := engine.CreateOrGetDocument(
ctx, docID, 143, "intake", "用户初始需求收集表", "V0.1", "",
authorID, "编制人A", reviewerID, "审核人B", approverID, "批准人C",
); err != nil {
t.Fatal(err)
}
docs, err := engine.ListWorkflowDocumentsByProject(ctx, 143)
if err != nil {
t.Fatal(err)
}
if len(docs) != 1 {
t.Fatalf("documents=%d", len(docs))
}
got := docs[0]
if got.AuthorID != authorID || got.AuthorName != "编制人A" {
t.Fatalf("author not projected: id=%d name=%q", got.AuthorID, got.AuthorName)
}
if got.ReviewerID != reviewerID || got.ReviewerName != "审核人B" {
t.Fatalf("reviewer not projected: id=%d name=%q", got.ReviewerID, got.ReviewerName)
}
if got.ApproverID != approverID || got.ApproverName != "批准人C" {
t.Fatalf("approver not projected: id=%d name=%q", got.ApproverID, got.ApproverName)
}
if got.AuthorID == reviewerID {
t.Fatalf("reviewer B must not become author: %+v", got)
}
if got.AuthorID == 99 {
t.Fatalf("stranger must not become author: %+v", got)
}
}