ci / vet-and-test (pull_request) Successful in 43s
Why: plan U3 remainder (R8/KTD13) - execution admission must be bounded and shared (leases in the database, parents yield while waiting so budgets cannot deadlock parent-child), and project authorization must distinguish read/edit/approve/execute/admin with default-deny for unconfigured actions. Evidence: full suite green on SQLite and against real PostgreSQL 18.6 + Redis (isolated instance); new tests - admission_pg_test (enforcement, per-user, concurrent no-oversell), runmgr admission tests (saturated-budget sync spawn progresses, background children share the budget, bounded queue 429), server action-matrix tests (default deny, legacy adapter read-only, action-aware authoritative + fail-closed). Impact: admission_leases (v21) with atomic lease accounting under an advisory lock; runtime.execBudgetGlobal/PerUser/execQueueMax settings (all zero = off, historical behavior preserved); conversation turns, background and sync subagents share the pool, parents yield around synchronous children and HITL waits; queue-full rejects 429 before writing; server gains ProjectAction matrix + SetProjectActionAuthorizer with legacy EXT-8 read fallback. Refs: proposals/2026-10-11-collaboration-storage-and-distribution.md U3 R8/KTD13; docs/guides/team-storage.md section 9.
90 lines
3.1 KiB
Go
90 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"testing"
|
|
|
|
core "agent-system-core"
|
|
)
|
|
|
|
// 协作存储 U3 切片 2(KTD13):动作授权矩阵。未配置的动作默认拒绝;
|
|
// 旧只读 EXT-8 适配器只保留 read 能力;action-aware 适配器注册后权威。
|
|
|
|
func newMatrixServer(t *testing.T) *Server {
|
|
t.Helper()
|
|
s := &Server{}
|
|
t.Cleanup(func() {})
|
|
return s
|
|
}
|
|
|
|
// 无任何适配器:全部动作拒绝(默认拒绝;与 EXT-8 未注册时读取 NotFound 同基调)。
|
|
func TestActionMatrixDefaultDeny(t *testing.T) {
|
|
s := newMatrixServer(t)
|
|
for _, a := range []ProjectAction{ProjectRead, ProjectEdit, ProjectApprove, ProjectExecute, ProjectAdmin} {
|
|
if s.authorizeProjectAction("alice", "p1", a) {
|
|
t.Fatalf("未注册适配器时 %s 应默认拒绝", a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 旧只读适配器:read 放行、其余动作不得自动获得写入能力(KTD13 兼容语义)。
|
|
func TestActionMatrixLegacyAdapterReadOnly(t *testing.T) {
|
|
s := newMatrixServer(t)
|
|
s.SetProjectAuthorizer(func(owner string, projectID core.ID) (bool, error) {
|
|
return owner == "alice", nil
|
|
})
|
|
if !s.authorizeProjectAction("alice", "p1", ProjectRead) {
|
|
t.Fatal("alice read 应放行(旧适配器)")
|
|
}
|
|
for _, a := range []ProjectAction{ProjectEdit, ProjectApprove, ProjectExecute, ProjectAdmin} {
|
|
if s.authorizeProjectAction("alice", "p1", a) {
|
|
t.Fatalf("旧只读适配器不得自动获得 %s 能力", a)
|
|
}
|
|
}
|
|
// 他人(动作越权):read 同样拒绝。
|
|
if s.authorizeProjectAction("bob", "p1", ProjectRead) {
|
|
t.Fatal("bob 越权 read 应拒绝")
|
|
}
|
|
}
|
|
|
|
// action-aware 适配器:注册后权威(read 与显式配置的动作放行,未配置动作
|
|
// 默认拒绝;出错 fail-closed)。
|
|
func TestActionMatrixActionAwareAuthoritative(t *testing.T) {
|
|
s := newMatrixServer(t)
|
|
// 同时注册旧适配器:action-aware 存在时不得回落旧适配器(权威唯一)。
|
|
s.SetProjectAuthorizer(func(owner string, projectID core.ID) (bool, error) {
|
|
return true, nil
|
|
})
|
|
s.SetProjectActionAuthorizer(func(owner string, projectID core.ID, action ProjectAction) (bool, error) {
|
|
switch action {
|
|
case ProjectRead, ProjectEdit:
|
|
return owner == "alice", nil
|
|
default:
|
|
return false, nil // approve/execute/admin 未授予
|
|
}
|
|
})
|
|
if !s.authorizeProjectAction("alice", "p1", ProjectRead) || !s.authorizeProjectAction("alice", "p1", ProjectEdit) {
|
|
t.Fatal("alice read/edit 应放行")
|
|
}
|
|
for _, a := range []ProjectAction{ProjectApprove, ProjectExecute, ProjectAdmin} {
|
|
if s.authorizeProjectAction("alice", "p1", a) {
|
|
t.Fatalf("未授予的 %s 应默认拒绝", a)
|
|
}
|
|
}
|
|
if s.authorizeProjectAction("bob", "p1", ProjectEdit) {
|
|
t.Fatal("bob 越权 edit 应拒绝")
|
|
}
|
|
// 适配器出错:fail-closed(错误不区分,避免泄露存在性)。
|
|
s.SetProjectActionAuthorizer(func(owner string, projectID core.ID, action ProjectAction) (bool, error) {
|
|
return false, errBoom
|
|
})
|
|
if s.authorizeProjectAction("alice", "p1", ProjectRead) {
|
|
t.Fatal("适配器出错应 fail-closed")
|
|
}
|
|
}
|
|
|
|
var errBoom = &boomErr{}
|
|
|
|
type boomErr struct{}
|
|
|
|
func (*boomErr) Error() string { return "boom" }
|