// reflector_double_wire_test.go — engine.New 装配期反射器双挂检测单测. // // ADR-0008 v2 § 引擎层反射器契约 (2026-05-02): cfg.ResponseReflector // 是 LLM-invisible engine-forced hook. 同名 Tool 进 cfg.Toolset 让 LLM // 又能见到反射器作为工具调, 模糊反射器 vs 工具语义. engine.New 装配期 // fail-fast 拒构造返 flyto.ErrReflectorDoubleWired. // // reflector_double_wire_test.go — engine.New construction-time tests // for ADR-0008 v2 reflector double-wire detection. package engine import ( "context" "errors" "testing" "git.flytoex.net/yuanwei/flyto-agent/pkg/flyto" "git.flytoex.net/yuanwei/flyto-agent/pkg/tools" "git.flytoex.net/yuanwei/flyto-agent/pkg/validator" ) // stubReflector is a minimal validator.StructuralValidator for tests. // Embeds StructuralMarker (sealed contract acknowledgement) and exposes // configurable Name() so tests can collide / not-collide with Tool names. // // stubReflector 是测试用最小 validator.StructuralValidator. 嵌入 // StructuralMarker (sealed 契约声明) + Name() 可配置, 让测试控制是否 // 跟 Tool name 撞. type stubReflector struct { validator.StructuralMarker name string } func (s *stubReflector) Name() string { return s.name } func (s *stubReflector) Validate(_ context.Context, _ validator.DiffInput) (validator.Verdict, error) { return validator.Verdict{Approved: true, ValidatorName: s.name}, nil } // TestNew_ReflectorDoubleWired_FailsFast verifies engine.New rejects // construction with flyto.ErrReflectorDoubleWired when ResponseReflector. // Name() collides with a Tool.Name() in cfg.Toolset (ADR-0008 v2 § // engine-level reflector contract). // // TestNew_ReflectorDoubleWired_FailsFast 验证 engine.New 在 // ResponseReflector.Name() 与 cfg.Toolset 中某 Tool.Name() 撞名时 // 拒构造返 flyto.ErrReflectorDoubleWired (ADR-0008 v2 § 引擎层 // 反射器契约). func TestNew_ReflectorDoubleWired_FailsFast(t *testing.T) { cfg := testConfig() cfg.Toolset = tools.Allowlist(&mockTool{name: "billcost_reflect"}) cfg.ResponseReflector = &stubReflector{name: "billcost_reflect"} _, err := New(cfg) if err == nil { t.Fatal("expected ErrReflectorDoubleWired, got nil error") } var ee *flyto.EngineError if !errors.As(err, &ee) { t.Fatalf("expected *flyto.EngineError, got %T: %v", err, err) } if ee.Code != flyto.ErrReflectorDoubleWired { t.Errorf("Code: got %q, want %q", ee.Code, flyto.ErrReflectorDoubleWired) } } // TestNew_ReflectorAndDifferentNamedTool_OK verifies a Tool whose name // does not collide with ResponseReflector.Name() is allowed (legitimate // case: reflector + unrelated utility tool coexist when caller exposes // both). // // TestNew_ReflectorAndDifferentNamedTool_OK 验证 Tool 名跟 // ResponseReflector.Name() 不撞时通过 (合法场景: 反射器 + 无关的 // 工具共存). func TestNew_ReflectorAndDifferentNamedTool_OK(t *testing.T) { cfg := testConfig() cfg.Toolset = tools.Allowlist(&mockTool{name: "fetch"}) cfg.ResponseReflector = &stubReflector{name: "billcost_reflect"} eng, err := New(cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if eng == nil { t.Fatal("expected non-nil engine") } } // TestNew_ToolsetNoneWithReflector_OK verifies Toolset.None() + a // ResponseReflector is the canonical ADR-0008 v2 quote-engine-probe // configuration and constructs cleanly (no double-wire collision since // the toolset is empty). // // TestNew_ToolsetNoneWithReflector_OK 验证 Toolset.None() + // ResponseReflector 是 ADR-0008 v2 quote-engine-probe 典型配置, 干净 // 构造 (toolset 空不可能撞名). func TestNew_ToolsetNoneWithReflector_OK(t *testing.T) { cfg := testConfig() cfg.Toolset = tools.None() cfg.ResponseReflector = &stubReflector{name: "billcost_reflect"} eng, err := New(cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if eng == nil { t.Fatal("expected non-nil engine") } } // TestNew_NoReflector_OK verifies the legacy path (no ResponseReflector) // still constructs cleanly even when a tool with the canonical reflector // name is in the toolset. ADR-0008 v2 detection only fires when // ResponseReflector is non-nil; nil = no-op (zero overhead). // // TestNew_NoReflector_OK 验证向后兼容路径 (无 ResponseReflector) 仍 // 干净构造, 即使工具箱里有典型反射器名字的工具. ADR-0008 v2 检测仅在 // ResponseReflector 非 nil 时触发, nil = 无侵入 (零开销). func TestNew_NoReflector_OK(t *testing.T) { cfg := testConfig() cfg.Toolset = tools.Allowlist(&mockTool{name: "billcost_reflect"}) // ResponseReflector 故意不设, 验证 nil 路径不触发检测. eng, err := New(cfg) if err != nil { t.Fatalf("unexpected error: %v", err) } if eng == nil { t.Fatal("expected non-nil engine") } } // TestStructuralValidator_SealedInterface_QuoteResponseReflector is a // compile-time assertion that the canonical platform-side reflector // (QuoteResponseReflector via embedding StructuralMarker, exercised in // platform/common/internal/billrecon/llm/validator_adapter.go) is // reachable from the engine layer through the sealed // StructuralValidator type. Since the engine layer cannot import the // platform layer (one-way dependency), this test instead asserts that // any local type embedding validator.StructuralMarker satisfies the // sealed contract -- i.e. the marker mechanism works as intended. // // TestStructuralValidator_SealedInterface_QuoteResponseReflector 编译 // 期断言: sealed StructuralValidator 接口可以经嵌入 StructuralMarker // 的本包类型 (stubReflector) 满足 -- 验证 marker 机制按设计工作. // platform 层 QuoteResponseReflector 无法在此引用 (单向依赖), 编译期 // 真实生效在 platform/common/internal/billrecon/llm/validator_adapter.go // 自身的 `var _ validator.StructuralValidator = (*QuoteResponseReflector)(nil)`. func TestStructuralValidator_SealedInterface_QuoteResponseReflector(t *testing.T) { // Compile-time: stubReflector embeds StructuralMarker so it // satisfies validator.StructuralValidator. If the marker method // becomes exported or the embedding contract changes, this fails // to compile. var _ validator.StructuralValidator = (*stubReflector)(nil) }