Files
weichen 92f9286e6c Merge layered exec environments into project decision work
Why: Integrate the exact Dev main pin while preserving trusted project context, vision routing, work-result context, and distinct migration identities.

Evidence: go vet ./...; go test ./... -count=1; go build ./...; CGO_ENABLED=0 go build ./cmd/agent-system; isolated PostgreSQL migration history tests.

Impact: Keep exec_env_layers at schema v35, move the feature migrations to v36-v46 in order, and fail closed on the old unpublished numbering.

Refs: 14e780c6fa32fdd4742b6232150c5efcb0d766ff; ab12fea474
2026-10-15 17:58:14 +00:00

489 lines
28 KiB
Go

package runtime
import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"strings"
"testing"
agentllm "agent-native/runtime/llm"
"agent-squad/squad"
"agent-system-backend/pkg/extension"
core "agent-system-core"
)
type projectRunCtx struct {
*fakeRunCtx
bundle extension.ProjectContextBundle
readErr error
reads int
manifests []extension.ProjectContextManifest
evidence func(string) (extension.ProjectEvidence, error)
workContext string
budgetSettings core.EffectiveSettings
sessionNote string
injections []ContextInjection
surfaces []core.SurfaceSnapshot
toolList []core.ToolDesc
}
func (c *projectRunCtx) Tools() []core.ToolDesc { return c.toolList }
func (c *projectRunCtx) SessionNote() string { return c.sessionNote }
func (c *projectRunCtx) Injections() []ContextInjection { return c.injections }
func (c *projectRunCtx) Surfaces() []core.SurfaceSnapshot { return c.surfaces }
func (c *projectRunCtx) Settings() core.EffectiveSettings {
if c.budgetSettings != nil {
return c.budgetSettings
}
return c.fakeRunCtx.Settings()
}
func (c *projectRunCtx) ProjectContext(context.Context, string, int) (extension.ProjectContextBundle, bool, error) {
c.reads++
return c.bundle, true, c.readErr
}
func (c *projectRunCtx) SaveContextManifest(_ context.Context, m extension.ProjectContextManifest) error {
c.manifests = append(c.manifests, m)
return nil
}
func (c *projectRunCtx) ProjectContextEnabled() bool { return true }
func (c *projectRunCtx) WorkContext() string { return c.workContext }
func (c *projectRunCtx) ProjectEvidence(_ context.Context, ref string) (extension.ProjectEvidence, error) {
return c.evidence(ref)
}
func TestProjectContextKeepsVisionRoutingAndImageInputs(t *testing.T) {
var gotModel string
var gotMessages []agentllm.Message
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
gotModel = model
gotMessages = append([]agentllm.Message(nil), msgs...)
return scriptStream(textTurns("done"), nil)(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{
fakeRunCtx: &fakeRunCtx{
ctx: context.Background(),
projectID: "project-1",
prof: core.AgentProfile{Model: core.ModelRoute{Chat: "plain-text"}},
history: []core.Message{{
ID: "m-image", Role: core.RoleUser,
Parts: []core.Part{{Type: core.PartText, Text: "inspect this http://127.0.0.1/private.png"}},
}},
attachments: []core.ParsedDocument{{
Meta: core.DocMeta{Filename: "chart.png", MIME: "image/png"},
Blocks: []core.DocBlock{{
Type: core.BlockImageRef,
Ref: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==",
}},
}},
},
bundle: extension.ProjectContextBundle{
Status: extension.ProjectContextReady, ProjectID: "project-1",
Items: []extension.ProjectContextItem{{SourceID: "source-1", Revision: "r1", Hash: "h1", Text: "CURRENT_PROJECT_FACT", Required: true}},
},
budgetSettings: core.EffectiveSettings{
"models": map[string]any{
"chat": map[string]any{"model": "plain-text", "baseUrl": "http://test"},
"vision": map[string]any{"model": "vision-model", "capable": "vision-model"},
},
"runtime": map[string]any{"verify": false, "composeFinal": false},
},
}
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
if gotModel != "vision-model" {
t.Fatalf("project image request model=%q, want vision-model", gotModel)
}
var images int
for _, msg := range gotMessages {
images += len(msg.Images)
}
wire := joinReq(gotMessages)
if images != 1 || !strings.Contains(wire, "CURRENT_PROJECT_FACT") || !strings.Contains(wire, "地址不在允许范围") {
t.Fatalf("project image request lost context or image behavior: images=%d request=%q", images, wire)
}
}
func TestProjectContextRefreshesEachTurnAndWorkContextAppearsOnce(t *testing.T) {
turns := [][]agentllm.Event{
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, StopReason: agentllm.StopToolCalls, ToolCalls: []agentllm.ToolCall{{ID: "tool-1", Name: "knowledge_search", Arguments: `{}`}}}}},
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "done", StopReason: agentllm.StopEnd}}},
}
base := scriptStream(turns, nil)
var requests [][]agentllm.Message
c := &projectRunCtx{
fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "project-1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "project-1", Version: "v1", AuthorizationEpoch: "auth-1", PublicationEpoch: "pub-1", Items: []extension.ProjectContextItem{{SourceID: "source-1", Revision: "r1", Hash: "h1", Text: "PROJECT_V1", Required: true}}},
workContext: "SAVED_WORK_CONTEXT",
}
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
requests = append(requests, append([]agentllm.Message(nil), msgs...))
if len(requests) == 1 {
c.bundle.Version = "v2"
c.bundle.AuthorizationEpoch = "auth-2"
c.bundle.PublicationEpoch = "pub-2"
c.bundle.Items[0].Text = "PROJECT_V2"
}
return base(ctx, model, msgs, tools, opts)
}}
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
if len(requests) < 2 {
t.Fatalf("model requests=%d, want at least 2", len(requests))
}
first := joinReq(requests[0])
all := first
for _, request := range requests[1:] {
all += "\n" + joinReq(request)
}
if strings.Count(all, "SAVED_WORK_CONTEXT") != 1 {
t.Fatalf("work context must appear once on turn 0: requests=%q", all)
}
if !strings.Contains(first, "PROJECT_V1") {
t.Fatalf("first request lost v1 project context: %q", first)
}
for i, request := range requests[1:] {
wire := joinReq(request)
if !strings.Contains(wire, "PROJECT_V2") || strings.Contains(wire, "PROJECT_V1") {
t.Fatalf("request %d did not refresh without stale carryover: %q", i+2, wire)
}
}
}
func TestProjectContextSecondTurnRefreshFailureBlocksModel(t *testing.T) {
turns := [][]agentllm.Event{{
{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, StopReason: agentllm.StopToolCalls, ToolCalls: []agentllm.ToolCall{{ID: "tool-1", Name: "knowledge_search", Arguments: `{}`}}}},
}}
base := scriptStream(turns, nil)
modelCalls := 0
c := &projectRunCtx{
fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "project-1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "project-1", Version: "v1", Items: []extension.ProjectContextItem{{SourceID: "source-1", Revision: "r1", Hash: "h1", Text: "PROJECT_V1", Required: true}}},
workContext: "SAVED_WORK_CONTEXT",
}
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
modelCalls++
c.readErr = errors.New("project authorization revoked")
return base(ctx, model, msgs, tools, opts)
}}
err := runner.Execute(c)
if err == nil || !strings.Contains(err.Error(), "project authorization revoked") || modelCalls != 1 || c.reads != 2 {
t.Fatalf("second-turn refresh failure was swallowed: err=%v modelCalls=%d reads=%d", err, modelCalls, c.reads)
}
}
func TestProjectContextRefreshesPerModelRequestWithoutPersistingOldSummary(t *testing.T) {
var requests [][]agentllm.Message
base := scriptStream(textTurns("private old model summary", "ok"), nil)
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
requests = append(requests, append([]agentllm.Message(nil), msgs...))
return base(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), sandbox: t.TempDir(), projectID: "project-1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}}}
c.bundle = extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "project-1", Version: "v1", Items: []extension.ProjectContextItem{{SourceID: "s1", Revision: "r1", Hash: "h1", Text: "summary-v1", Required: true}}}
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
c.bundle.Version = "v2"
c.bundle.Items[0].Text = "summary-v2"
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
if len(requests) != 2 || !strings.Contains(joinReq(requests[0]), "summary-v1") || !strings.Contains(joinReq(requests[1]), "summary-v2") || strings.Contains(joinReq(requests[1]), "summary-v1") || strings.Contains(joinReq(requests[1]), "private old model summary") {
t.Fatalf("project context not refreshed: first=%q second=%q", joinReq(requests[0]), joinReq(requests[1]))
}
if c.reads != 2 || len(c.manifests) != 4 || c.manifests[0].Delivery != "attempted" || c.manifests[1].Delivery != "responded" || c.manifests[2].Version != "v2" || c.manifests[3].Delivery != "responded" || c.manifests[2].AttemptID != c.manifests[3].AttemptID || c.manifests[3].InputSHA256 == "" {
t.Fatalf("actual request manifests missing: reads=%d manifests=%+v", c.reads, c.manifests)
}
wire, err := json.Marshal(requests[1])
if err != nil {
t.Fatal(err)
}
if want := fmt.Sprintf("%x", sha256.Sum256(wire)); c.manifests[3].InputSHA256 != want {
t.Fatalf("manifest hash %q does not match actual model request %q", c.manifests[3].InputSHA256, want)
}
}
func TestProjectContextResolutionFailureBlocksModelRequest(t *testing.T) {
called := false
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
called = true
return scriptStream(textTurns("bad"), nil)(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "project-1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}}, readErr: errors.New("permission revoked")}
if err := runner.Execute(c); err == nil || called {
t.Fatalf("context resolution failure reached model: err=%v called=%v", err, called)
}
}
func TestProjectRevocationScrubsOldMachineContextBeforeSecondTurn(t *testing.T) {
const old = "OLD_RESTRICTED_BODY"
turns := [][]agentllm.Event{
{{Kind: agentllm.KindTextDelta, Text: old}, {Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: old, StopReason: agentllm.StopToolCalls, ToolCalls: []agentllm.ToolCall{{ID: "c1", Name: "knowledge_search", Arguments: `{}`}}}}},
{{Kind: agentllm.KindTextDelta, Text: "done"}, {Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "done", StopReason: agentllm.StopEnd}}},
}
base := scriptStream(turns, nil)
var requests [][]agentllm.Message
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "p1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}, memEntries: []memEntry{{typ: "note", content: "STALE_MEMORY"}}, execOutput: old},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", Version: "v1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1", Items: []extension.ProjectContextItem{{SourceID: "doc", Revision: "r1", Hash: "h1", Text: old, Required: true}}},
budgetSettings: core.EffectiveSettings{"models": map[string]any{"chat": map[string]any{"baseUrl": "http://test"}}, "context": map[string]any{"userInstructions": "STALE_INSTRUCTIONS"}, "runtime": map[string]any{"verify": false, "composeFinal": false}},
sessionNote: "STALE_SESSION_NOTE", injections: []ContextInjection{{Content: "STALE_INJECTION", Constant: true}},
surfaces: []core.SurfaceSnapshot{{SemanticText: "STALE_SURFACE"}},
}
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
requests = append(requests, append([]agentllm.Message(nil), msgs...))
if len(requests) == 1 {
c.bundle = extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", Version: "v2", AuthorizationEpoch: "duty-2", PublicationEpoch: "pub-2", Items: []extension.ProjectContextItem{{SourceID: "public", Revision: "r1", Hash: "h2", Text: "CURRENT_PUBLIC_FACT", Required: true}}}
}
return base(ctx, model, msgs, tools, opts)
}}
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
if len(requests) != 2 {
t.Fatalf("model requests=%d, want 2", len(requests))
}
if c.fakeRunCtx.toolRuns != 1 {
t.Fatalf("expected restricted tool output before revocation, tool runs=%d", c.fakeRunCtx.toolRuns)
}
first := joinReq(requests[0])
for _, unproven := range []string{"STALE_MEMORY", "STALE_INSTRUCTIONS", "STALE_SESSION_NOTE", "STALE_INJECTION", "STALE_SURFACE"} {
if strings.Contains(first, unproven) {
t.Fatalf("unproven machine context %q reached first project request: %s", unproven, first)
}
}
second := joinReq(requests[1])
for _, forbidden := range []string{old, "STALE_MEMORY", "STALE_INSTRUCTIONS", "STALE_SESSION_NOTE", "STALE_INJECTION", "STALE_SURFACE"} {
if strings.Contains(second, forbidden) {
t.Fatalf("revoked machine context %q reached second model request: %s", forbidden, second)
}
}
if !strings.Contains(second, "CURRENT_PUBLIC_FACT") {
t.Fatalf("current context absent from second model request: %s", second)
}
}
func TestProjectSameScopeKeepsCurrentRunToolContext(t *testing.T) {
turns := [][]agentllm.Event{
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, StopReason: agentllm.StopToolCalls, ToolCalls: []agentllm.ToolCall{{ID: "c1", Name: "knowledge_search", Arguments: `{}`}}}}},
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "done", StopReason: agentllm.StopEnd}}},
}
base := scriptStream(turns, nil)
var requests [][]agentllm.Message
runner := &LLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
requests = append(requests, append([]agentllm.Message(nil), msgs...))
return base(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "p1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}}, execOutput: "AUTHORIZED_TOOL_RESULT"},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1", Items: []extension.ProjectContextItem{{SourceID: "fact", Revision: "r1", Hash: "h1", Text: "CURRENT_FACT", Required: true}}}}
if err := runner.Execute(c); err != nil {
t.Fatal(err)
}
if len(requests) < 2 || !strings.Contains(joinReq(requests[1]), "AUTHORIZED_TOOL_RESULT") || !strings.Contains(joinReq(requests[1]), "CURRENT_FACT") {
t.Fatalf("same-scope direct run lost current tool context: requests=%+v", requests)
}
}
func TestReadProjectEvidenceToolUsesScopedReader(t *testing.T) {
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{projectID: "p1"}, evidence: func(ref string) (extension.ProjectEvidence, error) {
if ref != "ref-1" {
return extension.ProjectEvidence{}, errors.New("reference denied")
}
return extension.ProjectEvidence{Reference: extension.ProjectEvidenceRef{ID: ref}, Text: "current authorized detail"}, nil
}}
st := &directRun{l: &LLMRunner{}, rc: c, eff: c.Settings()}
if got := st.runToolCtx(context.Background(), toolReadProjectEvidence, `{"reference":"ref-1"}`); got.IsError || !strings.Contains(got.Content, "current authorized detail") {
t.Fatalf("current evidence unavailable: %+v", got)
}
if got := st.runToolCtx(context.Background(), toolReadProjectEvidence, `{"reference":"../../arbitrary"}`); !got.IsError {
t.Fatalf("arbitrary evidence reference accepted: %+v", got)
}
}
func TestSubagentModelRequestUsesProjectContextAndManifest(t *testing.T) {
var sent []agentllm.Message
base := scriptStream(textTurns("done"), nil)
sub := &SubLLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
sent = append([]agentllm.Message(nil), msgs...)
return base(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "project-1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model", Lightweight: "test-lite"}}},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "project-1", Items: []extension.ProjectContextItem{{SourceID: "s1", Revision: "r1", Hash: "h1", Text: "current project fact", Required: true}}}}
if err := sub.Execute(c); err != nil {
t.Fatal(err)
}
if !strings.Contains(joinReq(sent), "current project fact") || len(c.manifests) != 2 || c.manifests[1].Delivery != "responded" {
t.Fatalf("background subagent missed project context: input=%q manifests=%+v", joinReq(sent), c.manifests)
}
}
func TestSubagentSameScopeKeepsNewToolResultAcrossTurns(t *testing.T) {
turns := [][]agentllm.Event{
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, StopReason: agentllm.StopToolCalls, ToolCalls: []agentllm.ToolCall{{ID: "search-1", Name: "knowledge_search", Arguments: `{}`}}}}},
{{Kind: agentllm.KindMessageEnd, Message: agentllm.Message{Role: agentllm.RoleAssistant, Content: "done", StopReason: agentllm.StopEnd}}},
}
base := scriptStream(turns, nil)
var requests [][]agentllm.Message
sub := &SubLLMRunner{Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
requests = append(requests, append([]agentllm.Message(nil), msgs...))
return base(ctx, model, msgs, tools, opts)
}}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{ctx: context.Background(), projectID: "p1", prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model", Lightweight: "test-lite"}}, execOutput: "NEW_AUTHORIZED_TOOL"},
bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1", Items: []extension.ProjectContextItem{{SourceID: "fact", Revision: "r1", Hash: "h1", Text: "CURRENT_FACT", Required: true}}},
toolList: []core.ToolDesc{{Name: "knowledge_search", Description: "Search", Parameters: json.RawMessage(`{"type":"object"}`)}},
}
if err := sub.Execute(c); err != nil {
t.Fatal(err)
}
if len(requests) != 2 || !strings.Contains(joinReq(requests[1]), "NEW_AUTHORIZED_TOOL") || !strings.Contains(joinReq(requests[1]), "CURRENT_FACT") || len(c.manifests) != 4 {
t.Fatalf("same-scope subagent lost authorized tool context: requests=%+v manifests=%+v", requests, c.manifests)
}
}
func TestProjectStreamRejectsProjectRunWithoutTrustedContextPort(t *testing.T) {
called := false
base := func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
called = true
return scriptStream(textTurns("bad"), nil)(ctx, model, msgs, tools, opts)
}
stream := GuardProjectStream(&fakeRunCtx{projectID: "p1"}, "goal", base)
for ev := range stream(context.Background(), "test", []agentllm.Message{{Role: agentllm.RoleUser, Content: "goal"}}, nil, agentllm.StreamOptions{}) {
if ev.Kind != agentllm.KindError {
t.Fatalf("missing scope port did not fail closed: %+v", ev)
}
}
if called {
t.Fatal("model reached without a trusted project context port")
}
}
func TestProjectStreamBlocksRequestExceedingFinalModelBudget(t *testing.T) {
called := false
base := func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
called = true
return scriptStream(textTurns("bad"), nil)(ctx, model, msgs, tools, opts)
}
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{projectID: "p1"}, bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", Items: []extension.ProjectContextItem{{SourceID: "large", Revision: "r1", Hash: "h1", Text: strings.Repeat("x", 11000), Required: true}}}, budgetSettings: core.EffectiveSettings{"models": map[string]any{"chat": map[string]any{"contextWindow": float64(4096), "outputReserve": float64(512), "contextBuffer": float64(512)}}}}
for ev := range GuardProjectStream(c, "goal", base)(context.Background(), "test", []agentllm.Message{{Role: agentllm.RoleUser, Content: "goal"}}, nil, agentllm.StreamOptions{}) {
if ev.Kind != agentllm.KindError {
t.Fatalf("oversized request did not fail: %+v", ev)
}
}
if called || len(c.manifests) != 0 {
t.Fatalf("oversized project request reached model or recorded an attempt: called=%v manifests=%+v", called, c.manifests)
}
}
func TestProjectStreamBlocksStaleMachineHistoryAfterDutyRevocation(t *testing.T) {
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{projectID: "p1"}, bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", Version: "v1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1"}}
calls := 0
base := scriptStream(textTurns("done"), nil)
guarded := GuardProjectStream(c, "goal", func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
calls++
return base(ctx, model, msgs, tools, opts)
})
initial := []agentllm.Message{{Role: agentllm.RoleSystem, Content: "base"}, {Role: agentllm.RoleUser, Content: "goal"}}
for range guarded(context.Background(), "model", initial, nil, agentllm.StreamOptions{}) {
}
c.bundle = extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", Version: "v2", AuthorizationEpoch: "duty-2", PublicationEpoch: "pub-2"}
stale := append(append([]agentllm.Message(nil), initial...), agentllm.Message{Role: agentllm.RoleAssistant, Content: "OLD_RESTRICTED_BODY"}, agentllm.Message{Role: agentllm.RoleTool, Content: "OLD_RESTRICTED_BODY"})
sawError := false
for ev := range guarded(context.Background(), "model", stale, nil, agentllm.StreamOptions{}) {
sawError = sawError || ev.Kind == agentllm.KindError
}
if !sawError || calls != 1 {
t.Fatalf("revoked history reached generic model stream: error=%v calls=%d", sawError, calls)
}
}
func TestProjectStreamRebuildsFirstRequestWithPriorMachineHistory(t *testing.T) {
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{projectID: "p1"}, bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1"}}
called := false
var requests [][]agentllm.Message
baseTurns := scriptStream(textTurns("ok", "done"), nil)
base := func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
called = true
requests = append(requests, append([]agentllm.Message(nil), msgs...))
return baseTurns(ctx, model, msgs, tools, opts)
}
msgs := []agentllm.Message{{Role: agentllm.RoleSystem, Content: "old system"}, {Role: agentllm.RoleUser, Content: "old goal"}, {Role: agentllm.RoleAssistant, Content: "OLD_RESTRICTED_BODY"}, {Role: agentllm.RoleTool, Content: "OLD_RESTRICTED_BODY"}, {Role: agentllm.RoleUser, Content: "current goal"}}
guarded := GuardProjectStream(c, "goal", base)
sawError := false
for ev := range guarded(context.Background(), "model", msgs, nil, agentllm.StreamOptions{}) {
sawError = sawError || ev.Kind == agentllm.KindError
}
if sawError || !called || strings.Contains(joinReq(requests[0]), "OLD_RESTRICTED_BODY") || !strings.Contains(joinReq(requests[0]), "current goal") {
t.Fatalf("first request was not safely rebuilt: error=%v called=%v sent=%q", sawError, called, joinReq(requests[0]))
}
second := append(append([]agentllm.Message(nil), msgs...), agentllm.Message{Role: agentllm.RoleAssistant, Content: "NEW_AUTHORIZED_STEP"}, agentllm.Message{Role: agentllm.RoleTool, Content: "NEW_AUTHORIZED_TOOL"})
for ev := range guarded(context.Background(), "model", second, nil, agentllm.StreamOptions{}) {
sawError = sawError || ev.Kind == agentllm.KindError
}
if sawError || len(requests) != 2 || strings.Contains(joinReq(requests[1]), "OLD_RESTRICTED_BODY") || !strings.Contains(joinReq(requests[1]), "NEW_AUTHORIZED_TOOL") {
t.Fatalf("second request failed to keep only same-scope new tool history: error=%v requests=%+v", sawError, requests)
}
}
func TestProjectStreamRejectsPriorMachineHistoryWithoutFreshGoal(t *testing.T) {
c := &projectRunCtx{fakeRunCtx: &fakeRunCtx{projectID: "p1"}, bundle: extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "p1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1"}}
called := false
base := func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
called = true
return scriptStream(textTurns("bad"), nil)(ctx, model, msgs, tools, opts)
}
msgs := []agentllm.Message{{Role: agentllm.RoleSystem, Content: "base"}, {Role: agentllm.RoleUser, Content: "old goal"}, {Role: agentllm.RoleAssistant, Content: "OLD_RESTRICTED_BODY"}}
sawError := false
for ev := range GuardProjectStream(c, "goal", base)(context.Background(), "model", msgs, nil, agentllm.StreamOptions{}) {
sawError = sawError || ev.Kind == agentllm.KindError
}
if !sawError || called {
t.Fatalf("machine history without a fresh goal reached model: error=%v called=%v", sawError, called)
}
}
type projectSquadCtx struct {
*entryFake
manifests []extension.ProjectContextManifest
}
func (c *projectSquadCtx) Run() core.Run {
return core.Run{ID: "run-squad", Owner: "alice", ProjectID: "project-1"}
}
func (c *projectSquadCtx) ProjectContext(context.Context, string, int) (extension.ProjectContextBundle, bool, error) {
return extension.ProjectContextBundle{Status: extension.ProjectContextReady, ProjectID: "project-1", AuthorizationEpoch: "duty-1", PublicationEpoch: "pub-1", Items: []extension.ProjectContextItem{{SourceID: "squad-source", Revision: "r1", Hash: "h1", Text: "squad project fact", Required: true}}}, true, nil
}
func (c *projectSquadCtx) SaveContextManifest(_ context.Context, m extension.ProjectContextManifest) error {
c.manifests = append(c.manifests, m)
return nil
}
func TestSquadEntryProjectRequestUsesContextBeforeWorkspaceFailClosed(t *testing.T) {
var sent []agentllm.Message
calls := 0
base := writeThenStop(nil, nil)
runner := &SquadRunner{Router: fixedRoute{mode: squad.RouteDirect}, Checker: squad.MinimalChecker{}, Hitl: squad.HitlPolicy{Mode: squad.HitlDisabled},
Stream: func(ctx context.Context, model string, msgs []agentllm.Message, tools []agentllm.ToolDecl, opts agentllm.StreamOptions) <-chan agentllm.Event {
calls++
sent = append([]agentllm.Message(nil), msgs...)
return base(ctx, model, msgs, tools, opts)
}}
dir := t.TempDir()
c := &projectSquadCtx{entryFake: &entryFake{text: "write report", sandbox: dir, settings: core.EffectiveSettings{"models": map[string]any{"chat": map[string]any{"model": "test-model", "baseUrl": "http://test"}}, "storage": map[string]any{"dataDir": dir}}}}
err := runner.Execute(c)
if err == nil || !strings.Contains(err.Error(), "missing target deliverable output.md") {
t.Fatalf("project squad did not fail closed without audited workspace tools: %v", err)
}
if calls < 1 || !strings.Contains(joinReq(sent), "squad project fact") || len(c.manifests) < 2 || c.manifests[len(c.manifests)-1].Delivery != "responded" {
t.Fatalf("squad model missed project context: input=%q manifests=%+v", joinReq(sent), c.manifests)
}
}