Files
Agent-System/internal/projectinstance/document_access.go
T

133 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package projectinstance
import (
"context"
"chat-agent-system/internal/workflowdef"
)
const (
DocAccessActionView = "view"
DocAccessActionEdit = "edit"
DocAccessActionSubmit = "submit"
DocAccessActionReview = "review"
DocAccessActionApprove = "approve"
DocAccessActionRun = "run"
AccessCodeDocTaskDisabled = "DOC_TASK_DISABLED"
AccessCodeNotCurrentStage = "DOC_TASK_NOT_IN_ACTIVE_STAGE"
AccessCodeForbiddenDuty = "DOC_DUTY_FORBIDDEN"
AccessCodeDutyNotManaged = "DOC_DUTY_NOT_MANAGED"
AccessCodeInstanceClosed = "WORKFLOW_INSTANCE_CLOSED"
)
// DocAccessDecision 统一文档任务访问判定结果。
type DocAccessDecision struct {
Allowed bool `json:"allowed"`
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
// CheckDocTaskAccess 对治理实例下的文档任务做判定。
//
// GetGoverningInstanceForAccess:从未激活 → view 放行、run 带 WORKFLOW_NOT_ACTIVATED(HTTP 转 202-blocked)、其余拒绝;
// completed/terminated → InstanceClosed;存储错误 fail-closed。
// deny_only_submit:run/edit 对项目成员放行;submit/review/approve 按对应单一职责,创建者不隐式越权。
func (s *Service) CheckDocTaskAccess(
ctx context.Context,
projectID int64,
userID int64,
taskKey string,
action string,
isCreator bool,
) DocAccessDecision {
allow := DocAccessDecision{Allowed: true}
if s == nil || s.store == nil || projectID <= 0 || userID <= 0 || taskKey == "" {
return allow
}
gov, err := s.store.GetGoverningInstanceForAccess(ctx, projectID)
if err != nil {
return DocAccessDecision{Allowed: false, Code: AccessCodeInstanceClosed, Message: "工作流实例不可用"}
}
if gov == nil {
return classifyAccess(DocTaskPlan{Enabled: true}, action, userID, isCreator, false)
}
if IsClosedInstanceStatus(gov.Status) {
return DocAccessDecision{
Allowed: false,
Code: AccessCodeInstanceClosed,
Message: "工作流实例已结束",
}
}
detail, err := s.store.GetInstanceByID(ctx, gov.ID)
if err != nil || detail == nil {
return DocAccessDecision{Allowed: false, Code: AccessCodeInstanceClosed, Message: "工作流实例不可用"}
}
var docInst *DocTaskInstance
var stageKey string
for _, st := range detail.Stages {
for i := range st.DocTasks {
if workflowdef.SameTaskKey(st.DocTasks[i].TaskKey, taskKey) {
copyDoc := st.DocTasks[i]
docInst = &copyDoc
stageKey = st.StageKey
break
}
}
if docInst != nil {
break
}
}
if docInst == nil {
return allow
}
if docInst.Status == "skipped_disabled" {
return DocAccessDecision{
Allowed: false,
Code: AccessCodeDocTaskDisabled,
Message: "该文档已被创建者禁用",
}
}
// narrative 快照不按活动阶段拦截编制;职责、禁用和实例关闭检查仍在后面。
if stageKey != "" && stageKey != detail.ActiveStageKey && !gov.ConfigSnapshot.NarrativeStageGating() {
if action != DocAccessActionView {
return DocAccessDecision{
Allowed: false,
Code: AccessCodeNotCurrentStage,
Message: "文档不在当前活动阶段",
}
}
}
if action == DocAccessActionView {
return allow
}
planTask := findDocTaskPlan(gov.ConfigSnapshot, taskKey)
if planTask == nil || !planTask.Enabled {
return DocAccessDecision{
Allowed: false,
Code: AccessCodeDocTaskDisabled,
Message: "该文档已被创建者禁用",
}
}
return classifyAccess(*planTask, action, userID, isCreator, true)
}
func findDocTaskPlan(plan WorkflowPlan, taskKey string) *DocTaskPlan {
for _, st := range plan.Stages {
for i := range st.DocTasks {
if workflowdef.SameTaskKey(st.DocTasks[i].TaskKey, taskKey) {
return &st.DocTasks[i]
}
}
}
return nil
}