Files
HomeAgent/third_party/homeagent-sdk/sdk/knowledge.go
JianFeeeee dfc05780fd feat(kbtree): 知识库暴露范围配置 —— 按树状只暴露指定分类
## 问题

kbtree 是**唯一**把知识库开放给外部进程的通道(HomeAgent 自己的 agent
走进程内直调 knowledge_* 内核工具,不经此),但它只有 listen_addr 与
token 两个配置,**没有任何范围限制**:拿到 token 的任何 agent 都能
/tree 列出全部条目、/search 取回任意条目全文。

本机库里混着个人内容(航空发动机教材摘录、课表、身份合并规则),
不该 broadly 可读。

## 改动

1. `internal/plugins/kbtree/scope.go`(新):暴露范围语义
   - 留空 = 全部可见(范围是"限制"不是"必填",留空保持既有行为)
   - 前缀按**路径分段**匹配:public 命中 public 与 public/tech,
     但**不**命中 publication(否则 publication 意外暴露)
   - 根下无分类的条目在范围非空时不可见 —— 它没有分类可匹配,
     放行等于范围形同虚设
   - 分隔符容忍逗号/分号/空白/换行/竖线:这是给人手填的字段

2. `plugin.go`:注册 `expose_categories` 配置项,接入**全部四个端点**
   - /tree      服务端裁剪子树(就地改,不重建:TreeView 字段多)
   - /categories 过滤路径列表
   - /counts    过滤计数并**重算 total**(数量本身也是信息泄露)
   - /search    ★ 过滤结果条目;这处最关键:
     只过滤 /tree 而放过 /search 等于范围形同虚设(换个 ?q= 就能拿到全文)。
     同时修正 limit 语义 —— 范围外条目不占名额,范围内条目不会被挤掉。

3. SDK 契约补 `Knowledge.Category`(纯增量)
   - 此前 `sdk.Knowledge` 只有 Name/Content,内核明明返回了 Category
     却在 knowledge_impl 的拷贝里丢掉 ⇒ 外部服务无法按分类判定,
     范围过滤在 SDK 层根本做不了。
   - Name/Content 均保留,无删除。

## 判据(8 条 + 4 组变异)

范围过滤最容易"只做一半",所以每个端点都单独钉。

★ 判据补强一处:初版只查条目名(priv1),结果「/categories 不过滤」
  这个变异**完全逃过** —— 分类端点返回的是路径不是条目名。
  补上分类路径断言(private)后判红。

变异验证:
- /search 不过滤      → 泄露 priv1 全文        ✓ 判红
- /categories 不过滤  → 泄露 private 分类路径   ✓ 判红(补强后)
- /counts 不过滤      → TestCategoriesAndCountsEndpoint 判红
- 前缀退化为字符串前缀 → publication 被误暴露      ✓ 判红

★ 过程中我的 fake 有两处与真实内核不符,先修 fake 再修实现:
  1. 漏了内核 treeLocked 的"子分类提升一层" ⇒ 得到 children=0 的假空树
  2. filterTree 无差别清空 t.Items ⇒ 整棵树只剩空壳节点
  (第一版的实现是"看着测试红就改",实际是 fake 在骗我)

全量 41 包绿。SDK 接口纯增量,未发布故无需冻结检查。
2026-09-26 15:31:15 +08:00

23 lines
901 B
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package sdk
// KnowledgeAPI provides access to the knowledge store.
type KnowledgeAPI interface {
Search(query string, topK int) ([]*Knowledge, error)
Add(name, content string) error
List() ([]string, error)
}
// Knowledge represents a knowledge entry.
type Knowledge struct {
Name string `json:"name"`
// Category 是该条目的父分类路径(如 "tech/go"),根下条目为空。
//
// 为何加这个字段:对外服务(kbtree)要做**暴露范围过滤**就必须知道
// 每条结果属于哪个分类 —— 过滤只能发生在服务端(客户端过滤等于
// 没过滤,范围外内容已经随响应发出去了)。
// 之前这里只有 Name/Content,内核明明返回了 Category 却在
// knowledge_impl.SearchIn 的拷贝里丢掉,导致外部无法按分类判定。
Category string `json:"category,omitempty"`
Content string `json:"content"`
}