Files
HomeAgent/internal/sdk/secrets_impl.go
JianFeeeee 3f834794c5 refactor: SDK module 路径统一改为 hologramagentsdk(59 文件 + go.mod)
## 为什么必须做

SDK 仓(独立仓库)已把 module 从 `github.com/JianFeeeee/homeagentsdk`
改名为 `hologramagentsdk`。主仓通过 `replace` 直接引用它的**物理目录**:

    replace github.com/JianFeeeee/homeagentsdk => ./third_party/homeagent-sdk

一旦 SDK 侧改成新 module 名,主仓立刻炸:

    go: ./third_party/homeagent-sdk@ used for two different module paths
        (github.com/JianFeeeee/hologramagentsdk and github.com/JianFeeeee/homeagentsdk)

Go **不允许**同一目录被两个 module 路径 replace。所以不能"新旧并存",
只能把主仓 59 个文件的 import 全部改到新名,并删掉旧 replace。

## 一个被证伪的前提

我最初按"third_party/homeagent-sdk 是独立仓库,不能在此仓修改其源码"
来处理,并因此判断两个仓的改动可以隔离。实际上主仓是靠 `replace` 指向
该目录的物理副本 —— **两个仓的改动不可能真正隔离**:SDK 侧改 module 名,
主仓必然跟着改 import。

这个前提应该在做任何改动之前用 `go list -m` 验证,而不是假设。

## 连带修掉两处

| 缺陷 | 性质 |
|---|---|
| `TestLuaSDKMockSingleSource` 失败 | SDK 的 `sdk/lua/sdk.lua` 变了,主仓内嵌副本未同步(跑 `scripts/sync-lua-sdk.sh` 修复) |
| `sdkModuleNames` 分散两处 | 已在 SDK 仓合并为单一数据源(见 SDK 仓 725732b) |

第一条是**副本一致性判据抓到的**,不是我自己发现的 —— 这类判据的价值在此:
两个仓必然有副本,靠人记住"改了 A 要同步 B"是做不到的。

## 验证

- `go build ./...` / `go vet ./...` 干净
- `go test ./... -count=1`:**58 包 0 失败**
- `internal/plugin`(Lua SDK 副本一致性)+ `internal/sdk` 全绿
- 主仓 `third_party/homeagent-sdk/` 与 SDK 仓 `main` 无差异
2026-10-23 15:51:02 +08:00

112 lines
3.9 KiB
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
// SecretsAPI 的内核实现:桥接到 `internal/security` 的进程级变量库。
//
// ## 为何是"桥接"而不是"另建一套"
//
// 变量库是**进程级单例**(`security.Global()`):主/子/驻留子 agent 共享
// 同一套别名表(设计 S10)。插件若自建一套,两库互不可见 ——
// 内核拦截时登记的变量插件看不到,插件登记的拦截又不认识,
// 同一个凭据可能两处各存一份而使用者不知哪个权威。
//
// 所以这个实现只是把调用转发到那个单例。**没有第二份存储**。
//
// ## 未启用时的行为
//
// `security.Global()` 为 nil 时**不返回 nil**,而是返回一个 Available()
// 为 false 的实现。理由:nil 会让插件到处判空,而判空漏一处就 panic
// (本仓在 `*SensitiveInterceptor` 上吃过这个亏)。给一个明确的
// "不可用" 实现,插件只需问 `Available()`。
import (
"fmt"
internalSecurity "github.com/JianFeeeee/HologramAgent/internal/security"
pubsdk "github.com/JianFeeeee/hologramagentsdk/sdk"
)
// secretsImpl 实现 pubsdk.SecretsAPI。
type secretsImpl struct{}
// NewSecretsAPI 构造变量库接口(内核装配时注入给每个插件)。
func NewSecretsAPI() pubsdk.SecretsAPI { return &secretsImpl{} }
func (s *secretsImpl) Available() bool { return internalSecurity.Global() != nil }
// AvailableForRPC 是 proc 桥用来**明确报告**可用性的入口。
//
// ★ 为什么不能靠 List 推断:未启用时 List 返回空列表(**不报错**),
// 于是"库是空的"与"库不存在"在 RPC 层长得一样 —— 实测踩到:
// 插件的 Available() 误判为 true,接着 Store 才失败,错误信息变成
// "存储失败"而不是"能力不可用",排查时方向完全错。
func (s *secretsImpl) AvailableForRPC() bool { return s.Available() }
func (s *secretsImpl) Store(value, note string) (string, error) {
st := internalSecurity.Global()
if st == nil {
// ★ 响亮失败:不静默降级成"存到别处"或"不存"。
// 插件以为存好了而实际没存,比直接报错危险得多。
return "", fmt.Errorf("安全模块未启用(core.security.enabled=false),无法登记凭据")
}
return st.Submit(value, note)
}
func (s *secretsImpl) List() []pubsdk.SecretInfo {
st := internalSecurity.Global()
if st == nil {
return nil
}
infos := st.List()
out := make([]pubsdk.SecretInfo, len(infos))
for i, in := range infos {
out[i] = pubsdk.SecretInfo{
Alias: in.Alias, Source: in.Source, Note: in.Note,
CreatedAt: in.CreatedAt, UpdatedAt: in.UpdatedAt, Hits: in.Hits,
}
}
return out
}
func (s *secretsImpl) Forget(alias string) error {
st := internalSecurity.Global()
if st == nil {
return fmt.Errorf("安全模块未启用,变量库不可用")
}
return st.Forget(alias)
}
func (s *secretsImpl) Fill(text string) (string, int, []string) {
st := internalSecurity.Global()
if st == nil {
return text, 0, nil
}
return st.Fill(text)
}
func (s *secretsImpl) Encrypt(plain []byte) (string, error) {
c := internalSecurity.ConfigCodec()
if c == nil || !c.Enabled() {
// ★ 拒绝明文落盘:插件以为存的是密文而实际是明文,是最危险的静默降级。
return "", fmt.Errorf("内核未装配加密能力,拒绝以明文存储(core.security.enabled=false?)")
}
return c.Seal(string(plain))
}
func (s *secretsImpl) Decrypt(stored string) ([]byte, error) {
// 非密文原样返回(兼容插件已有的明文文件,避免升级即坏)
if !internalSecurity.IsEncrypted(stored) {
return []byte(stored), nil
}
c := internalSecurity.ConfigCodec()
if c == nil || !c.Enabled() {
return nil, fmt.Errorf("有密文但内核未装配加密能力(密钥换了?)")
}
plain, err := c.Open(stored)
if err != nil {
return nil, err
}
return []byte(plain), nil
}
var _ pubsdk.SecretsAPI = (*secretsImpl)(nil)