You've already forked agentic-coding-workflow
bugfix
This commit is contained in:
@@ -61,6 +61,13 @@ func (r *permFakeRepo) GetSessionByID(_ context.Context, id uuid.UUID) (*acp.Ses
|
||||
out := *s
|
||||
return &out, nil
|
||||
}
|
||||
func (r *permFakeRepo) GetSessionByStepID(context.Context, uuid.UUID) (*acp.Session, error) {
|
||||
return nil, errs.New(errs.CodeAcpSessionNotFound, "not found")
|
||||
}
|
||||
func (r *permFakeRepo) GetCrashedSessionForResume(context.Context, uuid.UUID) (*acp.Session, error) {
|
||||
return nil, errs.New(errs.CodeAcpSessionNotFound, "not found")
|
||||
}
|
||||
func (r *permFakeRepo) ResetSessionForResume(context.Context, uuid.UUID) error { return nil }
|
||||
|
||||
func (r *permFakeRepo) DecidePermissionRequest(_ context.Context, id uuid.UUID, status acp.PermissionStatus, chosen *string, decidedBy *uuid.UUID) (*acp.PermissionRequest, bool, error) {
|
||||
r.mu.Lock()
|
||||
@@ -89,6 +96,24 @@ func (r *permFakeRepo) ExpirePendingPermissionRequestsBySession(_ context.Contex
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *permFakeRepo) ListPendingPermissionRequestsByUser(_ context.Context, userID uuid.UUID) ([]*acp.PermissionRequest, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
var out []*acp.PermissionRequest
|
||||
for _, p := range r.reqs {
|
||||
if p.Status != acp.PermissionPending {
|
||||
continue
|
||||
}
|
||||
sess, ok := r.sessions[p.SessionID]
|
||||
if !ok || sess.UserID != userID {
|
||||
continue
|
||||
}
|
||||
cp := *p
|
||||
out = append(out, &cp)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *permFakeRepo) statusOf(id uuid.UUID) acp.PermissionStatus {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -310,3 +335,68 @@ func TestPermission_CancelSession_UnblocksDecide(t *testing.T) {
|
||||
}
|
||||
require.Equal(t, acp.PermissionExpired, repo.statusOf(repo.onlyReqID()))
|
||||
}
|
||||
|
||||
// fakeMaintainer 满足 acp.ProjectMaintainer:仅当 projectID 命中 allow 集合时放行。
|
||||
type fakeMaintainer struct {
|
||||
allow map[uuid.UUID]bool
|
||||
}
|
||||
|
||||
func (f fakeMaintainer) CanWriteProject(_ context.Context, callerID uuid.UUID, isAdmin bool, projectID uuid.UUID) (bool, error) {
|
||||
if isAdmin {
|
||||
return true, nil
|
||||
}
|
||||
return f.allow[projectID], nil
|
||||
}
|
||||
|
||||
// 注入 ProjectMaintainer 后:会话所属 project 的任意 maintainer(非会话 owner)
|
||||
// 也可处理待审权限请求(autonomy roadmap §11)。
|
||||
func TestPermission_Resolve_ProjectMaintainer_Allowed(t *testing.T) {
|
||||
repo := newPermFakeRepo()
|
||||
owner := uuid.New()
|
||||
maintainer := uuid.New()
|
||||
pid := uuid.New()
|
||||
sid := uuid.New()
|
||||
repo.sessions[sid] = &acp.Session{ID: sid, UserID: owner, ProjectID: pid}
|
||||
svc := acp.NewPermissionService(repo, nil, time.Minute, nil)
|
||||
svc.SetProjectMaintainer(fakeMaintainer{allow: map[uuid.UUID]bool{pid: true}})
|
||||
sess := handlers.SessionContext{SessionID: sid, UserID: owner}
|
||||
|
||||
go svc.Decide(context.Background(), sess, "req-1",
|
||||
[]byte(`{"name":"danger.tool"}`), optionsAllowReject())
|
||||
var reqID uuid.UUID
|
||||
require.Eventually(t, func() bool {
|
||||
reqID = repo.onlyReqID()
|
||||
return reqID != uuid.Nil && repo.statusOf(reqID) == acp.PermissionPending
|
||||
}, time.Second, 5*time.Millisecond)
|
||||
|
||||
// 非 owner 但是 project maintainer → 可处理。
|
||||
require.NoError(t, svc.Resolve(context.Background(), acp.Caller{UserID: maintainer}, reqID, true, "allow_once"))
|
||||
require.Equal(t, acp.PermissionApproved, repo.statusOf(reqID))
|
||||
}
|
||||
|
||||
// 非 maintainer 的外部用户仍被拒(fail-closed);项目不在 allow 集合时也拒。
|
||||
func TestPermission_Resolve_NonMaintainer_Forbidden(t *testing.T) {
|
||||
repo := newPermFakeRepo()
|
||||
owner := uuid.New()
|
||||
pid := uuid.New()
|
||||
otherPid := uuid.New()
|
||||
sid := uuid.New()
|
||||
repo.sessions[sid] = &acp.Session{ID: sid, UserID: owner, ProjectID: pid}
|
||||
svc := acp.NewPermissionService(repo, nil, time.Minute, nil)
|
||||
// maintainer 仅对 otherPid 有权,而会话属于 pid。
|
||||
svc.SetProjectMaintainer(fakeMaintainer{allow: map[uuid.UUID]bool{otherPid: true}})
|
||||
sess := handlers.SessionContext{SessionID: sid, UserID: owner}
|
||||
|
||||
go svc.Decide(context.Background(), sess, "req-1",
|
||||
[]byte(`{"name":"danger.tool"}`), optionsAllowReject())
|
||||
var reqID uuid.UUID
|
||||
require.Eventually(t, func() bool {
|
||||
reqID = repo.onlyReqID()
|
||||
return reqID != uuid.Nil && repo.statusOf(reqID) == acp.PermissionPending
|
||||
}, time.Second, 5*time.Millisecond)
|
||||
|
||||
err := svc.Resolve(context.Background(), acp.Caller{UserID: uuid.New()}, reqID, true, "allow_once")
|
||||
ae, ok := errs.As(err)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, errs.CodeAcpSessionNotOwned, ae.Code)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user