Add ResolveDocTaskDuty, require the system policy on save/preview, lock the policy row during activation, and derive A/A/A assignments. Co-authored-by: Cursor <cursoragent@cursor.com>
96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package projectinstance
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
|
||
"github.com/jackc/pgx/v5"
|
||
)
|
||
|
||
// AuthorOnlyPolicySnapshot 系统级「允许编制人兼任审核批准」总授权快照。
|
||
// 字段语义对齐 sysadmin.AuthorOnlyApprovalPolicy;本包不导入 sysadmin
|
||
// (module-boundaries.yaml 未授权),由组合根提供适配实现。
|
||
type AuthorOnlyPolicySnapshot struct {
|
||
Allowed bool
|
||
Revision int64
|
||
}
|
||
|
||
// AuthorOnlyPolicySource 组合根注入的窄只读/事务锁接口。
|
||
type AuthorOnlyPolicySource interface {
|
||
GetAuthorOnlyApprovalPolicy(ctx context.Context) (AuthorOnlyPolicySnapshot, error)
|
||
LockAuthorOnlyApprovalPolicy(ctx context.Context, tx pgx.Tx) (AuthorOnlyPolicySnapshot, error)
|
||
}
|
||
|
||
func (s *Service) SetAuthorOnlyPolicySource(src AuthorOnlyPolicySource) {
|
||
if s == nil {
|
||
return
|
||
}
|
||
s.authorOnlyPolicy = src
|
||
}
|
||
|
||
func planUsesAuthorOnly(plan WorkflowPlan) bool {
|
||
for _, st := range plan.Stages {
|
||
if !st.Enabled {
|
||
continue
|
||
}
|
||
for _, dt := range st.DocTasks {
|
||
if dt.Enabled && dt.DutyPolicy == DutyPolicyAuthorOnly {
|
||
return true
|
||
}
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func (s *Service) requireAuthorOnlyAllowed(ctx context.Context) (AuthorOnlyPolicySnapshot, error) {
|
||
if s == nil || s.authorOnlyPolicy == nil {
|
||
return AuthorOnlyPolicySnapshot{}, ErrAuthorOnlyPolicyUnavailable
|
||
}
|
||
snap, err := s.authorOnlyPolicy.GetAuthorOnlyApprovalPolicy(ctx)
|
||
if err != nil {
|
||
return AuthorOnlyPolicySnapshot{}, fmt.Errorf("%w: %v", ErrAuthorOnlyPolicyUnavailable, err)
|
||
}
|
||
if !snap.Allowed {
|
||
return snap, ErrAuthorOnlyPolicyDisabled
|
||
}
|
||
return snap, nil
|
||
}
|
||
|
||
func (s *Service) loadAuthorOnlyCapability(ctx context.Context) GovernancePolicyCapability {
|
||
cap := GovernancePolicyCapability{Allowed: false, Revision: 0}
|
||
if s == nil || s.authorOnlyPolicy == nil {
|
||
cap.BlockedReason = "unavailable"
|
||
return cap
|
||
}
|
||
snap, err := s.authorOnlyPolicy.GetAuthorOnlyApprovalPolicy(ctx)
|
||
if err != nil {
|
||
cap.BlockedReason = "unavailable"
|
||
return cap
|
||
}
|
||
cap.Allowed = snap.Allowed
|
||
cap.Revision = snap.Revision
|
||
if !snap.Allowed {
|
||
cap.BlockedReason = "policy_disabled"
|
||
}
|
||
return cap
|
||
}
|
||
|
||
type activateAuthorOnlyLockKey struct{}
|
||
|
||
func withActivateAuthorOnlyLock(ctx context.Context, fn func(context.Context, pgx.Tx) (AuthorOnlyPolicySnapshot, error)) context.Context {
|
||
if fn == nil {
|
||
return ctx
|
||
}
|
||
return context.WithValue(ctx, activateAuthorOnlyLockKey{}, fn)
|
||
}
|
||
|
||
// runActivateAuthorOnlyLock 执行激活事务内策略行锁;第三个返回值表示是否注册过闭包。
|
||
func runActivateAuthorOnlyLock(ctx context.Context, tx pgx.Tx) (AuthorOnlyPolicySnapshot, error, bool) {
|
||
fn, _ := ctx.Value(activateAuthorOnlyLockKey{}).(func(context.Context, pgx.Tx) (AuthorOnlyPolicySnapshot, error))
|
||
if fn == nil {
|
||
return AuthorOnlyPolicySnapshot{}, nil, false
|
||
}
|
||
snap, err := fn(ctx, tx)
|
||
return snap, err, true
|
||
}
|