wangjia
ed22965d18
chore(todo): 合并本地 main 的 2 个 open 待办(golden 清理 + 协议选择)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 8s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 17s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 5s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 22s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Successful in 9s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Successful in 4m17s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 13s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 19:28:08 +08:00
wangjia
0698dc6138
chore(todo): 更新 TODO 看板(stats-overhaul 收口期新增条目)
...
ci-pangolin / Lint — shellcheck (push) Successful in 10s
ci-pangolin / OpenAPI Sync Check (push) Successful in 16s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 5s
ci-pangolin / Flutter — analyze + test (push) Successful in 29s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 4s
ci-pangolin / Go — build + test (push) Successful in 12s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 13s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Successful in 4m22s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 14s
ci-pangolin / Lint — shellcheck (pull_request) Successful in 4s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 21s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 8s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 14s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Successful in 4m17s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 13s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 18:14:05 +08:00
wangjia
1a6eb7ac9a
docs(stats): stats-overhaul 计划/测试清单 + index 登记
...
- stats-overhaul 实现计划(md 真相源 + html 阅读版)
- 功能×测试覆盖清单(feature-test-coverage-checklist.html)
- docs/index.html 登记上述文档
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 18:13:49 +08:00
wangjia
b98ad9dce4
chore(design): stats-overhaul 设计 WIP — CONTRACT + ui_kits 统计页规格
...
stats-overhaul 的设计稿(CONTRACT.md + mobile/tablet/desktop ui_kits)。
注:这批 ui_kits 描述的是统计页「上聚合·下分设备」原方案;统计页后续已
重设计为「月/周/日 周期卡 + 设备下拉 + 两周折线」(见 client/lib/screens/
stats_page.dart)。设计稿与新页布局有出入,待更新到新布局(单列 TODO 跟踪)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 18:13:49 +08:00
wangjia
bf84492eef
feat(client/native): stats-overhaul Phase1 — 四端实时统计采集端
...
各原生侧把 libbox/Clash 的实时上下行/延迟经冻结的 pangolin/vpn/stats channel
生产真实帧:
- iOS/macOS 新增 StatsClient.swift(LibboxNewCommandClient,statusInterval 1s,
uplink/downlink/总量 + urltest 延迟映射契约字段),VpnManager/VpnChannel 状态
观察驱动 start/stop,pbxproj 登记。
- Android PangolinVpnService.kt 延迟修复。
契约字段 uploadBytes/downloadBytes/uploadSpeed/downloadSpeed/urltestResults 不改。
真机验证(连接后实时跳动)为运行时步骤,见计划 Phase1 待办。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 18:13:48 +08:00
wangjia
636a3bbf2f
feat(server/stats): stats-overhaul Phase2 — 每设备归因 + GB 综合配额
...
服务端记账从「账户」细到「每设备」(每设备独立 dp_uuid),配额单位分钟→GB
按账户综合卡控。000015_per_device_usage 迁移(mysql+sqlite 双份):devices.dp_uuid
+ usage_device_daily 表 + plans.daily_mb。handler_grpc 按 dp_uuid 回映射
(user_id,device_id) 双写账户+每设备;usage 服务/handler 暴露 /v1/usage(/devices)。
含 sqlite_per_device / usage handler 测试。
注:此迁移 prod 已 migrate up 运行、部署二进制已内嵌;本次补提交使 git 与线上一致。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 18:12:00 +08:00
wangjia
4f9d2d2cf3
fix(client): 补提交 stats-overhaul 漏掉的 account_api/kernel_process WIP
...
同 device_usage:account_api.dart(deviceUsage/usage 方法)、kernel_process.dart
及对应测试一直未提交,本地有→analyze/test 过,但不在 git→Windows 构建报
「deviceUsage isn't defined」。补齐使提交树自洽可构建。
孤儿组件 device_stat_row/metric_card 已无人引用,不提交。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:53:56 +08:00
wangjia
a7c3795e5a
fix(client): 补提交漏掉的 device_usage 模型 + 测试(Windows 构建缺文件)
...
device_usage.dart 一直是 untracked(stats-overhaul WIP 漏提交),本地有所以
analyze/test 过,但不在 git → bundle → Windows 构建报「找不到 device_usage.dart /
DeviceUsage isn't a type」。account_providers/account_api/golden 测试都引用它,补提交。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:51:25 +08:00
wangjia
6f480acad5
build(client/windows): installer 版本 bump 1.0.4 → 1.0.5(含统计页重设计)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:47:46 +08:00
wangjia
01ebafc05e
feat(client/stats): 统计页重设计 — 月/周/日周期卡 + 设备下拉 + 两周折线 + 修刷新
...
布局重做(对照确认稿):
- 3 张周期卡(本月/本周/今日),每张 = ↓下行 · ↑上行 · 使用时长(PeriodCard)
- 右上设备下拉(_DeviceDropdown,先只挂「全部」;每设备归因打通后填设备名,见 TODO #9/#10)
- 柱状图 → 折线图:最近两周(14 天)按天流量(UsageLineChart,CustomPainter 零依赖)
数据源统一 + 修刷新(根治「本月流量冻住」):
- 月/周/日 + 折线全从单一源 usageProvider(30) 聚合(取最后 N 天点求和,末点即今日,
避开 UTC/本地日期坑);不再 me.weekly + usage 两条管线
- usageProvider/deviceUsageProvider 改 autoDispose(进页重取)+ 下拉刷新(invalidate)
+ 刷新连带 meProvider.refresh。后端每 60s 更新,无需更勤
UI 全走 PangolinColors/Text/Spacing token 单源、零硬编码;中英 l10n key 补齐。
widget 数值断言测试(刀1)按新结构重写并通过;统计/连接页 golden 重生成
(非 CI 闸;components/auth 基线未动)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 17:43:14 +08:00
wangjia
47147a1041
build(client/windows): installer 版本 bump 1.0.3 → 1.0.4(含节点国旗)
...
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 16:32:57 +08:00
wangjia
855d9e0e30
feat(client): 连接页桌面节点胶囊(_NodePill)加地域国旗
...
桌面连接页「已连接」下的节点框是 _NodePill,此前只有文字「节点名·延时」+
智能选择 zap,无国旗。在地域前加 CountryCode(size 20)国旗(无效码回退码块),
智能选择时国旗 + zap 并存。手机/平板的 _CurrentNodeCard 本已有旗。
桌面连接页非 golden 覆盖(含旋转动画),无 golden 变更。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 16:32:24 +08:00
wangjia
8eb7d04fe1
feat(client): 节点地区图标改国旗(SVG 填满裁切),未知码回退码块
...
CountryCode 由 2 字母码块改为渲染真国旗:country_flags 包按 region 码出 SVG
旗(BoxFit.cover 填满裁切),全平台含 Windows 都显示(emoji 国旗在 Windows
不渲染,故走 SVG)。FlagCode 判定无效码(AUTO/占位/非地区码)时回退原码块;
选中态由 accent 底色改为 accent 边框环。
CountryCode 共享于节点列表/连接页/统计页,故三处节点图标统一变国旗,
对应 tablet_servers/connect/stats + desktop_stats golden 已 Mac 重生成
(这些页非 CI golden 闸;CI 闸的 components/auth 基线未动)。
依赖:country_flags ^3.1.0(解析 3.3.0)。analyze 干净,非 golden 测试全过。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 16:25:48 +08:00
wangjia
4c1a289633
fix(nodes): 节点健康/connect 绑定 agent 在线状态,不再伪装连上
...
本次 google 打不开暴露:agent 离线 6 天,节点却一直显示健康、还让客户端
"连上"(凭证推送失败被 _ = 吞掉),实际节点不认 UUID。三处收口:
- Hub.IsOnline(nodeUUID):基于本地 agent 流注册的存活信号(单实例权威;
多实例需 Redis presence,另议)。
- ListNodes:effectiveNodeStatus 把 DB='up' 但 agent 离线的节点降级为
"down",不再永远显示健康(status 列只反映供给/调度,不反映 agent 死活)。
- ConnectNode:agent 离线即 503 ErrNodeUnavailable(凭证持久化留待 resync),
在线时 push 失败也 503——不再吞 pushErr、不再下发节点无法兑现的配置。
测试:TestHub_IsOnline(注册/结束/未注册)、TestEffectiveNodeStatus
(up+离线→down 等)。新增 apierr.ErrNodeUnavailable(503)。
掉线告警 + 静默吞错全量审计另起 TODO。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 08:36:31 +08:00
wangjia
3527537c96
fix(connect): 4 处 500 记 slog.Error;加连接路径 schema 绑定测试
...
ConnectNode 四条 ErrInternal 路径(entitlement/配额/节点/配置渲染)此前只写
500 不记 err,导致「no such column: p.daily_mb」这种 SQL 错全静默 → 难排查。
改为各记 slog.Error(含 user/node/err)。
新增 store 层测试 TestSQLite_ConnectPathSchema:在跑过完整迁移(含 000015)的
真 SQLite 库上,跑连接路径依赖 015 schema 的三条查询——EntitlementForUser
(断言 DailyMB 从 plans.daily_mb 取到 102400)、EnsureDeviceDpUUID(devices.dp_uuid)、
AccountDayBytes——任一引用了没有迁移建的列即 SQL 报错、测试变红,守住本次
「查询引用了未迁移列」一类回归。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 08:11:32 +08:00
wangjia
77ce809d48
fix(auth): SMTP 信封发件人用裸地址,修 501;发信失败记日志
...
两个真问题,前者被后者掩盖:
1. mailer.go 把带显示名的 SMTP_FROM(「穿山甲 <noreply@x>」)直接当 SMTP
信封发件人(MAIL FROM)传给 smtp.SendMail,Resend 报 501 Bad sender address
syntax → 验证码发不出。改用 net/mail 解析出裸地址作信封发件人,From: 头
仍保留完整显示名。
2. service.go 两处异步发信把错误静默吞了(_ = SendCode/SendAlreadyRegistered),
导致 SMTP 故障日志无痕、难排查(本次端口被封+501 都被吞)。改为失败时
slog.Error 记日志,邮箱经 maskEmail 打码,绝不记验证码(守 no-secret-in-logs)。
测试:TestEnvelopeFrom(显示名/裸址/unicode → 裸址)守 501 回归;TestMaskEmail
守日志脱敏。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-28 07:50:37 +08:00
wangjia
b12519be0a
fix(client/windows): runner 加 /utf-8 编译选项,根治 C4819 中文注释编译失败
...
win32_window.cpp 含中文注释(UTF-8 无 BOM),在 zh-CN 机器(代码页 936)+ 新版
MSVC 上报 C4819「字符无法用当前代码页表示」,叠加 runner 的 /WX 直接 error
C2220 编译失败。APPLY_STANDARD_SETTINGS 加 /utf-8,让 MSVC 按 UTF-8 解析
所有 runner 源码,永久根治。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-27 20:49:36 +08:00
wangjia
8d3c5da7ee
fix(client/windows): installer 覆盖修复 — 装新版前静默卸载遗留 AppId 的 1.0.0
...
1.0.0 的 AppId 是非法 GUID(...PANGOLIN0001),1.0.1 起改成合法 GUID。
AppId 不同 → Inno 视为两个独立产品 → 控制面板里 1.00/1.01 并排不覆盖。
当前 AppId 已稳定会原地升级;再在 ssInstall 前把遗留 AppId 的残留装
静默卸掉(/VERYSILENT),清掉已并存的 1.0.0 幽灵。版本 bump 1.0.2→1.0.3。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-27 20:42:54 +08:00
wangjia
e7b37d7719
test(ci): 刀3 — CI 单点韧性:runner 离线告警(缺口③)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 9s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 23s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 5s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 37s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 6s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Successful in 19s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 17s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Successful in 4m16s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 15s
ci-pangolin / Lint — shellcheck (push) Successful in 5s
ci-pangolin / OpenAPI Sync Check (push) Successful in 23s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (push) Successful in 6s
ci-pangolin / Flutter — analyze + test (push) Failing after 15s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (push) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (push) Successful in 5s
ci-pangolin / Go — build + test (push) Successful in 7s
ci-pangolin / E2E Smoke — L4 进程级端到端 (push) Successful in 19s
ci-pangolin / Go — integration (mysql/redis testcontainers) (push) Failing after 4m29s
ci-pangolin / Golden — 视觉回归 (components + auth) (push) Successful in 13s
CI 整条吊在一台 mac,关机/掉线即全停且无人知道。新增
deploy/runner/runner-deadman.sh(仿 bootstrap/monitor/deadman-watch.sh):
放另一台常在线主机,每 3 分钟查 gitea Actions API 里 mac-pangolin-2 的 status,
连续 offline 到阈值 → Telegram 告警,恢复也通知;API 取不到时跳过不误报。
响应字段已对照真 gitea API 核验({"runners":[{"name":...,"status":"online"}]}),
shellcheck 干净,实跑验证 online→不告警、缺失→进告警分支。
docs/ci-runner.md 增「韧性」节(告警部署 + cron/launchd)并标注持久化激活仍待
本机手动跑(pkill/launchctl,Claude 跑不了);验证段 job 数 9→10。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 23:04:35 +08:00
wangjia
75cdbf583f
test(server): 刀2 — v2ray 真实采集后端测试(缺口②)
...
V2RayUsageSource 是「线上按租户记账」链路的源头(agent 读 sing-box v2ray_api
StatsService per-user 计数器 → 聚合 → ReportUsage),此前零测试覆盖
(复查发现之前以为有单测是错的)。本刀按用户定的范围只补 CI 可跑的后端采集:
- TestParseUserStat:表驱动,正常 uplink/downlink + 非 user/段数异常应 ok=false。
- TestV2RayCollect:起假 StatsService(loopback gRPC,ServiceName 与 client.go 的
规范全名一致),让真 Collect() 跑真链路,断言:按 dp_uuid 聚合 up/down 正确、
方向不串、全 0 用户与非 user 计数器被丢弃、SessionMinutes=1、且请求以
Reset_=true + pattern "user>>>" 发出(窗口取值清零语义)。
纯 go test、无 docker / 无真 sing-box;自动进 go-server job + 计入覆盖率闸。
真 sing-box 真出网 e2e 仍按计划留文档手测。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 23:01:27 +08:00
wangjia
60daa6b399
test(client): 刀1 — 统计页「上屏数值对不对」widget 断言(缺口①)
...
闭合最初诉求「统计准不准」的前端那一半:后端记账已 L4 e2e,前端此前只到
解析单测(解析对 ≠ 渲染对)。新测试把真实形态的后端响应(/v1/me·/v1/usage·
/v1/usage/devices)经真 ApiClient→AccountApi→provider 链(连带测 JSON 解析)
喂给 StatsPage,断言:
- 指标卡:本月流量 ΣgbTotal=3.0、本月时长 Σmin/60=2.0、未连接延迟=节点 ping 42
- 周柱:7 根来自 me.weekly_gb(取唯一值核对)
- 分设备:2 行,每行流量/时长正确
注入 MockClient 不打真网络;用 find.byWidgetPredicate 匹配 MetricCard/DeviceStatRow
公共字段,无需改生产代码。改坏期望值已验证转红(非永真)。自动并入 flutter-client job。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 22:59:05 +08:00
wangjia
55d368d98b
docs(test): 测试架构补「⑩ 已知缺口/测试盲区」单一跟踪源
...
把散在对话里的盲区收进架构文档,作为单一跟踪源(补一项划一项):
- A 覆盖盲区:前端统计上屏数值未验证(诉求半覆盖)、真实流量数据路径
(注入非真出网,最大盲区)、原生隧道层零自动化、契约双份可能漂移。
- B 运维/工程缺口:CI 单点 mac(持久化未激活)、go-integration 裸宿主脆性、
analyze 严格化(B1)+ golden 全集(B2)被 stats-overhaul 耦合阻塞、
golden 本地难重生。
每项标严重度 + 现状为什么 + 何时怎么补;同步登记 docs/index.html。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 21:30:54 +08:00
wangjia
8356547c2f
test(ci): 第7刀 — CI 依赖缓存(C 效率)
...
host-mode runner 宿主机持久,给四个拉依赖的 job 挂宿主缓存目录,
消掉每次全量下载:
- go-server / e2e-smoke:挂 $HOME/.cache/pangolin-ci/{gomod,gobuild}
→ /go/pkg/mod + /root/.cache/go-build。
- flutter-client / golden:挂 $HOME/.cache/pangolin-ci/pubcache → /root/.pub-cache。
本地实测 go build:首次 204s(含全量 downloading)→ 二次 1s(0 downloading)。
testcontainers 镜像由 Docker Desktop 宿主层天然缓存,无需额外处理。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 21:25:38 +08:00
wangjia
f8f4ee0ce8
test(ci): 第6刀 — CI gate 补全(覆盖率闸 A1 + 集成测试 job A2)
...
A 类「闸的完整性」:
- A1 覆盖率闸(防断崖,非追高):
· go-server job 加 -coverprofile,host 侧解析 total,< 30% 失败(当前 34.9%)。
· flutter-client job 加 --coverage,host 侧 awk 解析 lcov,< 28% 失败(当前 32.7%)。
阈值留 ~5% 缓冲,只拦回归断崖,不卡日常波动。
- A2 集成测试进 CI(新 go-integration job,Job 10):
真 mysql:8/redis 经 testcontainers 跑 -tags integration(跨库可移植/按租户
记账/配额/兑换/节点生命周期/migrate·时区——sqlite 单测覆盖不到的真库行为)。
· 在 host-mode runner 宿主机直接跑 go(不套 golang 容器):testcontainers 要真
docker,DooD 在 Docker Desktop(mac)上网络不通;依赖宿主 go + docker。
· -p 1 串行:一次只起一个 mysql 容器,避免并发压垮 Docker Desktop。
covers/ 覆盖率产物入 .gitignore。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 21:19:34 +08:00
wangjia
0fd3bce7a5
fix(server): 修复腐烂的集成测试套件 + devices 注册回填 last_seen
...
这批 -tags integration 测试(usage/auth/codes/devices/store)因依赖冲突
长期编译不过、从未进 CI 跑过,代码静默腐烂。本次逐层修复:
- 依赖:testcontainers-go v0.34→v0.43(原 v0.34 配 docker v28.3.3 编译失败:
archive.Compression/sockets.DialPipe undefined)。
- auth: LoginOutcome 重构成 Tokens 嵌套后,测试仍引用扁平 RefreshToken;
users 测试 schema 缺 totp_enabled 列 → GetUserByEmail 报错 → SendCode 500。
- usage/auth/codes/devices: DSN 里 time_zone='+00:00' 作 URL query 透传时 '+'
被解码成空格 → MySQL Error 1298 ' 00:00';改百分号编码。
- store: 测试 DSN 缺 multiStatements=true → 多语句迁移 Error 1064;
WithConfigFile("") 在 v0.43 被拒,改写真 my.cnf 设 +08:00 真正考验 UTC 覆盖。
- devices(产品 bug):insertDeviceTx 把 last_seen 写进库却没回填返回值,
刚注册的设备 API 响应 last_seen=null 与库不一致;TestFullChain 据此把关。
修复后完整 integration 套件 -p 1 串行全绿(25 包 + 5 testcontainers 包)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 21:19:10 +08:00
wangjia
97b2ac02f1
ci: pangolin runner launchd 持久化(start script + plist)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 8s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 20s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 43s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 6s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Successful in 2m5s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 2m11s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 34s
mac-pangolin-2 从临时后台进程改成开机自启的 launchd 服务,仿 jiu 的 com.jiu.act-runner:
- deploy/runner/act_runner_pangolin_start.sh:等 jiu relay(13000)就绪 → 起 daemon →
退出自重启;PATH 含 docker/node/go(host 模式 job 要用)
- deploy/runner/com.pangolin.act-runner.plist:RunAtLoad + KeepAlive
- docs/ci-runner.md:持久化章节改为实际部署步骤(cp + launchctl load)
复用 jiu 的 relay 13000(端口已占,不另起)。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 20:06:21 +08:00
wangjia
034a585e3b
docs(ci): runner 设置记录(决定用 1 个 mac-pangolin-2 + 清理/持久化)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 5s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 17s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 39s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 2m5s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 2m13s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 31s
记录 pangolin CI 的 self-hosted runner:决定保留 1 个(mac-pangolin-2,项目级、
mac host 模式、label nas、复用 jiu 的 relay 13000),含 config、为何 host+mac、
relay 原因、已删 orphan(mac-pangolin id5)的清理命令、持久化(launchd)TODO。
登记进 docs/index.html。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:52:54 +08:00
wangjia
65bd7c381b
fix(test): e2e 去掉 /v1/usage/devices 断言(stats-overhaul WIP)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 8s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 20s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 43s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 2m5s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Successful in 1m58s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 39s
CI 暴露:e2e smoke 断言 /v1/usage/devices 返 200,但该路由(deviceUsageHandler)是
未提交的 stats-overhaul WIP,committed server 没注册它 → 404 → assertStatus200
(t.Errorf 非致命)记错 → TestE2ESmoke FAIL(尽管 HTTP/enroll/ReportUsage 全 ✓)。
本地复现不了(工作区有 WIP、路由在)。去掉该断言,/v1/usage 保留。
这是第 4 个'测试依赖未提交 WIP'实例,与契约测试同源。待 stats-overhaul 合并后补。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:38:15 +08:00
wangjia
62a86c5c0e
fix(ci): shellcheck 去掉多余的 shellcheck 命令(预存 bug)
...
ci-pangolin / Lint — shellcheck (pull_request) Successful in 6s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 20s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 7s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 43s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 2m19s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 2m11s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 33s
koalaman/shellcheck 镜像 ENTRYPOINT 已是 shellcheck;ci.yml 命令又写了一遍,
变成 'shellcheck shellcheck -S warning …',把字面 'shellcheck' 当文件检查→
'openBinaryFile: does not exist' 报错。去掉多余的 shellcheck 即可。
这是 CI 从没跑过、谁都没发现的预存 bug,首次真跑暴露。本地验证去掉后 exit 0。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:25:21 +08:00
wangjia
dc22328eb4
ci(debug): shellcheck 改挂整个 repo + 加 checkout 诊断
...
ci-pangolin / Lint — shellcheck (pull_request) Failing after 9s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 42s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 4s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Successful in 2m7s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 2m15s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 37s
shellcheck 在 mac runner 上 $PWD/deploy 挂载为空(openapi 的 design/server 子路径却
能挂),怀疑 act host checkout 没拉 deploy。改挂 $PWD:/repo(codegen 已证整 repo 挂载
可用)+ 加 ls 诊断,定位 deploy 是否在 checkout 里。整 repo 挂载在 Linux 也对。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:21:29 +08:00
wangjia
eb1bd11439
fix(test): 契约测试解耦 stats-overhaul WIP(CI 抓到的真问题)
...
ci-pangolin / Lint — shellcheck (pull_request) Failing after 9s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 18s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 6s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 39s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Successful in 2m2s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 2m14s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 31s
CI 暴露:我的契约测试引用了未提交的 stats-overhaul 文件,本地能过(文件在工作区)、
CI checkout 已提交代码就崩:
- api_contract_test.dart import device_usage.dart(?? 未提交)→ flutter analyze 报
'Target of URI doesn't exist'
- usage/contract_test.go 引用 DeviceUsagePoint/deviceUsageResponse(HEAD service.go/
handler.go 均无)→ go-server 编译失败
摘掉 /v1/usage/devices(DeviceUsage)契约,保留 /v1/me + /v1/usage(已提交)。
待 stats-overhaul 合并后再补 DeviceUsage 契约。
验证:flutter analyze test/contract 'No issues';go vet usage 无错。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:13:27 +08:00
wangjia
9a2d14adbe
fix(ci): 去掉误提交的 .claude/worktrees gitlink
...
ci-pangolin / Lint — shellcheck (pull_request) Failing after 5s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 20s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 7s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 36s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 5s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5s
ci-pangolin / Go — build + test (pull_request) Failing after 2m17s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 2m17s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 38s
.claude/worktrees/iridescent-sleeping-lampson 是个 git worktree 目录,被误提交成
gitlink(mode 160000)。.gitignore 已有 .claude/worktrees/ 但它在那之前就提交了。
git rm --cached 去跟踪(不删磁盘 worktree)。修 actions/checkout 的 submodule 警告
(No url found for submodule path)+ 清理仓库。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 18:01:18 +08:00
wangjia
58e06708f4
test(ci): 闸2 Go go vet 进 CI(支柱-adjacent;golangci-lint 暂受 go1.25 阻)
...
ci-pangolin / Lint — shellcheck (pull_request) Failing after 15s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 26s
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 5s
ci-pangolin / Flutter — analyze + test (pull_request) Failing after 29s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 4s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 5m10s
ci-pangolin / Go — build + test (pull_request) Failing after 5m55s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 14m36s
ci-pangolin / Golden — 视觉回归 (components + auth) (pull_request) Successful in 35s
go-server job 加 go vet ./...(项目工具链,零版本摩擦):
- golangci-lint(errcheck/depguard)暂用不了:其 go1.23 构建 < 项目 go1.25.10,
报版本错;待 golangci-lint 跟上 go1.25 再上 errcheck(支柱4)+ depguard(import 方向)
- go vet 当前零违规,接进 go build+vet+test 链
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 13:51:41 +08:00
wangjia
945c2e2f8d
test(ci): 闸2 Dart 支柱4 lint 升 error 级(真守门)
...
analyze 闸修好后(exit 0),把支柱4 两条规则提升为 error 级:
- only_throw_errors / avoid_catching_errors → analyzer.errors: error
- 即使 CI --no-fatal-infos,这俩违规仍致命(挡 merge);当前零违规
- 将来 throw 字符串 / catch Error → CI 红(支柱4:错误是有类型的值)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 13:45:31 +08:00
wangjia
bb2688cede
test(ci): L3 golden 进 CI(components+auth 子集,Linux 权威基线)
...
L3 视觉回归真进 CI(此前卡 docker daemon 未起 + dirty 纠缠;现启 Docker 落地干净子集):
- 在 ghcr.io/cirruslabs/flutter 容器重生成 components+auth 的 14 张 Linux 权威基线
(auth_login/connect 三态/quota/smart_card;mac 渲染与 CI Linux 不一致,故钉死 Linux),
容器内 --update-goldens 后无 flag 复跑幂等通过
- ci.yml 新增 golden job(第9个):flutter test components_golden_test + auth_redesign_golden_test
- 解耦干净:这俩只 import clean widget;stats-overhaul 的 l10n 改动纯新增(现有键未动)
→ 渲染不受影响、不裹挟 dirty;tablet/desktop-stats golden 仍耦合 stats-overhaul,待合并后并入
- docs/test-architecture.html:L3 状态更新为「已进 CI(子集)」
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 13:20:55 +08:00
wangjia
63bb31066f
test(ci): 闸1 接缝脚手架 new-seam.sh(支柱1:让对的写法成默认)
...
预防式闸:一条命令生成「接口 + Impl + Fake + Riverpod provider + flow-test 桩」,
外部依赖从一开始就藏接口后、可注入、自带 Fake、可测——把支柱1 变成阻力最小路径。
- scripts/new-seam.sh <PascalName> [lib子目录]
- 验证:生成样例 SampleProbe → flutter analyze 无 issue + test 过 → 清理
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:17:58 +08:00
wangjia
c5223af4a1
test(ci): L3 golden 权威环境脚本(进 CI 前置准备)
...
golden 真进 CI 卡两个硬约束:Linux/mac 渲染不一致(mac 基线在 CI 全红)+ 基线绑定
stats-overhaul dirty 工作区。本提交备好权威环境,不裹挟 dirty、不破坏:
- scripts/update-goldens.sh:在 CI 同款镜像 cirruslabs/flutter 重生成 Linux 权威基线
(开发者改 UI 后用它刷新,而非裸 mac --update-goldens,保持与 CI 一致)
- docs/test-architecture.html:L3 现状更新 + 列明启 CI 硬闸的两个前置条件
- 真启闸需:有 docker 的机器生成并提交 Linux 基线 + dirty 工作区合并(本机无 docker)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:16:16 +08:00
wangjia
76dbdcfa83
test(client): L2 点连接能连上 流程 + ConnectApi 注入(支柱1)
...
- connection_provider:加 connectApiFactoryProvider(原内部 new ConnectApi 改成
可注入工厂,支柱1 接缝即接口)——默认行为不变
- test/unit/flow_connect_test.dart:真 toggle → fetchConfig(注入 MockClient 回占位
config)→ bridge.start → statusStream 确认 → on → 再 toggle → off;
验证「非乐观翻转」红线(on 必由 bridge 确认)
- 全量 unit/widget 47 全过(重构不破坏)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:13:40 +08:00
wangjia
95fad37989
test(client): L2 登陆/重启续登/登出 流程(真 AuthNotifier)
...
驱动真实 AuthNotifier(非桩),仅在可注入接缝 TokenStore 处换内存实现,覆盖最初诉求:
- 登陆:saveTokens → isLoggedIn + 令牌持久化
- 重启续登:新 AuthNotifier 从同一 store 恢复登录态(无需重输密码)
- 登出:logout → 未登录 + store 清空,再重启仍未登录
- 3 用例全过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:10:52 +08:00
wangjia
cb6fc25bc9
test(client): L1 数值格式化纯函数单测 + 从 widget 抽出(支柱3)
...
- lib/util/format.dart:抽出 formatSpeed/formatDuration(原私有于 connect_page
_speed / connect_button _fmt,混在 widget 里没法单测)
- connect_page/connect_button 改用公共纯函数(渲染输出不变)
- test/unit/format_test.dart:10 用例覆盖速率 B/s→GB/s 各单位边界 +
时长 HH:MM:SS 补零/小时不封顶/取模 60
- 验证:format 10 + widget 8 全过、analyze 无 error/warning
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 12:06:34 +08:00
wangjia
64f96fe018
refactor(api): /v1/me 收敛 expire_at → 只发 expires_at(无 web 用户中心)
...
之前后端同时发 expire_at + expires_at(分别给 app + web 用户中心)。现已无 web 端,
双发是纯冗余,统一只用 expires_at:
- account.go:meResponse 删 ExpireAt 字段 + GetMe handler 不再赋值
- client me.dart:fromJson 删 (expires_at ?? expire_at) 别名回退
- 两端契约快照同步:Go meResponse 冻结集去 expire_at(12→11 key)、删 Dart 别名测试
- 验证:go test httpapi 绿、flutter test contract(14)绿、analyze EXIT 0、e2e 全链路绿
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 10:08:50 +08:00
wangjia
dbd183ee00
fix(ci): 修 flutter analyze 闸长期 exit 1(形同虚设)
...
发现:CI flutter analyze 退出码 1(1204 个 info,0 error/warning),其中 1191 来自
第三方 vendored 包 lucide_icons_patched —— 这道闸一直红/无效。
- analysis_options.yaml:exclude packages/lucide_icons_patched/**(去 1191 噪音)
- ci.yml:flutter analyze --no-fatal-infos(info 是建议级不该硬挡 merge;
error/warning 仍致命)→ analyze EXIT 0、闸真正可用
- 顺手清 11 个 clean 文件 info:auth_api 删 >>>AUTHLOG 调试 print 残留(+unused sw)、
account_page/plan_card withOpacity→withValues、vpn_bridge where(is Map)→whereType、
测试文件删多余 import + final→const
- 剩 2 个 info 在 stats-overhaul dirty 文件(stats_page/device_stat_row),留其合并清
- 验证:flutter analyze --no-fatal-infos EXIT 0(1204→2)、flutter test 48 全过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 07:39:14 +08:00
wangjia
6bd5c04cba
test(ci): 第5刀c e2e-smoke CI job + 文档 L4 状态更新
...
- ci.yml 新增 e2e-smoke job(golang:1.25 + apt openssl/curl/python3);容器内无
docker daemon → 脚本走内嵌 miniredis 兜底,无需 docker-in-docker——CI 走的
正是本机已验证绿的同款路径
- docs/test-architecture.html:L4 从「完全缺·最大缺口」更新为已落地
scripts/e2e-smoke.sh + server/test/e2e/
- 注:本机无 docker,e2e job 未在真 nas runner 实跑;逻辑等同本机已绿的
miniredis 路径,首次 CI 运行需观察 apt 包/go module 下载
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 07:06:35 +08:00
wangjia
fd82d82fe3
test(e2e): 第5刀b L4 gRPC 全链路(enroll→ReportUsage→统计真入库真读出)
...
在 HTTP 段基础上接 gRPC mTLS 全链路,真验证「统计准不准」:
- 脚本:生成 Node CA + gRPC server 证书(镜像 deploy.sh)、起 server 带 gRPC、
nodectl 签 bootstrap token,传 E2E_GRPC_ADDR/CA/token/node 给 driver
- driver:复用 agentd.EnsureEnrolled 真 TCP enroll → enroll 到的 client cert
建 mTLS → ReportUsage 注入 1GiB/100MiB(dp_uuid=用户) → 轮询 /v1/usage
断言 bytes_up=1GiB·bytes_down=100MiB 真入库真读回
- 本地全链路绿;go build ./... 不受影响(tag e2e)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 07:04:26 +08:00
wangjia
b3cbeef7cf
test(e2e): 第5刀a L4 进程级冒烟编排 + HTTP 段
...
真起 server 二进制(sqlite + Redis)跑 HTTP 用户旅程,验证 in-process 测试给不了的
信心(二进制起/迁移/路由/鉴权/DB 端到端):
- scripts/e2e-smoke.sh:起栈编排(Redis 三选一:已有 addr / docker / 内嵌 miniredis
兜底)+ 随机端口 + trap 清理;调 Go driver
- server/test/e2e/smoke_test.go (tag e2e):造用户(复用 argon2 HashPassword)
→ 登录 → /v1/me(dp_uuid/plan/email) → /v1/usage 鉴权 + 无 token 401
- server/test/e2e/miniredis (tag e2e):无 docker 时 Redis 兜底 launcher
- 本地绿;go build/test ./...(无 tag)仍 25 包绿,tag e2e 文件被静默跳过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-25 06:59:39 +08:00
wangjia
6df453f057
test(ci): 第4刀a pre-commit hook(闸3 提交前本地把关)
...
复用 CI 同款检查的快子集,提交前本地拦截:
- .githooks/pre-commit:红线词 + 可移植SQL + codegen 漂移(成功静默/失败打详情)
- ci/install-hooks.sh:一键启用(core.hooksPath=.githooks)
- 不放 flutter/go test(慢,留 CI 闸4);可 --no-verify 临时跳过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-24 23:49:41 +08:00
wangjia
747ceb06f0
test(contract): 第2刀c Go 契约快照(/v1/me·usage·UsageEntry)+ Go CI job
...
支柱 2(契约单源)后端守门 + 两端对账:
- httpapi/contract_test.go:冻结 /v1/me 字段面(12 key,与 Dart _frozenMeKeys 对账,
注明后端多发 uuid/dp_uuid + expire_at 别名)
- usage/contract_test.go:冻结 /v1/usage(UsagePoint)、/v1/usage/devices
(DeviceUsagePoint + envelope)字段面
- pb/agentv1/contract_test.go:冻结 agent↔控制面 UsageEntry 字段面
- ci.yml:新增 go-server job(golang:1.25 build+test)——此前 server 测试未进 CI
- 本地:5 契约用例 + 全量 go test ./...(25 包)绿
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-24 23:35:23 +08:00
wangjia
149aa90cfa
test(contract): 第2刀b Dart 契约快照(stats + /v1 API 字段面冻结)
...
支柱 2(契约单源)守门 — 堵 mac-stats-0 一类跨进程契约悄悄分叉:
- test/contract/stats_contract_test.dart:冻结 pangolin/vpn/stats payload
字段面 + status 四值,改契约即测试红、逼同步原生产出端 + 文档
- test/contract/api_contract_test.dart:冻结 /v1/me、/v1/usage、
/v1/usage/devices 的 snake_case 字段面
- ci.yml:flutter job 测试纳入 test/contract;本地 15 用例全过
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-24 23:32:00 +08:00
wangjia
c34d8d8fb5
test(ci): 第2刀a codegen 漂移检查(token 单源不漂移)
...
支柱 2(契约单源)守门:
- ci/check-codegen-drift.sh:重生成 pangolin_tokens.gen.dart,与已提交版本
不一致即失败(改了 CSS 真相源没重生成,或手改了生成物 *.gen.dart)
- ci.yml:新增 codegen-drift job(node:20 镜像,含 git/bash)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-24 23:28:41 +08:00
wangjia
2bf6701def
test(ci): 第1刀 可移植 SQL 扫描硬闸 + 五支柱 PR 自检模板
...
执行机制四道闸 → 闸4(CI 硬闸) + 评审兜底落地:
- ci/scan-portable-sql.sh:扫 server Go 运行时查询禁用 MySQL 专属构造
(UTC_TIMESTAMP/UTC_DATE/NOW()/FIELD(/ON DUPLICATE KEY/IFNULL),
豁免方言层 dialect*.go / _test.go / 注释行;当前 152 文件 0 违规
- ci.yml:新增 portable-sql-scan job;push 触发路径补 server/**
- .gitea/PULL_REQUEST_TEMPLATE.md:可测试性五支柱 + 测试 + 红线自检清单
- 守支柱 3(纯逻辑/IO 分离·双库可移植),见 docs/dev-conventions.html
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com >
2026-06-24 23:25:26 +08:00