Files
artex/db/exploration_sources_test.go
dela 0335d572de
ci / go (push) Waiting to run
ci / go-db (agent) (push) Waiting to run
ci / go-db (config) (push) Waiting to run
ci / go-db (db) (push) Waiting to run
ci / go-db (evidence) (push) Waiting to run
ci / go-db (llmrec) (push) Waiting to run
ci / go-db (server) (push) Waiting to run
detections / detections (push) Waiting to run
web / web (push) Waiting to run
docs / links (push) Canceled after 0s
First Commit
2026-10-09 08:38:16 +08:00

432 lines
17 KiB
Go

package db
import (
"fmt"
"testing"
)
func TestInheritedActivityReadsRequireTerminalIntent(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) - skipping", err)
}
defer d.Close()
expID, err := d.CreateExploration("source activity boundary", "source activity boundary")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _, _ = d.Exec(`DELETE FROM explorations WHERE id=$1`, expID) })
store := d.Exploration(expID)
intentID, err := store.AddIntent(map[string]any{"summary": "source work"}, 1, nil, "planner")
if err != nil {
t.Fatal(err)
}
appendStep := func(kind, summary string) int64 {
t.Helper()
id, appendErr := store.AppendActivity(Activity{
NodeID: &intentID, Worker: "worker", Kind: kind, Summary: summary, Detail: summary + " detail",
})
if appendErr != nil {
t.Fatal(appendErr)
}
return id
}
textID := appendStep("text", "shared text")
thinkingID := appendStep("thinking", "private reasoning")
usageID := appendStep("usage", "private accounting")
resultID := appendStep("result", "shared result")
if err := store.SetIntentState(intentID, "done"); err != nil {
t.Fatal(err)
}
page, more, err := store.ActivityPageForTerminalIntent(intentID, 0, 10)
if err != nil || more || len(page) != 2 || page[0].ID != textID || page[1].ID != resultID {
t.Fatalf("terminal page boundary: page=%+v more=%v err=%v", page, more, err)
}
list, cursor, err := store.ActivityListForTerminalIntent(intentID, 0, 10)
if err != nil || len(list) != 2 || cursor != resultID {
t.Fatalf("terminal list boundary: list=%+v cursor=%d err=%v", list, cursor, err)
}
trace, err := store.ActivityTraceForTerminalIntent(intentID, 10)
if err != nil || len(trace) != 2 {
t.Fatalf("terminal trace boundary: trace=%+v err=%v", trace, err)
}
hits, err := store.ActivityTraceSearchForTerminalIntent(intentID, "shared", 10)
if err != nil || len(hits) != 2 {
t.Fatalf("terminal search boundary: hits=%+v err=%v", hits, err)
}
details, err := store.ActivityByIDsForTerminalIntents([]int64{textID, thinkingID, usageID, resultID})
if err != nil || len(details) != 2 || details[0].ID != textID || details[1].ID != resultID {
t.Fatalf("terminal detail boundary: details=%+v err=%v", details, err)
}
// Reopening a source intent must close every inherited activity read even if
// callers still hold a stale terminal-state snapshot.
if err := store.SetIntentState(intentID, "running"); err != nil {
t.Fatal(err)
}
page, _, err = store.ActivityPageForTerminalIntent(intentID, 0, 10)
if err != nil || len(page) != 0 {
t.Fatalf("reopened intent leaked through page: page=%+v err=%v", page, err)
}
list, _, err = store.ActivityListForTerminalIntent(intentID, 0, 10)
if err != nil || len(list) != 0 {
t.Fatalf("reopened intent leaked through list: list=%+v err=%v", list, err)
}
trace, err = store.ActivityTraceForTerminalIntent(intentID, 10)
if err != nil || len(trace) != 0 {
t.Fatalf("reopened intent leaked through trace: trace=%+v err=%v", trace, err)
}
hits, err = store.ActivityTraceSearchForTerminalIntent(intentID, "shared", 10)
if err != nil || len(hits) != 0 {
t.Fatalf("reopened intent leaked through search: hits=%+v err=%v", hits, err)
}
details, err = store.ActivityByIDsForTerminalIntents([]int64{textID, resultID})
if err != nil || len(details) != 0 {
t.Fatalf("reopened intent leaked through detail: details=%+v err=%v", details, err)
}
}
func TestExplorationDirectSourceReadView(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) - skipping", err)
}
defer d.Close()
grand, err := d.CreateTask("grand source", "grand goal", nil, 0, 0)
if err != nil {
t.Fatal(err)
}
source, err := d.CreateTaskWithOptions("direct source", "source goal", TaskCreateOptions{
SourceTaskIDs: []int64{grand.ID},
})
if err != nil {
t.Fatal(err)
}
current, err := d.CreateTaskWithOptions("current", "current goal", TaskCreateOptions{
SourceTaskIDs: []int64{source.ID},
})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = d.DeleteTask(current.ID)
_ = d.DeleteTask(source.ID)
_ = d.DeleteTask(grand.ID)
})
grandStore := d.Exploration(grand.ExplorationID)
sourceStore := d.Exploration(source.ExplorationID)
currentStore := d.Exploration(current.ExplorationID)
grandFact, err := grandStore.AddNode(KindFact, map[string]any{"summary": "grand-only fact"}, 0, "confirmed", "worker", nil)
if err != nil {
t.Fatal(err)
}
sourceFact, err := sourceStore.AddNode(KindFact, map[string]any{"summary": "direct source fact"}, 0, "confirmed", "worker", nil)
if err != nil {
t.Fatal(err)
}
sourceFinding, err := sourceStore.AddNode(KindFinding, map[string]any{"summary": "direct source finding"}, 0, "confirmed", "worker", nil)
if err != nil {
t.Fatal(err)
}
sourceIntent, err := sourceStore.AddIntent(map[string]any{"summary": "source work"}, 9, nil, "planner")
if err != nil {
t.Fatal(err)
}
if err := sourceStore.Link(sourceIntent, RelYields, sourceFinding); err != nil {
t.Fatal(err)
}
currentIntent, err := currentStore.AddIntent(map[string]any{"summary": "current work"}, 1, nil, "planner")
if err != nil {
t.Fatal(err)
}
currentFact, err := currentStore.AddNode(KindFact, map[string]any{"summary": "current fact"}, 0, "confirmed", "worker", nil)
if err != nil {
t.Fatal(err)
}
sources, err := currentStore.DirectSourceStores()
if err != nil {
t.Fatal(err)
}
if len(sources) != 1 || sources[0].Task.TaskID != source.ID {
t.Fatalf("want only direct source %d, got %+v", source.ID, sources)
}
frontier, err := currentStore.Frontier(20)
if err != nil {
t.Fatal(err)
}
if len(frontier) != 1 || frontier[0].ID != currentIntent {
t.Fatalf("source intent leaked into local frontier: %+v", frontier)
}
if claimed, err := currentStore.ClaimIntent(sourceIntent, "wrong-task-worker"); err != nil || claimed {
t.Fatalf("source intent must not be claimable: claimed=%v err=%v", claimed, err)
}
if got, err := currentStore.GetNodeWithSources(sourceIntent); err != nil || got != nil {
t.Fatalf("live source intent must not be readable as inherited history: node=%+v err=%v", got, err)
}
if got, err := currentStore.ListByKindWithSources(KindIntent, 100); err != nil || len(got) != 1 || got[0].ID != currentIntent {
t.Fatalf("live source intent leaked through inherited list: nodes=%+v err=%v", got, err)
}
if got, err := currentStore.FindingIntentsWithSources(); err != nil || got[sourceFinding] != 0 {
t.Fatalf("live source intent leaked through finding lineage: lineage=%+v err=%v", got, err)
}
facts, err := currentStore.ListByKindWithSources(KindFact, 100)
if err != nil {
t.Fatal(err)
}
seenCurrent, seenSource, seenGrand := false, false, false
for _, fact := range facts {
switch fact.ID {
case currentFact:
seenCurrent = !fact.Inherited && fact.SourceTaskID == 0
case sourceFact:
seenSource = fact.Inherited && fact.SourceTaskID == source.ID
case grandFact:
seenGrand = true
}
}
if !seenCurrent || !seenSource || seenGrand {
t.Fatalf("unexpected direct-source fact view: current=%v source=%v grand=%v", seenCurrent, seenSource, seenGrand)
}
gotSource, err := currentStore.GetNodeWithSources(sourceFact)
if err != nil || gotSource == nil || !gotSource.Inherited || gotSource.SourceTaskID != source.ID {
t.Fatalf("source node lookup: node=%+v err=%v", gotSource, err)
}
gotGrand, err := currentStore.GetNodeWithSources(grandFact)
if err != nil || gotGrand != nil {
t.Fatalf("indirect source must be invisible: node=%+v err=%v", gotGrand, err)
}
// Existing write methods remain bound to currentStore.expID. Passing an
// inherited id is a no-op and cannot mutate the source blackboard.
if err := currentStore.SetNodeState(sourceFact, "dismissed"); err != nil {
t.Fatal(err)
}
unchanged, err := sourceStore.GetNode(sourceFact)
if err != nil || unchanged == nil || unchanged.State != "confirmed" {
t.Fatalf("inherited fact was mutated: node=%+v err=%v", unchanged, err)
}
if err := sourceStore.SetIntentState(sourceIntent, "done"); err != nil {
t.Fatal(err)
}
if got, err := currentStore.GetNodeWithSources(sourceIntent); err != nil || got == nil || !got.Inherited {
t.Fatalf("terminal source intent should become readable history: node=%+v err=%v", got, err)
}
if got, err := currentStore.FindingIntentsWithSources(); err != nil || got[sourceFinding] != sourceIntent {
t.Fatalf("terminal source finding lineage missing: lineage=%+v err=%v", got, err)
}
sourcePlannerStep, err := sourceStore.AppendActivity(Activity{
Worker: "planner", Kind: "text", Summary: "private source planner", Detail: "private source planner transcript",
})
if err != nil {
t.Fatal(err)
}
openSourceIntent, err := sourceStore.AddIntent(map[string]any{"summary": "source work still running"}, 1, nil, "planner")
if err != nil {
t.Fatal(err)
}
if err := sourceStore.SetIntentState(openSourceIntent, "running"); err != nil {
t.Fatal(err)
}
openSourceStep, err := sourceStore.AppendActivity(Activity{
NodeID: &openSourceIntent, Worker: "source-live-worker", Kind: "text",
Summary: "private live source work", Detail: "private live source transcript",
})
if err != nil {
t.Fatal(err)
}
sourceStep, err := sourceStore.AppendActivity(Activity{
NodeID: &sourceIntent, Worker: "source-worker", Kind: "tool_result", Tool: "HTTP",
Summary: "source-secret response", Detail: "source-secret full detail",
})
if err != nil {
t.Fatal(err)
}
currentPlannerStep, err := currentStore.AppendActivity(Activity{
Worker: "planner", Kind: "text", Summary: "current planner", Detail: "current planner detail",
})
if err != nil {
t.Fatal(err)
}
currentThinkingStep, err := currentStore.AppendActivity(Activity{
Worker: "planner", Kind: "thinking", Summary: "current thinking", Detail: "current thinking detail",
})
if err != nil {
t.Fatal(err)
}
grandStep, err := grandStore.AppendActivity(Activity{
NodeID: &grandFact, Worker: "grand-worker", Kind: "tool_result", Tool: "HTTP",
Summary: "grand-only response", Detail: "grand-only detail",
})
if err != nil {
t.Fatal(err)
}
trace, err := currentStore.ActivityTraceWithSources(sourceIntent, 20)
if err != nil || len(trace) != 1 || trace[0].ID != sourceStep || !trace[0].Inherited || trace[0].SourceTaskID != source.ID {
t.Fatalf("source trace lookup: trace=%+v err=%v", trace, err)
}
hits, err := currentStore.ActivityTraceSearchAllWithSources(0, "source-secret", 20)
if err != nil || len(hits) != 1 || hits[0].ID != sourceStep || !hits[0].Inherited {
t.Fatalf("source trace search: hits=%+v err=%v", hits, err)
}
grandHits, err := currentStore.ActivityTraceSearchAllWithSources(0, "grand-only", 20)
if err != nil || len(grandHits) != 0 {
t.Fatalf("indirect source trace leaked: hits=%+v err=%v", grandHits, err)
}
liveHits, err := currentStore.ActivityTraceSearchAllWithSources(0, "private live source", 20)
if err != nil || len(liveHits) != 0 {
t.Fatalf("live source trace leaked: hits=%+v err=%v", liveHits, err)
}
if liveTrace, err := currentStore.ActivityTraceWithSources(openSourceIntent, 20); err != nil || len(liveTrace) != 0 {
t.Fatalf("live inherited intent trace leaked: trace=%+v err=%v", liveTrace, err)
}
details, err := currentStore.ActivityByIDsWithSources([]int64{
sourcePlannerStep, openSourceStep, sourceStep, currentPlannerStep, grandStep,
})
if err != nil || len(details) != 2 || details[0].ID != sourceStep || !details[0].Inherited || details[1].ID != currentPlannerStep || details[1].Inherited {
t.Fatalf("source trace detail allow-list: details=%+v err=%v", details, err)
}
if detail, err := currentStore.ActivityDetailWithSources(sourcePlannerStep); err != nil || detail != "" {
t.Fatalf("source planner transcript leaked: detail=%q err=%v", detail, err)
}
if detail, err := currentStore.ActivityDetailWithSources(openSourceStep); err != nil || detail != "" {
t.Fatalf("live source intent transcript leaked: detail=%q err=%v", detail, err)
}
if detail, err := currentStore.ActivityDetailWithSources(currentPlannerStep); err != nil || detail != "current planner detail" {
t.Fatalf("local activity detail behavior changed: detail=%q err=%v", detail, err)
}
if detail, err := currentStore.ActivityDetailWithSources(currentThinkingStep); err != nil || detail != "current thinking detail" {
t.Fatalf("local thinking detail behavior changed: detail=%q err=%v", detail, err)
}
}
func TestTaskAssetContextUsesDirectSourceScopeAndAnchors(t *testing.T) {
d, err := Open(testDSN(t))
if err != nil {
t.Skipf("postgres unavailable (%v) - skipping", err)
}
defer d.Close()
grand, err := d.CreateTask("grand assets", "grand goal", nil, 0, 0)
if err != nil {
t.Fatal(err)
}
source, err := d.CreateTaskWithOptions("source assets", "source goal", TaskCreateOptions{SourceTaskIDs: []int64{grand.ID}})
if err != nil {
t.Fatal(err)
}
current, err := d.CreateTaskWithOptions("current assets", "current goal", TaskCreateOptions{SourceTaskIDs: []int64{source.ID}})
if err != nil {
t.Fatal(err)
}
assets := d.Assets()
sourceTestedDomain := fmt.Sprintf("context-tested-%d.invalid", source.ID)
sourceUntestedDomain := fmt.Sprintf("context-untested-%d.invalid", source.ID)
sourceAnchoredOnlyDomain := fmt.Sprintf("context-anchor-only-%d.invalid", source.ID)
grandDomain := fmt.Sprintf("context-grand-%d.invalid", grand.ID)
sourceTestedID, err := assets.UpsertRootDomain(UpsertRootDomainReq{Domain: sourceTestedDomain, TaskID: source.ID})
if err != nil {
t.Fatal(err)
}
sourceUntestedID, err := assets.UpsertRootDomain(UpsertRootDomainReq{Domain: sourceUntestedDomain, TaskID: source.ID})
if err != nil {
t.Fatal(err)
}
sourceAnchoredOnlyID, err := assets.UpsertRootDomain(UpsertRootDomainReq{Domain: sourceAnchoredOnlyDomain, TaskID: source.ID})
if err != nil {
t.Fatal(err)
}
grandID, err := assets.UpsertRootDomain(UpsertRootDomainReq{Domain: grandDomain, TaskID: grand.ID})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
_ = d.DeleteTask(current.ID)
_ = d.DeleteTask(source.ID)
_ = d.DeleteTask(grand.ID)
_, _ = assets.DeleteByIDs([]int64{sourceTestedID, sourceUntestedID, sourceAnchoredOnlyID, grandID})
})
if _, err := assets.AddAgentScope(source.ID, "root_domain", sourceTestedDomain, "test", "agent"); err != nil {
t.Fatal(err)
}
if _, err := assets.AddAgentScope(source.ID, "root_domain", sourceUntestedDomain, "test", "agent"); err != nil {
t.Fatal(err)
}
if _, err := assets.AddAgentScope(grand.ID, "root_domain", grandDomain, "test", "agent"); err != nil {
t.Fatal(err)
}
if _, err := d.Exploration(source.ExplorationID).AddNode(KindFact, map[string]any{"summary": "tested source asset"}, 0, "confirmed", "worker", []int64{sourceTestedID}); err != nil {
t.Fatal(err)
}
if _, err := d.Exploration(source.ExplorationID).AddNode(KindFact, map[string]any{"summary": "anchored-only source asset"}, 0, "confirmed", "worker", []int64{sourceAnchoredOnlyID}); err != nil {
t.Fatal(err)
}
if _, err := d.Exploration(grand.ExplorationID).AddNode(KindFact, map[string]any{"summary": "indirect tested asset"}, 0, "confirmed", "worker", []int64{grandID}); err != nil {
t.Fatal(err)
}
scopes, err := assets.ListTaskScopeWithSources(current.ID)
if err != nil {
t.Fatal(err)
}
if len(scopes) != 2 || scopes[0].TaskID != source.ID || scopes[1].TaskID != source.ID {
t.Fatalf("direct source scopes only: %+v", scopes)
}
cov, err := assets.TaskCoverageWithSources(current.ID)
if err != nil {
t.Fatal(err)
}
if cov.Denominator != 3 || cov.Tested != 2 {
t.Fatalf("combined coverage: %+v", cov)
}
untested, total, err := assets.ListUntestedAssetsWithSources(current.ID, "", 10, 0)
if err != nil {
t.Fatal(err)
}
if total != 1 || len(untested) != 1 || untested[0].ID != sourceUntestedID {
t.Fatalf("combined untested assets: total=%d assets=%+v", total, untested)
}
hosts, err := assets.HostsByTaskWithSources(current.ID)
if err != nil {
t.Fatal(err)
}
hostSet := map[string]bool{}
for _, host := range hosts {
hostSet[host] = true
}
if !hostSet[sourceTestedDomain] || !hostSet[sourceUntestedDomain] || !hostSet[sourceAnchoredOnlyDomain] || hostSet[grandDomain] {
t.Fatalf("direct source hosts only: %v", hosts)
}
refs, err := d.Exploration(current.ExplorationID).AssetRefsWithSources(sourceAnchoredOnlyID)
if err != nil || len(refs) != 1 || !refs[0].Inherited || refs[0].SourceTaskID != source.ID || refs[0].Kind != KindFact {
t.Fatalf("source asset refs provenance: refs=%+v err=%v", refs, err)
}
grandRefs, err := d.Exploration(current.ExplorationID).AssetRefsWithSources(grandID)
if err != nil || len(grandRefs) != 0 {
t.Fatalf("indirect source asset refs leaked: refs=%+v err=%v", grandRefs, err)
}
graph, err := assets.BuildCoverageGraph(current.ID, current.ExplorationID)
if err != nil {
t.Fatal(err)
}
seenAnchoredOnly := false
for _, node := range graph.Nodes {
if node.AssetID == sourceAnchoredOnlyID {
seenAnchoredOnly = node.Tested && node.InScope
}
}
if !seenAnchoredOnly {
t.Fatalf("anchored-only source asset missing from coverage graph: %+v", graph.Nodes)
}
}