Files
goclaw/cmd/gateway_subagent_recovery_test.go
T
Duc Nguyenandntduc bb7712a9ff fix(collaboration): harden delegated task isolation (#1486)
* feat(collaboration): isolate delegated artifacts and child runs

Isolate delegated inputs and outputs behind secure artifact exchange lifecycles. Scope Agent Link tasks by tenant and root agent, and enforce delegation spawn-tree boundaries. Add process-wide child-run admission and preserve logical media paths across native, MCP, and sandbox execution.

* fix(collaboration): harden delegated task isolation

Enforce tenant and root-agent task scope across migrations and stores. Add exactly-once async completion delivery, delegated sandbox boundaries, and confined artifact and media recovery across runtime surfaces.

* fix(collaboration): recover interrupted async tasks

* fix(collaboration): normalize persisted child-run status

---------

Co-authored-by: ntduc <ntduc@cpp.ai.vn>
2026-07-30 14:17:40 +07:00

66 lines
1.8 KiB
Go

package cmd
import (
"context"
"errors"
"sync"
"testing"
"time"
"github.com/nextlevelbuilder/goclaw/internal/store"
)
type scriptedSubagentTaskRecovery struct {
mu sync.Mutex
failures int
recovered int64
calls int
}
func (s *scriptedSubagentTaskRecovery) RecoverInterrupted(ctx context.Context) (int64, error) {
if !store.IsMasterScope(ctx) || store.TenantIDFromContext(ctx) != store.MasterTenantID {
return 0, errors.New("recovery did not receive explicit master scope")
}
s.mu.Lock()
defer s.mu.Unlock()
s.calls++
if s.calls <= s.failures {
return 0, errors.New("database temporarily unavailable")
}
return s.recovered, nil
}
func (s *scriptedSubagentTaskRecovery) callCount() int {
s.mu.Lock()
defer s.mu.Unlock()
return s.calls
}
func TestRecoverInterruptedSubagentTasksRetriesBeforeTraffic(t *testing.T) {
recovery := &scriptedSubagentTaskRecovery{failures: 2, recovered: 3}
stores := &store.Stores{SubagentTaskRecovery: recovery}
recovered, err := recoverInterruptedSubagentTasks(
context.Background(), stores, time.Millisecond,
)
if err != nil {
t.Fatalf("recoverInterruptedSubagentTasks: %v", err)
}
if recovered != 3 || recovery.callCount() != 3 {
t.Fatalf("recovery = (%d rows, %d calls), want (3, 3)", recovered, recovery.callCount())
}
}
func TestRecoverInterruptedSubagentTasksStopsOnShutdown(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel()
recovery := &scriptedSubagentTaskRecovery{failures: 1}
stores := &store.Stores{SubagentTaskRecovery: recovery}
_, err := recoverInterruptedSubagentTasks(ctx, stores, time.Hour)
if !errors.Is(err, context.Canceled) {
t.Fatalf("recovery error = %v, want context.Canceled", err)
}
if recovery.callCount() != 1 {
t.Fatalf("recovery calls = %d, want 1", recovery.callCount())
}
}