Files
AskaEth 20160c66d8
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(security): 代审口径 A 落地(仅子代理发起的敏感调用做实质判定 + fail-closed + 理由进审计与小字)
2026-10-05 12:12:31 +08:00

57 lines
1.8 KiB
Go

package orchestration
import (
"context"
"sync"
"testing"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// dispatchTaskRunner 记录每个子代理运行的 context(含其中挂的派发任务标记)。
type dispatchTaskRunner struct {
mu sync.Mutex
ctx []context.Context
}
func (r *dispatchTaskRunner) Run(ctx context.Context, _ string, _ []model.Message) (*loop.Result, error) {
r.mu.Lock()
r.ctx = append(r.ctx, ctx)
r.mu.Unlock()
return &loop.Result{Text: "ok", Steps: 1, StopReason: "stop"}, nil
}
func (r *dispatchTaskRunner) last() context.Context {
r.mu.Lock()
defer r.mu.Unlock()
return r.ctx[len(r.ctx)-1]
}
// TestSubagentTaskRidesRunContext 锁定口径 A 的结构化来源:编排层在驱动子代理 Run
// 之前把**派给它的任务目标**挂到运行 context 上,代审判定器据此识别「这次调用由
// 子代理发起」并拿到判越界所需的任务。缺失这条挂载 ⇒ 子代理的敏感调用会静默绕过
// 实质判定(本用例就是那个缺口的第一道闸)。
func TestSubagentTaskRidesRunContext(t *testing.T) {
t.Parallel()
s := newScheduler(t, Options{MaxParallel: 1})
m := &dispatchTaskRunner{}
s.runner = m
sub, err := s.Spawn(context.Background(), AgentSpec{Goal: "整理 data/ 下的导出文件"})
if err != nil {
t.Fatalf("Spawn: %v", err)
}
if _, werr := s.Wait(context.Background(), []string{sub.ID}); werr != nil {
t.Fatalf("Wait: %v", werr)
}
got, ok := loop.SubagentTaskFrom(m.last())
if !ok {
t.Fatal("子代理的运行 context 上没有派发任务标记(代审的越界判定将无从触发)")
}
if got != "整理 data/ 下的导出文件" {
t.Fatalf("派发任务 = %q, want 派给该子代理的任务目标", got)
}
}