Register the workflow total switch, require expected_revision for updates, and expose AuthorOnlyPolicySource for activation locking. Co-authored-by: Cursor <cursoragent@cursor.com>
97 lines
3.0 KiB
Go
97 lines
3.0 KiB
Go
package sysadmin
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// SettingKeyAllowAuthorOnlyApproval 系统级「允许编制人兼任审核、批准」总授权。
|
|
// 默认关闭;无环境变量绕过;仅系统管理员可写。
|
|
const SettingKeyAllowAuthorOnlyApproval = "workflow.allow_author_only_approval"
|
|
|
|
// AuthorOnlyApprovalPolicy 供 projectinstance 消费的窄策略快照。
|
|
type AuthorOnlyApprovalPolicy struct {
|
|
Key string `json:"key"`
|
|
Allowed bool `json:"allowed"`
|
|
Revision int64 `json:"revision"`
|
|
}
|
|
|
|
// AuthorOnlyPolicySource 组合根注入给 projectinstance 的只读/事务锁接口。
|
|
// sysadmin 是唯一写入 owner;调用方不得自行写 system_settings。
|
|
type AuthorOnlyPolicySource interface {
|
|
GetAuthorOnlyApprovalPolicy(ctx context.Context) (AuthorOnlyApprovalPolicy, error)
|
|
LockAuthorOnlyApprovalPolicy(ctx context.Context, tx pgx.Tx) (AuthorOnlyApprovalPolicy, error)
|
|
}
|
|
|
|
// GetAuthorOnlyApprovalPolicy 读取当前总授权。缺行按默认关闭(revision=0);
|
|
// 存储不可用或查询失败返回错误,不得伪装成默认关闭。
|
|
func (s *Service) GetAuthorOnlyApprovalPolicy(ctx context.Context) (AuthorOnlyApprovalPolicy, error) {
|
|
out := AuthorOnlyApprovalPolicy{
|
|
Key: SettingKeyAllowAuthorOnlyApproval,
|
|
Allowed: false,
|
|
Revision: 0,
|
|
}
|
|
if s.store == nil {
|
|
return out, ErrStoreUnavailable
|
|
}
|
|
raw, _, _, revision, err := s.store.Get(ctx, SettingKeyAllowAuthorOnlyApproval)
|
|
if err != nil {
|
|
if errors.Is(err, ErrSettingNotFound) {
|
|
return out, nil
|
|
}
|
|
return out, err
|
|
}
|
|
allowed, err := decodeStrictBool(raw)
|
|
if err != nil {
|
|
return out, fmt.Errorf("sysadmin: decode %s: %w", SettingKeyAllowAuthorOnlyApproval, err)
|
|
}
|
|
out.Allowed = allowed
|
|
out.Revision = revision
|
|
return out, nil
|
|
}
|
|
|
|
// LockAuthorOnlyApprovalPolicy 在调用方事务内锁定策略行,供激活与关闭串行化。
|
|
func (s *Service) LockAuthorOnlyApprovalPolicy(ctx context.Context, tx pgx.Tx) (AuthorOnlyApprovalPolicy, error) {
|
|
out := AuthorOnlyApprovalPolicy{
|
|
Key: SettingKeyAllowAuthorOnlyApproval,
|
|
Allowed: false,
|
|
Revision: 0,
|
|
}
|
|
if s.store == nil {
|
|
return out, ErrStoreUnavailable
|
|
}
|
|
raw, _, _, revision, err := s.store.GetForUpdate(ctx, tx, SettingKeyAllowAuthorOnlyApproval)
|
|
if err != nil {
|
|
if errors.Is(err, ErrSettingNotFound) {
|
|
// 迁移应已种子化;缺行按关闭处理但不伪装 DB 故障。
|
|
return out, nil
|
|
}
|
|
return out, err
|
|
}
|
|
allowed, err := decodeStrictBool(raw)
|
|
if err != nil {
|
|
return out, fmt.Errorf("sysadmin: decode %s: %w", SettingKeyAllowAuthorOnlyApproval, err)
|
|
}
|
|
out.Allowed = allowed
|
|
out.Revision = revision
|
|
return out, nil
|
|
}
|
|
|
|
func decodeStrictBool(raw json.RawMessage) (bool, error) {
|
|
if len(raw) == 0 {
|
|
return false, ErrInvalidSettingValue
|
|
}
|
|
var v bool
|
|
if err := json.Unmarshal(raw, &v); err != nil {
|
|
return false, ErrInvalidSettingValue
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// Ensure AuthorOnlyPolicySource 由 Service 实现。
|
|
var _ AuthorOnlyPolicySource = (*Service)(nil)
|