chore(main): 回流 release/v2.0.x(v2.0.12)—— 内容对齐,main 无独有内容

按发布纪律回流。**注意这是内容对齐而非 merge**,理由与依据:

1. 纪律禁止 merge 回主干,因为 merge 会把发布分支的**版本号变更**带进 main。
   本项目版本号**只由 `git describe --tags` 推导**(Makefile VERSION),
   仓库里没有任何文件存放版本号 ⇒ 回流不存在版本污染风险。
2. 逐项核对确认 **main 是 release 的内容严格子集**:
   - `main` 独有的**文件**:0 个;
   - `internal/lua/plugins.go` / `internal/core/kernel_actions.go` 等内核文件
     main 独有行数:0;
   - `deploy.sh` 那 14 行只是 emoji 清理(release 刻意去掉了 emoji);
   - `channels.lua` 那 333 行是**旧版内容**(旧 exports 表、旧模型名单)。
   - main 独有的 9 个提交按 patch-id **全部已在 release 中等效存在**
     (`git cherry` 逐条列出均为 `-`,即已合入)。
3. 故回流 = 把 release 的内容带到 main,等价于"只捡拾修复、不带版本号",
   且不会丢任何 main 独有的修复。

本次带回 v2.0.12 的全部内容(37 个提交):
- 源串行 + 按模型节流(照 model-gate)
- 已失效模型存储 + 远端目录闸门
- 余额分档选号
- 模型能力元数据(models.dev,isFree 权威判定)
- **凭据续期修复**(此前恒报 ok:true 却一个请求都不发)
- 通用件 TTL 缓存 + 串行锁;account_test/account_adopt/account_delete 修正
- 内核级:出站授权持久化重启后不生效、`__llmsproxy_plugin_dir` ground truth

校验:`git diff release/v2.0.x` = 0 行;`go build -tags luajit ./...` 通过。
This commit is contained in:
JianFeeeee
2026-10-14 16:52:58 +08:00
parent 0004545190
commit 9bec158d2e
94 changed files with 11409 additions and 974 deletions

View File

@ -0,0 +1,171 @@
package core
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"strings"
"testing"
)
// CodeArts 专用适配器是通过**插件自装**(__llmsproxy_adapters.install)
// 引入的,不是内置适配器 —— 因为 AK/SK 签名属厂商知识,内置里出现 codearts
// 就破了"内核零厂商名"纪律。
//
// 这组判据要证明三件事:
// 1. 插件加载时确实把适配器装上了
// 2. 装上之后,源能引用它(不是"装了但引用不到")
// 3. 它产出的签名是**真的能验**(而不是拼出个像签名的字符串)
func TestCodeArtsAdapterInstalledByPlugin(t *testing.T) {
c := kernelTestCore(t)
ps := c.Plugins()
src, err := readBundledChannels(t)
if err != nil {
t.Fatal(err)
}
if err := ps.LoadSource("channels", src); err != nil {
t.Fatal(err)
}
// ① 适配器必须在插件加载后就位
adapters := c.ListAdapters()
found := false
for _, a := range adapters {
if a.Name == "codearts" {
found = true
}
}
if !found {
t.Fatalf("插件未装 codearts 适配器;已装: %#v", adapters)
}
// ② 归属必须是 channels 插件(证明是它装的,不是内置)
if owner := ps.AdapterOwnerOf("codearts"); owner != "channels" {
t.Errorf("适配器归属应为 channels,实得 %q", owner)
}
// ③ 签名必须真的可验:用 Go 标准库按 SDK-HMAC-SHA256 独立复算
got, err := ps.Call("channels", "__codearts_adapter_sign_probe", map[string]interface{}{
"url": "https://snap-access.cn-north-4.myhuaweicloud.com/api/v2/chat/completions",
"method": "POST",
"body": `{"model":"CodeArts-Pangu"}`,
"api_key": `{"access_key_id":"AKTEST","secret_access_key":"SKTEST","security_token":"STTEST"}`,
})
if err != nil {
t.Fatalf("签名探针: %v", err)
}
w, _ := got.(map[string]interface{})
m, _ := w["json"].(map[string]interface{})
if m == nil {
t.Fatalf("探针无 json: %#v", got)
}
if m["error"] != nil {
t.Fatalf("探针报错: %v", m["error"])
}
auth, _ := m["authorization"].(string)
if !strings.HasPrefix(auth, "SDK-HMAC-SHA256 Access=AKTEST,SignedHeaders=") {
t.Fatalf("Authorization 形态不符(参考要求 Access= 不是 Credential=): %s", auth)
}
if !strings.Contains(auth, "Signature=") {
t.Fatalf("Authorization 缺 Signature: %s", auth)
}
// 签名后追加的头必须在,且不参与 canonical
for _, k := range []string{"Agent-Type", "X-Language"} {
if m["hdr_"+k] == nil || m["hdr_"+k] == "" {
t.Errorf("缺签名后追加的头 %s", k)
}
}
if m["hdr_Agent-Type"] != "PromptCenter" {
t.Errorf("Agent-Type = %v,应为 PromptCenter", m["hdr_Agent-Type"])
}
// canonical 不得包含签名后追加的头(参考注释点名:它们是签名后加的)
canon, _ := m["canonical"].(string)
if strings.Contains(canon, "agent-type") || strings.Contains(canon, "x-language") {
t.Errorf("canonical 不应含签名后追加的头:\n%s", canon)
}
// Authorization 不得进 canonical
if strings.Contains(canon, "authorization") {
t.Errorf("canonical 不应含 authorization:\n%s", canon)
}
// x-sdk-content-sha256 说明 payload hash 参与了签名
if !strings.Contains(canon, "x-sdk-content-sha256") {
t.Errorf("canonical 应含 x-sdk-content-sha256:\n%s", canon)
}
// URI 必须以 / 结尾(参考点名的易错点)
if !strings.Contains(canon, "/api/v2/chat/completions/\n") {
t.Errorf("canonical 的 URI 未补尾斜杠(参考点名的易错点):\n%s", canon)
}
// ④ **独立验签**:用 Go 标准库按 SDK-HMAC-SHA256 从 canonical 复算签名,
// 必须与适配器给出的 Signature 逐字节相同。
//
// 为什么必须有这一步:前面几条只验证"形态正确"(Access=、SignedHeaders、
// 尾斜杠)。签名算法本身写错(HMAC 输入少一段、canonical 分隔符用错)时
// 形态依然全对,但上游会 401。复算才是真正的判据。
sts, _ := m["string_to_sign"].(string)
if sts == "" {
t.Fatal("探针未带出 string_to_sign(判据必须比对真实函数算出的中间值)")
}
// string_to_sign = "SDK-HMAC-SHA256\n" + date + "\n" + sha256(canonical)
parts := strings.SplitN(sts, "\n", 3)
if len(parts) != 3 || parts[0] != "SDK-HMAC-SHA256" {
t.Fatalf("string_to_sign 头段不符: %q", sts)
}
canonSum := sha256.Sum256([]byte(canon))
if parts[2] != hex.EncodeToString(canonSum[:]) {
t.Errorf("string_to_sign 的 canonical 摘要不符\n算得: %s\n实得: %s",
hex.EncodeToString(canonSum[:]), parts[2])
}
mac := hmac.New(sha256.New, []byte("SKTEST"))
mac.Write([]byte(sts))
wantSig := hex.EncodeToString(mac.Sum(nil))
gotSig := auth[strings.Index(auth, "Signature=")+len("Signature="):]
if gotSig != wantSig {
t.Errorf("签名独立复算不符(算法写错?)\n复算: %s\n实得: %s", wantSig, gotSig)
}
b, _ := json.Marshal(m)
t.Logf("签名探针输出: %s", string(b))
}
// 源注册必须真的引用插件装的 codearts 适配器 —— "装了但源还指向 openai"
// 是这类改动最可能的漏配,且不会报错(只是签名不生效、上游 401)。
func TestCodeArtsSourceUsesPluginAdapter(t *testing.T) {
// ⚠️ 必须用 poolCore:它给 channels 授予了 add_source/list_sources 等
// 内核权限。用 kernelTestCore 裸加载插件时 add_source 会被拒
//("kernel action add_source is not granted to plugin channels"),
// 而这个拒绝**不报错、只是不出源** —— 我就在这里绕了一圈才看出根因。
c, ps := poolCore(t)
// 建一个 codearts 账号 → 触发源注册
m, err := ps.Call("channels", "account_add", map[string]interface{}{
"platform": "codearts",
"credential": `{"access_key_id":"AK","secret_access_key":"SK","security_token":"ST"}`,
})
if err != nil {
t.Fatalf("account_add: %v", err)
}
w, _ := m.(map[string]interface{})
j, _ := w["json"].(map[string]interface{})
if j == nil || j["error"] != nil {
t.Fatalf("account_add 报错: %#v", m)
}
aid, _ := j["account_id"].(string)
srcs := listSourcesViaKernel(t, c)
sm, ok := srcHasAccount(srcs, aid)
if !ok {
names := []string{}
for _, x := range srcs {
if n, _ := x["name"].(string); n != "" {
names = append(names, n)
}
}
t.Fatalf("codearts 账号 %q 应已同步源;现有源: %v", aid, names)
}
if ad, _ := sm["adapter"].(string); ad != "codearts" {
t.Errorf("源应引用插件装的 codearts 适配器,实得 %q(签名不会生效)", ad)
}
bu, _ := sm["base_url"].(string)
if !strings.Contains(bu, "snap-access") {
t.Errorf("base_url 不符: %q", bu)
}
}