Skip to content

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 16:02
· 178 commits to master since this release
cfd850c

[v0.18.0] — 2026-09-03

修复

  • New API 中继链从「看起来绑上」变成持久且可证明(#1179,经 #1187 闭合):New API v1 登录返回的是短命 dashboard JWT,Metapi 却把它当长期凭据持久化,后续模型刷新因此塌成空列表;现代 New API 的令牌列表把 relay key 显示成掩码值,Metapi 又把掩码显示值当可用路由凭据落成 masked_pending;模型列表此前要等后台调度先跑过一遍才出现,登录时并不可得。现在登录时把 fresh v1 JWT 提升为 New API 的长期 dashboard PAT(发不出持久凭据就拒绝绑定),并用 live JWT 撤销临时上游登录会话——即使代理剥掉 refresh cookie 也照撤;steady state 用不过期的 PAT,不再重复登录,只有显式 login/recovery 才轮换 New API 那唯一一个 dashboard PAT。掩码 key 用一次所有权校验过的 /api/token/batch/keys 批量换回真实 key,掩码显示值绝不作为路由凭据返回,水合失败沿单 token 与 verify-token 路径向上抛而不是装作没有 token。端到端门禁同时收紧:EXPECT_RELAY=1 成为默认,账号模型为空、/v1/models 为空、结构化错误、非 2xx 完成、缺失或空内容、错误完成标记全部判失败;显式无通道链用 EXPECT_RELAY=0 报 SKIP 而不是 PASS;CI 把 New API 接到确定性的本地 OpenAI mock 上核对真实完成标记与上游收到的具体模型,且不再绕过被测 adapter 注入完整 relay key;复用下游 key 时重新断言其通配策略(空模型列表就是刻意 deny-all)。升级后需要重新登录一次;令牌不需要手工绑定。
  • 没有可用通道的 503 现在说清为什么(#1179,经 #1186 闭合):通道选择把「无可选」报成 (nil, nil),失败路径因此记 err=<nil> 并回 503 {"error":{"message":"No available channels"}}——「没有启用的路由匹配这个模型」「路由匹配但每个通道令牌未绑定/停用」「所有通道都在冷却,或下游 key 策略排除了站点」三种完全不同的运维问题输出一字不差。现在 proxy.ExplainNoChannel(...) 渲染一行紧凑结论:解释器的 verdict,加上路由匹配但无人合格时的主候选拒绝原因(没有可用通道(…):令牌不可用);它经可选 SelectionExplainer 接口触达路由器(与 AvailableModelsSource 同款 idiom),不能解释的单测桩降级回旧文案,解释出错只给出「无原因」而不是二次失败。原因同时进 503 body(稳定前缀 No available channels 保留)、WARN 日志 reason= 与运营面向的 all-failed 事件。
  • 重建路由不再随发出它的 HTTP 请求一起死,清缓存也不再删掉重建要用的输入(#1174,经 #1185 闭合):POST /api/routes/rebuild 曾把整趟 pass 跑在 r.Context() 上,refreshModels: true(UI 就是发这个)时每个活跃账号一次上游往返、单次上限 30s,真实车队要几分钟;web client 只给 30s 超时,于是客户端先挂断,日志里就是 context canceled,重建永久失败。现在 handler 用 context.WithoutCancel(r.Context()) 分离请求上下文并配自己的 30 分钟预算,浏览器或反向代理放弃不再取消它要求的路由状态;SyncAllAccountModels 的收尾重建跑在局部有界上下文上,被打断的 pass 仍用已拿到的可用性重组路由(这一步不碰上游);收尾重建总是执行(无变化时短路不写),不再被 success > 0 卡住。web client 的重建超时从共享 30s 改为 5 分钟,且不再绑定服务器忽略的 wait 标志。第二个缺陷:POST /api/settings/maintenance/clear-cache(UI:清除缓存并重建路由)曾删掉 token_routes、model_availability 和 route_channels,再排一个从这两张表重组 route_channels 的重建——要重建的东西已经被它删光:每条路由定义消失,每个已发现模型都要重新上游拉取(每账号 30s,凭据过期就直接失败),手工通道挂载也被抹掉。现在该端点只失效进程内缓存并排同一个真实后台重建,不删任何行;清业务行是 factory-reset 的职责,那个端点仍然会做。
  • 账号编辑保存的凭据模式不再蒸发,换不过去的模式被 400 拒绝(#1176,经 #1184 闭合):编辑对话框每次保存都发 credentialMode,但 AccountUpdatePayload 没有这个字段,handler 解码时把它丢掉——建账号时选 API key、编辑改成 session、拿到成功 toast,重开对话框又变回 API key。现在 credentialMode 进入更新 payload 并合并进 extraConfig(创建路径写的同一个存储,也是 ResolveStoredCredentialMode 第一个读的地方);请求的模式在碰任何凭据字段之前解析,因为 shouldMirrorAPIKeyToken 看的是已存模式——此前显式切到 session 也会把 session 凭据抄进 api_token,而 session 凭据不是 API key,代理的凭据回落会把它当 API key 发出去。存储凭据撑不起的模式现在 400 而不是落库:没有 accessToken 的 session、没有 apiToken 的 apikey,此前都会产出「行上声称可认证、实际永远不能认证」的账号。
  • AnyRouter 校验失败终于告诉运营它要什么凭据(#1133,经 #1195 闭合):运营配好 AnyRouter 站点、粘贴一段对上证明可用的 cookie,得到的只有 token verification failed,平台到底要什么没有任何提示。adapter 并不缺——AnyRouterAdapter 内嵌 NewApiAdapter,cookie/session 校验本来就会尝试,但它刻意关掉 token 管理(GetAPIToken 返回 nil, nil),因为 AnyRouter 的 API-key 流程不是 New API 的 /api/token/ 契约;校验因此返回 nil/空/unknown 时,bind 路径与 verify 路径都把信号扔掉、对所有平台回同一句泛泛的话。现在两处共用一个 credentialVerificationFailureMessage(platform):AnyRouter 明确说 access-token/API-key 绑定不支持、必须用 session 模式、字段接受 session=<value> 或完整 Cookie: 头、Platform User ID 仅在上游强制 New-Api-User/User-id 时才需要、绑定前必须用 tokenType=session 通过校验;其它平台保留原指引并补同一形状提示(session 平台 → session=<value> 或完整 cookie 头,API-key 平台 → API Key 模式)。
  • 「同步站点令牌」现在幂等,掩码 key 不再每点一次多一行(#1193,经 #1196 闭合):SyncTokensFromUpstream 此前只对 ready 行去重;上游在令牌列表里掩码 relay key(New API 系返回 sk-abc***xyz)时,行解析成 masked_pending,永远匹配不上,于是每次同步都走 INSERT、再插一份同一条 token 的副本——正是运营每点一次多一行的来源。现在按精确 key 值匹配已有行、优先 ready 行(已水合的 key 继续对账进同一可用行)、回落 masked-pending 行;UPDATE 保留匹配行解析出的 value_status 而不是强制写成 ready——掩码行诚实地保持掩码,不声称持有没有的 key。已知残余(文档化、不在本版修):#1187 之前绑定的账号可能留一条 masked_pending 旧行,之后同步取回真实 key 时会再插一条可用行、旧的掩码行留在原地——一次性、可见标记、不可路由的重复项,运营可删;对账它需要平台信息,而该 service 函数没有,为一条一次性外观行不值得加这堆机器。
  • 删除一张表不再让删除之前写的每个备份都恢复不了(#1201):删表曾让 validateBackupImportTableKeys 拒绝它不认识的任何 payload key,于是升级过本版之后,一个本来有效的旧备份恢复直接变成 400 {"error":"import failed: unknown table proxy_debug_traces"}(被现有的向后兼容测试抓到)。现在按来源区分而不是维护一张「退役表」清单:策略排除表(admin_sessions、admin_audit_logs …)无论来源都响亮 400;手写/粘贴 JSON 里的未知 key 仍 400——它最可能是拼写错误,其行会静默永远不落地;真正导出文件里的未知 key 说明是本 build 已删的表,跳过并在 import 与 preview 两个响应里报告为 ignoredTables(WebDAV 恢复路径同样)。跳过是安全的:import/preview 循环遍历注册表再拿 payload key 查注册表,不认识的 key 从不被读。legacyBackupTables 因此保留它的 28 个历史条目(含这四张已删表):它记录的是旧 build 写过什么、而不是本 schema——这正是它能当升级门的原因。

移除

  • 未发布的 Electron 桌面外壳连同公开的 /api/desktop/health 一起删除(#1197):electron/(main.js 600 行、preload.js、README、package.json、托盘图标)、scripts/build-electron.sh/.ps1、Makefile 的 electron-build target、web/scripts/desktop/generate-icons.mjs、两张 desktop 图标、npm 的 desktop:icons,以及 GET /api/desktop/health、它在 isPublicAPIRoute 白名单里的条目和对两个图标的根文件服务全部移除。删除证据:发布流水线从未产出过桌面产物——release job 只从 ./cmd/server 与 ./cmd/migrate 构建 metapi-<os>-<arch> 和 metapi-migrate-<os>-<arch>,workflow 里唯一的 "desktop" 字符串是 visual-regression 的 viewport 注释;外壳只能由维护者手工 make electron-build 跑到,且 web/src 里没有对应 ipcRenderer/contextBridge/window.electron 消费者,它只是把一个 SPA 装进窗口。删除顺带拿掉一个公开、未认证、为托盘而设的端点;scripts/regen-ts-fixture.sh 的就绪探针从 /api/desktop/health 改探 /ready。

  • update-center 的五个操作臂与调度器桩删除,状态卡保留(#1199):POST /api/update-center/check、PUT /config、POST /deploy、POST /rollback、GET /tasks/{id}/stream 加 scheduler/update_center.go 与 METAPI_ENABLE_UPDATE_CENTER 全部移除;这个调度器本来就是 log-only no-op(环境变量自己的文档也这么说)。GET /api/update-center/status 与它渲染的 UI 版本卡保留——UpdateCenterSection 仍用 api.getUpdateCenterStatus()。

  • model-tester 的流式/任务臂删除,同步探针保留(#1199):POST /api/test/proxy(连同 /stream、/jobs、GET/DELETE /jobs/{jobId})与 POST /api/test/chat/stream、POST /api/test/chat/jobs、GET/DELETE /api/test/chat/jobs/{jobId} 删除;这些 stream/job 臂从未实现成 SSE,model-tester/api.ts 在 master 的注释里自己承认。POST /api/test/chat(同步探针实际调用的那个)保留。证据:每个被删路由在 web/src/lib/api/ 都有客户端包装,master 上这些包装在 lib/api/ 之外零引用——没有组件、hook 或测试调过它们。

  • proxy debug-trace 子系统整个删除:两张表、五个索引、两个读路由、九个 PROXY_DEBUG_* 开关、设置 UI 与 web client(#1201):九个开关会被解析、持久化、热生效并渲染进设置 UI,两个路由也会把表读回来——但从没有任何东西写过一行:master 全树里仅有的 INSERT INTO proxy_debug_* 在 e2e/e2e_backup_test.go 与 handler/admin/stats_detail_test.go 测试种子里。读路由因此只能返回空列表,动任何开关都不改变行为;一个永久为空、却带九个用户可见控件的表面比没有更糟。

  • proxy_files、PROXY_FILE_RETENTION_* 与 scheduler/file_retention.go 删除(#1201):master 上每个生产引用都是管线而非使用——auth/context.go 里的注释、修剪它的 retention 调度器、备份包含、DDL 与注册表;没有 writer,没有 reader。这张表被创建、备份、修剪,却从未被写入。

  • admin_snapshots 与 admin-snapshot 调度器删除(#1201):warmTarget 是显式 stub(自己的注释写着「Stub: regenerate snapshot…」);调度器里唯一碰这张表的语句是 DELETE FROM admin_snapshots WHERE expires_at < ?——周期性清理没有任何代码插入过的行。而每趟 pass 仍然扇出 4 个 goroutine 加一个 wg.Wait 只为了打 debug 日志。该调度器有用的那一半(usage aggregator 的 RunProjectionPass)没有丢:usage-aggregation 调度器仍在自己的 interval 上跑它。

  • RESPONSES_COMPACT_FALLBACK_TO_RESPONSES_ENABLED 与 ShouldFallbackCompactResponsesToResponses 删除(#1201):git grep ShouldFallbackCompactResponsesToResponses master -- '*.go' | grep -v _test.go 只剩定义本身;它的两个测试(一个 table test 与一份固定期望值矩阵)随之一并删除——只练不可达函数的测试不把守任何东西。

  • RouteRefreshWorkflow 家族删除(#1187):生产代码没有它的实现者,连其配置字段与 test-only mock 一起移除;路由器去掉这个没人实现的 refresh 接口后照常构建与选择。

  • 六个无调用者的 helper 与四个不可能失败的测试删除(#1198、#1190):stripModelPrefix、normalizeURLForDetection、normalizeURLProtocol(platform 内 git grep 只有定义 + 各自测试)、setReadinessDrainingForTest(一行转发 markReadinessDraining 的 pass-through,测试改直接调后者)、cronRunner.removeJob、toISOTime 全部移除。同时删掉四个 vacuous 测试:TestOneApiAdapter_BalanceQuotaMinusUsed(零断言)、TestOneApiAdapter_BalanceParseLogic(只断言本地字面量算术、不调产品代码)、TestOpenAiAdapter_GetModels(接受任何结果)、TestListAdapters_Copy(唯一的 if 是空 body,注释承认「只保证不 panic」)——只练不可达函数或不可能失败的测试只会制造维护重量、报告假信心。scheduler 里的 TestClampInt 也随之删除(重测 config.ClampInt,config 自己已把守),TestFormatTimeToSQL/TestCountResults 折进表格。

  • 14 份维护者过程史从公开仓移出(#1191):docs/internal/STATE.md(点状产品状态)、docs/internal/log.md(按日收口日志)、docs/internal/progress/MASTER.md(会话任务台账)、docs/internal/benchmark.md(一次性测量)与 docs/internal/analysis/ 下 9 份(竞品研究、凭据 IA、db pool 预算、P0-585 验证、包边界、Redis 共享状态、TS→Go gap、UI/UX 审计、wave12 需求真相)删除;它们的角色改由 GitHub issues/releases(现状与开放项)、CHANGELOG.md(版本叙事)、docs/architecture.md §"Ownership and boundary map"(包归属 + 例外边,已由 docs/package_boundary_test.go 机器强制)与 scripts/verify-cascade-prod.sh + e2e/e2e_p0585_production_test.go(运营门级 cascade 证据流程)承接。

  • 再清掉两份已无读者的维护者笔记(#1194):docs/internal/analysis/e2e-acceptance-platform.md(运营门级 live-credential 验收制度 + wave 历史)与 docs/internal/design/events-structured.md(冻结工作项的「实施中」设计笔记)删除;引用它们的两处注释改为就地陈述契约,web/knip.config.ts 里引用已删 web/scripts/oneoff/** 的 ignore 条目一并移除。

  • docs/api.md 从 456 行删到 28 行(#1187):原来是重复 deep-link 桩的堆积,现在只剩领域索引;各领域页仍是详细出处。

  • 无破坏性迁移(#1201):已升级的安装保留这四张孤儿表;没有代码读它们,也没有代码 drop 它们。cmd/migrate 只拷贝注册表里的表,所以方言迁移不会搬走它们(永远为空的)行。已写进 docs/deployment.md。

  • routing 里整条死的 pricing override 链与一个 fmt.Errorf 别名删除(#1204):NormalizePricingRatio、EstimateProxyCostFromModel、BuildPricingOverrideModel 全仓零调用者——非测试引用只剩它们自己的定义与文档注释。只经这三个函数可达的私有符号一并删除:asFiniteNumber(唯一调用者是 NormalizePricingRatio)、jsonNumber 接口(唯一用例是 asFiniteNumber 里一个 type-switch 分支)、ProxyBillingPricingOverride(唯一用例是 BuildPricingOverrideModel 的参数)。jsonNumber 的注释自称是为了「避免在 asFiniteNumber 的每个调用点导入 encoding/json」——导入只落在一个文件里、不落在调用点,所以它什么也没买到;何况全仓只有一处 UseNumber()(handler/admin/site_announcements.go),json.Number 本来就永远到不了那个 type switch。三个只练这些函数的测试随之删除,其中 TestEstimateProxyCostFromModel_MatchesCalculateModelUsageCost 断言的是「别名返回被别名者返回的东西」,它自己的注释写着 "thin alias over CalculateModelUsageCost"。scheduler 的 formatErr 是 fmt.Errorf 的一行转发(func formatErr(f string, args ...any) error { return fmt.Errorf(f, args...) }),六个调用点改为直接调用 fmt.Errorf,它自己的测试一并删除;helpers.go 保留 stringsTrimLower(真实逻辑:trim + 小写 + 空值默认 "active",两个生产调用者)。存活的 pricing API(FallbackTokenCost、CalculateModelUsage*、ResolveCacheRatio、DefaultCacheRatioForModel、SetCacheRatioDefaults、CacheAwarePerMillionRates)一字未动且仍有覆盖。

开发者可见

  • schema 注册表 37 → 33 张表,allStructs 35 → 31(#1201):删表后注册表随之收缩;两个自动迁移门禁(TestPostgresAutoMigrateAllTables / TestSQLiteAutoMigrateAllTables)改为遍历 AllTableNames(),不再手抄第三份清单——「这个方言创建注册表声明的表」是它们的工作,而注册表出现之前手抄清单正是漂移来源。绝对库存只在 TestTableCount 里以字面量 31 钉住一次,并交叉核对 struct 清单与 tableColumnCount;加表/删表因此必须是刻意动作。两个门禁都在真实 PostgreSQL 上验过(PG_TEST_DSN,跑而非跳过)。
  • handler/admin/accounts.go 1726 行按关注点拆成 6 个文件(#1192):路由注册、handler struct、两张快照缓存留在 accounts.go;login/verify/rebind 进 accounts_auth.go,创建进 accounts_create.go,列表/分页/过滤/快照进 accounts_list.go,update/delete/batch 进 accounts_write.go,health-refresh 结果类型与 refreshBalance 进 accounts_health_refresh.go。纯移动:六个文件声明的 func 集合与 master 的 accounts.go 28 对 28 完全相同,归一化 body diff 为空,签名/接收者/可见性/JSON 形状/路由/调用点零改动。
  • 四个 web zod schema 套件折成表格(#1188):accounts-schema、sites-schema、tester-schema、checkin-schema 四个套件把「每个输入一个断言」的形状声明一次、输入写成 it.each 行,共 −227 行、零新抽象、零产品代码改动;前端用例数 46→47(accounts 的空白凭据 session/api-key 两模式拆成两行,一个模式的回归不再躲在另一个后面)、30→30、24→24、21→21,断言一个没丢。
  • 账号 handler 的 14 个同形状状态码测试折成两张表(#1189):全部 body 只有「造一个请求、断言一个状态码」的 14 个函数变成 TestAccounts_BadRequestValidation(7 行,全部 POST→400)与 TestAccounts_ErrorResponseCodes(7 行,POST/GET/PUT 各自期望码),14 → 14 场景一一对应;只动了 3290 行套件里的 −69 行,无产品代码、无新 helper。
  • 七个 status-badge-variant 套件折成一个 29 行布线门禁(#1200):七个按 feature 复制的 status-badge-variants.test.tsx(637 行)并成一个表驱动布线门禁 + dashboard 自己的小文件,8 文件 +281/−538。这是「feature X 的列单元格对状态 Z 渲染语义变体 Y」的布线断言,不是 Badge 原语 recipe 断言(原语契约留在原文件不动)。29 个列/badge 用例进一个 it.each 表,dashboard 的 3 个留在原文件(其形状确实不同:mock @/lib/api、整段渲染、数组 badge);前端用例 32 → 32。两个独立变异探针(accounts 的 healthy.variant、routes 的 channel-summary 阶梯)都红,证明门禁真的把守。
  • proxy 重试/失败判定 104 个 t.Run 折成表格(#1202):retry_policy_test.go 473→223 行、67→6 个 t.Run 块、决策用例 71→71(>= 500 always retryable 原来一个 subtest 内循环 5 个码,现在是 5 行离散行,失败时各自具名);failure_judge_test.go 338→246 行、37→16 个块、23 折叠→23、14 个不同形状的用例不动。零断言丢失是机械核对的,不是肉眼:每个 ShouldRetryProxyRequest(...) / ShouldAbortSameSiteEndpointFallback(...) 的输入→期望 tuple 从原文件抽取后与新表行按集合比对,71=71 且两个差集为空;TestDetectHasUpstreamOutput 14=14、TestHasCompletionContentFromPayload 9=9。
  • 15 份 per-adapter PlatformName 测试并成一张 16 行注册表门禁(#1203):每个 platform/<x>_test.go 各自一个单断言测试,现在 TestAdapterPlatformNames 一张表全装下,并新覆盖 sensetime(此前根本没有 per-file 测试,只在 TestGetAdapter_SenseTime 里间接断过)。正确性与完整性刻意分开:每行 wantName 是显式字面量(生产改名必红);行跑完后一个 anti-shrink 交叉检查断言表名集合与注册表 ListAdapters() 双向相等——加 adapter 不补行会红,删行也会红。两个半面都用变异探针验证过。
  • handler/admin 的 18 个错误路径测试折成两张数据行表(#1206):sites_test.go 与 account_tokens_test.go 里各 9 个「造一个库、发一个请求、断言一个状态码」的同形函数,变成 TestSites_ErrorStatusCodes 与 TestTokens_ErrorStatusCodes:18 → 18 个子测试、名字保留(…/Create_EmptyName)。行是纯数据({name, seed, method, path, body, wantStatus, wantErr}),循环体只做一件事:按 seed 播种、把 {id} 换成播种出来的 id、按 method 派发到既有的 doGet/doPostJSON/doPutJSON/doDelete、断言——每行不再各自带一个匿名函数,也没有新 helper 或新 harness,与 #1189 / #1202 / #1203 已合入的表格风格同形。消息片段照旧断言(Create_Duplicate 仍要求 "already exists")。Postgres 双胞胎刻意不动:它们走另一套方言 harness,把 SQLite 行混进去会让一次失败说不清是哪个方言。两个变异探针(sites 空名 400→404、tokens 非法 action 400→404)分别把两张表打红,生产文件按 sha256 逐字节还原。
  • CI 的 bun install 对瞬时故障有界重试,Dockerfile 缓存挂载路径修对(#1181):两个缺陷都在绿色构建里看不见。① bun install 对瞬时 registry 故障零重试:损坏/截断 tarball 报 Integrity check failed,这不是被测提交的属性、重跑就装得上,却能挂掉必选检查、挡住合并;仅 2026-09-03 一天就命中四个不同包、跨三个 job(a11y 的 recharts、visual-regression 的 @base-ui/react 与 @oxlint/binding-linux-x64-gnu、docker-push 的 lightningcss-linux-arm64-musl)。② Dockerfile 的 cache mount 从未缓存过任何东西:挂在 ~/.bun/install-cache,bun 读写的是 ~/.bun/install/cache——一个连字符 vs 一个斜杠,那句「keeps the Bun install cache across builds」注释是假的,每次镜像构建都从零重下全部 tarball。两个调用点现在共用 scripts/bun-install.sh 里一份策略(放在 scripts/ 因为 .dockerignore 把 .github 排除在构建上下文外);重试刻意很窄,只对 tarball 完整性/解压失败、连接重置/超时、DNS 与 fetch 错误重试,--frozen-lockfile 不匹配、manifest 缺失、404、生命周期脚本报错一律第一次就失败,重试前清安装缓存。scripts/bun_install_wiring_test.go 把接线钉住:workflow 的 run: 不得裸调 bun install、恰好 5 处 web-deps 安装必须走脚本、Dockerfile 必须调它且挂 bun 真实缓存路径、脚本必须保留有界尝试/清缓存/非瞬时不重试三个分支。
  • 仓库卫生缺口补齐(#1194):.gitignore 补 .env.*(保留 !.env.example)、*.log 与 /dist-bin/;playwright 缓存 key 从哈希不存在的 web/bun.lockb 改为三处哈希 web/bun.lock(此前 hashFiles 对不存在路径返回空串、缓存 key 恒定、依赖升级从不失效);Makefile 删除没有 recipe 的 .PHONY(p0585-probe、p0585-e2e);docs/internal/design/a11y-checklist.md 删掉一条已解决的 Wave-9 残留;docs/README.md 补上内部地图缺的 internal/web-package-boundaries.md 与 internal/design/state-stability.md。

已知遗留(本版未做)

  • #1132(请求头模板)是新产品面,继续排队:冻结规则是核心链被证明是绿的之前不加产品面;本版只做收紧与删除,不接它。
  • itoa 在 handler/proxy 保留:那份把它判死的审计是错的——它有约 40 个同包测试调用者(account_tokens_test.go、token_routes_test.go、tags_test.go、search_test.go、videos*_test.go)加上 handler/proxy/helpers_test.go 里自己的门禁(service/checkin/round_aggregation_test.go:92 的本地 itoa 无关)。
  • 删掉 TestOpenAiAdapter_GetModels 后,OpenAiAdapter.GetModels 不再有直接单元门禁:它此前也并非真被把守——旧测试接受任何结果(_ = models; _ = err);真正门禁需要一个 httptest 上游,那是单独一件事。ListAdapters 的情况不同、已不再无人把守:#1203 的 anti-shrink 交叉检查调用它并断言其名字集合与表双向相等,少返回一个 adapter 就红——被删的 TestListAdapters_Copy 原本只断言「不 panic」。
  • PayloadRules 没有运行时消费者、OpenAiServiceTierRules 没有读者,而 docs/configuration.md 仍把两者写成可用:从 v0.17.1 带过来,仍未决——改文档还是建引擎;本版未动。