Files
DeepSeek-Reasonix/internal/assembly/boot/composition_linked_skill_test.go
SivanCola 073079d675 feat(composition): load preset resources on demand
Problem: selecting a custom preset captures unused skill templates and
extension packages and waits for optional MCP startup.

Root cause: resource snapshot creation also served as the runtime permission
model, making selection cost scale with installed resource payload.

Fix: add live capability-grant bindings while preserving legacy snapshots.
Resolve current skill/MCP/extension/hook/command authority at use, defer
optional startup, retain stable provider contracts, and fence new event
and archive formats from older readers. Use lightweight status summaries
and per-instance extension call leases for safe concurrent replacement.

Verification: root and Desktop suites, full boot/builtin suites, affected
storage/controller/frontend tests, lifecycle race checks, frontend interaction
tests and typecheck, lint/vet/check, actionlint, and Windows cross-compilation.
Twenty-sample real switch measurements include build, persist and publication;
64 MiB unused templates remain outside the binding and stay below the local
500 ms P95 target. Native Windows execution is delegated to hosted CI.
2026-10-20 18:49:01 +08:00

112 lines
3.8 KiB
Go

package boot
import (
"errors"
"os"
"path/filepath"
"testing"
"reasonix/internal/contract/agentcomposition"
"reasonix/internal/contract/config"
"reasonix/internal/state/agentpreset"
)
func TestCompositionRetainsLinkedSkillAcrossRestart(t *testing.T) {
for _, layout := range []string{"directory", "file"} {
t.Run(layout, func(t *testing.T) {
home, root := compositionTestWorkspace(t, "openai")
source := t.TempDir()
writeFile(t, source, "SKILL.md", "---\nname: linked\ndescription: linked skill\n---\nORIGINAL SKILL")
writeFile(t, source, "references/guide.md", "ORIGINAL REFERENCE")
target, link := source, filepath.Join(home, "skills", "linked")
if layout == "file" {
target, link = filepath.Join(source, "SKILL.md"), link+".md"
}
if err := os.MkdirAll(filepath.Dir(link), 0o700); err != nil {
t.Fatal(err)
}
if err := os.Symlink(target, link); err != nil {
t.Skipf("symlink creation unavailable: %v", err)
}
d := agentcomposition.Builtins()[0]
d.ID, d.Persona.Prefix = "user:linked", "Custom persona"
if _, err := agentpreset.New(home, root).Save(d, ""); err != nil {
t.Fatal(err)
}
opts := Options{Home: home, WorkspaceRoot: root, AgentComposition: d.ID, resolvedShell: pinnedEffectShell()}
first, err := buildSnapshotRuntime(t.Context(), opts)
if err != nil {
t.Fatal(err)
}
defer first.Controller.Close()
binding := first.Controller.CompositionBinding()
if binding == nil {
t.Fatal("custom persona was not bound")
}
if err := os.RemoveAll(source); err != nil {
t.Fatal(err)
}
opts.AgentComposition, opts.Composition = "", binding
restored, err := buildSnapshotRuntime(t.Context(), opts)
if err != nil {
t.Fatal(err)
}
defer restored.Controller.Close()
if first.Snapshot.CacheHash() != restored.Snapshot.CacheHash() {
t.Fatal("restoring the linked skill changed the provider prefix")
}
found := false
for _, sk := range restored.Controller.Skills() {
if sk.Name != "linked" {
continue
}
found = true
if layout == "directory" {
data, err := os.ReadFile(filepath.Join(filepath.Dir(sk.Path), "references", "guide.md"))
if err != nil || string(data) != "ORIGINAL REFERENCE" {
t.Fatalf("linked skill reference not retained: %q %v", data, err)
}
}
}
if !found {
t.Fatal("linked skill was lost")
}
if err := config.DefaultActivationStore().SetSkillEnabled("linked", root, config.ActivationProject, false); err != nil {
t.Fatal(err)
}
if err := restored.Controller.CompositionExecutionError(); !errors.Is(err, agentcomposition.ErrUnavailable) {
t.Fatalf("linked skill revocation ignored: %v", err)
}
})
}
}
func TestCompositionLinkedSkillRejectsLinkedResources(t *testing.T) {
home, root := compositionTestWorkspace(t, "openai")
source, outside := t.TempDir(), t.TempDir()
writeFile(t, source, "SKILL.md", "---\nname: linked\ndescription: linked skill\n---\nSKILL")
writeFile(t, outside, "private.txt", "must not be retained")
if err := os.Symlink(filepath.Join(outside, "private.txt"), filepath.Join(source, "reference.md")); err != nil {
t.Skipf("symlink creation unavailable: %v", err)
}
link := filepath.Join(home, "skills", "linked")
if err := os.MkdirAll(filepath.Dir(link), 0o700); err != nil {
t.Fatal(err)
}
if err := os.Symlink(source, link); err != nil {
t.Fatal(err)
}
d := agentcomposition.Builtins()[0]
d.ID = "user:linked"
if _, err := agentpreset.New(home, root).Save(d, ""); err != nil {
t.Fatal(err)
}
result, err := buildSnapshotRuntime(t.Context(), Options{Home: home, WorkspaceRoot: root, AgentComposition: d.ID, resolvedShell: pinnedEffectShell()})
if result != nil {
result.Controller.Close()
}
if !errors.Is(err, agentcomposition.ErrUnavailable) {
t.Fatalf("linked resource was not refused: %v", err)
}
}