Files
Agent-System/internal/sysadmin/policy.go
T
weichenandCursor a2cbaf1e33 feat(sysadmin): CAS author-only policy with transactional audit
Register the workflow total switch, require expected_revision for
updates, and expose AuthorOnlyPolicySource for activation locking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 09:15:15 +00:00

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)