[2.3.0] - 2026-09-04
-
[改进] CI 新增三道静态分析/审计门禁:
lint-go(golangci-lint v1.64.8,20 个策划 linter,含 stylecheck)、vuln-go(govulncheck v1.7.0 源码模式)、audit-web(pnpm audit,advisory 不影响合并);五个既有必需 job id 与行为不变。Go 构建阶段改为只 COPYcmd/与internal/,并在-trimpath -buildvcs=false下编译,减少缓存失效并清除构建器路径。 -
[修复]
/api/auth/sessions在两条last_seen_at同秒的会话之间顺序不稳定(ORDER BY last_seen_at DESC无 tiebreaker),导致已提交的 golden 样例在两次 CI 运行间翻转、test-go随机变红。现在排序加id DESC总序(双方言主键可移植),且 service 层把当前会话恒定锚在首位;不同秒会话的顺序不变。 -
[改进](行为变更)
FUND_EDGE_AUTH_ENABLED默认由 true 翻为 false:浏览器写路径默认只认fund_sessioncookie +X-Fund-Request+ Origin 白名单;旧的X-Fund-Edge-Key兼容层改为显式开启(仍依赖边缘代理注入该头的部署必须在 .env 设FUND_EDGE_AUTH_ENABLED=true并继续提供强FUND_EDGE_KEY)。变量名、取值拼写与 fail-closed 语义不变;deploy/docker-compose.yml、.env.example、deploy/README.md与AGENTS.md的默认值/文案已同步。 -
[修复]
/mcp在认证之前没有任何限流,随机 bearer token 洪泛会让每个请求都强制走一次完整 ECDSA 验签——公网面上最廉价的 CPU 放大点。现在在 MCPAuth 之前挂一个粗粒度按来源 IP 的令牌桶(新增FUND_MCP_PREAUTH_RPM,默认 600/分钟、burst 60),与认证后的按 key 桶(FUND_MCP_RPM)相互独立,因此 401 永远不会消耗 key 配额;已认证路径语义不变(#40)。 -
[修复] OAuth 访问令牌校验在
exp缺失时会跳过过期检查(Expiry==0),任何忘记写该声明的签发路径都会静默铸出永不过期的令牌。现在exp为必填,并拒绝iat超前服务器时钟 5 分钟以上的令牌;当前签发方本来就总带exp,但校验方不再依赖这一点(#40)。 -
[修复] 部署忘记设
FUND_ENV时会静默跳过全部生产密钥下限检查。现在操作者声明了公网面也会触发加固:FUND_PUBLIC_BASE_URL主机非回环,或FUND_ALLOWED_ORIGINS含非回环主机;纯回环部署(本地开发、CI smoke compose 在 localhost 用占位 key)继续豁免,检查项本身不变(#40)。 -
[修复]
/oauth/consent的 POST 校验了会话与一次性consent_token,但没有复核 Origin/Sec-Fetch-Site。现在经browserMutationAllowed补上与其他浏览器写路径同等的纵深防御(真正的防线仍是 SameSite 加单次消费 token);不发 Origin 的非浏览器客户端(基于 curl 的 CI smoke)继续可用(#40)。 -
[修复] 定投计划的 20:00 CST 执行窗口一旦被错过(重启、发版、宿主机挂起)就永久丢失。现在调度器按两条路径回补 due date,且都原样复用
RunDCAAutoInvest,因此order_id与dca_plan_executions台账继续记录 due date 而非回补日,按日幂等仍是原有的 claim-insert + 台账机制:启动追赶(在过期行情刷新之后、经startup_refreshclaim 每 CST 日一次)与 tick 兜底(每天 06:00-09:59 CST 窗口内首个 tick,内存内每日一次 + 持久化crawl_log(__sched_dca_backfill))(#40)。 -
[修复] 定投买入的
amount/nav是最后一条以全浮点写份额的资金路径(会落 33.333333333333336)。现在在执行边界用包内round4取整到 4 位小数,与台账存储单位净值的 4dp 精度对齐,preview 报告同一个取整值;整除结果(100/2=50)不变,既有断言全部保持(#40)。 -
[改进] 快照估值列改为在唯一写入路径上算定:
held_shares取整到 4dp(与净值解析对称),total_cost/current_value/unrealized_pnl/pnl_pct取整到 2dp(与展示层对称)。份额求和先落到 4dp 基准再做HeldSharesDust粉尘判定,因此浮点残差恰为 0、阈值只判真正的小额持仓;-0归一为0,残差不再通过 API 露出「-0」。latest_nav保持透传(解析时已是 4dp,ModeLight 有意保留存储值)(#40)。 -
[新功能] 一致性校验新增份额对账:按基金交叉核对 transactions 的
SUM(signed_share_change)与 portfolio_snapshot 的SUM(held_shares)(跨组合行求和,因为交易是基金级),两侧都经snapshot.RoundShares落到同一个 4dp 基准(即 Recalc 的基准);漂移超过snapshot.HeldSharesDust才报告,单次报告的逐基金条目上限 20 条(#40)。 -
[改进] XIRR 终值在净值缺失时不再静默按 0 计入。现在发出显式退化告警,把「数据缺失」与「真实亏损」区分开;portfolio service 增加可选 slog 汇聚点,未注入时回落到
slog.Default(),因此既有接线无需改动(#40)。 -
[改进] 数据库结构演进改为按方言编号的追加式迁移清单,由
ensureSchema应用并记录进schema_migrations表(SQLite 与 PG 同形):0001 基线建表、0002 transactions 列默认值(PG 目录修复,SQLite 仅记录)、0003 portfolio_snapshot 复合主键(PG,SQLite 仅记录)、0004 nav_history 证券化列(吸收internal/jobs/nav_schema.go,后者成为db.EnsureNavHistoryColumns的惰性薄封装)、0005transactions(order_id,fund_code)唯一索引(保持尽力而为:历史转换腿可能永久违反它)。每条迁移先探测再变更,因此未版本化的既有库首次启动会探测/跳过/记录,之后在版本闸门后为空操作(#41)。 -
[修复] 行为变更(已接受):非唯一索引的创建改由迁移 0001 强制,缺失时启动失败而非仅告警——缺索引列的历史库会在首次启动就暴露缺陷,而不是永远静默无索引运行;只有
(order_id,fund_code)唯一索引保留告警契约(#41)。 -
[改进] crawl 与行情刷新的重复实现收敛为
internal/service/admin单一来源:证券「过期+缺失」筛选(MCP 与 jobs 各一份、逐字相同)、逐证券批处理循环(3 份)、complete|partial|error状态判定(3 份)、以及「新鲜度→推荐→空集检查→批处理」序列(3 份)各收敛为一份实现,MCP 与 REST 端点改为薄协议适配层。同时消除internal/mcp→internal/jobs、internal/httpapi→internal/jobs两条仅为取状态字符串而存在的跨层依赖(#41)。 -
[修复] MCP
crawl_nav的stale_only逐证券失败此前只进failed_codes、完全不打日志(静默吞错误,且与 REST/jobs 两份实现行为不一致),现在会记录日志;响应形状与字段不变(#41)。 -
[测试] 线型契约测试从单向变为双向闸门:
internal/httpapi/golden_test.go把 12 个 schema 域、33 个代表性 GET/写端点的真实种子响应导出到packages/contracts/testdata/golden/(GOLDEN_UPDATE=1再生成,默认逐字节对比;时钟/运行时长/Go 版本/会话字段清洗为哨兵值,stale-day 种子按各计算方时区基准对齐——SQLjulianday用 UTC、Go 用 CST),packages/contracts/golden.test.ts再经显式「文件→schema」映射把每个金样本喂回 zod SSOT(34 例)。此前完全没有线型形状测试的 admin/alerts/analysis/reports 由此获得覆盖;funds.test.ts正例改读金样本而非手抄 fixture,负例保持手写。本轮未发现契约漂移(未改动任何 zod schema),契约测试 108 → 142 全绿(#41)。 -
[测试] 21 个存量测试文件的手抄
CREATE TABLEfixture 迁移到testutil.OpenTempDBWithProductionSchema(经真实启动路径EnsureSQLiteSchema建库,含schema_migrations与强制索引),使生产 DDL 漂移把这些测试变红,而不是藏在测试专用表后面;仅刻意构造遗留/缺陷 schema 的迁移探针保留手抄 DDL(#41)。 -
[修复] 图表此前在 render 期间直接读
<html data-theme>且无订阅,切换主题只有恰好在路由变化时重挂载的图表会重绘,跟随系统prefers-color-scheme变化则完全留下旧配色。现在useThemeKey用 MutationObserver(attributeFilter: data-theme + data-convention)订阅根元素并经useSyncExternalStore暴露已解析主题,挂载中的图表会重建 option 并沿既有notMergesetOption 路径重绘,无需路由重挂载;纳入data-convention是因为涨跌色互换同样喂给readChartTheme(#41)。 -
[改进] 行情页不再每 60 秒轮询:合法的 SSE 指数帧本就按与
GET /api/market/indices相同的形状经 zod 解析,现在直接回写共享的["market-indices"]查询缓存。查询键提取为lib/queries.ts导出的MARKET_INDICES_QUERY_KEY,作为useIndices与useMarketStream的唯一共享来源;保留首次 REST 拉取,脏帧仍跳过缓存写入。帧应用抽成纯函数applyIndicesFrame,使「解析或跳过」契约无需 EventSource 即可单测(#41)。 -
[修复] 写路径错误出口统一走
mutationErrorMessage:ApiError已知码映射中文文案(login/setup 各自的页面内码表合并为一张),ZodError、网络错误、合成的http_*与 snake_case 机器码一律回落到调用方的中文兜底文案,不再把技术噪声泄露给用户。替换了全部 9 处 mutation/toast/表单出口(定投 ×3、洞察、系统、报告、删除对话框、交易表单、设置改密、顶栏登出)(#41)。