Files
Jyotisha/docs/BUG_HISTORY.md
T
Jesse_Chen 56577504d8
Independent Staging Quality Gate / validate (push) Successful in 10m8s
Independent Staging Quality Gate / publish (push) Successful in 13m20s
docs: record BUG-344 fix SHA
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-21 20:38:05 +08:00

768 KiB
Raw Blame History

Bug History

本文件是本仓库产品与代码 Bug 的长期知识库。处理任何 Bug 前先读并搜索本文件;完成修复或确认阻塞后,在同一变更中更新本文件。

基础设施、外部引擎、碎片目录和开工预检类风险仍记录在 docs/research/pre_work_error_ledger.md。同一问题若同时影响两类台账,应互相引用,不复制大段内容。

使用流程

  1. 用报错原文、接口路径、状态码、模块名和用户操作搜索本文件。
  2. 找到相似记录时,先验证既有防复发措施是否仍存在,再定位新的回归入口。
  3. 修复必须包含与风险相称的自动化测试;生产问题还要记录部署版本和脱敏后的生产验证。
  4. 完成后更新已有记录,或按下方模板添加新记录。复发问题必须填写 复发自,不能伪装成无关的新问题。
  5. BUG 编号必须唯一。追加新记录前先搜索本文件当前最大编号,并从其后继续分配;出现重复编号时保留较早记录的编号,为后加入的记录分配新编号,并同步修正指向它的 相关记录复发自
  6. 不记录姓名、出生资料、邮箱、用户/案例 ID、Cookie、JWT、密码、密钥、完整请求体或模型原文。

状态定义

  • investigating:现象已确认,根因未确认。
  • blocked:缺少权限、环境或外部条件,当前无法继续验证。
  • mitigated:用户影响已被安全降级,但根因或全链路尚未闭环。
  • resolved:根因已修复,并有自动化或真实环境证据。
  • regressed:历史问题再次出现;必须关联原记录并说明旧防线为何失效。

新记录模板

## BUG-NNN | 简短标题

- 状态:investigating | blocked | mitigated | resolved | regressed
- 首次发现:YYYY-MM-DD
- 最近更新:YYYY-MM-DD
- 影响面:页面、接口或模块
- 用户现象:脱敏后的可观察现象
- 触发条件:最小复现路径
- 根因:已验证的技术原因;未知时明确写未知
- 修复:实际采取的最小修复
- 验证:测试、生产 smoke、迁移账本或监控证据
- 防复发:测试、约束、监控或发布门禁
- 相关记录:BUG-NNN / ERR-NNN;没有则写无
- 复发自:BUG-NNN;首次出现则写无
- 修复版本:Git SHA、迁移版本或待发布

已记录问题

BUG-001 | 对话删除不可用或只改变前端状态

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-21
  • 影响面:会话列表、DELETE /api/sessions/[id]、Supabase chat_sessions
  • 用户现象:用户点击删除后对话无法真正删除,或删除交互与账户弹窗不一致。
  • 触发条件:登录后删除自己的一条历史会话。
  • 根因:删除曾缺少所有者约束的服务端入口和对应数据库授权,前端也没有统一的站内确认面。
  • 修复:增加所有者约束的服务端删除接口与数据库 grant,并统一站内确认弹窗。
  • 验证:tests/test_session_management_entrypoints.pyfrontend/tests/chat-session-delete-contract.test.ts
  • 防复发:删除必须经服务端所有权校验;测试同时锁定 API、迁移和前端入口。
  • 相关记录:无
  • 复发自:无
  • 修复版本:e3d619c65b8c425650ed1fcf5794

BUG-002 | Onboarding 首次请求 409、重复请求或读到旧问题

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-21
  • 影响面:POST /api/onboarding、首页入门问题缓存与恢复
  • 用户现象:出生资料已经填写,第一次请求仍返回资料未完成或 pending,随后又请求一次并返回缓存结果。
  • 触发条件:入门资料完成、缓存生成尚未结束或资料在并发生成期间发生变化。
  • 根因:缓存就绪/处理中状态没有完整绑定当前资料指纹,旧请求完成时可能覆盖新资料的生成结果。
  • 修复:用全部决策字段生成无明文 SHA-256 身份;领取和完成都使用版本、时间戳和 pending 身份 CAS,并校验账户所有权。
  • 验证:onboarding route/cache/candidate completion 测试矩阵,历史完整前端套件 455/455。
  • 防复发:缓存命中、pending、超时回收和完成写入必须绑定同一资料身份;任何旧请求只能返回安全 pending
  • 相关记录:无
  • 复发自:无
  • 修复版本:28d58d45ee925c308e5d5e13d595346ec02a9ffbd9

BUG-003 | 浏览器原始报错 “The string did not match the expected pattern” 泄露给用户

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-21
  • 影响面:生时校正自动流程、候选时间确认、“都不符合”分支
  • 用户现象:选择候选时间或“都不符合”后直接看到浏览器英文 DOMException。
  • 触发条件:自动或手动生时流程中的底层浏览器/传输异常进入未归一化错误分支。
  • 根因:部分 journey effect 直接展示实现层异常信息,没有统一转换为安全、可操作的中文错误。
  • 修复:所有相关 mutation/effect 统一经 birthTimeUserError 归一化,屏蔽 DOMException、网络和语法实现细节。
  • 验证:frontend/tests/birth-time-user-errors.test.ts 包含该英文原文回归用例。
  • 防复发:禁止 setError(caught.message);新增异步入口必须走统一错误映射。
  • 相关记录:无
  • 复发自:无
  • 修复版本:a38a096

BUG-004 | 点击生时校正后先进入中间卡片,失败时无法自然回到首页

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-22
  • 影响面:首页生时校正入口、首轮加载与恢复 UI
  • 用户现象:点击入口后先看到“开始生时校正”或“正在恢复账户里的校正进度”卡片;依赖失败时停留在重试卡片。
  • 触发条件:首轮校时 RPC 尚未返回或返回失败。
  • 根因:页面在首轮有效 turn 生成前就切换到专用校正会话。
  • 修复:首轮生成期间保持首页;只有有效首轮 turn 返回后才进入校正会话,失败以首页输入区提示呈现。
  • 验证:frontend/tests/consultation-entrypoint.test.ts 及生产入口 smoke。
  • 防复发:会话切换以“首轮可展示结果”而非“请求已发出”为边界。
  • 相关记录:ERR-087
  • 复发自:无
  • 修复版本:dc0077e

BUG-005 | 保存出生时间返回 PATCH /api/account 500

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-22
  • 影响面:账户出生资料、Supabase profiles
  • 用户现象:选择记录时间和误差后显示“暂时无法保存账户资料”,接口返回 500。
  • 触发条件:账户已被候选时间写入 reported 字段,之后再次编辑原始出生时间声明。
  • 根因:旧触发器把候选/活动时间复制为用户报告时间,同时数据库又把报告时间视为不可变字段。
  • 修复:报告时间保持可编辑,禁止候选时间反写原始声明,修复不一致历史行并增加来源/时间一致性约束。
  • 验证:迁移 20260721140000 已进入生产账本;生产合成账户修改与恢复均返回 200。
  • 防复发:候选、活动、用户报告时间保持独立语义;profile persistence 测试锁定迁移行为。
  • 相关记录:ERR-088
  • 复发自:无
  • 修复版本:dc0077e20260721140000_repair_reported_birth_time_revision.sql

BUG-006 | 生时校正首轮在没有历史事件时进入确认态并返回 409

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-22
  • 影响面:POST /api/birth-time-conversation 首轮、计费释放
  • 用户现象:等待约一至两分钟后首轮返回 action_conflict,费用预留被释放。
  • 触发条件:技术扫描在零条历史事件时直接返回 ready_for_confirmation
  • 根因:应用构建了 confirming 首轮,而数据库契约只允许首轮为 active;技术就绪错误绕过了三条有效历史事件的业务门槛。
  • 修复:所有技术包统一经过 MINIMUM_SCOREABLE_EVENTS 门禁;未满三条时清除 result ID,并保持 pending_validation/active
  • 验证:核心 orchestrator/e2e 回归测试;生产首轮随后成功创建并收费一次。
  • 防复发:确认态只能由三条以上有效、已发生、可评分事件触发。
  • 相关记录:ERR-089
  • 复发自:无
  • 修复版本:b8ed740

BUG-007 | finance 证据通过应用校验但被数据库拒绝为 409

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-22
  • 影响面:生时校正技术包、事件证据、Supabase durable validators
  • 用户现象:真实首轮计算完成后仍返回统一的 action_conflict
  • 触发条件:D2/D11 产生 finance 建议主题,或摘要包含应用已支持的 domain 字段。
  • 根因:TypeScript 已支持 finance 和摘要 domain,初始 SQL 枚举及表约束仍是旧版本。
  • 修复:向前迁移同步 evidence request、life event、private candidate、public recap 和事件表约束。
  • 验证:迁移 20260721150000 已进入生产账本;迁移契约测试和生产首轮 smoke 通过。
  • 防复发:应用证据枚举变更必须同时更新 durable SQL,并由迁移契约测试检查。
  • 相关记录:BUG-006、ERR-090
  • 复发自:无
  • 修复版本:b981c4e20260721150000_align_conversational_finance_domain.sql

BUG-008 | 后续证据把范围压得过窄后返回 503

  • 状态:resolved
  • 首次发现:2026-07-21
  • 最近更新:2026-07-22
  • 影响面:生时校正后续回答、技术包构建
  • 用户现象:首轮、“都不符合”、暂停恢复均正常,但提交后续明确事件时返回 service_unavailable
  • 触发条件:事件评分产生的窄区间不足两个时间样本或不足两个可区分分盘主题。
  • 根因:这是“证据仍不足”的正常业务状态,代码却把它作为 TypeError 依赖故障终止。
  • 修复:显式分类区间区分度不足;撤回本次过度收窄,保留上一候选范围、评分证据和未确认状态,然后继续提问或安全保存范围。
  • 验证:核心回归 85/85;生产从失败点续跑通过;全新生产 smoke 覆盖资料、首轮、“都不符合”、暂停恢复、多事件、范围终态、计费和问题交接。
  • 防复发:任何新候选范围必须先满足技术证据契约;不满足时回退,不返回 503,也不伪造确定分钟。
  • 相关记录:ERR-091
  • 复发自:无
  • 修复版本:b981c4e

BUG-009 | 未校正的已填报时间被降级为零星盘咨询

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生日初始化、普通咨询路由、生时校正提示
  • 用户现象:已填写具体出生时间但没有完成生时校正时,普通问题仍提示只能回答一般知识或必须先完成校正。
  • 触发条件:出生时间来源包含有效具体分钟,但状态不是 confirmed,且当前聊天没有旧的临时授权状态。
  • 根因:前端把“使用未校正填报时间”设计成逐会话授权;没有授权时默认退回 general_no_birth_time,因此完全绕过现有的未校正星盘安全模式。
  • 修复:具体填报时间现在自动进入 unverified_birth_time,保留禁止精确应期的安全边界但不禁用个人分析;无具体分钟时直接进入无分钟模式;移除校正 toast、弹窗和每条回答前重复的校正警告,并把生日入口收敛为“知道准确时间 / 不确定准确时间”两个选择。
  • 验证:frontend/tests/birth-time-consultation-consent.test.tsfrontend/tests/consultation-entrypoint.test.tsfrontend/tests/consultation-birth-time-mode.test.tsfrontend/tests/birth-time-intake.test.ts
  • 防复发:咨询路由测试锁定“有效填报分钟无需授权即可使用”;页面契约禁止重新引入生时校正 toast 或阻断式选择。
  • 相关记录:BUG-003、BUG-004
  • 复发自:无
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支与 staging 验收结果为准)

BUG-010 | 浏览器直连 Supabase 导致自托管 PostgreSQL staging 误报未配置

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-27
  • 影响面:聊天首页启动、会话模型选择、退出登录;自托管 PostgreSQL staging。
  • 用户现象:数据库和身份服务正常,首页却显示“Supabase 尚未配置”。
  • 触发条件:AUTH_PROVIDER=self-hosted 且不提供浏览器 Supabase 公钥。
  • 根因:页面启动、会话模型更新和退出登录仍直接创建浏览器 Supabase client;仅服务端 API 已切换到同源 PostgreSQL 路径。
  • 修复:/api/account 返回当前用户完整 profile;首页通过 /api/account/api/sessions/api/sessions/:id 读写,退出使用 self-hosted identity client。
  • 验证:frontend/tests/chat-session-write.test.tsfrontend/tests/session-model-persistence.test.tsfrontend/tests/account-api.test.ts、ESLint、next build
  • 防复发:首页不得引用 createBrowserSupabaseClient;会话与 profile 只能经同源 API 访问。
  • 相关记录:BUG-001、BUG-003
  • 复发自:BUG-010
  • 修复版本:待发布 staging

BUG-011 | 对话消息暴露内部证据审计状态

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:普通问答消息、Agent 回答顶部区域
  • 用户现象:回答正文上方显示“证据状态:not-applicable”和“证据链摘要 · not-applicable”等工程审计信息,正文下方还显示单条回答的报告下载控件。
  • 触发条件:任意已完成的 Agent 回答,尤其是后端返回 not-applicable 时。
  • 根因:消息行对每条非思考态 Agent 消息无条件渲染 claim boundary 与 Technique Audit Table;未识别状态又直接回退显示原始状态值。
  • 修复:从普通聊天消息行移除内部证据徽章、审计面板和单条回答报告下载控件;证据状态和 workflow receipt 仍随消息保存并供内部约束使用。
  • 验证:frontend/tests/claim-boundary-badge.test.tsfrontend/tests/evidence-audit-panel.test.tsfrontend/tests/consultation-report-export.test.ts 锁定聊天消息不再挂载这些内部组件。
  • 防复发:聊天消息渲染契约禁止直接展示 techniqueTruthworkflowReceipt;需要运营或调试时使用独立的受控界面。
  • 相关记录:BUG-009
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-012 | 自动生时流程重构后再次直出实现层错误

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正自动出题与评分轮询、use-birth-time-automatic-journey-effects
  • 用户现象:自动流程失败时可能再次显示底层异常原文,而不是稳定、可操作的中文提示。
  • 触发条件:自动出题或评分轮询 Promise 进入异常分支。
  • 根因:出生时间流程重构保留了 automatic effect 中直接读取 caught.message 的旧分支;原回归测试后来只检查 guided hook,因此没有锁定 automatic hook 的两个入口。
  • 修复:automatic effect 的出题与轮询异常统一经过 birthTimeUserError;回归测试同时检查 guided 与 automatic 两个 hook,并禁止两种直接展示 caught.message 的写法。
  • 验证:frontend/tests/birth-time-user-errors.test.ts
  • 防复发:错误归一化测试按入口文件验证安全不变量,不再依赖单文件精确调用次数。
  • 相关记录:BUG-003
  • 复发自:BUG-003
  • 修复版本:待提交(PR #25

BUG-013 | 精简生时校正文案后真实 Chromium 测试等待已删除标题

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:frontend/tests/conversational-rectification-component.test.ts、前端完整质量门禁
  • 用户现象:页面已经正确渲染候选时间、经历和输入控件,但测试持续等待并最终报 Timed out waiting for async initial turn
  • 触发条件:运行真实 Chromium 390px 组件回归测试,并把 active turn 注入精简后的生时校正界面。
  • 根因:产品把重复的“当前判断”叙事改成“已记录”行动文案后,浏览器测试仍以旧标题作为异步渲染完成信号。
  • 修复:测试改为等待候选代表时间与已记录经历两个稳定结构信号,不再绑定可变标题文案。
  • 验证:frontend/tests/conversational-rectification-component.test.ts
  • 防复发:真实浏览器等待条件优先绑定语义结构和状态数据,不用已批准可调整的展示标题充当加载边界。
  • 相关记录:BUG-004
  • 复发自:无
  • 修复版本:待提交(PR #25

BUG-014 | 能力审计新增案例验证主题后质量门禁仍断言旧主题集合

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:tests/test_api_server_security.py、Python quick quality gate
  • 用户现象:能力审计正确返回 Case Validation 并将案例验证评为可产品化,但质量门禁仍按旧主题集合和 thin 等级失败。
  • 触发条件:运行能力审计测试,且应用可见主题已包含案例验证入口。
  • 根因:案例验证能力进入 _app_visible_topics 后,对应主题集合和 UX 等级断言都没有同步更新。
  • 修复:把 Case Validation 纳入预期可见主题,并把 case_validatorthin 队列移入 excellent 断言,保持测试与同一审计规则一致。
  • 验证:tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sourcesPython quick quality gate。
  • 防复发:扩展应用可见主题时必须同时更新能力审计契约测试;精确集合断言继续用于发现意外增删。
  • 相关记录:无
  • 复发自:无
  • 修复版本:待提交(PR #25

BUG-015 | 分钟校正 safeguards PR 与主线证据契约分叉

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:分钟候选输入身份、公共 holdout 校验、三引擎证据包、PR quality gate
  • 用户现象:PR 与 main 在五个文件发生冲突,聚焦测试通过但完整 quick quality gate 在 API 安全测试收集阶段失败。
  • 触发条件:基于旧基线开发 v1 holdout safeguards,同时主线独立升级到 v2/v3 证据契约并移除旧 API store 导入。
  • 根因:PR 复用了旧 holdout 字段和 true node 默认,未执行与 CI 相同的 quick quality gate;主线已经采用 mean node 和更严格的 sealed holdout schema。
  • 修复:以主线为准新增兼容的输入指纹、邻近分钟探针和语义哈希;新增 v4 独立审核、日级事件、假分钟承诺门禁及非生产 intake;不恢复旧自动评估循环。
  • 验证:分钟校正聚焦回归、脚本直接执行检查、Ruff、Python compilation 和 quick quality gate。
  • 防复发:新 safeguards 必须以当前 schema 向前升级;候选身份必须继承产品计算默认;PR 验收必须包含 workflow 实际执行的 quick quality gate。
  • 相关记录:BUG-009、BUG-014、ERR-045、ERR-053、ERR-086
  • 复发自:无
  • 修复版本:d7d9703

BUG-016 | 生时校正首轮被扫描稀疏度或候选分区超时打成 503

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:首页“先完成生时校正”、POST /api/birth-time-conversation 首轮技术包
  • 用户现象:点击生时校正后仍停留在普通咨询页,并显示“生时校正暂时无法继续”;接口返回 503。
  • 触发条件:候选扫描包含真实但不连续的时点差异,或动态候选分区依赖在 45 秒内没有返回。
  • 根因:技术包只承认相邻分钟切换,错误拒绝了稀疏扫描中的真实范围差异;同时首轮把用于改善问题排序的候选分区依赖当成不可降级的硬依赖。生产版本 b981c4e 的日志分别记录了 RectificationTechnicalPacketRangeErrorcandidate_differences TimeoutError
  • 修复:稀疏扫描差异改用明确的范围级文案,禁止伪称相邻分钟切换;超过六小时的宽范围首轮跳过分钟级候选分区,较窄范围在候选分区发生已知超时、取消、配置或服务故障时保留服务端扫描证据并继续收集,不再返回 503;未知编程或契约错误仍失败关闭。
  • 验证:frontend/tests/conversational-technical-packet.test.tsfrontend/tests/conversational-rectification-route.test.ts、真实 Chromium 390px 组件回归和 npm run build 通过;完整前端套件 844 项中 837 项通过,余下 7 项均为沙箱 Chromium / PostgreSQL 端口环境失败,针对性 Chromium 在沙箱外 8/8 通过。
  • 防复发:首轮必须区分核心扫描失败与可降级的候选排序依赖;稀疏差异文案必须显式否认相邻分钟切换;生产验收必须核对接口状态与实际进入校正会话。
  • 相关记录:BUG-004、BUG-008、ERR-091
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-017 | 候选范围终态继续原问题必定返回 409

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正范围终态、POST /api/consult、durable 原问题交接
  • 用户现象:生时校正安全结束并保存候选范围后,点击“继续原问题”仍提示原问题状态变化,接口返回 409。
  • 触发条件:分钟确认发布门禁未通过,校正以 candidate.status=pending_validation 完成;页面按未确认分钟选择 unverified_birth_time 并携带 durable handoff。
  • 根因:页面正确区分确认分钟与保存范围,但咨询接口把所有 handoff 硬限制为 verified_chart;范围终态因此在计费和模型调用前被拒绝。
  • 修复:handoff 允许 verified_chartunverified_birth_time 两种星盘模式,继续拒绝 general_no_birth_time、旧校正入口和不一致 request ID;分钟确认门禁保持不变。
  • 验证:生产合成账号复现 409 mode_changedfrontend/tests/consultation-birth-time-mode.test.ts 锁定范围终态模式与一般咨询隔离;发布后需复跑 durable claim、正常咨询和聊天删除 smoke。
  • 防复发:范围终态和精确确认必须分别覆盖 continuation 模式;不得把“拥有 handoff”错误等同于“已经确认出生分钟”。
  • 相关记录:BUG-008、BUG-009、BUG-016、ERR-085、ERR-091
  • 复发自:无
  • 修复版本:待提交(生产 smoke 待当前精确 SHA 发布后补齐)

BUG-018 | 首次保存未确认出生时间没有写入档案状态

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:账户出生资料保存、未确认星盘咨询、生时校正后继续原问题
  • 用户现象:已经保存“大概时间”等出生资料,但继续咨询时仍提示出生时间状态发生变化,接口返回 409。
  • 触发条件:档案没有旧的 active minute、legacy minute、candidate 或 case pointer,第一次经账户接口保存未确认出生时间声明。
  • 根因:账户写入只在清除旧候选结果时补写 birth_time_status=reported;干净档案和首次 upsert 被提前跳过,留下完整声明但空状态,随后被服务端星盘真值校验拒绝。
  • 修复:所有未确认声明变更都原子写入 reported 并清空不可沿用的应用结果;首次创建档案时也写入同一状态;确认分钟仍禁止被普通资料编辑覆盖。
  • 验证:frontend/tests/account-api.test.ts 覆盖空状态档案与首次档案写入;生产使用认证账户重新保存合法声明后复跑范围终态 handoff、模型咨询、计费和会话删除。
  • 防复发:出生声明与 birth_time_status 必须由同一次服务端写入建立,不允许依赖后续校正流程补齐状态。
  • 相关记录:BUG-009、BUG-017
  • 复发自:无
  • 修复版本:本次自动滚动修复提交

BUG-019 | 原问题交接租约过期后永久显示处理中

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正范围终态、跨设备/失败重试、durable 原问题 handoff
  • 用户现象:一次继续咨询在输出前失败后,刷新仍无法重试,接口返回“原问题状态已经变化”。
  • 触发条件:handoff 处于 claimedexecuting,两分钟租约已经过期,客户端重新加载交接。
  • 根因:数据库 projection 只看持久化 state,把过期租约仍投影为 in_progress;客户端因此不会发起 claim,而执行 RPC 又正确拒绝过期租约,形成永久卡死。
  • 修复:projection 将已过期的 claimed/executing 租约投影为 pending,复用既有加锁 claim RPC 原子接管;未过期租约仍保持 in_progress,双设备隔离不变。
  • 验证:迁移契约测试锁定过期租约恢复语义;生产制造过期 claim 后重新加载、claim、执行模型咨询和 settle。
  • 防复发:所有租约型状态的读取投影必须同时解释 lease deadline,不允许只依赖枚举 state。
  • 相关记录:BUG-017、BUG-018
  • 复发自:无
  • 修复版本:待提交

BUG-020 | 生时校正把语言问答包装成高阻力卡片表单

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:首页生时校正会话、经历录入、候选核对与更正
  • 用户现象:每轮同时出现候选卡、领域按钮、年份与月份下拉、经历回顾卡和确认卡;用户需要理解多层控件才能回答一个问题,移动端尤为费力。
  • 触发条件:进入生时校正并提交或更正一条真实经历。
  • 根因:后端已有自由文本证据契约,但前端仍用结构化表单和多卡片包装;校正叙事 Agent 也未加载 Jyotish Skill 来生成自然、逐问式的取证措辞。
  • 修复:改为一段助理提问加一个自然语言输入框;候选状态和证据历史收进可展开进度区;保留明确确认、更正、持久化、计费、计算和分钟验证门禁;叙事 Agent 加载 Jyotish Skill,但服务端技术包仍是候选事实和确认权限的唯一来源。
  • 验证:语言优先组件静态与真实 Chromium 390px 回归 8/8 通过;校正路由与叙事契约 23/23 通过;目标文件 ESLint 和 Next.js production build 通过。
  • 防复发:语言问答不得重新拆成领域按钮或日期下拉;Skill 只能改进问题策略与措辞,不得生成、重算或确认候选时间。
  • 相关记录:BUG-008、BUG-009、BUG-015、BUG-016、BUG-017
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-021 | 自然语言真实事件在分钟评分入口被静默丢弃

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正自然语言抽取、历史事件持久化、分钟评分、首轮与兜底叙事
  • 用户现象:用户已经提供带日期的真实经历,系统仍反复索取证据;首轮回答展示完整 D 层技术清单,却没有用一个具体问题推进取证。19 世纪事件、疾病、手术、事故和丧亲尤其容易无法参与评分。
  • 触发条件:日期早于 1900 年;事件属于健康或重大压力;累计有效事件超过 6 条;或叙事模型未满足首轮全量技术层合同而进入确定性兜底。
  • 根因:前端日期正则写死 1900–2099;健康类关键词落入 other,并在路由中被过滤,尽管后端已有 health_pressure 与 D30 评分支持;路由另有独立的 6 条截断;叙事校验强制首轮正文列出全部稳定层、敏感层和值。
  • 修复:日期抽取支持 1000–2099 的四位历史年份;健康、事故、丧亲映射到既有 health_pressure/D30 合同并贯通持久化、旧数据导入和公开 turn schema;评分截断统一复用 8 条收敛上限;技术包和 validation receipt 继续完整保存,但非最终用户正文只呈现候选范围、未确认边界和一个高信息量问题,确定性兜底也遵守相同语言交互。
  • 验证:抽取、叙事、路由聚焦测试 59/59 通过;真实案例回放、编排和端到端契约 65/65 通过;目标文件 ESLint、Python replay/compilation、git diff --check 和 Next.js production build 通过。公开 smoke 中 Steve Jobs、Einstein、Marie Curie 分别保留 4、3、4 条可评分事件,产品过滤结果与结构化 oracle 一致。
  • 防复发:新增 18xx 中文与 ISO 日期、健康/事故/丧亲、8 条事件上限和单问题兜底断言;技术层可进入服务端证据包,不得重新进入普通会话正文;family/other 仍是持久化背景,不得伪装成已评分领域。
  • 相关记录:BUG-008、BUG-009、BUG-015、BUG-016、BUG-020
  • 复发自:BUG-020
  • 修复版本:待提交(本地可测)

BUG-022 | 生时校正入口等待首轮请求完成后才切换会话

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:首页生时校正入口、普通咨询中的“先完成生时校正”、侧栏恢复校正会话
  • 用户现象:点击生时校正后长时间停留在首页或原咨询 session,直到恢复、建案、计算和会话持久化全部完成才突然跳转,用户容易误以为点击无效并重复操作。
  • 触发条件:startresume、durable handoff 或 session persistence 任一请求响应较慢。
  • 根因:openBirthTimeRectification 虽然先设置了 loading,但专属 session 的插入、激活和校正 surface 的开放都位于首个异步请求之后;surface 还要求首个 turn 已存在,因此 loading 状态无法在目标会话中显示。
  • 修复:创建或复用专属 session 后,在任何 await 之前立即插入并激活该 session;校正 surface 允许以空 turn 渲染既有“正在建立校正记录…”状态;增加同步 in-flight 门禁防止快速重复点击发出多个启动请求;失败时新建 session 回滚到来源会话,复用 session 则保留可见错误与重试入口。
  • 验证:frontend/tests/consultation-entrypoint.test.ts 21/21 通过,锁定 session 插入、激活和 loading surface 必须早于首个异步请求,并锁定单请求门禁;目标文件 ESLint、git diff --check 和 Next.js production build 通过;真实应用内 Chromium 从首页点击时,在后台恢复完成前已经显示“正在建立校正记录…”,从普通会话切回校正 session 也立即显示目标会话。
  • 防复发:首页卡片、普通 session 建议和侧栏恢复必须继续共用同一启动函数;不得重新用首个 turn 是否返回作为 session 可见性的条件。
  • 相关记录:BUG-016、BUG-020、BUG-021
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-023 | 初始化地址保存后的加载提示仍指向生时评估

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:新用户初始化流程、出生地点保存后的全屏加载提示
  • 用户现象:用户填完出生地址后,页面实际准备进入首页,但加载提示仍显示正在生成生时评估,造成流程去向与界面文案不一致。
  • 触发条件:初始化流程完成出生时间填写,并提交最后一步出生地点。
  • 根因:出生时间保存和出生地点保存共用 saving_profile 展示阶段;该阶段文案仍按旧流程描述为即将生成生时评估。
  • 修复:新增独立的 entering_home 展示阶段,仅在 saveOnboardingPlace 提交地址时使用,显示“正在进入首页 / 出生资料已保存,正在为你准备首页。”;出生时间保存继续使用原 saving_profile 阶段。
  • 验证:聚焦测试锁定地址提交与 entering_home 的绑定及目标文案;目标文件 ESLint 与 git diff --check 通过。
  • 防复发:初始化步骤的加载文案必须绑定实际导航结果;不得通过修改共享 saving_profile 文案改变出生时间提交阶段的语义。
  • 相关记录:BUG-022
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-024 | 侧栏会话标题与更多操作被拆成两块

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:聊天记录侧栏、当前会话选中态与更多操作入口
  • 用户现象:会话标题显示在一块选中背景中,右侧更多操作却以独立圆形按钮悬在外侧,看起来像两个不一致的控件。
  • 触发条件:侧栏展开并选中任意会话。
  • 根因:选中背景只应用在左侧 .session-main,右侧 .session-menu-trigger 又单独使用 50% 圆角和悬停背景。
  • 修复:把选中、悬停和聚焦背景统一应用到整行 .session-row;子按钮保持透明,并移除更多操作按钮的独立圆形底色。
  • 验证:侧栏契约测试锁定整行选中背景、透明标题按钮和非圆形更多操作按钮;目标文件 ESLint 与 git diff --check 通过。
  • 防复发:会话标题和行内操作必须共享同一个行级状态面,不得分别绘制互相竞争的选中背景。
  • 相关记录:无
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-025 | 健康压力追问被数据库误判为校正操作冲突

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正首条自然语言回答、POST /api/birth-time-conversation、Supabase 对话持久化契约
  • 用户现象:用户提交一条带年月的真实经历后等待约一分钟,接口返回 409 action_conflict,页面提示加载最新进度后重试;重复提交仍无法进入下一问。
  • 触发条件:技术包为下一轮选择 health_pressure(健康与重大压力)作为待补证据领域。
  • 根因:应用层已把 health_pressure 作为正式领域,并允许语言问答每轮只追问一个重点领域;durable SQL 既缺少该领域,又要求 evidence request 至少包含两个领域。完整计算和叙事生成完成后,save_conversational_rectification_turn 才以 conversational_action_conflict 拒绝公开 turn。
  • 修复:新增向前迁移,将 health_pressure 同步加入四个 durable validator 和事件证据表约束,并把 evidence request 基数从 2–4 对齐为应用契约的 1–4;保留现有幂等、版本和候选确认门禁。
  • 验证:真实浏览器复现确认只有 conversational_rectification_valid_evidence_request 失败,其余公开 turn 子结构、事件、回执和私有候选均通过;迁移应用到测试 Supabase 后,用同一条“2023 年 3 月离家来北京工作”重放,POST /api/birth-time-conversation 返回 200,日志为 actionKind: answerresultCategory: success,事件规范化保存为 2023-03 · 离家来北京工作,页面继续自然追问学业经历且输入框恢复可用;聚焦迁移测试锁定四个 validator 与表约束必须共同支持该领域。
  • 防复发:应用 evidence domain 枚举新增值时,必须同时更新 TypeScript schema、公开 recap/request、私有候选、事件表约束和迁移契约测试;不得把正常领域扩展映射为通用 409。
  • 相关记录:BUG-007、BUG-020、BUG-021
  • 复发自:BUG-007
  • 修复版本:待提交(测试 Supabase smoke 通过)

BUG-026 | 首页与会话改版后旧测试阻断生产发布门禁

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:production 发布门禁、首页主题入口、会话输入区、生时校正组件服务端渲染测试
  • 用户现象:production 应用构建成功,但完整前端测试有 7 项失败,导致最新 main 无法通过发布前验证。
  • 触发条件:运行完整前端测试;旧断言仍查找已移除的首页证据预览、固定 180px 模型菜单、无条件输入区类名和旧 profile 文案来源;旧手动 workflow 还固定在 Node 20,而当前 @mastra/core 明确要求 Node 22.13 以上。
  • 根因:首页与普通 session 的布局和文案已经更新,测试契约没有随产品行为同步;Jyotish Skill Tests 的 Node 版本也落后于其他 production 门禁和应用依赖声明,使 Node 20 把 GSAP ESM 错误解析成 CommonJS namespace。
  • 修复:测试改为锁定当前首页主题选择、内容自适应模型菜单、带首页修饰类的停靠输入区和 profileDraft 文案来源;GSAP 使用正式命名导出;手动 production 测试 workflow 与其余门禁统一到 Node 22。
  • 验证:6 个相关测试文件、Node 22 完整前端测试、ESLint、production build 与 patch check 全部通过后方可发布。
  • 防复发:产品 UI 结构调整时必须在同一提交同步源码契约测试;workflow 的 Node 主版本不得低于已锁定运行时依赖声明的最低版本。
  • 相关记录:BUG-022、BUG-024、BUG-025
  • 复发自:无
  • 修复版本:待提交(production gate

BUG-027 | 已完成的生时校正会话打开后误建新案例

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正历史会话恢复、范围终态展示、首页继续入口与计费
  • 用户现象:范围校正完成后刷新,点击侧边栏原会话会卡在“正在建立校正记录”,或跳到另一个会话并提示加载最新进度;首页同时重新显示开始入口。
  • 触发条件:已绑定 case 的生时校正会话对应 completed/abandoned 终态,而账户接口只投影当前未完成 case;或者账户另有一个更新的未完成 case。
  • 根因:会话打开逻辑只按账户当前 case 决定 resume/start,没有优先恢复所点击会话自己的 rectificationCaseId;历史终态加载后又会覆盖账户当前可恢复 case,异步账户刷新还拒绝接受 null 或另一个最新 case。
  • 修复:已绑定的生时校正会话始终以自身 case id 执行只读 resume;只有首页或未绑定会话才按账户当前 case 复用或创建;历史终态不再覆盖另一个未完成 case,终态后账户刷新接受服务端最新投影。
  • 验证:入口契约测试锁定历史会话按自身 case 恢复、首页重新校正仍创建独立会话,以及历史终态不覆盖当前未完成 case;发布后用 production 合成账号复查范围终态、刷新恢复、首页入口与余额不变量。
  • 防复发:聊天会话的 rectificationCaseId 是历史查看的第一绑定键;账户 rectificationCase 只代表当前未完成案例,二者不得相互替代。
  • 相关记录:BUG-017、BUG-019、BUG-020
  • 复发自:无
  • 修复版本:待提交(production gate

BUG-028 | 生时校正收到具体经历后仍重复泛问

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正自然语言追问、事件补充信息合并、会话可见回复
  • 用户现象:用户已经描述自己的具体经历,助理没有针对该内容继续追问或反馈,而是重复上一轮的泛化问题。
  • 触发条件:用户提供的事件缺少年月并在下一轮单独补时间;或已经回答技术包当前首选领域后,下一轮仍沿用同一领域提示。
  • 根因:存在三处断链:会话组件忽略服务端已经生成的针对性 turn.narrative,转而根据公开 evidence request 重组固定问题;用户先说事件、下一轮只补年月时,两轮分别保存为“无日期事件”和“无内容日期”,未形成可评分事实;服务端下一问直接采用技术包首选领域,没有排除本轮已经回答的领域。
  • 修复:会话组件改用独立的可见叙事函数,优先承接用户最近一条具体但不完整的经历并只追问缺失项;编排器把下一轮仅含日期或仅含内容的补充合并回最近一条待澄清事件,同时用 correctsEvidenceIds 保留 append-only 修订链;下一问从技术包允许领域中优先选择尚未回答的领域,并同步覆盖公开 evidence request。
  • 验证:相关自然语言抽取、叙事、编排、路由、公开案例回放与端到端测试共 111 条全部通过;回归覆盖“先说离家工作、再补 2023 年 3 月”后合并为 2023-03 · 离开家去北京开始工作、具体事件缺日期时回复必须复述该事件并询问年月、关系领域回答后下一问切换到事业领域。目标文件 ESLint 与补丁检查通过。
  • 防复发:保持“服务端叙事是用户可见回复真相源”的纯函数测试;每条待澄清证据必须测试跨轮补全与修订链;下一领域必须测试排除有效 recap 已覆盖领域;端到端断言不得只绑定泛化提示词。
  • 相关记录:BUG-020、BUG-021
  • 复发自:BUG-021
  • 修复版本:待提交(本地可测)

BUG-029 | 生时校正仍有未回答区分领域时提前结束

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正多领域追问、候选范围停滞终止策略
  • 用户现象:用户只回答两轮后,系统仍有财务或关系等可区分领域未询问,却直接保存宽候选范围并结束。
  • 触发条件:自然语言单轮抽取出多条可评分经历,使累计数量达到最低门槛;候选范围连续两轮未变化,同时技术包仍建议尚未回答的领域。
  • 根因:停滞终止只检查可评分经历数量和候选范围是否连续不变,没有检查当前技术包是否仍存在未回答的区分领域。
  • 修复:停滞结束前增加未回答建议领域门禁;仍有可区分领域时继续自然追问,全部建议领域已覆盖后才允许按范围停滞安全结束。证据数量上限和无可区分问题终止保持不变。
  • 验证:编排器回归锁定学业、搬迁、事业三轮后必须继续询问尚未覆盖的关系领域,补充关系事件后才保存未确认范围;本地真实流程修复前复现为两轮即结束且范围仍为 04:3006:30
  • 防复发:停滞终止测试必须同时断言候选范围未变化和技术包建议领域已全部回答,不能只按事件条数结束。
  • 相关记录:BUG-019、BUG-028(编号存疑:BUG-019 疑为重复编号合并前的旧编号,可能应指 BUG-021)
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-030 | 职位和管理职责变化未识别为事业证据

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时校正自然语言证据分类、事业领域覆盖判断、下一轮追问
  • 用户现象:用户已经说明“开始承担管理职责”或“职位发生明显变化”,系统记录事件后仍继续要求事业经历。
  • 触发条件:事业变化使用“职位”“任职”或“管理职责”描述,但没有出现“工作”“升职”“职业”等原有关键词。
  • 根因:事业领域分类词表缺少常见的岗位和职责变化表达,导致可评分事件被归入 other,无法计入事业领域覆盖。
  • 修复:将“职位”“任职”“管理职责”加入事业领域分类规则,保留既有日期、评分和持久化契约。
  • 验证:抽取器回归覆盖三种带年月的岗位与职责变化表达,均分类为 career、保留月份并可参与评分;本地真实流程已复现修复前的漏判行为。
  • 防复发:事业领域分类测试必须覆盖入离职、晋升以及不含“工作/职业”字样的职责变化表达。
  • 相关记录:BUG-028、BUG-029
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-031 | 生时纠正未收敛仍永久扣费且事件事实未进入评分契约

  • 状态:resolved
  • 首次发现:2026-07-22
  • 最近更新:2026-07-22
  • 影响面:生时纠正计费终态、事件评分输入、候选计算指纹、用户结果说明
  • 用户现象:用户完成多领域问答后只得到宽候选范围,当前排盘时间没有更新,但已永久扣除点数;同时用户描述的具体经历虽然出现在对话记录中,传给分钟候选评分器时只剩领域和日期。
  • 触发条件:公开分钟盲测门禁尚未通过,流程按范围结束或用户主动放弃;以及可评分经历带有具体事件摘要时进入生产评分路径。
  • 根因:启动结算在候选计算完成后立即把预留点数标记为 charged,范围终态和放弃终态没有对应退款;前端评分载荷与 Python 输入规范只序列化 id/domain/date/precision,丢弃 eventSummary,导致不同事实可能共享同一规范输入。
  • 修复:新增向前迁移,在范围完成或主动放弃的同一数据库事务中将已收费状态幂等转换为 released、退回点数并更新动作回执;只有通过分钟门禁且用户明确确认的分钟保留收费。评分载荷新增可选 summary,Python API 规范化并限制长度,候选输入契约升级为 rectification-candidate-input-v2,把具体事件摘要纳入 canonical hash。用户终态文案明确说明未纠正成功、本次不计费、候选代表时间不会替换当前排盘时间。
  • 验证:前端生时纠正路由、编排器、存储和旅程引擎聚焦测试 77 条通过;Python 对话数据库契约、事件 API 与候选评分测试 67 条通过。回归明确断言事件摘要变化会改变 canonical input hash,但 can_applyconfirmation_allowedcan_narrow_to_minute 仍保持关闭;SQL 契约断言范围结束与放弃均在终态事务内退款,且迁移不写入 active_birth_time
  • 防复发:任何新增事件评分字段都必须进入前端载荷、Python 规范化输入和 canonical hash;任何非 confirmed 终态都必须有幂等计费回收断言;不得用候选范围中点或代表时间替换用户的当前排盘分钟。真正分钟确认仍依赖独立来源审计、sealed blind replay、准确率/误确认率及 ±1/2/5 分钟稳定性门禁。
  • 相关记录:BUG-019、BUG-028、BUG-029(编号存疑:BUG-019 疑为重复编号合并前的旧编号,可能应指 BUG-021)
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-032 | 生时校正覆盖历史回答且完成后无法返回原问题

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正逐轮问答、Agent 可见叙事、完成态原问题交接
  • 用户现象:多轮回答后,用户经历全部连续显示在右侧,页面只保留最新一条 Agent 回复;Agent 收到具体事项后仍重复模板式说明;完成后点击“返回原问题”没有可见跳转。
  • 触发条件:在同一个生时校正 case 中连续提交两轮以上经历;或从普通咨询问题进入校正并走到完成态。
  • 根因:聊天区直接遍历 evidenceRecap 生成全部用户气泡,却只渲染当前 turn.narrative,因此旧 Agent 回复天然被覆盖;可见叙事层和编排器在 Agent 生成回答后又用固定进度文案覆盖,且 Agent prompt 没有收到最新具体事件;完成态页面会提前自动领取 continuation,handoff 缺失时又错误地把当前校正 session 当作来源 session。
  • 修复:controller 为当前 case 维护交错的 assistant/user 消息序列,成功提交后原子追加用户原话与新 Agent 回复,聊天区只按该序列渲染;可见层优先采用 Agent 原始 narrative,中间轮 prompt 带入最新事件并要求先具体回应再追问,编排器保留通过校验的 Agent 文本;删除完成态自动 continuation,只在用户点击时领取,并按本地 handoff、持久化 return session、最近普通咨询 session 的顺序寻找真实返回目标。
  • 验证:聚焦 controller、Agent narrative、visible narrative、orchestrator 和首页 handoff 测试 89 条通过,覆盖 assistant → user → assistant 历史、具体事件进入 prompt、旧模板不覆盖 Agent 回答、仅点击后返回来源 consultation session。组件 SSR 测试在当前 Node 环境仍被仓库既有 GSAP ESM registerPlugin 加载错误阻断,尚未进入业务断言。
  • 防复发:主聊天区不得从 evidence recap 反推当前会话消息;任何 Agent narrative 后处理不得抹掉已校验的具体回应;完成态 continuation 不得自动领取,也不得以当前 rectification session 作为来源会话兜底。若需要刷新后永久恢复每轮 Agent 原文,必须扩展服务端 turn/RPC 持久化契约,不能用当前 narrative 冒充完整历史。
  • 相关记录:BUG-018、BUG-019、BUG-028(编号存疑:BUG-018、BUG-019 疑为重复编号合并前的旧编号,可能应指 BUG-020、BUG-021
  • 复发自:BUG-018、BUG-028(编号存疑:BUG-018 可能应指 BUG-020
  • 修复版本:待提交(本地可测)

BUG-033 | 生时校正缺少连续事件语义导致模板式跳问

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正中间轮 Agent prompt、事件纠错与追问顺序、聊天区可见回答
  • 用户现象:用户已经补充某件经历的原因、主动或被动、结果或后续转折,Agent 没有继续处理当前事件,反而重复要求泛化领域事件;通过校验的自然回答后还会追加“累计条数”“下一领域”“本轮区分重点”等模板。
  • 触发条件:同一事件需要两轮以上补齐,历史事件与最新表述存在日期矛盾,或某领域已有证据但当前事件仍缺关键细节。
  • 根因:narrative context 只携带本轮抽取结果,没有完整事件账本、原始表述、纠错状态和未决事实;编排器又无条件把确定性进度模板拼到 Agent narrative 后面,因此模型既无法发现跨轮矛盾,也无法决定应先补完当前事件还是切换领域。
  • 修复:中间轮 narrative context 新增最新用户原话、最近事件账本、有效/失效纠错状态和未决证据;prompt 明确要求先完成当前事件、核对日期冲突、合并同一事件的原因与结果且不得重复计分,每轮只问一个信息量最高的问题;通过 grounding 校验的 Agent narrative 直接作为可见回答,仅在模型 fallback 时保留确定性进度文案。
  • 验证:聚焦测试覆盖完整事件账本进入 prompt、历史日期与最新原话同时可见、连续事件规则进入 output contract、有效 Agent 回答不再追加进度模板,以及 fallback 仍保持安全的一问式文案。测试数据全部为虚构案例,不写入真实用户经历。
  • 防复发:不得把“领域是否出现过”当作切换话题的唯一条件;任何新增对话规划信息必须先进入受限 narrative context,并保持 minute_holdout_not_readyconfirmation_allowed: falsecan_narrow_to_minute: false 等分钟安全门禁不变。
  • 相关记录:BUG-028、BUG-029、BUG-032
  • 复发自:BUG-028、BUG-032
  • 修复版本:待提交(本地可测)

BUG-034 | 生时校正 Agent 降级只记录 200 导致模板回退不可诊断

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正中间轮自然语言回答、服务端可观测性、网页端响应时延
  • 用户现象:用户提交明确事件后等待约 53 秒,网页仍显示“当前累计”“下一步”“本轮区分重点”等固定进度模板;接口日志只有成功的 200,无法看出 Agent 已经连续两次生成或校验失败。
  • 触发条件:叙事模型调用抛错、输出不是契约 JSON,或输出未通过 packet grounding 校验并在第二次重试后降级。
  • 根因:generateRectificationNarrative 会把所有失败收敛为安全 fallback,但此前仅把失败类型写入持久化 validation receipt,没有输出脱敏运行日志;补充日志后真实网页请求确认了两类误杀:其一,校验器机械要求中文必须逐字包含“已经发生/过去、年、月”,导致语义正确的“后来在什么时候”“年份和月份”等自然问法被丢弃;其二,只要一段自然叙事同时提到多个既有年份并出现“还是”,就会被误判为禁止的宽年份选项问卷,即使“还是”实际在询问毕业方式或职业转折类型。
  • 修复:fallback 边界保留结构化脱敏日志;日期请求改为识别“发生、后来、开始、毕业、入职、离职”等历史语义和“什么时候、年份、月份”等日期语义,缺少硬边界时只修补内部请求,不再用固定模板覆盖 Agent 正文;宽年份问卷仅拦截明确的年份二选一、A/B 年份选项或年份区间选择,不再因正文包含多个已知事件年份而误杀;当 Agent 的确认段只有说明、没有面向用户的具体问题时,将内部 evidenceRequest.prompt 投影到聊天气泡,且避免重复追加制度化提示。
  • 验证:聚焦 narrative、orchestrator、route 测试 76/76 通过;新增“多个已知年份 + 非年份还是问句”与“说明需要更多信息但没有直接问题”的回归覆盖;真实网页账号完整保留历史气泡,2017 年入职和 2020 年主动离职均得到针对事件内容的自然确认,后者明确追问“离职后下一份工作或职业转向在何年何月开始”,没有再次落入“当前累计/下一步/本轮区分重点”模板。测试前先释放未确认校正费用并清理校正数据,点数从 207 恢复到 208;新测试正常收取 1 点后为 207。
  • 防复发:叙事 fallback 必须同时保留安全用户文案、持久化回执和不含个人数据的运行时诊断;HTTP 200 不得作为自然语言 Agent 正常工作的唯一证据;禁止年份选项问卷的规则必须判断年份之间的选择结构,不能使用全文“出现多个年份 + 任意还是”作为替代。
  • 相关记录:BUG-032、BUG-033
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-035 | 生时校正发送错误内容后无法撤回修改

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正文字输入、事件证据提交与对话历史
  • 用户现象:用户发现刚发送的经历写错后只能等待 Agent 完成本轮,再通过下一轮更正;输入框在生成期间被锁定,错误内容可能直接进入事件账本。
  • 触发条件:在生时校正中提交自由文本回答后立即发现日期或事实写错。
  • 根因:校正回答会立即调用持久化命令,界面只有发送和等待状态,没有普通 session 已有的发送撤回窗口;仅中止浏览器请求也不能保证服务端停止保存。
  • 修复:复用普通 session 的安全语义,在真正发起校正命令前提供 2.5 秒撤回窗口;发送后立即显示用户气泡和停止按钮,撤回时取消定时任务、移除临时气泡、把原文恢复到输入框并重新聚焦。只有窗口结束后才调用校正接口,因此成功撤回的本轮不会生成、不会写入证据历史,也不计入校正任务。
  • 验证:新增真实 Chromium 回归断言,覆盖发送、停止、草稿恢复和延时后未调用 answer;本地登录账号手测中,唯一测试文本撤回后仍保留在输入框,等待超过窗口后未进入“正在核对”、未出现在用户历史消息中。目标文件 ESLint 与 git diff --check 通过;独立 Node 组件套件当前仍被既有 GSAP Node 导入错误阻断。
  • 防复发:撤回必须发生在业务命令发出前;不得把客户端 fetch.abort() 误认为服务端事务已取消。
  • 相关记录:BUG-018、BUG-032(编号存疑:BUG-018 疑为重复编号合并前的旧编号,可能应指 BUG-020)
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-036 | Agent 化生时校正上线前被旧模板组件断言阻断

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生产发布门禁、生时校正前端组件测试
  • 用户现象:最新 main 已保留 Agent 自然语言回复与历史气泡,但生产测试门禁失败,无法进入部署。
  • 触发条件:运行生产 Jyotish Skill Tests 的完整前端测试套件。
  • 根因:组件测试仍断言旧版确定性模板会覆盖 Agent 叙事、经历摘要带固定“已记录”前缀,并直接调用无撤回窗口的提交表达式;真实 Chromium 等待条件也仍匹配旧文案。
  • 修复:更新 5 条过期断言,使其验证 Agent 原始叙事、折叠进度中的经历摘要、2.5 秒撤回后提交路径和当前移动端文案;不回退现有业务实现。
  • 验证:生时校正组件测试、完整前端测试、生产质量门禁与生产部署工作流;以对应提交 SHA 的线上健康检查为最终验收。
  • 防复发:对话呈现测试应验证用户可见契约,不再把旧模板句式或内部调用参数写成不必要的固定实现约束。
  • 相关记录:BUG-034、BUG-035
  • 复发自:无
  • 修复版本:待提交

BUG-235 | 连续事件补充回答丢失状态并重新进入日期模板

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正多轮问答、事件证据账本、Agent 自然追问
  • 用户现象:用户先提供“2017年5月参加工作”,Agent 继续确认“正式工作还是实习/兼职”;用户回答“正式工作”后,系统却把这句话当成新事件,重新追问“具体是什么年月/只记得年份也可以”,既重复问题又丢失上一轮的年月。
  • 触发条件:Agent 的追问属于已有事件的日期补充或细节补充,但公开 turn 只保存问题文本,没有保存追问目标事件和问题类型。
  • 根因:编排器仅根据当前消息是否含日期决定分支;上一轮没有持久化 event_dateevent_detailnew_event 状态和目标 evidenceId,导致无日期的补充回答落入固定非评分模板;后续虽让 narrative Agent 输出了目标状态,进度装饰层仍无条件重建 evidence request 并覆盖为 new_event;细节合并还会被事件分隔符错误拆成多个证据。
  • 修复:为 evidence request 增加向后兼容的 follow-up 状态机,记录问题类型和目标事件 ID;narrative Agent 被要求在追问已有事实时输出对应状态;进度装饰只维护确定性的下一领域,不再覆盖 Agent 可见问题对应的 follow-up 目标;服务端按状态将日期或细节 append-only 合并到目标事件并保留 correctsEvidenceIds,只有 new_event 才创建独立事件;细节合并改为单事件解析后覆盖组合摘要,避免标点导致错误拆分。
  • 验证:叙事、编排与存储聚焦测试 81/81 通过;真实两轮编排回归覆盖 Agent 先追问已有事件细节并持久化目标,然后“2017-05 参加工作”后回答“正式工作”仍保留 2017-05、形成“参加工作;正式工作”并不再出现日期模板;旧 turn 缺少 follow-up 时仍按兼容路径处理。目标文件 ESLint、相关 TypeScript 检查和 git diff --check 通过。
  • 防复发:每个中间轮必须断言追问类型、目标 evidence ID 和下一轮的事件合并结果;模型回退时默认显式 new_event,不得把已有事件细节当成新事实;跨轮测试必须断言用户可见回复不重复已回答的日期问题。
  • 相关记录:BUG-028、BUG-033、BUG-034
  • 复发自:BUG-033
  • 修复版本:待提交(本地可测)

BUG-037 | 生时校正刷新后把真实对话重建成“已记录”模板

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正历史持久化、刷新/重新进入恢复、Agent 一问一答呈现
  • 用户现象:同一轮实时回答时 Agent 会针对经历自然追问,但刷新页面或重新进入生时校正后,旧回复全部变成“已记录这段经历:……”;用户原话也被日期和事件摘要替代,看起来像 Agent 又退回固定模板。
  • 触发条件:已有两轮以上生时校正回答后刷新网页、从首页重新进入,或在当前页面同步一个更新的持久化案例。
  • 根因:数据库已保存每轮 Agent narrative,但恢复 RPC 只返回 latest_turn;前端为了补齐历史,使用 evidenceRecap 机械合成用户气泡和“已记录”助手气泡。实时链路使用内存中的原话与真实 narrative,恢复链路却使用另一套有损数据源,导致刷新前后表现不一致。
  • 修复:为校正 turn 向前新增受约束的 nullable user_message,answer 保存事务同时持久化用户原话;新增仅限 service_role 的 history load/save/completion wrapper RPC,按轮次返回最近 200 轮 userMessage + narrative;客户端响应契约支持恢复历史,首次加载和同案例重新同步均直接渲染原始一问一答。旧记录只在有明确原始 evidence 时回填用户文本;无法恢复的旧轮次只显示真实最新 Agent narrative,不再伪造模板。
  • 验证:控制器回归覆盖首次恢复、旧数据 fallback、同案例重新同步和“不得出现已记录这段经历”;聚焦校正测试 90/90 通过;本地 PostgreSQL 从头应用全部迁移成功,并验证新列与 history RPC 的 service_role/authenticated 权限边界。测试内容均为虚构经历,不写入真实用户资料。
  • 防复发:任何对话历史必须从持久化的原始 user/assistant turn 恢复;事件摘要只能用于进度和评分,不得反向伪造聊天内容。网页验收必须同时检查实时回答和刷新后的同一历史。
  • 相关记录:BUG-032、BUG-034、BUG-036
  • 复发自:BUG-032
  • 修复版本:待提交

BUG-236 | 首次进入生时校正长期停留在建立记录

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:首次创建生时校正 case、首条 Agent 引导消息、进入校正页面的等待时间
  • 用户现象:点击生时校正后已经进入统一加载动画,但长时间停留在“正在建立校正记录…”。
  • 根因:建档事务在写入首轮和完成扣点前同步等待 Mastra Agent 的结构化输出;模型 SDK 自带重试,叙事校验层也允许重试一次,慢请求和双重重试会把整段时间全部暴露给用户。实测本地星盘扫描约 0.08 秒,不是主要瓶颈。
  • 修复:首轮叙事的两次校验尝试共享 10 秒中止信号,并关闭 Mastra SDK 内部重试;10 秒内生成成功仍使用 Agent 自然回答,超时或两次校验失败则使用现有的安全、可评分 fallback 完成建档。中间轮和最终总结不受该首轮时限影响。
  • 验证:新增回归断言,确保首轮两次叙事尝试共享同一个 AbortSignal;目标 narrative 测试、ESLint、TypeScript 与生产构建通过后记录最终结果。
  • 防复发:首轮建档不得无限等待模型,也不得同时开启 SDK 重试和业务校验重试;耗时预算必须覆盖整个首轮生成,而不是每次尝试单独重新计时。
  • 相关记录:BUG-034
  • 修复版本:待提交(本地可测)

BUG-038 | 老案例摘要被误标成原始对话且第七条事件后无法继续

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生产生时校正老案例恢复、累计 7–8 条可评分事件后的继续问答
  • 用户现象:部署历史恢复修复后,老案例刷新仍显示“已记录这段经历”;继续回答一条明确事件后,页面提示暂时无法继续并把文本退回草稿。
  • 触发条件:案例来自原始消息持久化上线之前,且旧事件 evidence 能提供规范化 raw_text;或累计可评分事件超过 6 条。
  • 根因:首版迁移把旧 evidence 的规范化摘要回填到 user_message,却没有标记它不是逐字捕获的聊天原文,恢复 RPC 因而把合成摘要当成真实历史;产品收敛上限允许 8 条事件,但 Python 事件评分 API 仍只接受最多 6 条,前端第 7 条后稳定收到 400。
  • 修复:为 turn 增加 user_message_captured 来源标记,只有 answer 事务当场保存的逐字用户文本才进入可见历史;旧案例没有可靠原文时只显示真实最新 Agent narrative,不再展示伪造气泡。事件评分 API 上限与产品常量统一为 8,并增加八事件回归。
  • 验证:迁移从零应用并检查来源标记与 RPC 权限;八事件 API 测试、聚焦对话测试、生产构建与真实生产浏览器继续问答/刷新/重新进入 smoke。
  • 防复发:消息历史必须携带来源可信度,事件 evidence 不得默认等价于聊天原文;跨服务的事件数量上限必须由同一契约测试锁定。
  • 相关记录:BUG-034、BUG-037
  • 复发自:BUG-037
  • 修复版本:0850619eaf5002736542463394720b0ab1949ce9

BUG-237 | 生时校正入口旧快照重复 start 导致 409

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正首页入口、未完成 case 恢复、重复 start 防护
  • 用户现象:用户点击进入生时校正时,接口发送新的 start,返回 409 action_conflict,页面无法进入校正会话。
  • 触发条件:前端账户快照没有包含已有未完成 case,或账户快照在出生声明变更后隐藏了旧 case,但数据库中仍存在该用户的未完成 V3 case。
  • 根因:前端只根据内存账户状态选择 start/resume;数据库会阻止同一用户创建第二个未完成 V3 case,旧快照因此被拒绝。
  • 修复:首次 start 收到 409 后刷新账户状态;如果刷新得到可恢复 case,自动使用最新 caseIdturnVersion 执行 resume,不重复扣点;如果刷新后仍没有声明匹配的 case,则保留原错误,避免未经用户确认自动放弃旧校正记录。
  • 验证:frontend/tests/consultation-entrypoint.test.ts 23/23 通过,覆盖 409、刷新账户、使用最新 case 版本恢复;目标文件 ESLint 与 git diff --check 通过。完整套件仍有既有 CSS 断言和数据库权限测试失败,与本修复无关。
  • 防复发:生时校正入口必须把 start 视为可恢复的幂等操作;收到 case 冲突时先刷新 durable 状态,再决定恢复或提示用户;不得直接再次扣费、创建第二个 case,或在声明不一致时静默删除旧 case。
  • 相关记录:BUG-034、BUG-236
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-039 | 生时校正 Agent 回答等待结束后一次性出现

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正首次进入、回答传输、Agent 生成状态、移动端对话可读性
  • 用户现象:首次进入时只显示“正在建立校正记录”,看不到后台生成的第一条 Agent 引导;用户发送经历后也只能看到“正在核对星盘信息”,待模型、校验和保存全部完成后,Agent 整段回答一次性出现,与普通 session 的逐段生成体验不一致。
  • 触发条件:任意生时校正 startresumeanswer 命令成功返回自然语言 narrative。
  • 根因:/api/birth-time-conversation 固定使用 Response.json(turn),客户端也完整读取并校验 JSON 后才更新控制器;即使 narrative 已生成,传输层和聊天渲染层都没有增量事件契约。首次入口另由首页直接执行 start/resume,没有把生成中的首条 narrative 投影给尚未初始化的校正组件。
  • 修复:成功响应在客户端声明支持时改用 application/x-ndjson;服务端只对已经完成技法校验并持久化的 narrative 分块发送 delta,最后发送完整 durable turn。客户端逐行解析并即时投影到同一个 assistant 气泡,首次 start/resume 也把增量引导传入空白校正界面;最终 turn 到达后无缝转为 settled 历史。错误响应继续使用既有安全 JSON,已输出 delta 的中断不自动重放,避免重复文字。代理技法校验、事件事务和分钟安全门禁均保持在流输出之前。
  • 验证:新增 route 分块顺序与禁用代理缓冲回归、client NDJSON 增量解析回归、controller 在 durable turn 未到达前发布流式文本回归、组件 streaming 气泡回归;聚焦校正测试、真实 Chromium 390px 组件测试、ESLint、TypeScript 与 production build 通过。
  • 防复发:生时校正成功响应不得退回一次性 JSON 作为唯一前端路径;可见增量内容必须来自已校验 narrative,不得直接透传未完成或未通过约束的模型 token。
  • 相关记录:BUG-034、BUG-037、BUG-038
  • 复发自:无
  • 修复版本:本次流式修复提交

BUG-238 | 服务层重复 start 未在扣费前复用同一声明的未完成 case

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正 start 编排、重复点击/多标签进入、首轮等待时间与扣费预留
  • 用户现象:前端收到新的 start 后仍等待模型计算,最终返回 409 action_conflict;账户刷新未必能返回可恢复 case,导致用户无法进入会话。
  • 触发条件:同一用户使用不同 actionId 重复进入生时校正,或两个入口并发发起 start。
  • 根因:编排器只按当前 actionId 查询 case;数据库在 reserve/create 阶段才执行“同一用户只能有一个未完成 V3 case”的约束,应用因此先做了不必要的计算和扣费预留。
  • 修复:在 reserve 前增加账户级未完成 case 查询;同一 declaredBirthInput 直接返回已有公开 turn,不重复计算或扣费;声明不一致继续稳定返回冲突。并发竞态在释放本次预留后重新读取账户,复用同声明的胜出 case。
  • 验证:frontend/tests/conversational-rectification-orchestrator.test.ts 32/32 通过,覆盖同声明重复 start 复用、声明不一致冲突和既有预留重试;后续需补充线上部署后的真实 smoke。
  • 防复发:start 幂等性必须同时在入口、编排器和 durable RPC 三层成立;任何新增 actionId 的 start 都必须先检查账户级未完成 case,不能把数据库冲突当成正常流程。
  • 相关记录:BUG-034、BUG-236、BUG-237
  • 复发自:BUG-237
  • 修复版本:待提交(本地可测)

BUG-040 | Agent 活动状态与回答被错误渲染为互斥状态

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:普通 session 与生时校正共用的 Agent 消息行、流式回答反馈、完成态识别
  • 用户现象:Agent 尚未产出正文时只显示 orb 活动状态;开始出现正文后状态与答案的关系不连续,完成后活动状态直接消失,用户无法从同一消息位置判断回答仍在生成还是已经结束。
  • 触发条件:任意 assistant 消息在 thinkingstreamingsettled 三种视图状态之间切换。
  • 根因:共享 ChatMessageRow 使用条件分支把 thinking 渲染为仅有 AgentActivityStatus,又只在 streaming 时显示 composing 状态;settled 分支只保留正文,因此活动状态和正文被实现成了互斥内容,而不是同一消息的连续生命周期。
  • 修复:assistant 消息始终在同一气泡内先渲染活动状态,再渲染当前已有正文;thinking/streaming 继续使用 working/composing orbsettled 只把状态文案切换为“回答已完成”,并把 orb 替换为 Lucide 完成图标。普通 session 与生时校正共享该行为,React 消息 identity 与正文内容保持不变。
  • 验证:消息视图回归覆盖 working、composing、completed 三态、正文与状态同时存在、完成态 Lucide 图标;相关普通 session 与生时校正契约测试通过,并执行真实浏览器检查。
  • 防复发:Agent 活动状态只能描述消息生命周期,不得控制正文是否渲染;完成态必须保留稳定的视觉反馈,不能以卸载整个状态区域代替状态转换。
  • 相关记录:BUG-039
  • 复发自:无
  • 修复版本:4a9b1dc

BUG-239 | 旧数据库校验器把首条新事件回答误报为 409

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正首条及后续普通新事件回答
  • 用户现象:case 与请求版本同为 0,提交首条经历仍返回 409 action_conflict
  • 根因:应用已为证据请求加入可选 followUp 状态,但当前数据库校验器仍只接受旧字段;显式的 new_event 与旧默认语义相同,却在保存边界被拒绝。
  • 修复:保存任何 turn 时都移除语义冗余的 followUp: new_event,兼容旧校验器;event_dateevent_detail 仍等待对应数据库迁移后持久化。
  • 验证:直接 RPC 探针确认旧格式为 true、带 followUpfalse;新增 store 回归测试覆盖首轮与后续 turn 的兼容投影。
  • 防复发:新增可选持久化字段时,默认语义必须能向旧数据库降级;数据库迁移未应用前不得把兼容字段直接写入 durable RPC。
  • 相关记录:BUG-235、BUG-237、BUG-238
  • 修复版本:待提交(本地可测)

BUG-041 | 生时校正生成回答时消息列表不自动跟随到底部

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正消息历史、用户发送后的撤回窗口、Agent thinking / streaming / settled 回答阶段
  • 用户现象:用户发送经历或 Agent 开始生成回答后,新内容出现在消息列表下方,但列表停留在旧位置;用户必须手动向下滚动才能看到生成状态和最新回答。
  • 触发条件:生时校正已有足够内容使独立消息容器产生纵向滚动,然后发送新经历或接收流式 Agent 回答。
  • 根因:普通 session 有独立的底部跟随 effect,生时校正虽然使用可滚动的 .rectification-message-list,但组件没有保存该容器的 ref,也没有在消息、提交状态、流式文本或最终 turn 更新时调整容器滚动位置。
  • 修复:为生时校正消息容器增加专用 ref,并在用户消息、生成状态、流式增量、错误及最终 turn 生命周期变化后滚动该容器到底部;生成过程中直接跟随,完成后平滑定位,且尊重 reduced-motion,不调用会牵动外层页面的 scrollIntoView
  • 验证:真实 Chromium 390px 回归制造独立消息容器 overflow,并分别断言初始历史、乐观用户消息、流式 Agent 增量和完成回答都保持在底部;相关聚焦测试、ESLint、TypeScript 与生产 smoke。
  • 防复发:生时校正的消息生命周期新增阶段必须进入底部跟随依赖;真实浏览器测试必须验证容器自身的 scrollTop,不能只检查正文是否出现在 DOM。
  • 相关记录:BUG-039、BUG-040
  • 复发自:无
  • 修复版本:本次自动滚动修复提交

BUG-240 | 非评分回答绕过 Agent 并显示固定澄清模板

  • 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交 3ca30ed7 中丢失,此处依据同批次记录与该提交内容补记,非原文)
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正缺少年月、未来事件、换方向和非评分更正后的自然问答
  • 用户现象:用户用自然语言补充“化学专业”“后来换了工作”等内容后,页面直接显示“你提到……具体内容我已经记下了。它大致是什么年月?”,与前后 Agent 语气断裂,也可能忽略刚才已经建立的事件上下文。
  • 根因:编排器发现本轮暂时没有可评分事件后,直接进入同步 nonScoringTurn();该函数完全没有调用 narrative Agent,而是用固定字符串生成可见回复。Mastra 和模型没有获得生成这轮回答的机会。
  • 修复:非评分分支先用当前候选技术包、完整事件账本、最新用户原话和未决证据调用现有 narrative Agent;状态机只在后台固定本轮追问属于 event_dateevent_detail 还是 new_event,不再替代 Agent 的可见措辞。模型或技术包调用失败时仍保留确定性安全文案,避免正常澄清变成 5xx。
  • 验证:编排回归 32/32 通过;相关叙事、编排和存储测试 81/81 通过。新增断言确认无年月事件会得到 Agent 针对该事件生成的单一自然问题,并持久化 event_date + evidenceId,不再出现旧固定模板;未来事件、换方向和已有评分事件的行为保持兼容。
  • 防复发:任何能继续对话的业务分支都必须优先经过 narrative Agent;状态机可以决定证据目标和可评分性,但不得直接占用正常用户回复。确定性模板只能作为模型或技术计算失败时的技术兜底。
  • 相关记录:BUG-033、BUG-034、BUG-235
  • 复发自:BUG-033
  • 修复版本:待提交(本地可测)

BUG-042 | 生时校正沿用系统滚动条导致视觉割裂

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正独立消息列表的桌面端滚动反馈
  • 用户现象:生时校正右侧直接显示浏览器或操作系统默认滚动条,与站内温和、低对比的编辑式界面不一致。
  • 触发条件:生时校正历史内容超过消息区高度,在桌面浏览器出现纵向滚动条。
  • 根因:.rectification-message-list 只声明了 overflow-y: auto,没有提供跨浏览器的产品内滚动条颜色、宽度和交互状态。
  • 修复:为生时校正消息区增加细宽、透明轨道、低对比圆角拇指;默认隐藏拇指,仅在指针移动、列表滚动或键盘焦点进入消息区时短暂显示,停止操作后自动隐藏。Firefox 使用标准 scrollbar 属性,Chromium / Safari 使用 WebKit 伪元素,颜色继续取现有设计 token。
  • 验证:CSS 契约锁定默认透明、交互显现及标准与 WebKit 两套样式,聚焦组件测试、目标 ESLint、production build 与生产浏览器 smoke。
  • 防复发:独立滚动容器必须复用产品 token,并同时覆盖标准 scrollbar 属性和 WebKit 伪元素;默认态不得持续抢占视觉注意力,也不得引入渐变、重阴影或高饱和装饰。
  • 相关记录:BUG-041
  • 复发自:无
  • 修复版本:本次简约滚动条修复提交

BUG-043 | 生时校正把后台证据状态重复渲染成可展开管理面板

  • 状态:resolved
  • 首次发现:2026-07-23
  • 最近更新:2026-07-23
  • 影响面:生时校正消息流、候选进度、历史经历与更正入口
  • 用户现象:Agent 已经在自然对话中确认和追问经历,消息区底部仍额外显示“当前候选 · 待验证”、候选范围、历史经历列表和“更正”按钮;用户需要理解并操作第二套记录界面,破坏一问一答的连续性。
  • 触发条件:任何已有候选或至少一条 evidence recap 的生时校正案例。
  • 根因:语言交互改版后仍保留旧产品流程的 <details> 进度与证据管理面板,把本应仅供后台评分和恢复使用的结构化状态再次暴露给用户。
  • 修复:从生时校正消息流移除整块候选进度与历史经历管理面板,不再显示候选状态、范围、经历列表或逐条更正按钮;后台 evidence、评分与持久化保持不变,最终可确认状态仍通过明确确认动作呈现。
  • 验证:组件与真实 Chromium 回归锁定页面不包含候选进度、历史经历面板或更正按钮,同时保留自然对话、流式回答、撤回窗口、自动贴底和最终确认能力。
  • 防复发:结构化 evidence 是 Agent 的后台推理与持久化输入,不得在语言优先界面重复渲染成需要用户管理的卡片或表单;需要纠正时继续通过自然语言表达。
  • 相关记录:BUG-020、BUG-034、BUG-042
  • 复发自:BUG-020
  • 修复版本:待提交

BUG-241 | 全球地点字段未迁移导致账户余额接口整体 500

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:GET /api/account、首页账户初始化、余额和生时校正入口
  • 用户现象:已登录用户访问本地首页时,账户接口返回 500 {"error":"暂时无法读取账户余额"},余额与账户状态均无法加载。
  • 根因:账户 GET 为全球出生地点新增了 birth_place_*timezone_* 字段查询,但当前 Supabase 尚未应用对应迁移,PostgREST 返回 42703 column profiles.birth_place_label does not exist。PATCH 已有旧表回退,GET 缺少同等兼容路径,并把资料字段错误误报成余额错误。
  • 修复:GET 检测缺列或 schema cache 错误后,回退到迁移前的 profile 字段集合;全球地点字段在响应中返回空值,余额、出生时间状态和未完成校正 case 继续正常读取。迁移应用后自动使用完整查询。
  • 验证:服务角色直接查询确认修复前错误为 42703frontend/tests/account-api.test.ts 增加旧表回退回归,另运行 TypeScript、目标 ESLint 与本地登录接口 smoke。
  • 防复发:向账户初始化查询增加非关键资料字段时,应用发布必须兼容迁移前后的数据库形状;不能让可选地点元数据阻断余额与核心账户状态。
  • 相关记录:BUG-237、BUG-238
  • 修复版本:待提交(本地可测)

BUG-242 | 全球地点迁移漏掉服务角色列权限导致账户保存 500

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:PATCH /api/account、初始化出生地点保存、全球地点资料更新
  • 用户现象:读取账户已经恢复,但提交出生资料返回 500 {"error":"暂时无法核对现有出生资料"}
  • 根因:账户 PATCH 的并发保护和缺失 profile 恢复路径通过服务角色读取、插入及更新 profiles;全球地点迁移只授予了 authenticated 更新权限,遗漏服务角色对六个新字段的列级 SELECT / INSERT / UPDATEPostgREST 返回 42501 permission denied for table profiles
  • 修复:在全球地点迁移中为服务角色补齐六个新字段的最小列级读取、插入和更新权限,不恢复表级宽权限。
  • 验证:迁移权限静态回归、生产事务 dry-run、正式授权、字段权限查询和真实登录 PATCH smoke。
  • 防复发:扩展服务端账户 upsert 字段时,必须同时审计 authenticated 自助保存和 service_role 并发读取/upsert 两条权限链。
  • 相关记录:BUG-237、BUG-241
  • 修复版本:待提交(本地可测)

BUG-243 | 选择全球地点后重复搜索且候选列表被卡片裁切

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:初始化出生地点搜索、全球地点选择与资料保存
  • 用户现象:选择“中国 · 河北省 · 邯郸市 · 峰峰矿区”后,完整标签又触发一次搜索并返回泛化的 Geoapify 结果;候选列表还会被出生地点卡片截断,后续选项看不全。
  • 根因:位置服务在没有完整出生时刻时合法返回 timezoneOffset: null,但页面只把数字 offset 视为已选择地点,因此父组件继续向 combobox 传入空值,选中标签被当成新查询;同时 onboarding 卡片使用 overflow: hidden 裁切了绝对定位的候选列表。
  • 修复:地点完整性改为接受“合法坐标 + IANA timezoneId”,数字 offset 可暂时为空;选中地点时使在途搜索序列失效并清空候选;出生时间过渡卡片允许候选列表溢出显示。
  • 验证:地点选择、出生资料完整性和全球地点目标测试覆盖 nullable offset、缺失 timezoneId、选择竞态保护与候选列表可见性。
  • 防复发:前端地点完整性必须与位置 API 的 nullable offset 合同一致;选择类异步组件必须在 commit selection 时废弃旧请求,浮层祖先不得无意裁切。
  • 相关记录:BUG-241、BUG-242
  • 修复版本:待提交(本地可测)

BUG-244 | 全球地点资料已保存但初始化接口仍判定未完成

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/onboarding、全球出生地点用户进入首页
  • 用户现象:用户已填写称呼、出生日期、时间线索和旧金山地点,保存成功后进入首页仍返回 出生资料尚未完成
  • 根因:onboarding 服务端完整性判断仍把中国 province_code + city_code 写死为必填,没有识别已经持久化的全球地点标签、经纬度和 IANA 时区。
  • 修复:服务端资料投影补齐全球地点字段;完整性判断接受“有效全球地点”或“旧中国行政区地点”,并复用出生资料共享校验。
  • 验证:新增旧金山 period_only + timezoneOffset null + America/Los_Angeles 回归用例,确认可生成首页初始问题。
  • 防复发:读取全球地点的服务端流程不得继续以中国行政区代码作为唯一地点完成条件。
  • 相关记录:BUG-241、BUG-243
  • 修复版本:待提交(本地可测)

BUG-245 | 全球地点缺少数字时区偏移导致生时校正入口 409

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation、仅填写时间段的全球出生地点用户
  • 用户现象:旧金山资料已完成并能进入首页,但点击生时校正返回 profile_incomplete
  • 根因:地点搜索在没有具体出生分钟时只保存 IANA 时区,数字历史 offset 合法为空;生时校正入口起初没有在计算前补算。补算 offset 后,Geoapify 的七位小数坐标又超过校正持久化合同的六位小数边界,仍被统一映射成 profile_incomplete
  • 修复:生时校正读取资料后复用现有本地时区服务,按出生日期和已申报时间或时间段参考时刻解析历史 offset;进入持久化合同前把经纬度规范为六位小数;旧 case 导入走同一路径。
  • 验证:旧金山 1955-02-24 + evening + America/Los_Angeles 回归确认请求历史时区服务、得到 -8、规范化 Geoapify 坐标后进入校正。
  • 防复发:资料保存可以暂缺 offset,但任何进入分钟计算的路径必须先通过 IANA 历史时区解析;外部地理编码坐标必须在进入耐久 JSON 合同前规范化,不能把 offset null 或坐标精度问题误报为资料缺失。
  • 相关记录:BUG-243、BUG-244
  • 修复版本:待提交(本地可测)

BUG-246 | 全球出生地点在兄弟接口中被误判、冲突或静默退化

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:GET /api/accountPOST /api/consultPOST /api/birth-time-journey、今日星语、合盘与遗留生时评估接口
  • 用户现象:全球出生地点已保存且可进入首页,但已有生时校正记录无法恢复并反复触发 action_conflict 409;正式咨询可能在扣点前返回资料不可用;部分旧功能仍要求中国行政区代码,合法全球资料会静默退回模板或 503。
  • 根因:全球地点迁移只修复了 onboarding 与 conversational rectification 主入口,多个兄弟读取路径仍各自维护旧合同:强制 timezone_offset 为数字、直接比较 Geoapify 七位小数坐标,或把中国省市区中心当作唯一地点真值。账户接口因此看不到数据库中实际存在的未完成 case,前端误判为需要重新创建。
  • 修复:抽取共享历史时区解析器,兼容数据库 snake_case 与浏览器 camelCase 资料,并按实际申报/确认时间调用本地 IANA 历史时区服务;账户恢复使用已存 case 的不可变 offset 作为仅用于匹配的 fallback,并把坐标统一到六位耐久精度;咨询在扣点前使用最终 active time 补算 offsetjourney 入口和 case loader 在严格解析前补算;今日星语、合盘和遗留评估优先使用真实全球经纬度与时区,中国行政区仅作兼容 fallback。
  • 验证:账户 nullable offset + 七位坐标可恢复旧金山已有 case,时区 ID 或地点 ID 不一致仍拒绝恢复;verified consultation 使用最终 active time 解析 -8 且解析失败不调用扣点;journey、今日星语、合盘和遗留评估的全球地点回归通过。两组聚焦套件共 84/84 通过,目标文件 ESLint、TypeScript --noEmitgit diff --check 通过。
  • 防复发:出生地点完成性与计算就绪性必须分层;资料层可保存 IANA timezoneId 且 offset 暂空,但所有星盘计算入口必须统一补算历史 offset。不得在新接口复制中国专用地点解析,也不得用未经规范化的外部坐标直接比较持久化声明。
  • 相关记录:BUG-243、BUG-244、BUG-245
  • 修复版本:待提交(本地可测)

BUG-044 | 数据库出生地点声明校验落后导致创建校正记录误报 409

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation、Geoapify 等全球地点用户首次创建生时校正记录
  • 用户现象:账户资料完整且不存在未完成 case,点击生时纠正仍返回 409 action_conflict;重试同一个已释放的 action 后可能转为计费失败。
  • 根因:TypeScript 持久化声明已经支持 placeIdplaceTypeprovidertimezoneIdtimezoneSource,但数据库函数 conversational_rectification_valid_declared_birth_input 仍只允许旧中国地点字段。合法全球地点声明在 create_conversational_rectification_case 内被判无效,并被 RPC 统一映射为 conversational_action_conflict
  • 修复:新增向前迁移同步数据库地点声明白名单,接受全球地点身份、IANA 时区来源及坐标字段;地点身份允许 citycityCodeplaceId 任一存在,同时保留旧中国地点兼容和数值边界校验。
  • 验证:数据库迁移单独执行成功;使用新的 actionId 对本地接口真实 smoke 返回 200 active,创建可恢复 case,首轮 narrative 非空;相同 actionId 重放仍返回同一 case,账户积分只从 100 扣至 99;无待交接内容时 handoff 按合同返回 204。相关持久化与全球地点测试 28/28 通过。
  • 防复发:任何扩展 declaredBirthInput.birthplace 的应用字段都必须同步更新数据库 JSON 校验函数并增加迁移文本回归断言;不得把声明校验失败笼统诊断为旧 case 冲突。
  • 相关记录:BUG-237、BUG-238、BUG-245、BUG-246
  • 修复版本:待提交(本地可测)

BUG-045 | 数据库追问合同落后导致第一条回答误报 409

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation、已创建校正 case 的第一条及后续自然语言回答
  • 用户现象:校正记录能够创建和恢复,但提交第一条人生事件后返回 409 action_conflictcase 的 turnVersion 保持不变,积分没有再次扣除。
  • 根因:应用的 evidence request 已支持带目标事件的 followUp: { kind, evidenceId },但共享 Supabase 尚未执行仓库已有迁移 20260723010000_align_conversational_follow_up_request.sql。旧版 conversational_rectification_valid_evidence_request 拒绝该合法字段,保存 RPC 将校验失败统一映射成 conversational_action_conflict
  • 修复:在 Supabase 项目 vtvnfqmonbfuxmqkqdlc 单独执行已有向前迁移,使数据库 evidence request 校验与当前 TypeScript 持久化合同一致;未执行其他迁移,也未重新部署应用。
  • 验证:对原 case 使用新 actionId 重试第一条回答返回 HTTP 200,turnVersion 从 0 递增到 1narrative 和目标 event_detail follow-up 正常返回;用同一 actionId 重放返回相同 turn,账户积分保持 99。服务端日志两次均为 actionKind=answerresultCategory=successbillingState=unchanged;持久化与迁移聚焦测试 27/27 通过。
  • 防复发:应用扩展 durable JSON 合同时,数据库迁移必须进入环境迁移账本并在发布验收中执行第一条真实写入 smoke;不能只用迁移文件存在或静态测试通过代替远端数据库合同验证。
  • 相关记录:BUG-237、BUG-238、BUG-044
  • 修复版本:数据库向前迁移已应用;应用代码待提交(本地可测)

BUG-046 | 生时校正刷新丢失首条引导并堆叠历史用户气泡

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正会话首次进入、连续问答及刷新后的历史恢复
  • 用户现象:首次进入时 Agent 的引导消息刷新后消失;已录入的人生事件被集中恢复成多条连续的右侧用户气泡,最后只剩一条 Agent 回复,破坏真实的一问一答顺序。
  • 根因:新会话没有把完整问答 transcript 持久化到 session;恢复接口只返回最新 turn,前端只能把最新 turn 中累计的 evidenceRecap 误当成逐轮聊天历史。数据库实际保存了每轮 birth_time_rectification_turns.narrative,用户原文也可通过 birth_time_rectification_event_evidence.source_turn_idraw_text 恢复,但旧恢复链路没有读取这些数据。
  • 修复:新会话从首条 Agent narrative 开始持久化完整消息序列,每次回答后保存真实的 Agent → 用户 → Agent transcriptresume 接口按 turn version 读取 narrative,并用 evidence 的 source turn 关联用户原文,返回耐久的交替历史;页面优先用数据库历史修复旧 session。无法可靠恢复用户原文的残缺旧 turn 不再伪造模板回复,相邻 Agent 状态只保留最新一条;最末级 legacy fallback 也只恢复最近一组可靠消息,不再展开全部累计 evidence。
  • 验证:client、controller、route 聚焦测试 52/52 通过;component 非 Chromium 测试 8/8 通过;TypeScript --noEmit、目标文件 ESLint 与 git diff --check 通过。按用户要求未运行 Chrome/Playwright。
  • 防复发:聊天历史必须以逐轮 durable transcript 或可验证的 turn/evidence 关联为真源;累计业务证据只能用于评分与摘要,不能被前端推断成消息列表,也不能为缺失历史生成看似真实的 Agent 文案。
  • 相关记录:BUG-024、BUG-029、BUG-032、BUG-045(编号存疑:无法判定这三个编号指向 BUG-024、BUG-029、BUG-032 本身,还是旧编号体系下实为 BUG-028、BUG-033、BUG-235
  • 修复版本:待提交(本地可测)

BUG-047 | 相对日期缺少上下文解析并触发模板式重复追问

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正连续追问、自然语言事件提取与澄清合并
  • 用户现象:Agent 已知用户在 1972年12月正式退学,继续追问彻底离校时间后,用户回答“来年1月份我彻底离开的学校”,系统仍重复询问具体年份和月份。
  • 根因:事件提取器把“来年、次年”等相对日期视为无日期文本,但不读取正在讨论的事件年份;澄清合并又只接受不含描述的纯日期回答;非评分分支随后用固定澄清模板覆盖模型已经生成的上下文回答。
  • 修复:对 event_dateevent_detail 追问使用目标事件或最近明确事件作为年份锚点,将“来年、次年、第二年、翌年”和“同年、当年、那年”的月份上下文化后再提取,同时保留用户原始文本;允许带自然描述的日期补充完成原事件,但仅在纯日期、同领域或低信息细节时合并,避免把“搬家”等明确新事件吞入上一条澄清;普通澄清优先显示 narrative Agent 的自然回答,确定性模板仅保留为模型失败兜底。
  • 验证:事件提取与编排聚焦测试 59/59 通过,覆盖“2022年12月正式退学 → 来年1月彻底离校”解析为 2023-01、带描述日期补充、相对日期无上下文时不臆造年份,以及不同领域新事件不误合并。目标 TypeScript、ESLint 与补丁检查通过;按用户要求未运行 Chrome/Playwright。
  • 防复发:事件状态机只负责持久化目标、证据质量与评分边界,不得替代 Agent 的可见对话;相对日期必须在明确的当前事件上下文内解析,缺少可靠锚点时保持待澄清;澄清合并必须同时判断日期、摘要和领域连续性。
  • 相关记录:BUG-033、BUG-235、BUG-240
  • 修复版本:待提交(本地可测)

BUG-048 | 生时校正消息更新后没有自动滚动到最新内容

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正聊天区、用户发送、Agent 思考态及回答更新
  • 用户现象:用户发送经历后,Agent 开始思考并生成回答,但消息列表停留在原位置,需要手动向下滚动才能看到最新内容。
  • 根因:普通 session 在消息列表末尾设置了滚动锚点和 scrollIntoView effect;生时校正使用独立的 rectification-message-list 滚动容器,却没有对应的末尾锚点和消息更新监听。
  • 修复:在生时校正消息列表末尾增加滚动锚点,并在消息数量、最新回答文本、思考状态、提交状态、turn version 和错误状态变化时滚动到末尾;生成中即时跟随,完成后平滑滚动,同时尊重 prefers-reduced-motion
  • 验证:组件目标回归测试通过;TypeScript --noEmit、目标 ESLint 与 git diff --check 通过。按用户要求未运行 Chrome/Playwright。
  • 防复发:新增独立聊天滚动容器时必须同时提供末尾锚点,并覆盖乐观用户消息、生成状态和回答文本更新三类触发源。
  • 相关记录:BUG-046
  • 修复版本:待提交(本地可测)

BUG-049 | 生时校正 Agent 提问悬空截断且回答缺少消息操作

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正自然语言回答、Agent 消息操作、当前轮次重跑
  • 用户现象:Agent 已经理解并复述了用户的教育经历,但回答最后停在“我需要确认一个关键信息——”,没有显示真正的问题;每条 Agent 回答下方也缺少赞、踩、复制和重跑操作。
  • 根因:Mastra 返回的结构化 JSON 可以通过校验,但其中 narrative 自身可能以破折号、冒号或“关键信息”等悬空引导语结束;旧逻辑只要在正文中看见“确认”等疑问词,就误判为已经包含可见问题,因此没有把同一结构化输出中的完整 evidenceRequest.prompt 补到正文末尾。
  • 修复:增加悬空提问结尾识别;中间轮次若正文没有完整问题或停在悬空引导语,自动追加模型结构化输出中的具体问题。Agent 回答下方增加赞、踩、复制和重跑图标;赞踩互斥并保留在当前页面,复制写入剪贴板,只有最新回答可以重跑。
  • 重跑边界:新增独立、幂等的 regenerate 命令,使用上一轮已经持久化的用户原文和事件台账重新生成当前 Agent narrative;不重新提取事件、不追加用户消息、不重复评分、不改变候选范围、不扣点,并在当前消息列表和刷新后的耐久历史中替换原 Agent 回答。
  • 验证:TypeScript --noEmit 通过;叙事、Controller、client、route、orchestrator 聚焦测试 119/119 通过,覆盖悬空提问补全、无 payload 重跑、消息原位替换、证据与候选保持不变;组件静态/SSR 检查 9 项通过;目标 ESLint 与 git diff --check 通过。测试过滤器未按预期排除文件内的真实 Chromium 用例,该用例误启动后以 SIGTRAP 退出,未作为本次 UI 验收依据,且不再重试。
  • 防复发:结构化回答校验必须区分“出现疑问相关词”与“存在完整可回答的问题”;任何重新生成操作都必须与 evidence mutation、评分和计费分离,并通过相同 action receipt 保持幂等。
  • 相关记录:BUG-033、BUG-046、BUG-047、BUG-048
  • 修复版本:待提交(本地可测)

BUG-050 | 生时校正重跑期间重复显示旧回答和新思考气泡

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正 Agent 回答重跑交互
  • 用户现象:点击重跑后旧回答仍保留,列表底部额外出现一个思考消息,生成完成后旧回答才被替换。
  • 根因:重跑只复用了通用 pending 状态;消息列表继续渲染旧回答,同时通用 pending 分支又在列表末尾追加思考气泡。
  • 修复:组件记录当前重跑消息,立即在原消息位置显示思考态并隐藏操作栏;重跑期间不渲染通用末尾思考气泡,成功后原位显示新回答,失败后恢复旧回答。
  • 验证:组件静态回归、目标 ESLint、TypeScript 与补丁检查通过;按既定限制未运行 Chrome/Playwright。
  • 防复发:原位更新类操作必须把进行中状态绑定到目标消息,不得同时复用追加新消息的通用 pending UI。
  • 相关记录:BUG-048、BUG-049
  • 修复版本:待提交(本地可测)

BUG-051 | 既有事件的原因补充被误判为无日期新事件

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正自然语言事件补充、事件台账合并、Agent 可见回答
  • 用户现象:Agent 已围绕一条带年月的事件追问原因,用户回答原因后,系统仍固定回复“还差时间定位”,再次索要年份和月份。
  • 触发条件:模型可见问题是在追问既有事件的原因或具体表现,但结构化 followUp 偶尔错标为 new_event;用户随后用不带日期的自然语言回答。
  • 根因:澄清合并逻辑完全信任结构化 followUp,遇到 new_event 立即退出;原因回答因此被提取成新的无日期事件,并触发确定性的日期澄清模板覆盖正常对话。
  • 修复:当上一轮可见问题明确包含原因、表现或“哪些方面”等细节追问语义时,即使 metadata 错标为 new_event,也把回答合并回最近一条带日期的有效事件;同时移除“这件事很有用,但还差时间定位”的固定话术,保留不假定上下文的简短兜底。
  • 验证:orchestrator 回归覆盖“1972年12月退学 → 追问压力原因但 metadata 错标 → 经济负担导致无法继续”,断言沿用原日期、合并为同一有效事件且不再出现固定索时模板;目标 ESLint、TypeScript 与补丁检查。
  • 防复发:事件追问的可见语义必须能够兜底结构化 metadata 的偶发错标;已有日期事件的原因、性质和具体表现回答不得强制再次提供日期。
  • 相关记录:BUG-033、BUG-047
  • 复发自:BUG-047
  • 修复版本:待提交(本地可测)

BUG-052 | 确定性流程模板覆盖生时校正 Agent 的自然回答

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正首轮引导、中间事件追问、方向切换、未来事件、暂停、放弃与确认消息
  • 用户现象:模型已经结合上下文生成回答后,网页仍显示“已记录”“当前累计”“下一步”“还差时间定位”等重复话术;部分操作还会新增程序模板气泡,使对话像问卷而不是连续的一问一答。
  • 根因:编排层把事件提取结果、followUp metadata 和收敛状态当成可见文案的唯一真源,在模型生成之后又根据分支拼接或替换 narrative。原本用于事实安全和失败兜底的确定性逻辑因此越界成了对话作者,并且 metadata 的偶发误判会直接中断正常语义交流。
  • 修复:可见文案统一以 Agent narrative 为准;删除首轮候选前后缀、事件保存与计数进度、领域推荐、方向切换、无日期、未来事件、暂停、放弃和确认成功等确定性模板。事件提取、未来事件不计分、去重、更正、候选计算和确认时间原子落库继续作为不可见业务状态运行,提取失败或 metadata 标错不得覆盖模型回答。增加脱敏诊断日志,仅记录阶段、校验问题代码、重试和 fallback 类别,不记录用户原文或完整模型输出。
  • 表格边界:稳定层/敏感层、技法审计和事件证据三类表可在中间轮按上下文需要出现;最终确认前必须提供完整汇总。表中候选、范围、分盘、技法和引用只能来自 technical packet,不得编造、泄露私有分数或声称运行了未执行的技法。
  • 验证:叙事与编排单元测试覆盖 Agent 原文保留、结构化 metadata 不覆盖自然回答、未来事件后台排除、原因补充合并、暂停/放弃/确认无新增模板气泡,以及最终确认仍更新正式出生时间;合成 E2E 覆盖首轮、换方向、无日期和未来事件均保留模型可见回答。目标 ESLint、TypeScript、测试与补丁检查结果见本轮验证记录。
  • 防复发:状态机只能决定允许的动作和持久化状态,不能编写用户可见话术;新增任何确定性文本前必须证明它属于安全错误而非正常对话,并增加“模型输出未被覆盖”的回归断言。
  • 相关记录:BUG-046、BUG-047、BUG-049、BUG-051
  • 修复版本:待提交(本地可测)

BUG-053 | 首轮结构化结果为空时错误展示确定性引导模板

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正首轮 Agent 引导、Mastra 结构化输出兼容、首轮等待时限
  • 用户现象:新建生时校正后固定显示“当前仍在核对……先说一件已经发生的学业事件”,看起来像 Agent 回答,实际没有使用模型生成的自然文案。
  • 触发条件:DeepSeek 已完成一次 Mastra 调用,但 result.object 未形成结构化对象;提供商返回的文本结果未被继续解析,两次尝试均被记录为 schema 失败。
  • 根因:生产叙事适配器只解析 result.object,没有兼容 Mastra/模型组合返回 JSON 文本的情况;首轮失败后又调用 fallbackNarrative(),把程序模板伪装成了 Agent 消息。脱敏 receipt 为 fallbackUsed=trueschemaValidated=falseissues=["root:invalid_type"],不是超时错误。
  • 修复:优先使用通过 schema 的 result.object,为空时把非空 result.text 交回既有 JSON 提取和 packet 校验流程;首轮连续两次仍失败时返回可重试服务错误并释放预留计费,不再持久化或展示确定性首轮模板;中间轮既有安全 fallback 暂不改变。首轮等待策略的后续修复见 BUG-054。
  • 验证:叙事测试覆盖“两次不合格时拒绝而非展示模板”;路由测试覆盖 result.object 为空但 result.text 有 JSON 的兼容路径;目标叙事、编排和路由测试通过。
  • 防复发:模型适配层必须兼容结构化对象与 JSON 文本两种合法承载方式;正常首轮不得用程序模板冒充 Agent 成功回答,timeout、schema 和 grounding 失败必须在脱敏日志中分别可辨认。
  • 相关记录:BUG-046、BUG-052
  • 修复版本:待提交(本地可测)

BUG-054 | 首轮两次生成共用超时信号导致重试立即失败

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation 首轮生成与重新生成
  • 用户现象:移除首轮固定模板后,重新生成等待约 30 秒返回可重试的 service_unavailable 503
  • 触发条件:首轮模型生成超过共享的 30 秒 deadline;第二次尝试继承已经取消的 AbortSignal,无法获得独立生成时间。
  • 根因:两次尝试在循环外创建并共用一个超时信号;诊断日志又只保留最后一次异常,把首次超时误记为 schema_invalid
  • 修复:每次首轮尝试创建独立的 45 秒超时信号,累计两次尝试的脱敏问题代码,并将 TimeoutError / AbortError 明确归类为 timeout
  • 验证:本地开发日志复现请求在 30331ms 失败;回归测试锁定两次尝试使用不同信号并将超时记录为 timeout,目标测试、ESLint、TypeScript 与补丁检查通过。
  • 防复发:重试不得复用已取消的信号;超时、schema 和事实校验必须在脱敏日志中保持不同类别。
  • 相关记录:BUG-016、BUG-053
  • 复发自:BUG-053
  • 修复版本:待提交(本地可测)

BUG-055 | 首轮完整技术输出与重复重试放大模型延迟并触发 503

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation 首轮 Agent 引导、Mastra 结构化输出、60 秒路由预算
  • 用户现象:本地首轮请求等待约 49–90 秒后返回可重试的 service_unavailable 503;同一模型的轻量探针正常。
  • 触发条件:正式首轮把候选状态、时间范围、稳定层、敏感层、引用与完整 expert workflow 全部交给模型重复输出;一次超时后又立即进行第二次完整生成。
  • 根因:模型承担了程序已经确定的 packet 字段复制工作,正式 prompt 曾达到约 8.4k 字符;提供商延迟波动时,两次 45 秒调用可超过路由声明的 60 秒预算。一次 26 秒成功结果还曾因旧引用正则误判被丢弃。
  • 修复:生产模型只生成 narrativeevidenceRequest,候选状态、时间、范围、分盘层、引用和领域依据由服务端从 packet 确定性补全;首轮 prompt 精简到必要边界和建议领域;第一次纯超时不再触发第二次完整调用;单次等待上限调整为 47 秒,校验失败的短重试最多 7 秒;首轮若只写了介绍但漏掉问题,追加模型自己生成的 evidenceRequest.prompt,不使用程序话术模板。
  • 验证:正式本地认证请求的 prompt 从 4785 字符进一步降到 1399 字符,provider 在 29066ms 返回,接口 HTTP 200 并生成自然首轮回答;定向测试 66/66、ESLint、TypeScript 与补丁检查通过。
  • 防复发:模型结构化输出不得重复由服务器掌握的确定性 packet;生成总预算必须小于路由平台预算;首轮 timeout、schema、grounding 继续使用脱敏分类日志,不记录提示词、用户资料或模型正文。
  • 相关记录:BUG-053、BUG-054
  • 复发自:BUG-054
  • 修复版本:待提交(本地可测)

BUG-056 | 关键词领域分类阻断自然语义并在生成失败后回退到错误领域模板

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正自然语言事件分类、中间轮 Agent 回答与候选评分输入
  • 用户现象:带明确年月的研究院实习、研究员经历被保存为 other;同轮模型生成失败后,页面又固定追问学业事件,看起来像 Agent 没有理解刚才的事业经历。
  • 根因:确定性关键词分类被当作最终语义判断,事业词表没有覆盖所有自然表达;中间轮叙事连续失败后仍允许 fallbackNarrative() 生成固定业务话术,并按技术 packet 的首个建议领域继续提问。
  • 修复:单条事件落入 other 时,优先调用 narrative Agent 结合最近事件上下文返回允许的语义领域;模型不可用、超时或仍返回 other 时才保留确定性结果。首轮和中间轮叙事连续失败统一返回可重试服务错误且不保存本轮,只有最终确认阶段保留包含安全边界与三类表的确定性 fallback。
  • 验证:新增“2020年4月去石油化工研究院实习做研究员”语义分类回归,确认保存为 career 并可评分;新增中间轮不合格输出回归,确认返回 service_unavailable、不保存事件、不推进版本且不产生模板消息。事件提取、叙事、编排和路由定向测试 134/134 通过。
  • 防复发:正则只负责低成本初筛和模型不可用时的降级,不得覆盖 Agent 对自然语言事件的语义判断;非最终轮生成失败不得用业务模板伪装成成功回答;候选时间、分盘事实、未来事件和重复计分仍由程序校验。
  • 相关记录:BUG-033、BUG-235、BUG-047、BUG-053
  • 复发自:BUG-053
  • 修复版本:待提交(本地可测)

BUG-057 | 中间轮重跑在 Pro 模型延迟波动时直接返回 503

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversationregenerate 与其他非最终叙事轮、Mastra 模型降级、validation receipt
  • 用户现象:本地生时校正点击重跑后返回可重试的 service_unavailable 503,原回答仍保留但无法得到新的 Agent 回答。
  • 触发条件:中间轮技术 packet、事件账本和上下文组成约 5.7k 字符的提示词,首选 Pro 模型在提供商延迟波动时超过 47 秒单次等待上限。
  • 根因:BUG-055 为避免两次慢模型调用超过 60 秒路由预算,曾规定第一次纯超时直接结束;这避免了重复 Pro 调用,却也让瞬时延迟直接变成 503。受控本地复现中,同一重跑请求成功时总耗时约 45 秒,其中模型约 32 秒,证明服务和 Mastra 结构化输出可用,但原策略没有快速恢复路径。
  • 修复:将叙事预算调整为首选模型 38 秒和独立恢复模型 7 秒;第一次调用使用 deepseek-v4-pro,超时、schema 或事实校验失败后以新的 AbortSignal 调用 deepseek-v4-flash。生成结果携带实际模型 IDvalidation receipt 记录真正成功的模型;两次均失败时继续返回 503,不恢复固定业务模板。
  • 验证:新增 Pro 超时后 Flash 成功的回归,断言两次调用使用独立 signal、第二次 attempt 标记正确、receipt 写入 Flash 模型且不使用模板;新增 regenerate 双模型均失败的事务回归,断言不推进 turnVersion、不覆盖上一条 Agent 消息、不新增事件、不改变候选和计费。叙事、路由与编排定向测试 110/110 通过。
  • 防复发:慢模型与快速恢复模型必须共享明确的总预算而不是各自获得完整路由时限;receipt 必须记录实际成功 provider/model;生成失败只能作为可重试技术错误,不能提交半轮状态或用业务模板伪装成功。
  • 相关记录:BUG-053、BUG-054、BUG-055、BUG-056
  • 复发自:BUG-055
  • 修复版本:待提交(本地可测)

BUG-058 | 私有事件路由与未执行技法清单泄露到 Agent 回答并中断追问

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正中间轮自然语言回答、事件评分路由、Technique Audit 上下文与下一问题生成
  • 用户现象:用户提供带年月的真实经历后,Agent 主动解释内部“事件归类”,展示大量 not_evaluatedblocked 技法和确认权限,却没有继续提出下一个问题;回答像调试报告而不是连续的一问一答。
  • 根因:叙事上下文直接暴露事件的私有 domain、建议领域和完整 expert workflow 状态,模型因此围绕工程元数据组织回答;同时问题修复器只处理首轮或明显截断的句子,语法完整但没有问号的中间回答会原样通过。技法保持 not_evaluated 的直接原因则是当前有效、受支持且可评分的事件不足三条,路由尚未进入 scoreEvents(),并非 Mastra 无法调用技法。
  • 修复:新增叙事专用上下文,移除事件 domain、建议领域和内部评分;证据采集阶段只允许暴露已实际 usedpartial 的技法,不再展示未调用清单、阻塞项或确认权限。私有语义领域仍保留在服务端用于事件去重、跨领域路由和候选评分,但不得进入用户可见文案。所有非最终回答必须包含且只引导一个下一问题;模型正文漏问时追加同一次模型生成的 evidenceRequest.prompt,不使用固定业务模板。
  • 验证:新增回归覆盖事件正文可见但内部领域不可见、建议领域不进入叙事 packet、未调用技法不在采集轮展示,以及完整陈述漏问时补入模型自写问题;叙事、路由和编排聚焦测试通过,目标 ESLint、TypeScript --noEmitgit diff --check 通过。
  • 防复发:私有语义路由与用户叙事必须使用不同数据视图;未执行的技术状态不得被包装成已完成分析;每个非最终 Agent 回答都必须断言存在一个明确的下一问题,同时最终轮仍须使用真实技术 packet 生成可审计的三类表。
  • 相关记录:BUG-052、BUG-053、BUG-055、BUG-056、BUG-057
  • 修复版本:待提交(本地可测)

BUG-059 | Flash 恢复窗口过短导致模型波动时偶发 503

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:POST /api/birth-time-conversation 非最终叙事轮、Pro 超时后的 Flash 恢复请求
  • 用户现象:同一条合法回答偶发返回可重试 service_unavailable 503,随后原 action 幂等重试又能正常生成回答。
  • 根因:首选 Pro 模型等待上限为 38 秒,但恢复模型只有 7 秒;提供商延迟稍高时,两次尝试都会在接口 60 秒总预算之前被主动中止。原请求幂等重试约 43 秒成功并正常推进一轮,排除了数据库、case 版本和技术 packet 故障。
  • 修复:保留 38 秒 Pro 窗口,将独立 Flash 恢复窗口从 7 秒放宽到 14 秒;最坏模型等待仍为 52 秒,为鉴权、技术计算和持久化保留约 8 秒,不引入第三次请求或固定模板回退。
  • 验证:使用原 action 幂等重试返回成功并生成自然下一问;叙事、路由和编排聚焦测试、目标 ESLint、TypeScript --noEmitgit diff --check 通过。
  • 防复发:两次模型尝试的总预算必须显式小于路由 maxDuration;恢复模型窗口不能短于其实际常见尾延迟;非最终轮仍不得用确定性业务模板伪装生成成功。
  • 相关记录:BUG-055、BUG-057、BUG-058
  • 复发自:BUG-057
  • 修复版本:待提交(本地可测)

BUG-060 | 生时校正模型选择器显示 GPT-5.5 但请求实际使用默认 DeepSeek

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正首次引导、用户回答和重跑的叙事模型选择,以及失败操作的幂等身份
  • 用户现象:生时校正输入框下方明确选择 GPT-5.5,但接口请求没有携带模型标识;Agent 回答的延迟和实际调用模型与界面选择不一致。
  • 根因:普通会话会把当前会话的 modelId 发送给 /api/consult,生时校正控制器和首页首次 start 请求却没有把同一字段写入命令;服务端叙事生成器因此只能使用 RECTIFICATION_NARRATIVE_MODEL_ID,未配置时默认选择 DeepSeek。模型选择器此前只改变了会话 UI 状态,没有贯通生时校正 API。
  • 修复:为会生成 Agent 文本的 startanswerregenerate 命令加入可选 modelId;首页首次启动使用新建或恢复的生时校正会话模型,控制器每次发送时读取当前选择。路由从既有服务端模型目录解析所选模型,合法的 GPT-5.5 优先于配置默认模型,原恢复模型仍作为第二次尝试;过期或不可用模型以安全的 model_unavailable 409 返回,且不扣点。操作身份同时纳入模型 ID,避免失败后切换模型仍复用旧 action fingerprint。
  • 验证:增加请求序列化、首次启动、回答、切换模型后重跑、路由模型解析、不可用模型拒绝和服务分发回归;receipt 继续记录实际成功的 provider/model,而不是只记录界面选择。
  • 防复发:任何带模型选择器的会话入口都必须证明 UI 会话模型、发出的命令和服务端解析结果一致;新增生成命令时必须同时更新 schema、幂等身份、客户端和路由测试。
  • 相关记录:BUG-055、BUG-057、BUG-059
  • 修复版本:待提交(本地可测)

BUG-061 | 同版本恢复响应被丢弃导致本轮双方消息同时消失

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正回答成功后的消息同步、冲突恢复与持久化对话历史
  • 用户现象:接口已经成功返回新的自然语言回答和完整 conversationMessages,但页面没有显示本轮 Agent 回答,本轮用户气泡也在请求结束后消失。
  • 触发条件:请求在途时,父级状态先同步到与成功响应相同的 turnVersion;临时用户气泡随旧版本结束而移除,随后控制器的同版本保护又直接丢弃成功响应。
  • 根因:控制器把“同版本业务 turn 不应被晚响应覆盖”扩大成了“同版本响应的全部内容均不可采用”,没有区分业务状态与更完整的持久化消息 transcript。
  • 修复:更高版本响应仍优先、较旧响应仍丢弃;同版本响应只有在携带更完整的持久化历史,且其最后一条 Agent 文案与当前轮一致时,才只补齐消息列表并保留当前 turn。新版本响应也优先采用服务端持久化历史,避免本地推断覆盖耐久真源。
  • 验证:新增在途回答期间父级先同步到版本 8、随后恢复响应携带完整 Agent → 用户 → Agent 历史的回归;确认本轮用户与 Agent 气泡均恢复,同时原有“外部同版本 turn 不被晚响应覆盖”测试继续通过。controller、client、route 聚焦测试 61/61 通过。
  • 防复发:同版本保护必须分别比较业务 turn 与消息 transcript;完整且与当前 narrative 一致的耐久历史可以修复 UI,但不得用晚响应覆盖同版本外部业务状态。
  • 相关记录:BUG-046、BUG-048
  • 复发自:BUG-046
  • 修复版本:待提交(本地可测)

BUG-062 | 两位年份与模型领域标签导致合法经历被降级为未完成分析

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正自然语言事件提取、非最终轮叙事校验与恢复模型调用
  • 用户现象:用户用“21年”“23年”描述一段连续经历后,系统没有正常理解时间线,而是显示“这次分析暂时没有完成”的固定失败提示。
  • 根因:事件提取器只识别四位年份,两位年份会被视为缺少日期;同时,首选模型虽然成功生成了完整回答,但旧版结构化输出中的下一问题领域标签不在技术 packet 当前建议集合内,整段回答因此被判为 ungrounded_evidence_domain。恢复模型随后超时,编排层最终保存了确定性失败提示。
  • 修复:两位中文年份根据当前日期展开为最近且不晚于当前年的四位年份;旧版完整模型输出继续校验候选时间、范围、分盘和引用等事实字段,但下一问题的内部领域只保留与 packet 建议集合的交集,交集为空时才使用 packet 建议,不再因无害的领域措辞差异丢弃自然回答。
  • 验证:新增“21年元旦回家备考、21年底封控、在家到23年”的提取回归,以及旧版模型选择非建议领域的叙事回归;事件提取、叙事和编排聚焦测试共 109 项通过。
  • 防复发:自然语言年份解析必须覆盖常见缩写表达;服务端安全校验只约束可核验事实,不应让模型的对话选题标签覆盖或中断已通过事实校验的回答。
  • 相关记录:BUG-052、BUG-057、BUG-059
  • 修复版本:待提交(本地可测)

BUG-063 | 精确日期追问收到月日后重复确认同一节点

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正事件日期追问、上下文日期补全与 append-only 事件修订
  • 用户现象:Agent 已询问 2026 年 7 月上旬决定开公司的具体日期,用户回答“7 月 10 号”后,Agent 又询问是否指 2026 年 7 月 10 日及其对应节点。
  • 根因:日期提取器只直接识别带年份的中文日期;编排器只会补全“来年/同年”等相对年月,并且补全逻辑仅支持空日期,不支持把已有 2026-07 提升为 2026-07-10。简短回答因此被保存成新的未定日期事件,而不是目标事件的精度修订。
  • 修复:仅在明确指向既有事件的 event_date 追问中,从目标事件继承年份或年月;允许兼容的年到月、月到日精度提升,并继续以 correctsEvidenceIds 保存修订链。普通新事件不猜测缺失年份。
  • 验证:新增 2026-07 · 决定开公司 → 7 月 10 号 回归,断言生成 2026-07-10 修订、保留原事件、沿用原摘要且不再次询问日期归属。
  • 防复发:无年份月日只能在定向日期追问且存在可靠目标日期时补全;日期精度提升必须与目标已有年份或月份前缀一致。
  • 相关记录:BUG-028、BUG-047、BUG-062
  • 修复版本:待提交(本地可测)

BUG-064 | 窄候选被本地稳定性前置门控阻断导致长对话无法进入 VedAstro 验证

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正事件评分、三引擎校验、VedAstro 外部验证与最终确认阶段
  • 用户现象:用户跨教育、事业、搬迁、关系和财务等领域补充大量带时间事件后,候选范围已经缩窄,但系统仍持续追问,始终不给出可确认的纠正时间。
  • 触发条件:本地评分已经产生唯一领先且不超过 15 分钟的候选范围,但领先幅度、正负 1/2/5 分钟邻域稳定或 leave-one-event-out 诊断未全部通过。
  • 根因:外部验证入口错误依赖本地 can_apply=true;而本地 can_apply 又把邻域稳定和 leave-one-event-out 当作硬门槛。最终确认合同同时要求 VedAstro 通过,形成“本地未完全稳定则不调用 VedAstro、未调用 VedAstro则永远不能确认”的循环门控。
  • 修复:新增独立的外部验证就绪判定;当至少 3 条事件、2 个领域、必需分层完整、候选唯一领先且范围不超过 15 分钟时,即进入三引擎和 VedAstro 事件区分验证。邻域稳定与 leave-one-event-out 继续保留在审计 gate 和置信度诊断中,但不再进入 hard_blockers;最终确认仍要求本地窄候选、必需分层、三引擎一致、VedAstro 官方响应与事件区分全部通过,并继续等待用户明确确认后才写入出生时间。
  • 验证:将一组 12 条、覆盖 5 个领域的长对话事件固化为回归;修复前本地得到 05:0705:08 且 VedAstro 调用次数为 0,测试失败;修复后同一 fixture 进入外部验证,模拟官方事件扫描严格区分首选与次选后返回 confirm_minute。活动事件评分、API 与技法合同相关测试 42/42 通过;前端相关 95 项中 94 项通过,唯一失败为既有“最多十条事件”兼容测试,与当前已取消最大事件条数的产品规则冲突,不属于本次改动。
  • 防复发:测试必须同时覆盖“本地低置信但已形成窄候选”和“VedAstro 未执行或未区分时仍禁止确认”;研究稳定性诊断不得再次被用作外部验证的前置开关。
  • 相关记录:BUG-051、BUG-055、BUG-058
  • 修复版本:待提交(本地可测)

BUG-065 | 真实 VedAstro 事件扫描请求乘法导致生时校正数分钟无响应

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正进入外部候选区分后的同步响应时间、VedAstro SearchEvents 调用量
  • 用户现象:本地候选已经缩窄并开始真实 VedAstro 验证后,请求运行超过五分钟仍未完成,网页表现为长时间等待或最终服务不可用。
  • 触发条件:高严谨生时校正包含多条月级或年级事件,并且至少有两个候选分钟需要外部比较。
  • 根因:每个候选都会扫描全部可映射事件,而 VedAstro adapter 又把月级事件拆成约十二个采样日;十二条事件、两个候选时最坏接近 288 次同步 HTTP 请求。mock 每次立即返回,因此原回归只证明进入了 VedAstro,没有暴露真实网络延迟。
  • 修复:生时校正专用调用层按 VedAstro 原生 careerwealthmarriage 领域各选择一条最强事件;直接映射优先于代理映射,日级优先于月级和年级,同精度选择较新事件。月级和年级事件只取范围中点作为代表日,两个候选最多发起六次外部事件扫描;响应明确记录符合条件事件数、实际选中数和选择策略,不再暗示全部事件均已外部验证。通用 range scan 保持不变。
  • 验证:长对话回归断言十二条符合映射的事件只产生六次扫描,两个候选各覆盖事业、财富、关系三领域,所有扫描均为单日;相关 Python 测试 42/42 通过。使用真实 VedAstro key 重跑同一十二事件 fixture,首次完整命令约 9.7 秒、缓存后约 3.6 秒,官方响应状态为 official_verified。真实 SearchEvents05:07 与第二候选 05:21 返回相同的三领域命中与 36 分信号,因此正确保持 vedastro_candidate_not_discriminated,没有把无法区分的外部结果伪装成分钟确认。
  • 防复发:任何真实外部服务回归都必须同时锁定请求数量与代表事件选择,不得只用零延迟 mock 证明“已调用”;新增事件或采样策略时必须评估候选数 × 事件数 × 日期采样数的乘法上限。
  • 相关记录:BUG-055、BUG-064
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-066 | SearchEvents 被误作最终分钟裁判且未比较官方分钟敏感层

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:高严谨生时校正候选比较、VedAstro 官方验证与最终分钟确认
  • 用户现象:本地事件评分已经得到首选与次选分钟后,系统仍依赖 VedAstro SearchEvents 的事件命中差异决定能否确认;两个候选即使 Ascendant、宫位、D9、D10 或 Dasha 技术状态不同,只要事件搜索指标相同就无法继续收敛。
  • 触发条件:进入 VedAstro 外部验证的两个窄候选在 SearchEvents 中返回相同事件数量和 signal lift,或反过来事件搜索指标不同但官方分钟敏感快照相同。
  • 根因:旧外部验证合同把事件搜索指标当成候选分钟的最终判别器,却没有从 VedAstro 官方完整快照提取并比较 Ascendant/宫位边界、D9、D10 和 Dasha 边界身份。SearchEvents 适合补充事件背景,不足以独立证明某个出生分钟正确。
  • 修复:为两个排名最高的候选生成不含原始响应的 VedAstro 官方分钟快照,分别对 Ascendant 与宫位、D9、D10、Dasha 边界生成安全 fingerprint;至少一个官方分钟敏感层存在差异时才通过候选身份区分 gate。候选排序继续以本地已发生事件评分为准;SearchEvents 改为 event_background_validation,明确 used_for_decision=false。KP cusp/sub-lord 因当前已验证官方接口没有可审计结果而保持 unsupported,不以 IsPlanetInHouseKP 冒充。
  • 验证:官方快照适配器测试锁定四个分钟敏感层均生成非空 fingerprint、KP 明确 unsupported,且响应不泄露 API key 或 raw response;活动事件集成测试锁定 SearchEvents 指标完全相同时仍可由分钟敏感层差异完成区分,并锁定 SearchEvents 即使明显偏向首选、分钟快照完全相同时也必须保持 can_apply=falsevedastro_minute_sensitive_layers_not_discriminated。相关聚焦测试通过。
  • 防复发:任何最终分钟确认都必须区分“本地事件排序”“VedAstro 官方分钟技术身份验证”和“SearchEvents 背景验证”三种职责;两个官方快照有差异只证明候选技术状态不同,不得描述为 VedAstro 已独立证明本地首选分钟正确。
  • 相关记录:BUG-055、BUG-064、BUG-065
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-067 | 主分支同步后生时校正首轮流式、历史与重跑持久化回归

  • 状态:resolved
  • 首次发现:2026-07-24
  • 最近更新:2026-07-24
  • 影响面:生时校正首轮引导、NDJSON 流式回答、刷新后的交替历史、重跑持久化、Next.js 生产构建与迁移顺序
  • 用户现象:进入生时校正后首条 Agent 引导不显示或不流式;成功回答在页面上消失,刷新后 Agent 与用户消息错位;重跑后同一条用户消息被重复保存;同步主分支后开发测试可过但生产构建因 App Router 非法导出或缺失状态 setter 失败。
  • 根因:合并时删除了首轮 opening stream 与控制器 streamingAssistantText 的部分状态链,并把 NDJSON 客户端退回整包解析;持久化读取只恢复最终 turn,没有按 authored narrative 还原完整交替历史。重跑仍把上一条用户文本作为新 turn 保存,导致刷新后重复。两个迁移使用相同版本号;多个 route.ts 还导出了测试 helper,违反 Next.js App Router 生产构建的导出合同。最后,页面保留了 setRectificationOpeningAssistantText 调用但状态声明被删,只有完整生产 type-check 才暴露。
  • 修复:恢复服务端 NDJSON 增量事件和客户端逐块解析;恢复首轮 opening stream、控制器流式状态及持久化交替历史。重跑改为 assistant-only turn,数据库向前迁移允许 user_message = null,历史折叠时用新 Agent 回答替换上一轮回答而不复制用户气泡。迁移版本改为唯一顺序;将出生资料和全局地点 helper 移出 App Router route 文件,复杂生时校正实现移入 handler.ts,路由仅导出 HTTP handler 与受支持配置;补回首轮流式状态声明。
  • 验证:Next.js webpack 生产构建通过;除已知会使本机 Chrome 崩溃的真实 Chromium 组件测试外,前端测试 968/968 通过;VedAstro 适配器测试 24/24 通过;ESLint 0 error(保留 3 条既有 warning);git diff --check 通过。新增回归覆盖首轮流式增量、刷新后交替历史、assistant-only regenerate、迁移合同、全局出生地点 helper 与路由导出边界。
  • 防复发:发布门禁必须包含真实 next build --webpack,不能只跑单元测试或 tscApp Router route.ts 不得导出测试 helper;重跑必须以“替换上一轮 Agent 回答”为持久化语义;首轮消息、后续消息和刷新恢复必须共享同一对话历史合同。
  • 相关记录:BUG-052、BUG-053、BUG-055、BUG-058、BUG-060
  • 复发自:无
  • 修复版本:待提交(待生产验收)

BUG-068 | 生时校正确认词被当成新事件且重复追问同一日期

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:生时校正连续问答、事件日期确认、刷新后继续会话
  • 用户现象:Agent 问某个事件是否发生在明确年月,用户回答“是的”后,下一轮仍重复询问相同年月;确认词还可能被保存成一条日期待补充的新事件。
  • 触发条件:用户使用确认词、否定词、代词或承接上一问的简短自然语言回答,而当前轮没有再次写出完整事件和绝对日期。
  • 根因:叙事模型原先每轮只收到最新用户文字和事件账本,缺少同一 case 的连续 Assistant/User 历史;即使补回历史,候选日期仍只存在于上一轮可见文案中,没有作为业务状态持久化,确定性提取器仍可能把确认词当成新事件。依赖从 Agent 文案正则反推日期也会随文案改写、语言变化或恢复路径失效。
  • 修复:复用现有 turn 与 event evidence 存储,每轮向 Agent 注入同一 case 最近 40 条连续问答;同时在共享 follow-up 合同和数据库 JSON 校验中持久化 promptanswerModeproposedDate。Orchestrator 在事件提取前确定性处理 yes/no:肯定时把候选日期作为 append-only correction 合并到目标 evidence,否定时不生成事件并把同一目标切换为开放式日期追问。模型成功返回时,其 follow-up 不再被提取器的自动澄清覆盖;Narrative Agent 拒绝缺少结构化候选、重复已解决问题或继续指向已完成 evidence 的输出。
  • 验证:回归覆盖“2020年10月吗?→是的”、单独“不是”、“不是,是2021年10月”、刷新/resume 后候选日期仍存在,以及同一 actionId 重放不重复写入;断言确认词不成为独立事件、修正 lineage 正确、否定后切换为 free_text,并断言叙事 prompt 末尾连续包含上一条 Assistant 问题和当前 User 回答。
  • 防复发:任何承接式回答必须以持久化会话历史为第一语境;确认或否认是否改变业务事实必须由持久化 follow-up 状态和确定性状态机执行,不得再从自然语言文案反推候选日期。
  • 相关记录:BUG-235、BUG-063、BUG-067
  • 复发自:无
  • 修复版本:待提交(本地可测)

BUG-069 | 生时校正最终候选被收集阶段约束退回并无限追问

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:生时校正连续评分、候选收敛、VedAstro 外部验证与有限结果终止
  • 用户现象:用户连续提供多条真实经历后,本地候选已经缩小到单分钟或极窄范围,系统仍可能退回原始范围并继续追问;提问还可能停留在“为什么辞职、主动还是被动、造成什么影响”等不会改变当前评分的细节。
  • 触发条件:Technical Packet 收到单分钟候选或不足两个新建议领域;本地候选宽度已经适合外部验证但最终 margin 尚未达到确认阈值;事件达到旧的 3 条/2 领域门槛却未达到前端 4 条/3 领域门槛;候选连续多轮不再变化;或 family/other 背景事件被编排层计入评分覆盖。
  • 根因:收集阶段和最终确认阶段共用了错误门槛。Technical Packet 把“没有足够的新问题可问”当成候选无效,Orchestrator 又把窄候选失败静默替换为 baseRange;评分、技法合同和 Candidate Schema 分别使用 3/2 与 4/3 门槛,前两条事件不进入评分;plateau 只记录不终止,系统验证阻塞继续被转换成用户问题。外部验证入口还被误收紧为最终确认所需的 width <= 5margin >= 20%。同时 family/other 虽不进入 Python 评分器,却仍帮助编排层满足事件数和领域数;Agent 也继续追问不参与当前评分的原因、主动性和影响。原确定性校验只识别正确标注为 event_detail 的请求,模型可把同一详情问题错标成 new_event 绕过;最终轮也只靠提示词要求 evidenceRequest=null
  • 修复:建立前后端共享的收敛策略:第一条有效事件即评分,最终确认统一要求至少 4 条事件、3 个可评分领域、候选宽度不超过 5 分钟且 margin 不低于 20%。Technical Packet 接受单分钟和零建议领域;删除窄候选失败后退回 baseRange 的路径。候选在证据覆盖后连续两轮不变时结束为 completed + pending_validation,仅剩系统验证阻塞时直接结束有限结果,不再追问用户。外部验证单独使用最多 15 分钟的入口宽度,只要求唯一领先和必需层完整,不再提前要求最终 margin。编排层统一排除 family/other 的评分与确认计数,但继续持久化并展示为背景。Narrative Agent 的共享校验器禁止对已评分事件继续追问无计算价值详情,包括错标为 new_event 的详情语义;最终轮任何非空 evidenceRequest 都会触发重试,连续失败后才使用既有无追问 fallback。
  • 验证:前端聚焦测试覆盖结构化日期确认、单事件起评、单分钟候选、plateau/系统阻塞终止、家庭背景不进入评分计数、已评分事件详情错标重试,以及最终轮非空 evidenceRequest 重试。Python 聚焦测试覆盖长对话窄化后恢复 VedAstro 调用、4 条/3 领域统一门槛、官方分钟快照判别与 Technique Contract。
  • 防复发:必须区分“开始本地评分”“进入外部验证”“允许最终确认”三个阶段;没有新问题可问不得解释为候选无效。只有评分器实际支持的领域可以推进收敛计数,模型提问必须能改变日期、事件身份或评分领域,否则由确定性校验拒绝。
  • 相关记录:BUG-064、BUG-066、BUG-068
  • 修复版本:待提交(本地可测)

BUG-070 | 结构化日期确认迁移未接入生产迁移工作流

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:生产环境生时校正 follow-up 持久化、日期候选确认与数据库约束
  • 用户现象:代码已写入 promptanswerModeproposedDate,但生产迁移工作流不会上传或执行对应 migration;生产数据库仍可能按旧 JSON 合同拒绝新 turn。
  • 触发条件:从 GitHub Actions 手动执行生产生时校正 migration 的 checkapply 操作。
  • 根因:新增 20260725010000_structured_conversational_date_confirmation.sql 后,没有同步加入工作流的 scp 上传列表和远端顺序执行列表。
  • 修复:将该 migration 同时加入上传与执行列表,继续复用现有 checksum、迁移账本和单事务执行保护。
  • 验证:静态回归断言 migration 文件名在生产工作流中恰好出现两次,分别覆盖上传和执行路径。
  • 防复发:新增需要生产执行的 migration 时,必须同时更新受审生产迁移清单并由测试覆盖上传和执行两处引用。
  • 相关记录:BUG-068、BUG-069
  • 修复版本:待提交(本地可测)

BUG-071 | 结构化日期校验器的冗余正则在 PostgreSQL 执行时报错

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:生产环境生时校正首次建案、proposedDate 持久化与预留点数释放
  • 用户现象:开始生时校正时接口返回 409 action_conflict 和“请加载最新进度后再试”,预留点数随后自动释放且没有创建 case。
  • 触发条件:首轮 Agent 生成带 followUp.answerMode=yes_nofollowUp.proposedDate 的 evidence request,数据库开始执行结构化日期校验。
  • 根因:20260725010000_structured_conversational_date_confirmation.sql 同时保留了一个冗余的聚合日期正则;该正则括号不平衡,PostgreSQL 在函数运行时抛出 2201B invalid regular expression。下方按 year/month/day 精度分别校验的正则已经完整覆盖格式约束。
  • 修复:新增向前迁移 20260725020000_repair_structured_conversational_date_validator.sql,删除冗余聚合正则,只保留现有精度分支;同步加入生产迁移工作流。
  • 验证:生产事务回滚 dry-run 通过;以 2020-10 + month + yes_no 调用校验器返回 true;迁移账本、函数定义和生产健康状态均复核。
  • 防复发:数据库 JSON 合同的日期格式只维护一组精度分支;新增生产迁移时必须执行真实 PostgreSQL 函数调用,而不是只做 SQL 文本断言。
  • 相关记录:BUG-067、BUG-070
  • 修复版本:本次修复提交(生产已向前迁移)

BUG-072 | 生时校正首轮在零事件时执行全范围扫描并阻塞输入

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:首页生时校正入口、start 编排、首次会话持久化与咨询问题 handoff
  • 用户现象:从首页进入生时校正后长时间停留在加载状态;未知时间或宽时段资料最明显,首问出现后输入框也可能继续等待会话同步。
  • 触发条件:新建生时校正 case,尤其声明时间为全天未知或宽时段;首页没有待转交咨询问题时也会读取 durable handoff。
  • 根因:start 在尚无人生事件时仍构建 Technical Packet,按声明范围逐分钟重排,随后再调用叙事模型生成固定性质的首问;前端收到首轮后又串行等待 persistSession(),并且直接首页入口无条件读取 durable handoff。
  • 修复:start 只按已声明时间范围创建确定性首轮和待验证候选,不做分钟扫描或模型生成;第一条有效事件回答后才进入原技术计算与叙事链。首页先展示首轮并开放输入,再后台同步会话;只有存在本地或显式待转交问题时才读取 durable handoff。
  • 验证:Orchestrator、入口、客户端、路由、持久化与同会话写入队列聚焦测试 144/144 通过;目标 ESLint 与 git diff --check 通过。覆盖首轮不扫描/不生成叙事、先展示后按序后台持久化,以及无 handoff 时跳过 durable 读取。
  • 防复发:零证据首轮不得执行分钟级技术计算或模型调用;非关键会话同步不得阻塞已持久化 case 的首轮交互;可选 handoff 读取必须由实际 handoff 状态触发。
  • 相关记录:BUG-055、BUG-065、BUG-067
  • 修复版本:待提交(本地可测)

BUG-073 | 咨询入口与持久化出生时间能力不一致

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:首页状态文案、每日观察入口、推荐初始问题、/api/consult 出生时间模式判定
  • 用户现象:一类用户没有具体出生分钟,首页却仍诱导个人星盘问题;另一类用户已经保存准确到分钟的填报时间,旧标签页或旧客户端仍可能把咨询请求标为 general_no_birth_time,随后错误提示当前一般咨询模式不能生成个人星盘结论。
  • 触发条件:无分钟资料仍出现个人入口,或数据库已保存合法 family_exact / hospital_record / approximate 填报分钟,但客户端咨询模式停留在旧的 general_no_birth_time
  • 根因:首页没有复用出生时间路由结果来约束入口能力;同时 /api/consult 只在客户端请求星盘模式时加载 profile,收到 general_no_birth_time 时完全信任客户端状态,没有用持久化资料纠正陈旧模式。
  • 修复:首页复用 resolveBirthTimeConsultationRoute():无具体分钟时只显示一般知识文案和问题,有合法填报分钟时保留个人星盘入口。服务端对一般咨询请求也 best-effort 读取 profile;若持久化资料含合法未校正填报分钟则提升为 unverified_birth_time,若为 confirmed 且有合法 active time 则提升为 verified_chart,并继续由服务端 profile 构建星盘输入。period_only / unknown 仍保持一般咨询,不虚构分钟,也不把用户填报时间伪装成已校正时间。
  • 验证:聚焦测试覆盖无分钟首页入口、陈旧 general 请求被准确分钟 profile 提升为 unverified_birth_time,以及无具体分钟仍保持 general_no_birth_time;目标 ESLint、Webpack 构建与 git diff --check 通过。
  • 防复发:首页可点击入口必须与 resolveBirthTimeConsultationRoute() 的能力结果一致;服务端不得把客户端咨询模式当作出生资料真源,具体填报分钟必须进入 unverified_birth_time,只有 confirmed + active_birth_time 才能进入 verified_chart
  • 相关记录:BUG-009
  • 修复版本:待提交(本地可测)

BUG-074 | 生时校正首轮隐藏已知候选范围并让用户误以为系统没有读取资料

  • 状态:resolved
  • 首次发现:2026-07-25
  • 最近更新:2026-07-25
  • 影响面:生时校正新建 case 的确定性首轮引导
  • 用户现象:用户已经填写具体出生时间和误差范围,进入生时校正后却只看到泛化的重大事件提问,不知道系统实际正在核对哪个时间窗口。
  • 触发条件:新建 conversational-evidence-v3 case,首轮候选已经包含 rangeStart / rangeEnd,但可见 narrative 没有显示它们;旧生产版本还会调用模型生成同类泛化开场。
  • 根因:BUG-072 的快速首轮正确保留了候选范围,却只把范围写入结构化 candidate;首页不单独渲染该字段,固定 narrative 仍沿用了不含范围的旧提问。
  • 修复:复用首轮已经计算出的声明范围,在确定性 narrative 中明确显示当前待核对边界,并说明它不是已确认分钟;继续只问一件带年月的重要经历,不恢复零证据模型调用或分钟扫描。
  • 验证:Orchestrator 回归断言首轮正文包含真实 rangeStartrangeEnd、未确认边界和单事件提问;聚焦测试、目标 ESLint 与 git diff --check 通过。
  • 防复发:只要首轮已向用户展示生时校正状态,可见文案必须同时呈现结构化候选边界;不得只在隐藏状态中保存范围,也不得为了润色首问重新引入模型调用。
  • 相关记录:BUG-055、BUG-067、BUG-072
  • 修复版本:待提交(本地可测)

BUG-075 | 生时校正强制每轮提问并阻碍开放叙事

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正 Narrative Agent、非评分轮续接、确定性首轮、聊天消息完成态
  • 用户现象:用户叙述一段或多段人生经历后,Agent 会机械追加问题、反复继承上一轮追问,并在普通资料交流结束后显示“回答已完成”,整体表现像问卷收集器而不是能理解人生轨迹的校时助手。
  • 触发条件:非最终轮模型只做自然回应但同时返回结构化追问,或本轮没有生成新追问却沿用上一轮 evidenceRequest;所有 settled Assistant 消息都会渲染统一完成标签。
  • 根因:Narrative Prompt 强制每个非最终回复恰好以一个问题结束;服务端 repair 会把隐藏的 evidenceRequest.prompt 自动追加到正文;非评分轮会自动生成缺失信息追问并继承旧 evidenceRequest;通用消息组件把 settled 状态映射成“回答已完成”。
  • 修复:允许用户一次叙述一件或多件经历并自由连续表达,问题改为 Agent 可选行为;删除自动补问和已评分事件的聊天限制,普通自然回复使用 evidenceRequest: null;首轮只开放邀请叙述,非评分轮不再自动规划或继承问题,仅在明确的结构化纠正承接中保留必要状态;settled 消息不再渲染活动状态。事件抽取、保存、技术评分、候选收敛和分钟确认安全门保持不变。
  • 验证:Narrative Agent、Orchestrator 与聊天布局聚焦测试 101/101 通过;目标 ESLint 无错误或警告;Next.js 16 Webpack 生产构建与 git diff --check 通过。
  • 防复发:代码不得要求每轮必须提问,也不得把隐藏 prompt 自动拼接到 Agent 正文;没有可见短答问题时 evidenceRequest 应为 null,用户叙述节奏由 Agent 与用户共同决定,代码只维护记录、抽取、评分、收敛与确认边界。
  • 相关记录:BUG-070、BUG-072、BUG-074
  • 修复版本:待提交(本地可测)

BUG-076 | 完整出生资料已保存但权威档案状态为空

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:账户出生资料保存、星盘库同步、POST /api/consult 服务端资料校验
  • 用户现象:出生日期、家人确认的具体时间、地点、坐标和时区都已填写,星盘库副本也显示 birthTimeStatus: reported,但咨询仍返回“暂时无法核对完整出生资料”。
  • 触发条件:首次创建 profiles 行,或既有合法出生声明的 birth_time_status 为空且用户原样重新保存。
  • 根因:分钟校正主链合并时把 BUG-018 的首次档案派生状态分支退回为 {};共享 helper 又只在声明字段发生变化时写 reported,导致空状态行原样重存也无法自愈。chart_profiles.profile 只是星盘库 JSON 副本,不是咨询接口采用的权威出生资料。
  • 修复:账户路由统一调用共享状态派生 helper;新档案和合法声明空状态均原子写入 reported,确认分钟继续禁止被普通资料编辑覆盖;增加前向迁移,仅回填没有 active/candidate/case 应用结果且声明满足来源规则的空状态行。
  • 验证:账户 helper 回归覆盖新档案、空状态原样重存、候选失效和 confirmed 保护;迁移契约锁定安全过滤条件,并要求生产迁移工作流上传和应用各一次。
  • 防复发:服务端权威 profiles 的声明和状态必须同写;星盘库 JSON 不得作为咨询真值,历史空状态只能通过受约束前向迁移修复。
  • 相关记录:BUG-018、BUG-073
  • 复发自:BUG-018
  • 修复版本:待提交(本地可测)

BUG-077 | Python 诊断门状态导致历史事件评分误报 503

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正历史事件评分、Python 技术合同到 Web 候选结果的适配边界
  • 用户现象:用户提交第一条可评分历史事件后,请求返回 503,日志显示 stage=score_events error=ZodError,开放叙事无法继续收敛。
  • 触发条件:Python technique_contract.gates 返回诊断专用状态 diagnostic_fail
  • 根因:Python 引擎合法使用 diagnostic_fail 表示诊断未通过但不构成技术异常;Web wire schema 只接受 pass | fail | blocked | not_evaluated,因此在业务判断前拒绝整份候选结果。
  • 修复:Web wire schema 接受 diagnostic_fail,并在共享适配边界将其规范化为内部既有的 fail;不扩大持久化合同,不修改 Python 引擎。
  • 验证:适配器回归覆盖含 diagnostic_fail 的技术合同并断言内部状态为 fail;聚焦测试、目标 ESLint 与 git diff --check 通过。
  • 防复发:跨语言 wire schema 必须覆盖 Python 真源可返回的枚举;诊断状态在适配边界归一化,内部领域模型继续保持最小稳定集合。
  • 相关记录:BUG-067、BUG-075
  • 修复版本:本次修复提交

BUG-078 | 叙事模型超时导致已完成的事件评分整轮返回 503

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正开放叙事、历史事件保存、候选收敛、Narrative Agent 降级路径
  • 用户现象:历史事件已被成功抽取和技术评分,但主叙事模型与备用模型超时后,整轮返回 503,用户必须重试且无法看到已记录结果。
  • 触发条件:非最终轮的两次叙事生成均超时、报错或未通过安全校验。
  • 根因:安全的 packet 驱动兜底已经存在,却只允许最终轮使用;首轮和中间轮在两次生成失败后直接抛出 RectificationNarrativeUnavailable,把非关键的文案依赖变成了事件记录与收敛的硬依赖。
  • 修复:所有阶段在两次生成失败后统一使用既有的确定性安全兜底;保留已完成的事件记录、技术评分与候选推进,evidenceRequest 为 null,不机械追问,也不展示模型未验证内容。
  • 验证:回归覆盖首轮超时、首轮非法问卷、首轮非法技术层和中间轮虚构引用,均返回安全兜底且不泄漏被拒内容;Narrative Agent 聚焦测试、目标 ESLint、生产构建与 git diff --check 通过。
  • 防复发:叙事模型只能影响自然语言表达,不能成为事件持久化和确定性收敛的单点故障;降级输出必须来自已验证 packet。
  • 相关记录:BUG-067、BUG-075、BUG-077
  • 修复版本:本次修复提交

BUG-079 | 无可用路由领域时 Agent 追问导致 paused 会话返回 503

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正开放叙事、暂停后恢复、历史事件保存、Narrative Agent 到公开 turn 的适配边界
  • 用户现象:完整 synthetic smoke 在暂停并恢复后提交下一条历史事件,技术评分和模型叙事都成功,但接口返回 service_unavailable,事件无法保存。
  • 触发条件:技术 packet 的 suggestedDomains 为空,而模型自然提出了一个可继续回答的问题。
  • 根因:Narrative Agent 会把模型领域替换成服务端允许的领域;允许集合为空时产生 evidenceRequest.domains=[]。叙事校验未拒绝空数组,但公开 turn 合同要求至少一个领域,导致 turnFromNarrative() 在持久化前把整轮转换为 503。
  • 修复:在 Narrative Agent 的共享适配边界统一处理 authored 与 legacy 输出;没有任何服务端可验证路由领域时保留自然语言正文,只把可选 evidenceRequest 归一化为 null。不放宽公开持久化 schema,也不信任模型自报领域。
  • 验证:回归覆盖无路由领域的 authored 输出,并覆盖 pause → resume → answer 完整编排路径;确认正文保留、evidenceRequest=null、事件正常保存且不返回 503。
  • 防复发:模型自然语言不得成为事件保存的硬依赖;私有路由元数据为空时应省略可选状态,而不是生成违反持久化合同的半合法对象。
  • 相关记录:BUG-075、BUG-078
  • 修复版本:本次修复提交

BUG-080 | 开放叙事仍被技术上下文塑造成流程播报

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正普通叙事轮、自然对话、追问状态持久化
  • 用户现象:用户叙述“2016 年离家去外地上大学”后,Agent 仍回答“先记为、对校时有价值、参与候选核对、不会确认某个分钟”,像记录员而不是自然交谈。
  • 触发条件:普通 intermediate 轮生成叙事时仍向模型提供 Technical Packet、候选分钟、分盘、事件台账和未决证据。
  • 根因:此前开放叙事只取消了“每轮必须提问”,没有隔离普通聊天与技术收敛上下文;模型继续模仿内部流程措辞。叙事没有可见问题时,模型返回的隐藏 evidenceRequest 还会进入下一轮状态。普通提示不再暴露事件 ID 后,旧 authored schema 又仍强制模型提交已有 evidenceId,形成合同矛盾。
  • 修复:普通叙事轮只向模型提供最近真实对话和本轮用户原话,禁止主动播报记录、校时价值、候选范围、分盘、评分与收敛;只有用户主动询问技术结果时才提供受约束技术包。没有可见问题的隐藏 evidenceRequest 统一清空;自然追问只由模型表达问题意图,目标事件 ID 由服务器依据活动事件和未决证据绑定。首轮、最终轮及主动技术询问仍保留技术边界。
  • 验证:Narrative Agent 与 Orchestrator 联合回归 99/99 通过,覆盖普通 prompt 不含 packet/eventLedger、隐藏追问清空、服务器绑定 follow-up、相对日期承接、fallback、暂停恢复和并发幂等。
  • 防复发:普通聊天的可见措辞不得由技术 packet 或事件台账驱动;内部收敛状态只能记录和计算,不能成为 Agent 每轮必须复述的脚本。
  • 相关记录:BUG-074、BUG-075、BUG-078、BUG-079
  • 修复版本:本次修复提交

BUG-081 | 开放叙事只共情不继续引导

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正开放叙事的中间轮回复
  • 用户现象:用户讲完一段包含明确行动与转折的经历后,Agent 只分析感受和性格,没有自然询问后续经历,对话停在原地。
  • 根因:为消除问卷式强制追问,中间轮 prompt 将提问设为完全可选,且校验器只检查已有问题是否可见,没有要求事件轮承担继续引导叙事的职责。
  • 修复:普通中间轮 prompt 要求在本轮新事件后以一个贴着上下文的开放问题收尾;优先询问事件之后的下一步,继续禁止机械补年月、结果或转折。若模型仍只共情不引导,服务端直接补一句开放问题,不增加第二次模型调用;无模型可用时的安全兜底也保留自然后续问题。
  • 验证:回归覆盖“研究院实习后因价值观冲突辞职”场景,确认只共情不引导的回复会保留原文并补上后续问题,且不触发模型重试。
  • 防复发:开放叙事不等于被动倾听;事件轮默认负责自然推进,但不得恢复固定问卷模板。
  • 相关记录:BUG-075、BUG-080
  • 修复版本:本次修复提交

BUG-082 | 生时校正缺少独立证据状态机并把分钟峰值误导为产品答案

  • 状态:resolved
  • 首次发现:2026-07-26
  • 最近更新:2026-07-26
  • 影响面:生时校正建案、事件采集、日期修订、候选评分、暂停恢复、范围保存、原咨询问题 handoff 与扣费幂等
  • 用户现象:专业 Agent 可以持续收集跨领域事件、逐步补充日期并做稳定性复核,但 Web 流程把对话、抽取、评分和叙事绑在单次请求中;容易重复追问同一事件、出现等待状态无下一题、在低置信度下突出单个峰值分钟,并且刷新或多设备继续时缺少独立业务真源。
  • 根因:旧 conversational-evidence-v3 同时承担聊天体验和校时状态机;人生事件没有稳定的 eventId + revision 修订链,问题没有持久化目标事件,分钟扫描结果也没有强制经过范围聚类、日期敏感性、逐项排除和邻近分钟门控。聊天会话、计算任务和咨询 handoff 的生命周期耦合,无法独立恢复和仲裁并发。
  • 修复:新增 rectification-evidence-v4 独立领域模型、Case/Turn/Event Revision/Snapshot/Job/Handoff 持久化状态机和后台 Worker。先完成教育、迁移、关系、事业、财务、健康压力、家庭七领域覆盖,再对非日精度事件按 targetEventId 追加同一事件 revision;已追问或跳过的事件不循环。候选分钟先聚类,再通过事件数、领域数、范围宽度、邻近分钟、leave-one-out、日期敏感性和计算口径 hash 门;公开合同固定 canConfirmExactMinute=false,只允许用户主动保存候选范围,不更新 profiles.active_birth_time。Handoff 和扣费由 PostgreSQL lease、幂等 action 与 settlement receipt 保护。
  • 验证:前端 V4/domain/service/migration/replay/handoff/consultation continuation 33 个测试通过;Python 2 个测试通过;目标 ESLint 与 Webpack 生产构建通过。隔离 PostgreSQL 12 项不变量通过;真实 PostgreSQL + Python E2E 完成七领域与定向日期修订后进入 range_ready,主要范围为 05:2605:30,无空问题死状态,原 active_birth_time 不变且未主动保存前 acceptedRange 为空。静态浏览器验收确认日期修订输入框、范围保存按钮、非确认分钟文案和 390px 无横向溢出;真实登录态 UI 因隔离服务未连接认证配置,本轮未声称已验收。
  • 防复发:生时校正业务真源必须是 V4 Case 和 append-only 事件台账,不得从聊天正文反向恢复;任何公开结果只能显示通过稳定性门的范围,单分钟峰值不得成为可保存结果;证据不足必须生成下一题或明确暂停,不能进入 awaiting_answer + currentQuestion=nullV4 路径不得写 profiles.active_birth_time
  • 相关记录:BUG-067、BUG-069、BUG-072、BUG-074、BUG-075、BUG-080、BUG-081
  • 修复版本:待提交(本地可测)

BUG-083 | staging 质量门使用无可用 Linux sandbox 的浏览器运行时导致验收失败

  • 状态:resolved
  • 首次发现:2026-07-27
  • 最近更新:2026-07-27
  • 影响面:生时校正 V4 的 390px 真实浏览器验收、staging 镜像发布与后续部署
  • 用户现象:PR 质量门已通过且相同提交的其余 1031 个测试成功,但推送到 staging 后唯一的真实 Chromium 测试未能取得 DevTools 端口,导致不可变镜像未发布、部署未继续。
  • 触发条件:backend-quality-gate.yml 未显式供应带系统 sandbox 的浏览器,或安装 Playwright 默认的 chromium-headless-shell 后直接用原始二进制启动。
  • 根因:真实浏览器测试刻意保留 Chromium sandbox,但 Playwright 默认下载的 chromium-headless-shell 是原始构建;当前 GitHub Linux Runner 无法为该原始二进制建立所需 sandbox,进程以 SIGTRAP 退出。仅依赖 Runner 偶然预装的 Chrome 也不具备可重复性。
  • 修复:通过 Playwright 官方 chrome 安装通道供应系统级 stable Chrome 及其依赖,再执行真实浏览器合同;避免选择无系统 sandbox 包装的 headless shell,不跳过验收,也不增加 --no-sandbox
  • 验证:workflow 合同回归断言 Playwright stable Chrome 必须被安装;聚焦部署合同测试、完整前端测试、目标 ESLint、生产构建与 git diff --check 通过;staging 质量门和部署结果另行记录。
  • 防复发:保留浏览器 sandbox 的 CI 必须使用具备系统 sandbox 包装的浏览器安装通道,不能直接启动无相应宿主配置的原始 headless shell,也不能依赖 hosted runner 的偶然预装状态。
  • 相关记录:BUG-082
  • 修复版本:本次修复提交

BUG-084 | staging V4 Worker 与候选引擎位于不同 Docker 网络

  • 状态:resolved
  • 首次发现:2026-07-27
  • 最近更新:2026-07-27
  • 影响面:生时校正 V4 后台评分、第三条及后续可评分事件、staging 登录态验收
  • 用户现象:Case 创建和前两条事件成功,第三条事件触发候选引擎后 Worker Job 失败并返回 fetch failed
  • 触发条件:staging Worker 同时需要访问 PostgreSQL app 网络和 API 所在的 Compose default 网络。
  • 根因:rectification-v4-worker 只加入 app 网络;api 只在 default 网络。Worker 虽正确配置 JYOTISH_API_BASE=http://api:5200,但 Docker DNS 无法跨网络解析和连接 api
  • 修复:让 staging Worker 同时加入 defaultapp 网络,不改变 production compose,也不暴露 API 端口。
  • 验证:部署合同测试固定 Worker 的内部 API 地址和双网络配置;聚焦测试、Compose 渲染、目标 ESLint、git diff --check 及 staging 登录态 smoke 另行记录。
  • 防复发:需要同时访问内部 API 与 staging PostgreSQL 的服务必须在 Compose 合同中显式加入两个现有私有网络。
  • 相关记录:BUG-082
  • 修复版本:本次修复提交

BUG-085 | 生时校正回退为独立面板和固定领域问卷

  • 状态:investigating
  • 首次发现:2026-07-27
  • 最近更新:2026-07-28
  • 影响面:生时校正聊天 Surface、事件语义、后台 Job、候选计算、诊断、Reasoner、Renderer 与持久化主链
  • 用户现象:进入生时校正后看到独立的校正面板、证据区域和固定问题;交互不像普通 session,领域也不再根据用户刚讲的经历动态选择。
  • 触发条件:旧 V4 既在界面层使用独立校正结构,又让 question-planner.tsquestion-author.ts 直接决定领域顺序与问题文案;模型只负责写下一问,后台没有形成完整 Agent 决策闭环。
  • 根因:产品状态被压缩成“下一问字符串”,事件语义、候选特征、诊断结果、问题机会、模型决策和公开消息之间没有受约束的 durable contract;因此即使替换提示词,系统仍会沿用问卷式控制流,且无法审计模型为何选题或安全重放已完成 Job。
  • 修复:删除旧 question-planner.tsquestion-author.ts,将回答处理重构为完整 V5 主链:保存回答并创建后台 Job → Evidence Reconciliation → Candidate Engine / Feature Snapshot → Diagnostics → Opportunity Builder → Bounded Reasoner → Decision Validator → Renderer → Atomic Job Completion。可见层继续复用普通 session 聊天 SurfaceReasoner 只能选择服务端生成的 opportunity 或受约束动作,不能注入分钟、分数、事件或任意问题;Renderer 只表达已验证决定,候选范围不得表述为已确认出生分钟。Agent Run、Public Message、Diagnostics、Feature Snapshot、Pending Evidence 和事件修订均作为一等产物持久化。
  • 验证:67 个 TypeScript 聚焦合同全部通过,覆盖普通 session UI、完整 V5 artifact chain、Reasoner 单次诊断预算、Opportunity 选择、shadow/legacy 隔离和 range-only 输出;7 个 Python 服务合同通过。真实 PostgreSQL 14 已按 V4 → V5 顺序完成 migration dry-run,并跑通 processing → reasoning → rendering → complete、五类 artifact 各一条落库和 completed Job 幂等重放。tsc --noEmit 未出现 V5 新错误,只剩 birth-time-journey-engineidentity-auth-integrationonboarding-route 三处无关基线错误。当前完成边界为本地可测,尚未提交、推送、迁移 staging 或执行登录态 smoke。
  • 防复发:生时校正不得再次把模型降级为“问题文案生成器”;所有可见动作必须来自 server-owned opportunity,经 bounded reasoner、decision validator 和 renderer 后原子持久化。测试必须同时锁定 legacy/shadow 隔离、artifact 完整性、候选范围边界和 completed-job replay 指纹。
  • 相关记录:BUG-020、BUG-075、BUG-080、BUG-081、BUG-082、BUG-083、BUG-084、BUG-086
  • 修复版本:本地 V5 重构,待提交与 staging 验收

BUG-086 | 模型下一问可绕过当前事件而跳成领域问卷

  • 状态:investigating
  • 首次发现:2026-07-27
  • 最近更新:2026-07-28
  • 影响面:生时校正 V5 的当前事件延续、问题机会构建、诊断工具预算、模型决策验证和 Job replay
  • 用户现象:用户回答“2016 年离家去外地上大学”后,下一问直接变成“请说一次影响较大的搬家或长期迁居”,看起来仍按“升学 → 搬家”模板轮询,而没有承接刚才的具体经历。
  • 触发条件:当前事件仍缺必要精度,但旧 Worker 只校验模型返回结构;只要模型输出一个格式合法的新领域问题,就可以绕过当前事件和服务端已知证据缺口。
  • 根因:旧方案把“required continuation”作为给模型的提示,而不是服务器拥有的候选动作和最终决策约束;诊断结果也没有独立工具预算、持久化产物和可回放选择依据,无法阻止合法 JSON 携带错误业务路由。
  • 修复:Opportunity Builder 将未解决的当前目标设为独占路由,并只发布带稳定 ID、目标事件、效用分解和隐私成本的问题机会;Bounded Reasoner 最多执行一次只读诊断,最终只能选择活动 opportunity 或受限状态动作;Decision Validator 拒绝不存在、跨 Case、非活动或越权的机会,也禁止模型直接写问题、分钟、分数和事件。Reasoner 不可用、返回非最终诊断或耗尽预算时走同一确定性 fallback policyRenderer 根据 validated decision 生成自然语言承接,Worker 再通过单一 completion RPC 原子保存全部产物。
  • 验证:对抗合同覆盖“当前目标独占下一问”“只能选择服务端活动 opportunity”“诊断预算耗尽 fail closed”“模型不得注入问题/分钟/事件/分数”和“Reasoner/Renderer 不可用时确定性降级”。真实 PostgreSQL completed-job replay 已验证:相同完整 payload 指纹返回既有 Case;任一 artifact 改变且指纹不同会抛出 rectification_v5_replay_payload_mismatch,不会二次写入或接受漂移结果。当前仅完成本地验证,staging 行为仍待发布后验收。
  • 防复发:当前事件延续必须是服务端 opportunity 所有权规则,而不是 prompt 建议;模型输出即使结构合法,也必须经过 bounded tool budget、active-opportunity lookup、decision validation 和 completion payload hash 四层门控。
  • 相关记录:BUG-075、BUG-085
  • 修复版本:本地 V5 重构,待提交与 staging 验收

BUG-087 | 语义机会仍退化为固定文案并在拒绝后重复追问

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:生时校正 Agent 的证据协调、问题机会、Reasoner 上下文、Renderer、候选范围提示与 Skill 合同
  • 用户现象:对话会把月份已经明确的经历继续机械追问具体日期,按固定领域顺序轮询;用户表示不知道、跳过或换方向后仍可能回到同一事件,Renderer 还会重复套话和未变化的候选范围。
  • 触发条件:Question Opportunity 直接持久化最终 prompt,Renderer 再用服务端问题覆盖自然生成结果;缺失领域按固定数组选择,日期策略把所有非日精度事件视为未完成,且证据协调未完整区分拒绝、未知、换向和回答了另一事件。
  • 根因:机会合同混合了“为什么问、要补什么字段”和“最终怎么说”,导致 Reasoner 看不到最近对话语义、Renderer 无法安全自然表达;同时 target disposition 和重复追问预算不完整,固定领域与日期控制流绕过了信息增益、隐私成本和稳定性诊断。
  • 修复:升级为兼容旧 promptsemantic-question-v2,由 Builder 同时生成并按 utility 排序最多五个活动机会;补齐 resolved / unknown / declined / direction_change / answered_other_event / unresolved / not_applicable,限制同一目标连续追问和回答其他事件后的温和补问次数;月份默认足够,仅在日期敏感诊断不稳定时细化。Reasoner 获得脱敏的最近 Turn、事件、目标和机会语义;Renderer 改为验证自然问题并在失败时使用锚定 fallback,同时只在公开门首次通过或范围实际变化时播报范围,相同范围即使再次计算或收到 offer 决策也不重复播报。受限模型辅助提取仅补充确定性解析缺口,服务端继续校验原文子串和日期。
  • 验证:V6 语义合同、日期敏感性、拒绝/换向、回答新事件不覆盖旧事件、单次补问、Renderer 锚点/单问题/分钟注入/内部信息拒绝、候选范围去重、Reasoner 上下文、模型辅助提取和旧 Opportunity 兼容测试通过;四轮端到端测试覆盖月份职业事件、外地入学、无日期搬家换向和后续职业事件,并保留 exact-minute、Profile 写入、legacy/shadow、V5 候选引擎与 completed-job replay 边界。完整前端测试、lint、TypeScript 与 Python 结果见本任务交付记录。
  • 防复发:问题机会只表达服务器拥有的语义目标和约束,最终文案必须通过 realization validator;日期追问必须有诊断依据,拒绝/换向必须关闭目标,领域选择必须由 utility 和上下文驱动。Skill 明确禁止固定问卷、重复范围、唯一分钟、D60 驱动和公开内部评分/技术轨迹。
  • 相关记录:BUG-068、BUG-075、BUG-080、BUG-081、BUG-082、BUG-085、BUG-086
  • 复发自:BUG-085、BUG-086
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1,本地验证完成,待提交与 staging 发布

BUG-088 | TypeScript 全量检查被过期测试夹具阻塞

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:npx tsc --noEmit 本地发布前质量门
  • 用户现象:生产构建通过,但全量 TypeScript 检查在三个测试文件报错:事件评分输入仍传入服务端固定的 high_rigor、身份全局缓存清理被控制流误收窄为 never、onboarding 测试未归一化 PostgreSQL Date 联合类型。
  • 根因:测试夹具落后于现有生产合同;这些报错不来自 V6 Agent 运行时,但会让显式 TypeScript 验证失败。
  • 修复:删除客户端不应拥有的 high_rigor 输入;在异步请求结束后从 globalThis 重新读取身份缓存;按生产边界把 Date 归一化为日期字符串后再构建 onboarding cache identity。
  • 验证:npx tsc --noEmit、相关前端测试和生产构建通过。
  • 防复发:测试输入只使用公开类型拥有的字段;异步初始化的全局缓存不要依赖删除前的局部控制流;数据库日期联合类型在进入纯字符串合同前必须归一化。

BUG-089 | Staging CI 自动升级 MCP 2.0 导致旧 FastMCP 导入失败

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:Staging Backend Quality Gate 的 Python quick quality gate 与 staging 发布
  • 用户现象:本地完整验证通过,但 staging push 的质量门在导入 mcp_server.py 时失败,报 ModuleNotFoundError: No module named 'mcp.server.fastmcp'
  • 根因:requirements.txtpyproject.toml 仅声明 mcp>=1.0CI 在 2026-07-29 安装了不兼容的 mcp 2.0.0,而仓库当前服务端仍使用 MCP 1.x 的 mcp.server.fastmcp.FastMCP 导入合同。本地环境保留 mcp 1.25.0,因此未复现依赖漂移。
  • 修复:两个发布依赖入口统一限制为 mcp>=1.0,<2,继续使用已验证的 MCP 1.x API,不在本次发布中混入 MCP 2.0 迁移。
  • 验证:新增依赖合同测试同时读取 requirements.txtpyproject.toml,防止任一入口再次放宽到 MCP 2.xPython quick quality gate 与构建重新执行。
  • 防复发:运行时依赖的主版本兼容边界必须在全部安装入口保持一致;升级 MCP 2.x 必须作为独立迁移处理并先替换导入/API 合同。
  • 相关记录:BUG-087、BUG-088
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-090 | V6 审查发现用户可见分钟注入、家庭事件越权和最新事件排序错误

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:生时纠正 Renderer、模型辅助事件提取、Opportunity 与 Reasoner 最近事件上下文
  • 用户现象:模型可能在 acknowledgement 或 limitation 中声称唯一出生分钟;家庭健康事件可能被矛盾模型字段伪装成本人可评分事件;UUID 排序与创建时间相反时,下一问可能承接较早经历。
  • 根因:分钟安全验证只覆盖 question;辅助提取校验未拒绝 subject=self 与非空家庭 relatedPerson 的矛盾组合;账本的稳定 UUID 排序被误当作会话时间顺序。
  • 修复:全部用户可见 Renderer 字段统一执行出生分钟和内部信息安全校验并回落到服务器确定性文案;辅助提取在服务器拒绝主体/亲属矛盾并保持家庭事件 context_only 边界;Builder 与 Reasoner 显式按 createdAteventId、revision 稳定排序最近事件,不改变证据哈希使用的账本排序。
  • 验证:新增 acknowledgement/limitation 分钟注入、家庭 ICU 事件越权、UUID 与创建时间逆序的回归测试,并重新运行前端完整测试、lint、TypeScript 与构建。
  • 防复发:所有模型可写用户文案共享同一安全边界;模型提取不能决定评分主体;用于哈希的稳定顺序不得被复用为会话时序。
  • 相关记录:BUG-075、BUG-086、BUG-087
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-091 | Staging Case rollout 未启用 V5 Agent 导致 V6 继续输出固定模板

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:staging 生时纠正新 Case 与 V6 未完成 Case 的用户可见回复
  • 用户现象:用户提交“2016 年 9 月离家去外地上大学”后,回复仍固定为“我记下了这段经历。接下来请继续讲另一件……”,没有进入 Semantic Question Renderer。
  • 根因:Case rollout 只写入 V3 创建门和 smoke 状态,没有写入 RECTIFICATION_AGENT_V5_ENABLEDRECTIFICATION_AGENT_V5_SHADOWRECTIFICATION_AGENT_V5_CANARY_PERCENT;因此 selectRectificationDeploymentMode() 把新 Case 持久化为 v4_legacyOrchestrator 必然调用 Legacy Projector。
  • 修复:受控 staging rollout 现在原子写入 V5 Agent 开关,public 与 smoke rollout 使用 v5_agent、100% canary,并重建 web/workerstaging 中唯一满足 V6 版本、未完成、无 open Job 条件的错误 Case 已原位升级为 rectification-evidence-v5 / v5_agent,历史 Turn、Event、Job 与 Agent Run 保持不变。
  • 验证:rollout 脚本测试断言三项 V5 选择器只写一次,并在成功前核对 web/worker 容器实际读取的 enabledshadowcanarystaging 活跃 Case 聚合只剩 v5_agent;健康检查保持 exact SHA、public、ready。
  • 防复发:公开 Case rollout 必须同时控制创建门与 Agent deployment mode,并验证运行容器的实际环境;仅有 readyForNewCases=true 不再视为新对话 Renderer 已启用的充分证据。
  • 相关记录:BUG-085、BUG-086、BUG-087
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1

BUG-092 | Semantic Renderer 成功后仍被静默替换成固定领域模板

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:V6 ask_new_event Opportunity 排序、问题验证、Renderer telemetry 与 staging 用户可见下一问
  • 用户现象:用户提交“2020 年 4 月去石油化工研究院实习做研究员”后,系统仍显示“承接……请再说一件……哪次搬家、离乡或长期迁居……”,像固定问卷。
  • 根因:Builder 将最近六轮答案拼接为当前主题,使较早教育事件中的“离家/外地”再次提升 relocation,同时未覆盖领域奖励在已经满足最小领域数后仍占主导;Renderer 对自然 new_dated_event 问法使用过窄词面校验,校验失败后静默替换为 Builder 固定 fallbacktelemetry 仍记为 renderer succeeded
  • 修复:当前主题只读取最新回答或最新事件;达到两个可评分领域后显著降低纯领域覆盖收益并提高最新主题连续性;移除“承接……请再说一件……”拼接,fallback 改为锚定当前经历的单句问题;放宽自然新事件词面但继续执行单问题、锚点、内部信息和出生分钟安全校验;validator 回退单独记录 renderer rejected 与脱敏错误码。
  • 验证:真实两事件重放断言 career 机会优先于旧 relocation 关键词、月份不被细化、旧固定模板被拒绝、锚定研究院实习的自然问题被保留且不等于 fallback;完整前端、lint、TypeScript、Python V5 与 staging smoke 随发布记录执行。
  • 防复发:模型调用成功、Schema 成功和问题被接受必须分开观测;Opportunity utility 不得把历史关键词与“未覆盖领域”组合成伪装的固定轮询。
  • 相关记录:BUG-086、BUG-090、BUG-091
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1

BUG-093 | 首次候选评分因跨语言计算规格哈希不一致而失败

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:V5 Candidate Engine 首次达到三事件评分门后的 Feature Snapshot 原子持久化
  • 用户现象:第三件可评分事件已经保存在 Turn,但 Worker 最终显示“这次比较没有完成,回答已经保留,请再试一次”,Case 恢复上一问题且没有写入新事件。
  • 根因:服务器创建 Case 时由 TypeScript 对整数时区 8 计算规格哈希;Python 请求归一化把它变成浮点数 8.0,而 Python JSON 序列化保留 .0。两个运行时对语义相同的规格得到不同哈希,完成事务因此拒绝 Feature Snapshot 并抛出 rectification_v5_feature_snapshot_mismatch
  • 修复:Python 生成跨服务 Calculation Spec 时将整数值的经纬度和时区规范化为整数,使其 JSON 数字表示与 TypeScript JSON.stringify 一致;评分算法和候选矩阵不变。
  • 验证:新增已知 TypeScript 哈希向量测试,修复前稳定失败、修复后通过;staging Case e2d3e1d2-efb0-461d-9914-f890bc2b8569 的 PostgreSQL 日志确认原始异常,使用同一规格重放确认 Python Feature Snapshot 哈希恢复为 Case 哈希。
  • 防复发:跨语言持久化指纹必须使用已知向量验证 JSON 数字规范化,不能只在各自语言内断言自洽。
  • 相关记录:BUG-087、BUG-092
  • 修复版本:rectification-v5-matrix-scoring-1(仅修复输入规范化,算法版本不变)

BUG-094 | V5 Agent 消息操作栏在 V4 面板切换后消失

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:生时校正 V5 Agent 对话、助手消息反馈与当前问题重新生成
  • 用户现象:助手消息气泡下不再显示点赞、点踩、复制和重新生成操作。
  • 触发条件:生时校正页面使用 RectificationV4Panel 渲染 V5 Agent 会话。
  • 根因:旧对话组件中的消息操作栏没有迁入 V4/V5 共用面板,同时新 V4 API 没有与当前语义问题绑定的重新生成命令。
  • 修复:复用现有消息操作栏样式,仅为 v5_agent 的稳定助手消息恢复操作;重新生成只重写当前已验证 Semantic Question Opportunity 的自然语言实现,并通过用户、Case 版本、当前目标和 action ID 原子校验,不重跑事件提取、候选评分、诊断或 Job。
  • 验证:组件资格与反馈互斥测试、V4 service 幂等重放与数据不变量测试、Renderer 安全回落测试、迁移契约测试,以及 staging 精确 SHA 部署和浏览器验收。
  • 防复发:测试锁定 V5 Agent 操作栏、仅当前问题可重跑、legacy/shadow 不启用新 Renderer、重跑不改变 turns/events/snapshots/profile 且 completed Job 不被改写。
  • 相关记录:BUG-090、BUG-091、BUG-092、BUG-093
  • 复发自:无
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1

BUG-095 | 生时校正“思考中”无法证明实际执行步骤且刷新后不可溯源

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:V4/V5 生时校正运行状态、历史助手消息、候选计算与诊断的测试可观测性
  • 用户现象:页面只显示“正在核对星盘信息……”动画,无法判断本轮实际执行了哪些阶段、工具和技法;任务结束或刷新后也无法回看。
  • 根因:Job 只保存一个会被后续步骤覆盖的当前 phase,不能还原阶段历史;已持久化的 Public Message 与 Agent Run 工具记录没有被 Case API 投影到对应 TurnUI 的 thinking 状态只是客户端进度动画,不是模型 reasoning,也不是服务端执行收据。
  • 修复:将“分析过程”定义为与 Turn 关联、可持久化和刷新后可恢复的服务端执行收据;只投影实际发生的阶段、工具调用和 allowlist 技法。供应商显式返回的 reasoning 内容只有通过服务端来源校验与安全过滤后才可作为可选摘要,缺失或不安全时直接省略,不伪造且不读取 hidden chain-of-thought。
  • 安全边界:不公开分数、权重、贡献矩阵、内部 ID/字段、候选分钟、工具参数或原始结果、Prompt、模型内部信息和用户敏感原文;D60 不展示且不驱动结论;未执行、不可用或仅供参考的技法不得显示为已执行。
  • 兼容边界:历史无收据记录继续读取;v4_legacyv5_shadow 保持原有用户可见回复,shadow 仅持久化新产物而不展示分析轨迹;completed-job replay、原子 completion、canConfirmExactMinute === false 和禁止自动写入 profiles.active_birth_time 保持不变。
  • 防复发:API 与组件测试必须锁定 Turn 关联、刷新恢复、运行中真实 phase、仅展示实际工具/技法、无安全 reasoning 时不补写摘要,以及旧记录、legacy、shadow 的兼容行为。
  • 相关记录:BUG-011、BUG-086、BUG-087、BUG-094
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1

BUG-096 | PostgreSQL 日期对象导致生时纠正 Case 创建误报资料格式错误

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:staging 自托管 PostgreSQL 出生资料读取、历史时区偏移补全与生时纠正 Case 创建
  • 用户现象:已完成出生资料的账号启动生时纠正时返回 400,并显示“出生资料或提交内容格式不正确”。
  • 触发条件:数据库驱动将 birth_date 返回为 JavaScript Date,且历史资料的 timezone_offset 为空、需要根据日期和时区 ID 补全。
  • 根因:共享 resolveMissingBirthTimezoneOffset() 只把非空字符串识别为出生日期;合法 Date 被当成缺失值,补全提前返回,随后严格出生资料解析以 Historical timezone offset must be resolved before parsing 拒绝请求。
  • 修复:共享时区补全 resolver 在读取出生日期时同时接受有效 Date 与既有字符串,并统一归一化为 YYYY-MM-DD;不放宽后续 Zod 校验,也不改变生时纠正计算规格。
  • 验证:路由回归夹具改为 PostgreSQL 实际返回形态的 Date;聚焦测试、前端全量测试、lint、TypeScript、生产构建和 Python V5 服务测试通过,staging 精确 SHA smoke 随本次发布执行。
  • 防复发:所有从 PostgreSQL 进入纯日期字符串合同的共享边界必须先覆盖 string | Date 驱动返回类型;Case 创建继续要求历史时区偏移在严格解析前完成。
  • 相关记录:BUG-076、BUG-091、BUG-095
  • 复发自:无
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-097 | 生时纠正分析状态停滞且教育事件被换词重问为迁居

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:V5 Agent 生时纠正运行状态、历史消息布局、Semantic Question Opportunity 排序与 Renderer 安全回退
  • 用户现象:任务实际已经经过“生成语义问题机会、选择下一步动作、生成安全回复”,页面运行中却一直显示“正在整理你刚才提到的经历”;完成后的“分析过程”显示在 Agent 正文下方;用户已经说明离家去外地上大学的年月后,下一问仍把同一经历换词问成搬到新城市或长期离乡。
  • 触发条件:Job 轮询返回新的 phase,但聊天消息继续读取首次 Case 响应中的旧 data.job;同时教育事件原文中的“离家、外地”命中迁居领域关键词并抬高 relocation 机会,Renderer 失败回退后直接使用带“以当前事件为时间参照”的模板。
  • 根因:Hook 同时维护 data.job 与独立 Job state,轮询只更新后者;恢复 processing Case 时 API 没有返回 active Job,刷新后无法继续轮询;setInterval 允许并发请求,较旧响应可能覆盖较新 phase;机会排序把零散地点词当成迁居主题信号,没有优先采用最新账本事件的已确认领域;迁居 fallback 和问题验证器没有禁止把当前事件改写成另一件事件继续追问。
  • 修复:轮询结果原子合并回 data.job,消息状态直接跟随服务端 extracting_evidence / planning_question / reasoning / renderingCase Store 和 API 为 processing Case 恢复最新 pending/processing Job;轮询改为单飞 setTimeout 并拒绝较旧响应;把持久化分析收据移动到 Agent 正文上方并保留正文下方操作栏;迁居关键词收窄为明确搬迁动作,最新事件主题加权以账本领域为准;所有新事件 fallback 明确询问另一件独立事件,Renderer 拒绝旧跨事件模板、同义重复和与所选机会领域不匹配的问题。
  • 验证:组件测试锁定分析过程、正文、操作栏顺序、连续 Job phase 更新及乱序响应不倒退;服务测试锁定 processing 刷新恢复 active Job 和 shadow 年精度 legacy 细化;Agent 测试锁定外地上大学不会提升 relocation、事件数组顺序不改变排序、旧模板与跨领域实现触发安全 fallback;完整前端测试、lint、TypeScript、staging 构建、Python V5 服务测试和 staging 精确 SHA smoke 随本次发布执行。
  • 防复发:UI 只允许一个 Job 真源,恢复 processing Case 必须携带 active Job,轮询必须单飞且单调更新;新事件机会不得把 anchor 当作待细化目标;主题信号优先来自已验证事件领域,地点词不能单独代表迁居;Renderer 必须同时验证“另一件事件”、所选领域语义与安全边界。
  • 相关记录:BUG-090、BUG-093、BUG-094、BUG-095
  • 复发自:BUG-093、BUG-095
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-098 | V6 公开候选门禁未明确 LODO、技法层级与独立 holdout 边界

  • 状态:resolved/partial
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:V6 Python 候选扫描、Candidate Snapshot 公开范围门禁、生时纠正 Skill 与参考 Skill 能力声明
  • 用户现象:现有说明容易把“支持某技法”、同一 Case 内的留一诊断和参考 Skill 方法论误读为已完成独立验证;缺失 KP_cusps 或 D60 也可能被错误当成公开范围的统一硬阻塞。
  • 根因:文档没有把真实实现链、LODO public gate、active-domain required/optional/reference-only 技法策略和参考 Skill 的未实现能力分开;LOEO/LODO 使用同一事件贡献矩阵做事后减项,不能替代 prospective independent holdout。
  • 修复:记录 V6 的真实链路为跨午夜兼容的逐分钟 Python 扫描、事件贡献矩阵、Snapshot、LOEO/LODO、date sensitivity、neighbor stability 与 candidate split;公开范围新增 LODO 稳定性门禁,并按活跃可评分领域判定 required layerKP_cusps 为 optional、D60 为 reference-only、未知层失败关闭;事件 provenance 仅用于审计且不参与加权。
  • 验证:本工作树的聚焦合同覆盖跨午夜分钟枚举、LODO 低于 0.8 拒绝公开范围、required layer 缺失阻塞、KP_cusps/D60 缺失不阻塞,以及旧 Snapshot 兼容;本记录不把这些回归误写成独立 holdout 验证。
  • Partial / deferredper-Case independent holdout 因缺少 prospective sticky partition 与 calibration contract 延期。当前 LOEO/LODO 只证明同一 Case 内的敏感性,不得伪称 prospective、independent 或 calibrated validation 已完成。
  • 安全边界:继续禁止手工 supports/conflicts 伪评分、任意外部仓动态加载、唯一分钟结论和自动写入 profiles.active_birth_time;参考 Skill 只作方法与审计参照,不成为第二评分真源。
  • 防复发:公开候选必须同时通过事件/领域覆盖、范围宽度、邻近分钟、LOEO、LODO、日期敏感性、计算规格和 required-technique 门禁;任何 holdout 完成声明必须先有稳定分区持久化与校准验收证据。
  • 相关记录:BUG-082、BUG-090、BUG-093、BUG-095
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1,独立 holdout 延期

BUG-099 | 新事件追问可能预设事件存在、换词重复且 Renderer 回退不可追溯

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:Semantic Question Opportunity 生成与排序、跨领域事件去重、Renderer 问题校验、持久化分析历史
  • 用户现象:新事件问题可能直接询问“哪次”经历而暗示该事件必然发生;提示缺少便于回忆但不限定答案的具体线索;与最新事件语义相同的内容可能换一个领域名称再次追问;Renderer 模型问题被接受、被拒绝或改由服务器 fallback 后,历史分析收据无法明确区分实际路径。
  • 触发条件:生成 ask_new_event 时仅依赖领域模板或缺失领域;跨领域候选与最新事件共享同一人物、行动或生活转折但没有语义重叠降权;Renderer 不可用、调用失败或问题未通过校验而进入 deterministic fallback。
  • 根因:问题政策尚未明确存在性询问、非穷举回忆线索数量和禁止虚构年龄/日期窗口;utility 合同没有钉死跨领域语义重叠 penalty;分析历史没有要求持久化 Renderer 校验结果与服务器 fallback 来源。
  • 修复要求:所有新事件问题先询问相关经历是否存在,不得预设发生;提供 2–5 个明确标注为示例而非穷举的回忆线索,并允许用户回答其他经历或表示没有;不得发明年龄、人生阶段或日期窗口。若候选与最新事件跨领域但语义重叠,必须在排序前降低 utility,足以判定为同一事件换词时不得再次提问。每次 Renderer 尝试必须在分析历史中留下安全的分类收据,区分模型问题通过校验、模型问题被拒绝,以及服务器 deterministic fallback,并记录不含原始 Prompt 或用户敏感文本的原因类别。
  • 验证:rectification-agent-v6.test.ts 覆盖存在性问题、具体回忆线索、退出方式、禁止虚构年龄/日期窗口和跨领域同事件降权;rectification-analysis-trace.test.ts 覆盖持久化分析历史中的模型安全校验与服务器 fallback 分类;完整前端测试 1123/1123 通过。
  • 安全边界:分析历史只记录阶段、校验结果、fallback 布尔值或安全原因类别,不记录模型 Prompt、原始候选文本、隐藏推理、内部评分、事件原文或出生资料。
  • 防复发:新事件问题和 Renderer 分析收据必须作为服务端合同测试,而不是只测试最终展示文案;领域名称不同不得绕过同一事件的语义去重。
  • 相关记录:BUG-087、BUG-095、BUG-097
  • 修复版本:birth-time-rectification-v6 / rectification-agent-v6-1

BUG-100 | Staging migration 账本中的遗失历史文件阻塞安全发布

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:staging migration check、exact-SHA 应用发布
  • 用户现象:生时纠正 V6 修复已经通过质量门并合入 main,但 staging 在切换镜像前报 migration file missing: 20260727010000_admin_users.sql,因此仍运行旧版本。
  • 根因:staging 的 append-only migration 账本包含 5 条已不在任何受审仓库历史中的支付/管理 migration 记录;现有 runner 要求每一条账本记录都对应当前文件,因此按顺序安全停止。
  • 修复:在 migration runner 中加入 5 条静态 retired migration 记录,逐条固定校验从 staging 只读账本核对到的 SHA-256;只有文件名和 checksum 同时完全匹配时才允许继续。checksum 漂移、其他遗失 migration 或非法文件名仍然失败关闭;不删除账本记录,也不动态信任数据库返回值。
  • 验证:新增无数据库单元测试覆盖全部 5 条 retired migration 正确 checksum 通过、任一错误 checksum 拒绝、未声明遗失 migration 继续拒绝;staging 发布仍须通过原 migration check、exact digest 和 exact SHA 门禁。
  • 防复发:历史 migration 文件不得从受审仓库删除;若必须兼容已遗失记录,只能用代码审查过的静态 filename + checksum,并保留 fail-closed 测试。
  • 相关记录:BUG-099
  • 修复版本:staging migration integrity compatibility

BUG-101 | 正常访谈被 Opportunity 模板与 Renderer 回退主导,事件种类在 Python bridge 丢失

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:V5 Agent 生时纠正常访谈、事件修订暂存、公开问题生成、Python 候选评分语义
  • 用户现象:模型虽然参与推理,但下一步焦点、问题类别和公开回复仍主要由服务器硬编码的 Builder、Opportunity 分类与正则 Renderer 决定;同一轮难以自然识别多件事件,关系开始、结束或变化进入 Python 评分后又退化为通用 relationship 领域。
  • 触发条件:正常 v5_agent 路径依次调用 buildQuestionOpportunities()runBoundedReasoner()renderPublicTurn();事件通过 TypeScript/Python bridge 时只传 domain,没有保留 canonical event_kind
  • 根因:Agent 只在服务器预先枚举的机会中选择,无法基于完整 Case Dossier 自主理解当前访谈焦点并生成下一句;公开回复失败时继续由领域正则模板接管。与此同时 Python legacy request 把领域值当作事件种类,抹平关系事件的 start/end/change 语义。
  • 修复:新增 Director 两阶段合同:服务器提供完整 Case DossierDirector 可在一轮提出多个 create/revise evidence proposal;服务器验证原文、日期、目标和 opaque ID 后生成 revisions 并重算评分/诊断,Director 再自主选择焦点并直接生成自然问题与公开回复。服务器继续控制 status、phase、snapshot/range gate、内部 ID、精确分钟与单问题安全边界;输出失败只允许同一 Director 做一次安全 repair,再进入通用 fallback。v5_shadow 保留 legacy 可见投影与确定性证据行为,只持久化 Director artifacts。Python bridge 与评分 trace 继续传递 canonical event_kind,并为 relationship start/end/change 保留可验证的最小区分。
  • 验证:TypeScript 相关套件 114/114 通过,其中 Director 专项 7/7TypeScript tsc --noEmit 与修改文件 ESLint 通过;Python tests/test_active_rectification_events.py 14/14 通过。
  • 安全边界:模型不得公开内部 ID、分数、贡献矩阵、工具信息或精确分钟;proposal 必须引用最新回答中的原文与日期文本,所有持久化 revision、候选重算、门禁判断和原子提交仍由服务器拥有。
  • 防复发:正常 Agent 路径不得重新依赖 Opportunity 枚举或领域正则决定访谈内容;Director 合同测试必须覆盖多事件提议、修订目标验证、拒绝/不知道后的换焦点、range gate、内部信息泄露与一次 repair;Python 测试必须断言 event_kind 从输入穿透到规则 trace。
  • 相关记录:BUG-095、BUG-097、BUG-099
  • 修复版本:staging

BUG-102 | Director Dossier 丢失早期语境、历史 Pending Evidence 与拒答领域

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:V5 Agent Director 的长期会话理解、拒答保护、未解析证据续接与候选诊断循环
  • 用户现象:Director 已经替代正常路径的 Opportunity/Renderer,但超过十二轮的会话只收到“更早还有 N 轮”,declinedDomains 固定为空,历史未解决 Pending Evidence 未进入本轮 Dossier;一次只读诊断不足时无法继续观察后再决定下一问。
  • 触发条件:Case 超过十二轮、用户曾拒绝某个问题领域、前序 Turn 留下未解决证据,或 Director 连续需要两类候选诊断。
  • 根因:Dossier Builder 使用计数占位代替早期问答摘要,并未从 Turn 账本派生拒答领域;Job claim 合同也没有携带未解决 Pending Evidence。Director 的诊断处理使用单次 if,与 Dossier 声明的一次预算绑定。
  • 修复:Dossier 现在保留最近十二轮原文,并把更早问答压缩为有内容的受限摘要;从历史 Turn 派生拒答领域;Memory/Supabase Job claim 加载未解决 Pending Evidence 并与本轮新增项一并交给 Director。只读诊断改为最多两次的有界循环,公开回复、数据库状态、候选范围门禁与原子提交仍由服务器控制。Prompt 版本升级为 rectification-director-v2
  • 验证:Director 回归测试覆盖早期语境、拒答领域、Pending Evidence 和两次诊断循环;TypeScript 类型检查、相关 ESLint 与目标测试通过。
  • 安全边界:Pending Evidence 仅作为私有 Dossier 输入,公开文本仍经过内部信息、精确分钟、单问题和候选范围门禁校验;工具循环保持只读且最多两次。
  • 防复发:Dossier 测试必须断言早期语境不是计数占位、拒答领域和未解决证据可见;诊断测试必须断言循环有上限且最终返回非诊断动作。
  • 相关记录:BUG-099、BUG-101
  • 修复版本:local follow-up

BUG-103 | Director revise 可跨事件覆盖既有 Event ID

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:V5 Agent 事件修订暂存、事件账本身份连续性与后续候选评分
  • 用户现象:模型可把“大学入学”的既有 Event ID 修订成“搬家”或其他无关事件,并把未受原文约束的 proposedSummary 写入账本。
  • 触发条件:revise proposal 引用真实 Target ID 和回答中的日期/Span,但声明了不同 Domain、Kind、Subject、Related Person,或 Span 与原事件语义锚点不连续。
  • 根因:服务器只验证 Target ID 存在、Span/日期来自最新回答,没有验证 Revision 的事件身份连续性;持久化 Summary 直接采用模型提议文本。
  • 修复:stageAgentEvidenceProposals() 仅接受 Domain、Kind、Subject、Related Person 与 Target 一致,且最新原文事件摘要仍命中 Target 摘要或原始文本的修订;不连续的提议转为 Pending Evidence,不覆盖原 Event ID。合法修订的 Summary 改用服务器从已验证 Source Span 提取的事件摘要,身份字段与 Scoreability 继续沿用 Target。
  • 验证:新增回归测试证明合法日期修订保留 Event ID 并使用原文摘要,跨领域且含虚构 Summary 的 revise 不产生 Revision、只产生 Pending Evidence。
  • 安全边界:Agent 仍可创建新事件;跨事件内容必须走 create,不能借 revise 篡改既有账本身份。
  • 防复发:Revision 测试必须同时覆盖合法日期更正和跨事件覆盖拒绝,不能只断言 Target ID 存在。
  • 相关记录:BUG-101、BUG-102
  • 修复版本:local follow-up

BUG-104 | Director 可覆盖拒答、日期简答失效、Pending 误关闭与旧候选快照越权

  • 状态:resolved
  • 首次发现:2026-07-30
  • 最近更新:2026-07-30
  • 影响面:V5 Agent Director/Orchestrator、事件 Revision、Pending Evidence 生命周期、Event Kind 评分边界、公开候选范围与回复安全校验
  • 用户现象:明确的“不想说/记不清/换一个”可能被模型覆盖回未解决并继续追问;仅回答月份、日期或时间段无法修订当前事件;自然纠正事件类型或人物会失败;历史 Pending Evidence 可能永久残留或被无关 Revision 错误关闭;旧 relationship_end 评分快照仍可能公开或接受;技术层名称可能出现在公开回复。
  • 根因:模型处置优先级高于服务器确定性关闭状态;日期 Revision 复用了创建事件的完整语义要求;修订合同没有区分日期修订与重新分类;Pending completion 没有原子关闭合同,也未按缺口类型验证修订;relationship_end 缺少独立评分规则却保留旧 scoreable Snapshot;公开文本过滤只覆盖部分技术层。
  • 修复:服务器关闭状态不可被 Director 覆盖,且关闭后禁止用空 Target ID 的澄清/冲突 Focus 重开原事件;Evidence operation 拆为 create/revise_date/reclassify/ignore,日期简答继承 Target 身份与缺失年份,显式纠正追加同 Event ID Revision,人物变化进入 pending_reviewV5 completion 增加 ownership/target/replay 安全的 Pending resolution,并且 date_unresolved 只在日期确实变化后关闭;relationship_end 强制 pending_review,迁移清除由旧 scoreable 关系结束事件支持的最新 SnapshotDossier 与 acceptRange 双重拒绝旧快照;公开回复禁止全部 D-number 技术层、KP、Vimshottari、Narayana、Shadbala 与 Ashtakavarga,只允许公开已批准 Snapshot 的首个范围 Cluster。
  • 验证:Director、V6 Agent、V4 Domain/Service/Migration 与 Python Event Engine 回归覆盖明确拒答、空 Target ID 绕过、日期简答、事件重新分类、Pending 原子关闭与原因匹配、旧快照失效、技术层泄漏、多事件提取和 Event Kind trace;真实 PostgreSQL migration 应用测试通过。
  • 安全边界:服务器继续拥有事实验证、事件身份、Scoreability、候选范围和持久化权限;Agent 只提出结构化计划。禁止公开内部 ID、评分、技术 trace、代表分钟或第二 Cluster,禁止自动写入 Profile 出生时间。
  • 防复发:拒答保护必须有 Orchestrator/Director 级回归;Pending resolution 必须验证缺口已被对应 Revision 补齐;评分政策变化必须同时处理历史 Snapshot;公开技术层过滤按完整技术命名空间测试。
  • 相关记录:BUG-099、BUG-102、BUG-103
  • 修复版本:birth-time-rectification-v6 / rectification-director-v2

BUG-105 | 候选差异未驱动下一问、Event Kind 未进入评分且不可回答 Case 被错误复用

  • 状态:resolved
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V6 Agent 问题排序、V5 Python 贡献矩阵、V4 Case 创建/复用、Staging 新建校正后的首次回答
  • 用户现象:诊断已经显示候选簇在特定技术层存在差异时,访谈仍可能继续做通用领域轮询;关系确立与关系变化在评分中缺少语义差异;新建校正后首次提交回答可能返回“当前没有待回答的问题,请刷新后重试。”;已知主体的非评分事件还可能被错误追问“发生在谁身上”。
  • 触发条件:候选分歧只携带技术层而没有可行动的缺失证据;评分仅按 Domain 汇总;创建 Case 时复用 paused、没有 Active Job 的 processing、或没有 currentQuestionawaiting_answer Casepending_review 被等同于主体不明确。
  • 根因:Director Dossier 缺少 Candidate Contrast Packet,问题排序对同领域历史提问施加通用重复惩罚;共享评分入口没有按 Event Kind 和真实命中 Rule ID 调整贡献;Service、Memory Store 与 Supabase RPC 的可恢复 Case 条件不一致且没有算法版本隔离;主体澄清条件错误地依赖整个 Scoreability 状态。
  • 修复:共享诊断层按主候选与次候选的静态特征差异计算区分层、按事件贡献差值计算相关事件,再生成不暴露候选分钟的 Candidate Contrast Packet,用 Cluster Rank、区分层、相关事件和缺失 Event Kind 驱动问题机会;候选驱动的不同 Event Kind 不受通用同领域重复惩罚,并优先于无诊断依据的领域轮询,非评分事件不作为 Candidate Split Target;共享 Python 贡献矩阵按真实 Rule ID 对 relationship_startrelationship_change 应用不同 Profile,零 Activation 不凭空加分,relationship_end 继续 Fail Closed;算法版本升级到 rectification-v5-matrix-scoring-2;三层 Case 复用统一为仅恢复可回答 Case 或拥有 Active Job 的 Processing Case,且必须匹配算法版本;主体澄清仅针对 subject=other
  • 数据库:新增向前迁移,更新新 Case 默认算法版本、保留 range-scoring v1 与 matrix-scoring v1 历史 Candidate Snapshot 解析兼容、废弃未完成的旧算法 Case、标记其活跃 Job 为 Stale,并重建带可恢复状态和算法版本检查的 Case 创建 RPC;不写入 Profile 出生时间。
  • 验证:真实用户回放覆盖复读、大学入学并离家、开始工作、分手和负债,断言关系结束保持 pending_review、不会把大学入学换词重问为迁居、D9 内部差异转为关系确立/状态变化的自然存在性问题、公开问题不泄露技术层或候选分钟,并允许“没有、不知道、不想回答、换方向”;Service 回归覆盖 Paused、孤儿 Processing、空问题 Awaiting Answer、新 Case 首次回答与旧算法 Case 替换;Python 回归覆盖关系 Event Kind 反转候选排序及零 Activation。
  • 安全边界:Candidate Contrast 只在服务器内部使用,不向用户公开技术层、分数、代表分钟或第二候选簇;Agent 不能把 relationship_end 变为可评分事件,不能确认精确出生分钟,也不能自动写入 Profile。
  • 防复发:完整回放测试必须同时覆盖事件账本、问题排序、退出方式和公开文本;评分版本变化必须同步 Case 复用、数据库默认值和历史快照兼容;Case 创建测试必须随后真实调用一次 Answer。
  • 相关记录:BUG-101、BUG-102、BUG-104
  • 修复版本:local follow-up / rectification-v5-matrix-scoring-2

BUG-106 | Director 缺少统一工具 Observation 与可更新 Dossier

  • 状态:resolved
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V8 生时纠正 Director Runtime、Skill 决策策略、Agent Run 诊断轨迹和未完成 Case 版本元数据
  • 用户现象:Director 虽然能够请求只读诊断,但 Case、候选扫描和证据缺口仍由固定流程一次性塞入 prompt;取得结果后最多再读两次诊断,Dossier 本身不随观察更新,前端处理中也只显示单行状态。
  • 触发条件:最终决策需要依次读取案件、候选差异、证据缺口和某项稳定性诊断才能决定下一问;或模型重复请求本轮已经读取过的静态工具。
  • 根因:Director 合同只有 request_diagnosticDossier 没有 revision、Observation 和候选假设;Runtime 只累计旁路诊断数组,前端没有把既有 Job phase 投影成稳定的分析步骤。
  • 修复:新增服务器拥有的 case_readcandidate_scanevidence_gapdiagnostic_read 统一只读工具;最终规划首轮只提供 Runtime 与工具可用性,不再预载这些工具拥有的完整数据。每次调用按需暴露当前权威服务器投影、生成结构化 Observation、递增 in-run Dossier revision,并把更新后的 Dossier 交回同一 Director。循环最多 10 轮,静态工具按工具+诊断去重,越界后只允许一次强制收敛;前端复用现有 Job phase 显示“整理事件 / 比较候选 / 确定验证方向”三步进度。Skill/Prompt 升级为 birth-time-rectification-v8 / rectification-director-v4,向前迁移只推进未完成的 Agent Case,不改历史完成结果、评分算法或 Profile 出生时间。
  • 验证:Director 回归覆盖 case_read → candidate_scan → evidence_gap → diagnostic_read → final、每轮 Dossier revision/Observation 回灌、重复工具不重复执行、一次强制收敛和单问题最终动作;前端回归覆盖三个公开分析步骤;V8 迁移合同覆盖默认版本、未完成 Agent Case 范围和禁止写入 active_birth_time
  • 安全边界:服务器继续拥有事件事实、工具结果、候选 Snapshot、公开范围门禁、持久化与最终输出验证;Observation 只存在于本次 Agent Run 的内存 Dossier,不成为第二业务真源,也不向前端公开原始结果、内部技术层、候选分钟或评分。
  • 防复发:不得把每次诊断后的 prompt 改回“必须立即结束”;增加新工具时必须定义 observation 是否会在同一 Turn 变化、重复调用策略和总轮次上限。
  • 相关记录:BUG-101、BUG-102、BUG-105
  • 修复版本:birth-time-rectification-v8 / rectification-director-v4

BUG-107 | 开放问题收集年份事件后先切换新事件、下一轮再回访旧事件

  • 状态:resolvedstaging pending deployment
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V8 Director 最终规划、确定性 fallback、年份/季度精度事件的访谈连续性
  • 用户现象:开放问题收到一件只有年份的本人事件后,系统先用固定句式要求另一件经历;收到第二件月份明确的经历后,又突然回头追问第一件事件的月份,表现为事件焦点来回跳转。
  • 触发条件:上一问没有 questionTargetEventId,本轮新建的可评分事件只有 yearquarter 精度,同时 Director 调用失败、超时或计划被拒绝而进入 fallback;即使 Agent 正常返回,旧校验也未禁止它在未闭合宽日期事件时切换目标。
  • 根因:Orchestrator 只把上一问的 questionTargetEventId 传入最终 Dossier,没有把本轮新建且日期仍宽的事件提升为当前目标;Director 因而看见 targetDisposition=not_applicable。确定性 fallback 随即输出通用“再说一件经历”模板,后续 Director 才从完整账本重新发现旧事件缺月份。最终计划校验也只保护拒答目标,没有保护 unresolved / answered_other_event 目标不被放弃。
  • 修复:最终规划优先保留服务器 reconciliation 的未回答目标;否则把本轮新建的 year / quarter 可评分事件设为 unresolved 当前目标。计划校验拒绝在该目标未闭合时切换到无关新事件;fallback 按目标真实日期精度追问月份或时间段,并绑定真实 Event ID,而不是继续输出通用新事件模板。
  • 验证:Director 回归断言未闭合宽日期目标不能被新事件问题放弃,强制 fallback 会锚定该事件并请求 monthOrchestrator 回放断言开放问题收到年份事件后,下一问立即补月份且 targetEventId 指向新事件。相关 Director/分析轨迹测试、TypeScript 和 ESLint 均通过;完整前端测试 1157 项通过。
  • 防复发:事件连续性由服务器的 currentTargetEventId + targetDisposition 约束,不能只靠 Agent prompt;新事件的必要日期精度应在切换话题前闭合,用户明确“不知道/跳过/换方向”时才解除目标。
  • 相关记录:BUG-092、BUG-097、BUG-106
  • 修复版本:local / pending release

BUG-108 | V8 丢失事件承接说明且服务器领域模板覆盖 Agent 自主选题

  • 状态:resolvedstaging pending deployment
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V8 Director 最终回复、Candidate Contrast、确定性 fallback、前端可见问题历史与处理中动画
  • 用户现象:用户提供具体经历后,界面只显示下一问,没有显示 Agent 对新线索的承接和公开安全的价值说明;后续问题又容易落入预写的教育、迁居、关系、事业、财务、健康模板,甚至把验收案例中的“大学、实习、搬家”等措辞当成产品脚本。处理中还显示固定三步 checklist,而不是随真实 phase 变化。
  • 触发条件:v5_agent 最终规划生成了 publicReply,但持久化出口只保存裸问题;同时 Opportunity Builder 通过 domainPolicy、关键词、人工 recallEase/privacyCost 和领域 fallbackPrompt 预先决定候选问题,Renderer 再用领域词表校验模型输出,模型异常时 Director fallback 直接采用人工排序结果。
  • 根因:公开消息和可见问题使用了两个出口;更关键的是服务器同时承担了事实约束和访谈选题,形成“Builder 人工选题 → Director 采用排名 → Renderer 关键词裁决”的双重控制,Agent 实际只能改写模板。
  • 修复:持久化完整的 acknowledgement、公开安全的证据价值说明、limitation 与唯一问题;删除固定领域 domainPolicy、领域 recall cues、领域关键词匹配和 V8 Opportunity 排名传参。Candidate Contrast 仅提供完整、顺序稳定的事实观察,Director 根据完整账本、拒答记录、候选差异和只读工具自主决定方向与措辞。无当前目标且模型失败时使用 domain:null 的领域中立恢复问题;有未闭合目标时服务器只保护目标连续性并询问必要事实。保留拒答/隐私保护、事实来源验证、单问题、范围门、目标锚点和技术信息过滤。前端移除固定分析 checklist,并把真实 Job phase 映射到 thinking-orbs 状态。
  • 验证:回归覆盖 Builder 不再生成六领域问题、Candidate Contrast 返回全部可用缺口而不替 Agent 选题、Renderer 接受不在测试样例中的自然方向、Agent 自主问题不被领域 fallback 替换、拒答领域不能被重新打开、大学与研究院实习回放在模型失败时保持领域中立。npx tsc --noEmit、98 项聚焦测试、1160 项完整前端测试、touched-file ESLint 与 git diff --check 均通过。
  • 防复发:服务器只提供事实、能力和禁止项;正常 V8 选题与措辞归 Agent。测试案例只能验证不变量和复现回归,不能通过词表、权重或预写问题进入生产决策链。
  • 相关记录:BUG-105、BUG-106、BUG-107
  • 修复版本:staging branch / deployment manifest records exact SHA

BUG-109 | 生时校正模型文案可越过公开事实边界

  • 状态:resolved
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V5 Director 公开回复、下一问生成与手动重新生成
  • 用户现象:模型可能直接追问、遗漏服务器承接说明,或把未经计算的候选优劣写入公开问题。
  • 触发条件:Director 返回自由文案,或历史 selectedOpportunity 进入问题重新生成路径。
  • 根因:公开回复和问题 wording 曾由模型直接提供;历史兼容分支还可绕过新 focus 合同调用 renderer。
  • 修复:Agent 只选择结构化 focus;服务器根据事件账本、能力矩阵和 focus 生成承接、方法说明及单一问题;历史 selectedOpportunity 先转换为 focus,所有重新生成路径共用同一服务器 renderer。
  • 验证:Director、analysis trace、V4 service 聚焦测试 52/52TypeScript、ESLint 通过;前端全量测试 1164/1164。
  • 防复发:回归测试覆盖模型伪造候选结论、手动 regenerate 以及历史 selectedOpportunity 持久化路径。
  • 相关记录:无
  • 复发自:无
  • 修复版本:待发布

BUG-110 | 月份简答未继承目标事件年份导致重复追问并暂停

  • 状态:resolvedlocal
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V5 生时校正的目标事件日期补充、Director fallback 与下一问生成
  • 用户现象:已有“2016 年离家去外地上大学”事件时,用户回答“9 月”后没有生成同一事件的新 revision;系统仍把目标视为 unresolved,重复月份问题,随后因 question_repeated 进入暂停。
  • 触发条件:当前问题绑定一个仅有年份或季度精度的事件,用户只回答月份、月日、半年或月份区间,且 Agent 不可用或返回重复的临时问题。
  • 根因:确定性 reconciliation 只接受答案中重新出现完整年份的日期;Evidence 阶段又提前校验临时公开问题,导致有效证据提议可能被重复问题校验一并拒绝。
  • 修复:复用目标事件已有年份补全局部日期回答,成功后追加同一 Event ID 的 revision 并关闭目标;Evidence 阶段只验证证据和 target disposition,公开问题只在 final 阶段验证;fallback reason 同时保留原始异常和二次拒绝原因。
  • 验证:回归覆盖“2016 年事件 + 9 月 + Agent 强制不可用”的完整 Orchestrator 流程,确认生成 2016-09 revision、无 pending、下一问不再绑定原事件且状态保持 awaiting_answerDirector 测试确认记录 fallback_rejected:question_repeated
  • 防复发:单元测试分别锁定确定性日期继承、Evidence 阶段边界、fallback 原因和完整两轮回放。
  • 相关记录:BUG-104、BUG-107、BUG-109
  • 复发自:无
  • 修复版本:local / pending release

BUG-111 | Director 校验器覆盖 Agent 公开回复且复合事件缺少公开语义

  • 状态:resolvedlocal
  • 首次发现:2026-07-31
  • 最近更新:2026-07-31
  • 影响面:V5 Director 公开承接、方法说明、下一问、手动重新生成与复合事件语义
  • 用户现象:Agent 已生成自然承接和下一问时,服务器仍会替换为固定模板;“离家去外地上大学”只暴露教育语义,公开解释无法同时说明教育与迁居层面。
  • 触发条件:最新事件同时包含多个可核对维度,或 Director / 手动 regenerate 返回合规的自然文案。
  • 根因:最终计划校验器同时承担验证和重写职责;事件账本只暴露单一主评分领域;手动 regenerate 路径完全忽略模型输出并直接调用服务器问题模板。
  • 修复:事件账本为同一事件增加只读 publicSignals,保留一个主评分身份并补充公开 secondary signals,不新增 Event、不重复计分;最终校验器只验证 grounding、技术边界、候选结论、隐私、单问题、重复问题和目标连续性,不再覆盖合规 Agent 文案;手动 regenerate 改为 Agent 生成、服务器两轮安全校验,不合规后才使用确定性 fallback。
  • 验证:回归覆盖“离家去外地上大学”只保留一条事件但公开 education + relocation、D24 + D4 合法 grounding、家人健康不投射为本人 D30、合规 Agent 文案原样保留、未 grounding 技法与候选结论被拒绝、手动 regenerate 的 repair 与 fallback。
  • 防复发:公开语义只解释用户原话中已存在的复合信号;服务器继续拥有事件身份、评分、事实 grounding 和安全门,正常措辞与提问归 Agent。
  • 相关记录:BUG-107、BUG-108、BUG-109、BUG-110
  • 复发自:BUG-109
  • 修复版本:local / pending release

BUG-112 | 活动旧 Case 静默阻止新版 Agentic 生时校正

  • 状态:resolvedstaging pending deployment
  • 首次发现:2026-08-01
  • 最近更新:2026-08-01
  • 影响面:生时校正前端入口、V4 面板、V4 Reasoner 模型选择与运行信息
  • 用户现象:账户存在未结束的旧 Case 时,页面始终进入 /api/rectification/v4/cases/*;没有切换新版 Agent 的入口,模型选择器也不影响本轮 Reasoner,fallback 原因与部署版本不可见。
  • 触发条件:打开生时校正时 loadActiveRectificationV4() 返回活动 Case。
  • 根因:入口用 existing ? "v4" : "agentic" 静默分流;UI 未调用已有 abandon()Reasoner 只读取 Case 固定模型;API 未返回最新 Agent Run 的安全运行摘要。
  • 修复:活动旧 Case 改为显式二选一;进入新版前先结束旧 Case;V4 面板增加同一切换操作;本轮 Turn 模型优先传给 Reasoner并记录实际模型;Case API 只公开最新运行的 mode、model、skill、deployment SHA 与 fallback code。
  • 验证:frontend/tests/conversational-rectification-component.test.tsfrontend/tests/rectification-v4-service.test.ts
  • 防复发:入口合同禁止恢复静默 V4 分流;服务测试锁定本轮模型优先级与 runtime trace。
  • 相关记录:BUG-085、BUG-086、BUG-111
  • 修复版本:local / staging pending deployment

BUG-113 | 新版 Agentic 生时校正进入会话后不自动生成首次引导

  • 状态:resolved
  • 首次发现:2026-08-02
  • 最近更新:2026-08-02
  • 影响面:生时校正首页入口、Agentic 对话首次挂载、首次可见引导
  • 用户现象:进入“生时校正”后只创建普通 birth_time_rectification Session,页面保持空白;网络中没有 POST /api/rectification/agent,必须由用户先输入内容才会触发 Agent。
  • 触发条件:账户没有需要继续的旧 V4 Case,入口直接选择新版 Agentic 生时校正。
  • 根因:Agentic MVP 只实现了用户提交消息后的 send(),没有迁移旧 V4 在页面挂载时自动启动首次 Agent Turn 的交互契约;后续入口改为默认进入 Agentic 后,这个遗漏被直接暴露。
  • 修复:Agentic 对话首次挂载时只发送一次隐藏的内部启动指令,复用现有 /api/rectification/agent 流式路径;界面立即显示 assistant thinking,首条可见说明与问题继续由 Agent 生成,内部指令不渲染为用户消息。
  • 验证:组件回归测试锁定一次性挂载启动、隐藏内部指令和 Agent endpoint 调用入口;前端测试与 lint 覆盖修改文件。
  • 防复发:任何替换生时校正入口或会话实现的改动,都必须保留“用户无需先发消息即可收到 Agent 首次引导”的挂载契约。
  • 相关记录:BUG-085、BUG-112
  • 修复版本:Agentic web opening auto-start

BUG-114 | Agentic 生时校正启动指令伪装成用户消息且未知时间被误判为资料缺失

  • 状态:resolved
  • 首次发现:2026-08-03
  • 最近更新:2026-08-03
  • 影响面:首页生时校正入口、/api/rectification/agent 首次启动合同、出生资料门、候选范围与确认写入安全门
  • 用户现象:进入生时校正后浏览器发送一段“用户刚进入生时校正会话……”的隐藏 message;服务端随后返回“出生日期、时间或出生地点资料不完整”。同时产品入口仍可能恢复或创建 V4 Case,与当前 Agentic 工具链并存。
  • 触发条件:用户从首页进入生时校正;资料使用合法的“只知道时段”或“完全不知道时间”声明,或客户端与服务端出生资料状态不同步。
  • 根因:BUG-113 用伪装成用户消息的字符串补上自动启动,没有建立服务端拥有的 opening operation;产品 wrapper 仍保留 V4 active-case 分流;Agentic profile loader 又把没有具体分钟一律当成缺失,因此合法的不确定时间无法进入最新流程。
  • 修复:产品 wrapper 只挂载 AgenticRectificationChat,不再调用或恢复 V4 Case;首次请求改为 action: "opening",服务端把 opening context 注入 Agent Turn,客户端不再发送或渲染隐藏用户指令;首页在创建 Session 前复用 onboarding 资料门,服务端以 profile_incomplete 明确回退;period_only 使用已声明时段,unknown 使用 00:0023:59,跨午夜范围保持原样;gate 返回服务端候选范围,score、diagnostics、features、confirm 拒绝 Agent 自行发明范围,全天宽范围 scan 延后而不伪造中午出生时间。
  • 验证:Agentic 入口、Session、工具、首页入口与组件合同测试覆盖无 V4 产品分流、opening operation、资料回退、时段/未知时间、跨午夜、宽范围降级、候选范围一致性和确认写入门;前端完整测试、lint、build 与 staging 真实 smoke 随本次发布执行。
  • 数据边界:仅废弃 V4 产品入口,历史 V4 代码与数据暂时保留,不在本次发布中做破坏性删除或迁移。
  • 防复发:自动首轮必须是服务端明确 operation,不得伪装成用户文本;“不知道具体分钟”是合法资料状态,不得等同于资料缺失;所有评分与确认工具只能使用服务端候选范围。
  • 相关记录:BUG-112、BUG-113
  • 复发自:BUG-113
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-115 | ISO 出生日期被 Agentic 资料门误判并触发 opening 重试循环

  • 状态:resolvedlocal
  • 首次发现:2026-08-03
  • 最近更新:2026-08-03
  • 影响面:POST /api/rectification/agent、首页账户资料重新加载、Session 自动恢复、GET /api/account 请求频率
  • 用户现象:账户接口已返回完整出生日期、时间线索和地点,Agent opening 仍返回 profile_incomplete;页面随后重复请求 /api/account/api/rectification/agent。部署首轮修复后,刷新页面还会错误回到“先完成出生资料”。
  • 触发条件:数据库驱动把出生日期投影为 YYYY-MM-DDT00:00:00.000Z,同时当前活动 Session 是 birth_time_rectification
  • 根因:Agentic profile loader 和首页 readProfile() 都把持久化日期直接交给只接受纯 YYYY-MM-DD 的资料完整性校验;首轮只兼容了账户 JSON 中的 ISO 字符串,但 staging 自托管 PostgreSQL 数据层在 Agent loader 内实际返回 JavaScript Date,因此服务端仍误判 missing_birth_date。失败回调清空 rectificationSessionId 却未设置现有的自动恢复暂停状态,resume effect 又会立即重新挂载聊天并再次发送 opening。
  • 修复:共享持久化日期规范化函数统一接受合法 Date、ISO 字符串和纯日期字符串,由 Agent loader 与首页账户重新加载共同复用;服务端资料失败时先设置 rectificationError 暂停自动恢复,资料成功保存后再清除暂停状态。
  • 验证:staging 只读诊断确认目标账户的 birth_date 在服务端为 Date,时间、地点和误差字段类型均有效;回归覆盖 PostgreSQL Date、数据库 ISO 日期、非法日期,以及 profile failure 在清空 Session 前设置自动恢复暂停状态;完整前端测试 1200/1200、lint 0 error、production build 通过。
  • 防复发:数据库日期边界不得假设唯一 JavaScript 序列化形态;所有从账户持久化资料进入完整性校验的路径必须先走同一规范化函数;任何自动挂载请求的失败回调都必须先阻断对应的自动恢复条件。
  • 相关记录:BUG-016、BUG-114
  • 复发自:BUG-114
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-116 | Agent 工具步骤耗尽后静默完成且校正对话刷新即丢失

  • 状态:resolved(local,空流修复已先部署)
  • 首次发现:2026-08-03
  • 最近更新:2026-08-03
  • 影响面:POST /api/rectification/agent、Agentic 生时校正消息持久化、首次 opening、余额显示与刷新恢复
  • 用户现象:提交新的人生事件后接口只返回 {"type":"done","emitted":false},页面没有 Agent 回复;刷新页面后此前校正对话全部消失,并再次自动发送 opening、再次预扣咨询点数;页面余额可能保持旧值,让一次请求看起来像多次扣费。
  • 触发条件:Agent 在默认步骤上限内连续调用 rectification-* 工具但没有剩余步骤生成公开文本;或 Agentic 校正组件卸载/刷新,而 chat_sessions.messages 仍为空。
  • 根因:Mastra 默认步骤上限不足,路由又把空 textStream 当作正常完成;新版组件只把消息保存在 React 本地状态,没有复用现有 chat_sessions 持久化边界,自动 opening 也只检查本次组件实例的 ref;新 Session 还可能在数据库创建完成前挂载 Agent;请求完成后没有刷新账户余额。
  • 修复:Agent 步骤上限提升为 8,解析后仍无可见文本时返回明确 error 并退款,不再发送 done false;请求绑定当前用户的 birth_time_rectification Session,成功回复先原子更新完整消息再发送 done 并完成扣费;已有持久化消息拒绝重复 opening;客户端从 Session 初始化、成功后同步首页状态并刷新一次账户余额,未收到持久化成功的 done 时移除临时 Assistant;新 Session 先创建成功再挂载 Agent,并按 Session key 重建本地对话状态。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts 覆盖多步公开回复、空流退款合同、Session 归属与写回、刷新抑制 opening、失败流清理、新 Session 创建顺序;完整测试、lint、build 与 staging 真实刷新/扣费 smoke 随本次发布执行。
  • 数据边界:复用现有 chat_sessions.messages,不新增平行对话存储;不从用户粘贴内容擅自回填旧 Session;不修改身份、credits 历史或出生资料。
  • 防复发:公开回复必须同时满足“可见文本 + Session 持久化成功”才能发送完成事件;自动 opening 必须以服务端 Session 历史为准,不能只依赖组件内存。
  • 相关记录:BUG-113、BUG-114、BUG-115
  • 修复版本:空流修复 e65c8eeda2ff5916f88f18dd345c02beff045e8b / Session 持久化待本次 staging 发布

BUG-117 | 用户采纳最强候选后无法保存为平台排盘时间

  • 状态:resolvedlocal
  • 首次发现:2026-08-04
  • 最近更新:2026-08-04
  • 影响面:Agentic 生时校正候选结果、个人资料出生时间、后续咨询排盘时间
  • 用户现象:04:55 已是最强候选,用户多次明确表示“就用 04:55”,但 Agent 因唯一分钟确认门未通过而拒绝保存,个人资料和后续排盘仍未使用该时间。
  • 根因:系统把引擎候选、用户采纳和引擎唯一确认压缩成单一 confirmed 状态;没有可持久化的候选身份和用户采纳边界。
  • 修复:引入 candidate / accepted / confirmed 三态;服务端持久化候选身份、相对支持度、Session 所有权和 Profile 基线;新增 service-role 原子采纳 RPC;前端展示候选卡并允许用户采用;accepted 接入个人资料和全平台排盘。
  • 数据边界:相对支持度仅表示本次候选间的归一化比较,不是统计概率;保留 reported_birth_timeaccepted 不冒充引擎唯一确认。
  • 安全边界:RPC 校验用户、Session、结果身份、有效期、候选成员、最新结果和 Profile 基线;出生申报资料变化使旧候选失效;采纳不计费。
  • 验证:TypeScript 通过;聚焦测试 90/90;完整测试 1221/1221lint 0 error、3 个既有 warningproduction build 通过;本地 PostgreSQL 验证 04:55 写为 accepted、保留 05:00 reported time、重复采纳幂等,并验证出生申报时间变化会使结果失效且拒绝再次采纳。
  • 相关记录:BUG-113、BUG-114、BUG-115、BUG-116
  • 修复版本:待提交与发布

BUG-118 | 候选已落库但确认卡不显示,VedAstro 未执行被误述为未通过

  • 状态:resolvedlocalpending deployment
  • 首次发现:2026-08-04
  • 最近更新:2026-08-04
  • 影响面:Agentic 生时校正候选 SSE、self-hosted PostgreSQL 查询兼容层、确认门公开语义
  • 用户现象:rectification-confirm 已返回并持久化 selection_allowed=true 的候选时间,但页面只显示 Agent 文本,不显示候选确认卡;Agent 同时把 VedAstro 未执行、邻近分钟诊断和留一事件诊断混写成确认门未通过。
  • 根因:候选恢复查询调用 .gt("expires_at", now),而 staging 使用的 LocalPostgresQueryBuilder 未实现 gt,路由捕获读取异常后仍发送完成事件;外部验证本身只有在本地候选满足事件数、领域数、窄区间、唯一领先和必需层完整时才执行,missing_mandatory_layers 会使其保持 not_evaluated,并非 VedAstro 调用失败。邻近分钟与留一事件在 technique contract 中仅为诊断项。
  • 修复:在共享本地 PostgreSQL query builder 中实现参数化 gt 过滤;保留现有候选卡与 SSE 协议不另起状态;确认工具显式返回外部验证是否已调用、状态和原因,并要求 Agent 区分 not_evaluatedfail,不得把诊断项描述为硬阻塞。
  • 验证:真实 local PostgreSQL business client 回归覆盖未过期候选读取;Agentic 工具回归覆盖 not_evaluated 映射为 external_validation_invoked=falseSession、entry、candidate persistence 聚焦测试通过。
  • 防复发:self-hosted query builder 新增 Supabase/PostgREST 链式操作时必须由真实 PostgreSQL fixture 覆盖;公开文案必须按 external_engines.status 区分未执行、失败和通过。
  • 相关记录:BUG-116、BUG-117
  • 修复版本:待本次 staging 修复提交与部署验收

BUG-119 | 候选采用覆盖兼容出生时间且个人资料不显示双时间记录

  • 状态:resolvedlocalpending deployment
  • 首次发现:2026-08-04
  • 最近更新:2026-08-04
  • 影响面:Agentic 生时校正候选采用、个人资料出生时间展示、账户资料刷新、后续排盘时间
  • 用户现象:采用候选 05:06 后,账户虽然返回 active_birth_time=05:06,但个人资料仍只展示初始化填写的 05:00;数据库兼容字段 birth_time 同时被改成 05:06,导致“原始填报”与“校正采用”语义混在一起。
  • 根因:候选采用 RPC 主动把 active_birth_time 和兼容字段 birth_time 同时写为候选时间,旧 guard_birth_time_journey() 触发器还会双向镜像这两个字段;客户端 refreshAccount() 只刷新账户对象,没有同步个人资料展示使用的独立 profile state。
  • 修复:新增向前迁移解除 birth_time / active_birth_time 双向镜像,候选采用只写服务端拥有的 active_birth_time,并修复既有 Agentic 采用记录;保留 reported_birth_time 作为用户原始填报。账户刷新同步 profile 但不覆盖正在编辑的 draft;个人资料同时展示“当前排盘时间”和“原始填报时间”,候选卡明确采用边界并移除易被误解为概率的进度条。
  • 数据边界:reported_birth_time 是原始填报,active_birth_time 是平台当前排盘时间,birth_time 仅保留旧系统兼容用途;accepted 是用户采用,不等于引擎唯一确认。后续咨询继续读取 active_birth_time
  • 验证:聚焦账户、迁移、Agentic UI 合同测试 38/38;本地 PostgreSQL 完整迁移与业务测试通过,验证采用后 active_birth_time=04:55birth_time_status=acceptedreported_birth_time=05:00birth_time=null
  • 相关记录:BUG-117、BUG-118
  • 修复版本:待提交与发布

BUG-120 | Agent 仍在追问事件时过早显示候选采用卡且采用后无法改选

  • 状态:resolvedlocalpending deployment
  • 首次发现:2026-08-04
  • 最近更新:2026-08-04
  • 影响面:Agentic 生时校正确认门、候选卡展示时机、候选采用交互与数据库原子写入
  • 用户现象:Agent 回复仍在要求补充事件或确认日期时,页面已经显示三项候选并可立即采用;候选采用后所有选项被禁用,无法在同一批有效候选中改选。
  • 触发条件:确认工具取得至少三条事件、覆盖两个领域且返回候选,但引擎仍为 continue_rectification;或用户已经采用当前结果中的一个候选。
  • 根因:确认工具仅用事件数、领域数和候选存在性推导 selection_allowed,没有区分“继续收集证据”与“结束收集并邀请选择”;Agent 合同未禁止同轮追问和提供采用;前端与 RPC 又把首次采用误当成不可变终态。
  • 修复:rectification-confirm 新增显式 offer_selection,继续追问时必须为 false,仅在用户要求现在选择或本轮唯一下一步是选择候选时为 true;引擎真正通过唯一分钟确认门时仍自动允许确认。候选卡改为桌面端一行三列、移动端横向滚动,说明候选来自当前事件、可继续补充事件并重新计算;采用后保留其他候选可点击。向前迁移允许在候选结果仍有效且 Profile 基线未漂移时原子改选。
  • 数据边界:继续补事件不会把当前候选冒充最终结果;相对支持度不是统计概率;改选只更新 active_birth_time 和采用记录,不覆盖 reported_birth_time,也不写兼容字段 birth_time
  • 验证:Agent 工具与入口合同聚焦测试 37/37;真实本地 PostgreSQL 业务测试通过 04:55 -> 05:07 改选并保持 reported_birth_time=05:00birth_time=nullTypeScript、聚焦 ESLint 与 production build 通过;桌面端三列和移动端横向滚动截图已完成视觉检查。
  • 防复发:任何非唯一候选卡必须由显式选择阶段开启;同一 Agent 回复不得既索取新证据又提供采用操作;候选采用测试必须覆盖幂等、改选、过期结果和 Profile 基线漂移。
  • 相关记录:BUG-117、BUG-118、BUG-119
  • 修复版本:待提交与发布

BUG-121 | 月份与区间事件在确认工具中被序列化成错误日期格式并耗尽 Agent 步骤

  • 状态:resolved
  • 首次发现:2026-08-04
  • 最近更新:2026-08-04
  • 影响面:Agentic 生时校正结束收集、V5 评分/诊断、旧确认门适配、无回复退款兜底
  • 用户现象:用户明确表示不再补充事件后,接口返回“生时校正没有生成有效回复,本次不会扣除点数,请重新发送”,没有展示最终候选或后续选择。
  • 触发条件:历史证据同时包含 yearmonthrange 精度;Agent 在结束收集时调用 rectification-confirm。月份或年份事件被发送成完整日期,区间事件又可能使用 /to 等自然分隔形式。
  • 根因:共享事件 schema 只检查字符串长度;V5 转换只识别 .. 区间;toV3Event() 又把标准化后的 YYYY-MM-DDmonth/year 精度一起发送给只接受 YYYY-MM/YYYY 的旧确认端点。引擎持续返回 event date does not match its precision,Agent 在 8 个工具步骤内反复修正和重试,最终没有剩余步骤生成公开文本。该问题是 BUG-116 的输入契约残余变体,提高步骤数只能延后失败。
  • 修复:事件日期统一复用严格日历范围转换;V5 保留年月日和区间的 date_start/date_end,并兼容 ../to、中文范围符和紧凑年月范围;旧确认端点按精度发送严格的 YYYYYYYY-MMYYYY-MM-DD,区间按旧端点能力降级为年份证据且摘要仍保留原区间语义。工具 schema 同时明确推荐日期格式。
  • 验证:新增 V5 区间归一化和旧确认精度序列化回归;Agentic 工具/入口/会话合同测试 51/51,通过针对性 ESLint、tsc --noEmit 和 production webpack build。使用脱敏后的原始长对话本地重放,Agent 在 5 次工具调用内完成 gate -> score -> diagnostics -> confirm,工具错误 0,生成 610 字可见候选回复,不再触发空回复退款。
  • 防复发:任何送往旧事件引擎的日期必须由精度契约测试断言;新增日期表示必须先走共享日历校验,不能在调用端自行拼接或仅增加 Agent 重试步数。
  • 相关记录:BUG-116、BUG-118、BUG-120
  • 修复版本:本记录所在 staging 发布提交

BUG-122 | self-hosted staging 管理员看不到独立后台入口

  • 状态:superseded by BUG-123
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:self-hosted staging 账户菜单、GET /api/account、独立后台入口;不影响后台独立登录与 requireAdminSession
  • 用户现象:身份库已持久化 adminviewer 角色的用户登录主站后,账户菜单不显示后台入口;即使显示旧入口,主站 /admin 路径也会返回 404。
  • 触发条件:AUTH_PROVIDER=self-hosted,后台部署在与主站不同的 AUTH_ADMIN_ORIGIN,用户角色以逗号分隔形式持久化在 identity.users.role
  • 根因:主站 isAdminUser 对 self-hosted 模式直接返回 false,没有读取持久化角色;侧栏又把入口写死为主站相对路径 /admin/codes。既有后台鉴权已按持久化角色执行,但主站入口发现逻辑没有复用同一授权事实,独立域名部署合同也没有进入账户响应。
  • 修复:self-hosted 分支通过现有 ADMIN_DATABASE_URL 管理只读连接查询当前用户的 identity.users.role,仅 adminviewer 可见入口,且不使用 ADMIN_EMAILS 替代角色授权;GET /api/account 在服务端解析身份配置并返回 AUTH_ADMIN_ORIGIN + /admin/codesSupabase 模式继续返回 /admin/codes;账户与侧栏类型透传该 URL,并将文案改为“后台管理”。后台独立登录和 requireAdminSession 保持不变。
  • 验证:frontend/tests/admin-contracts.test.tsfrontend/tests/admin-users-contract.test.tsfrontend/tests/account-api.test.tsfrontend/tests/sidebar-contract.test.ts 锁定持久化角色、独立后台 URL、服务端环境边界和后台写权限门禁;目标 TypeScript、构建与 staging 登录态 smoke 结果另行记录。
  • 防复发:self-hosted 主站入口发现必须以 identity.users.role 为授权事实,不能退回邮箱 allowlist;客户端不得读取后台 origin 环境变量或硬编码主站 /admin 路径;后台 API 必须继续独立执行 requireAdminSession,入口可见性不得被当作授权。
  • 相关记录:BUG-010、BUG-083、BUG-084、BUG-123
  • 复发自:BUG-010
  • 修复版本:已由 BUG-123 的同域单会话架构取代

BUG-123 | self-hosted staging 双域后台与主站会话模型冲突

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-29
  • 影响面:staging Better Auth 配置、后台页面与 API、登录、账户入口、Caddy、部署校验和 smoke;生产配置不变。
  • 用户现象:管理员需要第二套后台域名和浏览器会话才能进入后台,主站登录态不能直接使用;viewer 还被当作后台只读角色,与仅数据库 admin 可进入的产品合同冲突。
  • 触发条件:self-hosted staging 同时配置用户与后台 origin/secret、Caddy 拆分两个站点,并按 Host 选择 Better Auth 实例。
  • 根因:早期隔离设计把后台浏览器 surface 当成第二套身份系统,导致入口发现、登录、Cookie、部署变量和授权策略重复;同时把入口可见性与 API 权限错误扩展到 viewer
  • 架构决策:后台复用主站 Better Auth user sessionidentity.users.role 的持久化 admin 是唯一后台授权事实。Better Auth 插件的 /api/auth/admin endpoint 继续在主站 fail-closed 404,未知 Host 继续 421
  • 修复:删除活动运行时后台 origin/secret 与 services.admin,服务端数据 client 和 requireAdminSession 统一读取 user session;后台 layout 增加服务端 gate,匿名转 /login、非 admin 不渲染;所有后台 API 保留独立 guardpayments/packages 改用 requireAdminSessionisAdminUser、账户入口和 Refine policy 收敛为 admin-only;登录取消 Host 分流;staging Caddy、Compose、环境校验、部署脚本、工作流和 smoke 收敛为同域。
  • 验证:身份 config/host/auth、admin policy/contracts、account/sidebar/login、部署/工作流与 admin layout/API guard 合同更新;针对性测试、TypeScript、Next build 与 git diff --check 结果记录在本次交付报告。生产部署未执行。
  • 防复发:活动运行配置和测试不得重新引入独立后台域名、AUTH_ADMIN_ORIGINBETTER_AUTH_ADMIN_SECRET 或浏览器 admin auth serviceviewer 对后台入口、页面、读 API 和写 API 均必须为 403;入口可见性不能替代 route guard。
  • 相关记录:BUG-010、BUG-083、BUG-084、BUG-122
  • 复发自:BUG-122
  • 修复版本:435e628806390e7ae138363491e7bae63ee801d4staging 已验收

BUG-124 | 后台支付入口分散且界面风格不一致

  • 状态:resolved
  • 首次发现:2026-07-29
  • 最近更新:2026-07-30
  • 影响面:后台 Refine 侧栏、/admin/payments/admin/packages、易支付配置与对话页充值入口。
  • 用户现象:支付记录与支付配置占用两个导航项,页面仍使用主站 standalone-page/admin-header/admin-section 样式;套餐新增表单常驻页面,后台默认退出入口还会触发登出,管理员难以直接返回对话;对话页支付入口缺少安全默认关闭和服务端创建订单硬门禁。2026-07-29 复发时,Z-Pay 配置不能折叠且占据长页面,后台受全局 html/body overflow:hidden 限制无法纵向滚动,套餐 API 与易支付配置 API 仍调用 self-hosted adapter 不支持的 Supabase builder/RPC。2026-07-30 部署 dd8e2ad9c7e76d0152b4563c43a45b1e26137035 后,GET /api/admin/payments 与套餐管理仍返回 500。
  • 触发条件:进入同域 /admin 后管理支付记录或套餐,或点击 Refine 侧栏底部默认 Logout;复发条件为进入支付管理、展开长配置或调用套餐 CRUD / 易支付配置读写。2026-07-30 的数据库权限复发在 admin_runtime 通过 ADMIN_DATABASE_URL 查询支付表时稳定触发。
  • 根因:首轮支付后台实现依赖 Supabase 专用关联 select、分页、计数和 Admin Auth 查询;self-hosted staging 的本地 PostgreSQL adapter 不支持这些 builder 能力,支付记录因此统一降级为“支付记录服务暂时不可用”。同页套餐设计也不符合最新后台信息架构,易支付配置响应漏投影 chat_enabled,chat 创建订单又依赖服务端提交网关后猜测跳转地址,不兼容标准易支付收银台表单页。复发遗漏源于上轮只把支付记录切换到 PostgreSQL,套餐与配置契约测试没有锁定 self-hosted 数据链,且未覆盖聊天全局滚动边界下的后台专用滚动容器。2026-07-30 的直接根因是 20260727020000_epay_packages_orders.sql 只向 Supabase 的 service_role / authenticated 授权,未向 self-hosted 后台实际使用的 admin_runtime 授予 payment_packagespayment_orders 权限,也未添加对应 RLS 策略;因此数据库健康且新 SHA 已部署,后台 SQL 仍被 PostgreSQL权限门禁拒绝。同日还确认 staging 迁移与部署工作流错误地要求目标 SHA 属于 main 历史,使完全独立的测试分支被生产分支阻塞;该控制面耦合导致为恢复 staging 而误合并生产 main。
  • 修复:支付记录改为通过 queryAdminRows 执行参数化 SQL,联表 public.payment_orderspublic.payment_packagesidentity.users,以窗口计数保留分页合同并用独立聚合 SQL输出统计;不再使用 Supabase builder 或 Admin Auth。后台在支付管理之后新增独立“套餐管理”资源和页面,套餐新增、编辑、停用、错误重试及原字段保持完整,支付页只保留概览、Z-Pay(易支付)渠道配置和支付记录。配置读取补回 chat_enabledchatEnabled。创建订单完成登录、开关、配置、SSRF、套餐和订单校验后,直接返回带 sign/sign_type 的标准 submit.php 收银台 URL,不服务端请求网关、不返回商户密钥;对话页用浏览器打开该 URL,套餐加载异常显示安全错误,正常 enabled=false 仍静默隐藏。复发修复将 Z-Pay 配置改为默认收起的 Ant Design Collapse,展开后才显示表单和操作;为 AdminApp 增加 admin-app-shell100dvh 独立纵向滚动边界而不改聊天全局规则;套餐 CRUD 全部改用 queryAdminRows 参数化 SQL、UUID 校验、returning 与 404;易支付读取仅在 PostgreSQL 42P01 时回退环境变量,保存直接参数化调用 public.admin_save_epay_settings 并使用函数返回行,保留原子审计和脱敏响应。2026-07-30 新增前向迁移 20260730010000_admin_payment_permissions.sql,向 admin_runtime 最小授予套餐读写、订单只读、易支付配置读取及保存函数执行权限,并为启用 RLS 的支付表补齐角色策略;不授予订单写入或删除权限。Gitea 与 GitHub 的 staging 迁移、部署和测试环境运维工作流统一 checkout staging,删除 staging SHA 属于 main 历史的要求;生产工作流保持不变。误合入 main 的 PR #1 已由 PR #2 的 revert 恢复,恢复后 main 内容树与合并前提交 43581ac0f75e7f157032503475e878bd53ad161d 完全一致。针对 run 1309,Gitea 两条远端工作流的 previous-SHA 探测与 registry login/logout 保持 sudo -n docker;脚本只接受受控的 dockersudo -n docker 数组分支并拒绝其他值,不使用 eval。run 1313 证明 sudo Docker login 已成功,但 run-staging-migration.sh 第 37 行无法写入 root-owned 部署树下的 /opt/jyotisha-staging/.state/mutation.lock。因此迁移与部署工作流改为通过 sudo -n env 传入受控环境并以 root 启动整个脚本,脚本内固定 DOCKER_BIN=dockerDOCKER_CONFIG 仍指向 incoming 的 .dockercleanup 使用 sudo -n rm -rf 删除脚本可能创建的 root-owned incoming 内容,Docker logout 仍使用 sudo。
  • 验证:frontend/tests/admin-contracts.test.ts 锁定支付、套餐资源顺序;frontend/tests/admin-payments-contract.test.ts 锁定本地参数化 SQL、identity.users 联表、套餐 SQL CRUD/UUID/404、独立套餐页面、默认折叠和后台专用滚动容器;frontend/tests/epay-settings.test.ts 锁定 chatEnabled 回显、queryAdminRows 读取、参数化 admin_save_epay_settings、不依赖 Supabase builder/RPC、默认折叠和不泄露 key。2026-07-29 运行三份契约测试共 27 项全部通过;ESLint、TypeScript 与 git diff --check 结果记录在本次交付报告。2026-07-30 线上健康响应证明部署 SHA 为 dd8e2ad9c7e76d0152b4563c43a45b1e26137035 且本地业务库、身份库均健康;静态权限审计确认支付迁移缺少 admin_runtime grant/RLS。新增权限迁移契约后,支付、套餐、配置三组 21 项回归全部通过。首次独立 staging 迁移 run 1300 在镜像校验阶段暴露 docker manifest inspect --verbose 对 ACR 返回单元素数组,而解析器只接受对象,触发 AttributeError: 'list' object has no attribute 'get'Gitea staging 迁移与部署已兼容单平台数组并增加聚焦契约测试。run 1309 进一步确认远端 deploy 用户对 /var/run/docker.sock 无权限;run 1313 的 sudo Docker login 已成功,随后在迁移脚本第 37 行因 deploy 用户不能写 root-owned /opt/jyotisha-staging/.state/mutation.lock 而终止,证明仅提升 Docker 命令不足以覆盖部署树写入。工作流回归现锁定整个脚本由 sudo -n env 启动、脚本内 DOCKER_BIN=docker、incoming Docker 配置不变、root-owned cleanup 使用 sudo,并继续保留 runner 对两种固定 Docker 命令形式的契约。staging 最终部署 SHA 为 1f44892a2cf210797e7dc74f49721a8f10c8849d;迁移台账确认 20260730010000_admin_payment_permissions.sql 于 2026-07-30 05:58:38 UTC 应用,数据库 ACL/RLS 与 admin_save_epay_settingsadmin_runtime 执行权限均已生效,web/api/postgres 容器健康,三个未登录管理 API 正确返回 401,部署后日志无相关 500。支付、套餐与易支付配置 21 项针对性回归通过,管理员随后确认 /admin/payments/admin/packages 已恢复。
  • 防复发:self-hosted staging 后台查询不得依赖 LocalPostgresDataClient 未实现的 Supabase builder、RPC 或 Admin Auth 能力;支付与套餐必须保持独立资源顺序。套餐与易支付配置契约必须显式拒绝 Supabase builder/RPC 并锁定参数化 SQL、404、原子函数写入和安全错误响应;支付配置必须默认折叠,后台必须拥有独立滚动容器且不得放宽聊天的全局 overflow:hidden。易支付配置读写测试必须同时覆盖数据库列和公开字段;创建订单只生成经公网 SSRF 校验的签名收银台 URL,商户密钥只能参与服务端签名,不得进入 URL、响应、日志或审计。对话支付默认关闭,UI 与创建订单 API 必须共享服务端开关;可用性测试不得提交伪订单或返回 URL、PID、密钥、headers/body。
  • 相关记录:BUG-122、BUG-123
  • 修复版本:d44a414(权限迁移),staging 部署 1f44892a2cf210797e7dc74f49721a8f10c8849d

BUG-125 | 个人报告入口对不可用出生时间状态错误开放

  • 状态:resolvedlocalpending staging deployment
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:首页个人报告 CTA、POST /api/reports 出生时间门槛
  • 用户现象:资料流程已经完成、但出生时间仍为 reportedcandidate 的用户会看到“生成个人报告”,点击后服务端必然返回 422 birth_time_not_usable
  • 触发条件:用户有咨询会话和消息,profileComplete=true,但当前排盘时间尚未被用户采用或引擎确认。
  • 根因:首页只用资料完整度判断入口可见性,没有镜像报告 API 的 accepted/confirmed + 有效 active time 门槛;UI 与服务端各自正确但组合后形成误导入口。
  • 修复:首页复用既有 isBirthTimeReadyForConsultation(profile),只有 acceptedconfirmed 且当前排盘时间有效时才显示个人报告入口;服务端门槛保持不变,不把候选范围或填报时间伪装成已采用时间。
  • 验证:frontend/tests/personal-report-entry.test.ts 15/15 通过,新增回归直接覆盖 reported=falsecandidate=falseaccepted=trueconfirmed=true 及缺失 active time 为 false;目标 TypeScript、ESLint 和 git diff --check 通过。
  • 防复发:任何报告出生时间状态扩展必须同时更新服务端事实门槛和客户端可见性测试;客户端不得仅以资料表单完成度推导报告可生成。
  • 相关记录:BUG-117、BUG-119
  • 修复版本:本次个人报告 staging 发布提交

BUG-126 | 正式报告页进入能力审计后质量门禁仍断言旧路由集合

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:tests/test_api_server_security.py、Gitea Staging Backend Quality Gate、正式报告页面可发现性审计
  • 用户现象:PR quality gate run 1453 中 290 项 Python 检查通过,但 test_capability_audit_scans_registry_and_local_sources 因扫描结果新增 reports/[reportId] 而失败。
  • 触发条件:新增 frontend/src/app/reports/[reportId]/page.tsx 后运行 API 安全 quick quality gate。
  • 根因:能力审计会动态扫描前端页面,新增报告 reader 被正确识别;精确路由集合测试仍锁定新增前的六个页面,且本地个人报告聚焦矩阵没有包含该跨层能力审计测试。这是 BUG-014 的同类契约更新遗漏。
  • 修复:将 reports/[reportId] 明确纳入能力审计预期路由集合,不隐藏或排除真实产品入口;将该测试纳入本轮修复后的本地和远端门禁复验。
  • 验证:tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources 本地聚焦通过;Gitea quality gate run 1459 在完整 runner 中通过,Python quick gate 291 passed / 1 skipped。
  • 防复发:新增或删除 Next.js 页面时必须运行能力审计安全测试;报告前端验收矩阵增加跨层 _scan_app_routes 契约,不能只运行 frontend/tests/personal-report-*
  • 相关记录:BUG-014、BUG-125
  • 复发自:BUG-014
  • 修复版本:本次个人报告 staging 发布提交

BUG-127 | 个人报告迁移成功后 self-hosted 精确表清单仍是旧值

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:frontend/tests/database-local-business.test.ts、self-hosted PostgreSQL 全迁移验收、Gitea Staging Backend Quality Gate
  • 用户现象:quality gate run 1456 的 1409 项 frontend 测试中 1408 项通过;真实 PostgreSQL fixture 成功创建 public.personal_reports 后,精确表集合断言因预期值缺少该表而失败。
  • 触发条件:在完整 Docker/PostgreSQL runner 中应用全部迁移并枚举 public schema 表。
  • 根因:个人报告迁移契约覆盖了双迁移语义、RLS、权限和版本唯一性,但既有 self-hosted 全库精确表清单没有同步新增 personal_reports;本机缺少 Docker CLI,无法执行该 fixture,问题由远端完整 runner 捕获。
  • 修复:在 self-hosted 全迁移测试中显式断言 20260806000000_personal_reports.sql 被应用,并将 personal_reports 按字典序加入精确表清单;不删除真实表、不放宽集合比较。
  • 验证:静态 personal-report migration tests 9/9 和迁移版本测试通过;Gitea quality gate run 1459 的真实 PostgreSQL fixture 与完整 frontend suite 通过,frontend 1409/1409。
  • 防复发:新增 self-hosted 业务表时必须同时更新全迁移 applied ledger 和精确 public 表集合;本地没有 Docker 时必须依赖并等待完整远端数据库门禁,不能仅凭迁移文本测试宣称数据库全绿。
  • 相关记录:BUG-126
  • 复发自:无
  • 修复版本:本次个人报告 staging 发布提交

BUG-128 | staging deploy 泄漏多行 SSH secret 且 env owner 契约互相冲突

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:Gitea/GitHub staging deploy 与 migration workflow、staging SSH 凭据、.env.staging* owner、加密备份和发布门禁;production 未受影响。
  • 用户现象:exact-SHA 自动 deploy run 1464 在应用切换前失败;Gitea job 日志把多行 staging SSH 私钥逐行显示,同时远端数据库 env validator 报 owner 不匹配。公网仍运行旧 SHA。
  • 触发条件:Gitea workflow 将多行 OpenSSH key 直接放入 step envroot 控制脚本验证一个由 deploy 持有的 mode-0600 env;此前 root rollout 临时文件又通过 mv 把 env owner 改成 root。
  • 根因:Gitea runner 不能可靠遮蔽多行 secret 的每一行;控制面同时混用了“当前脚本用户”和“部署树 owner”作为 env ownership 事实,rollout 覆盖文件时未保留原 owner/gid。
  • 修复:立即停止发布,生成并验证新 staging ED25519 key,精确撤销旧 authorized key,证明旧 key 无法登录,删除本地旧 key,更新 Gitea/GitHub staging secrets,并删除 28 个可能含旧 key 的 Gitea deploy/migration runs。STAGING_SSH_PRIVATE_KEY 改为单行 base64;所有 staging workflow 解码到 0600 临时文件并用 ssh-keygen 验证。deploy/migration 以部署树 UID 校验两个 envbackup helper 继续以 deploy 运行;rollout 临时文件显式保留部署树 owner/gid。
  • 验证:新 key 严格主机校验登录成功,旧 key 登录失败;新 Gitea/GitHub secrets 已更新;泄漏 run 1464 已删除;本地 workflow contracts 31/31、personal-report 142/142、owner regression、shell/YAML、TypeScript、ESLint、governance 和 pre-work 通过;Gitea quality gate run 1465 在完整 Docker/PostgreSQL runner 中成功。新 exact-SHA migration/deploy 仍按发布流程单独验收。
  • 防复发:禁止 staging workflow 直接注入多行私钥或打印 decoded secret 变量;env owner 必须由部署树身份决定,root 受控脚本不得用 root 临时文件改变持久 env owner。任何凭据日志暴露先轮换/撤销/清理,再修代码和重跑。
  • 相关记录:BUG-124、BUG-127、ERR-092、ERR-093、ERR-094
  • 复发自:无
  • 修复版本:f7a615a5bf11ed95b3a6c7e6d28dfe8150a825efstaging migration/deploy 与安全验收完成

BUG-129 | staging trusted-main checkout 无界 fetch 导致自动部署长期占用 mutation queue

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:Gitea staging deploy/migration 控制器的 trusted-main checkoutproduction 与 staging 应用数据面未受影响。
  • 用户现象:exact-SHA quality gate run 1473 成功后,自动 deploy run 1474git fetch --no-tags origin main "$DEPLOY_SHA" 长时间没有日志进展;fetch 后续自行恢复,run 最终于 18 分钟成功部署 02cc483b7c303e6cc0f26fb31462c50adb007f12。第一轮 bounded-retry 修复合入后,run 1480 的 3 次 120 秒 fetch 全部在服务端压缩 16,093 个对象时耗尽并 fail closedSSH/远端 mutation 未开始,公网/state 继续健康运行 02cc483b7c303e6cc0f26fb31462c50adb007f12
  • 触发条件:空仓库命令 git fetch --no-tags origin main "$DEPLOY_SHA" 同时请求分支和目标 SHA,导致 Gitea 为每次尝试枚举/压缩完整历史对象;runner 与服务端之间的传输无法在 120 秒内完成。
  • 根因:原控制器既没有命令级 timeout,也错误地为正常前向发布抓取 full-history dual ref。第一轮修复只增加 bounded retry,解决了无界占用,但旧回归测试只断言 timeout/attempt/ancestry,未限制传输对象范围,因而未拦住连续三次重新打包完整历史。
  • 修复:不再让 mutation runner 做任何 Git object fetch。成功 staging gate 从其已验证的 exact SHA 生成仅含 tracked deploy/ 与严格 manifest validator 的 controller.tar,将 tar SHA-256 写入四字段 manifest,并与 immutable image digests 一起上传。deploy/migration 从 exact successful gate artifact 下载 bundle,强制校验 controller SHA、tar hash、路径、重复项、类型和 2 MiB 上限后才解包;正常发布使用当前 main == staging controller,手工旧版 rollback 也不得执行旧 controller。refs 与 forward/rollback 关系通过有界 Gitea API 和完整 commit-DAG 路径证明,字段缺失、分页不完整、头不一致或证据冲突均 fail closed。
  • 验证:第一轮 bounded retry 的本地 workflow contracts 31/31、PR gates 1475/1477 与 staging gate 1479 成功;run 1480 证明 3 次 120 秒耗尽后无半部署。bundle 修复本地 manifest/workflow contracts 35/35、三份 YAML、解析后所有 shell/Python heredoc、真实 25-entry/122,880-byte controller tar hash/ZIP+TAR 安全检查、mutation Git-object-op=0、live Gitea commit-DAG、mandatory pre-work、ESLint、privacy 和 diff 检查通过;完整 PR gate 1481、staging gate 1483 成功。自动 deploy 1484 在 1 分钟内成功部署 e59f15d352787f3d05425ba8c459d092e9801a20,日志 mutation git fetch=0、controller hash check 存在、SSH secret 遮蔽且无私钥材料;main/staging/public/state 精确一致,5 个容器 restart count 均为 0health、Swiss Ephemeris、未登录 401、personal_reports、RLS、2 条 owner policies、精确 migration ledger、authenticated SELECT/DELETE-only 与 service-role CRUD 均通过。
  • 防复发:所有 release-controller 网络调用必须有命令级上限和失败闭合;mutation workflow 禁止 git fetch/ls-remote/cat-file/merge-base/checkout/init。控制器必须来自 exact successful gate 的 hash-bound artifact,正常与 rollback 均使用当前 reviewed controller;测试必须覆盖 artifact identity、tar safety、commit-DAG proof 和旧 Git object 路径为零。
  • 相关记录:BUG-128、ERR-094、ERR-095
  • 复发自:BUG-129 第一轮修复未覆盖对象范围
  • 修复版本:e59f15d352787f3d05425ba8c459d092e9801a20gate-attested controller bundle 已完成 staging exact-SHA 验收

BUG-130 | self-hosted 计费查询与订单领域调整缺少并发和审计边界

  • 状态:resolvedlocal
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:self-hosted LocalPostgresDataClient、支付商品与订单创建、账户会员查询、生时校正 reservation 查询、订单后台、兑换码后台及 20260806030000_settle_order_usage_authorization.sql
  • 用户现象:self-hosted runtime 无法执行 nested PostgREST select,部分时间和前缀筛选也缺少参数化 builder;一次性商品并发支付可能重复发放权益;订单后台只有读取能力,失败权益、人工补偿和账务退款缺少受控领域动作;兑换码写操作无法强制记录操作原因。
  • 触发条件:通过本地 PostgreSQL adapter 查询商品及权益、读取有效会员或 reservation 前缀;同一用户并发结算任意 oneTimePerUser 商品;管理员重试失败发放、人工补偿、登记线下退款,或创建、修改、撤销兑换码。
  • 根因:支付调用方依赖 self-hosted adapter 不支持的关联 selectadapter 又缺少 lte/like 参数化能力;一次性限制没有由不可变订单快照和数据库唯一约束共同承担;订单状态和余额缺少统一的管理员领域函数、幂等请求、乐观版本及审计合同;旧兑换码 RPC 不接收 reason。
  • 修复:商品及权益改为两次简单查询后在服务端组装,adapter 增加参数化 lte/like;订单创建写入不可变 oneTimePerUser 商品快照,结算与人工补偿统一通过 (user_id, product_code) 唯一兑换记录原子阻止重复发放;新增 admin_adjust_order,只允许 retry_grantcompensaterecord_refund,强制 billing.adjustments.write、二次认证、reason、request ID 幂等、expected version 和审计,账务退款明确不调用外部支付网关;兑换码 create/update/revoke 新 RPC 均强制 reason,旧无 reason 签名撤销 runtime 执行权限。未直接修改余额或绕过领域函数改订单状态。
  • 验证:真实 PostgreSQL tests/database-billing-adjustments.test.ts 通过,覆盖一次性商品并发仅一次成功、快照不可变、重试/补偿/账务退款、权限、reason、幂等、版本冲突、审计及旧 RPC 权限撤销;综合 tests/database-billing-admin.test.tstests/database-local-business.test.ts 通过;计费 route/reauth/contract 测试 38/38 通过;目标 ESLint 与补丁检查通过。全仓 TypeScript 当前被共享工作树中非本任务的 tests/identity-auth-integration.test.ts:412 类型错误阻断。
  • 防复发:self-hosted 支付查询不得重新引入 nested PostgREST selectLIKE/范围条件必须参数化;一次性权益必须同时依赖不可变快照和数据库唯一约束;订单与兑换码后台不得直接更新余额或订单状态,所有写入必须经过带 reason、权限、幂等和审计的领域 RPC。
  • 相关记录:BUG-124
  • 复发自:BUG-124
  • 修复版本:待提交(本地可测)

BUG-131 | staging quality gate exact-SHA checkout 因过严低速阈值单次失败

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:Gitea Staging Backend Quality Gate validate/publish 的 exact-SHA checkoutstaging mutation controller、应用数据面与 production 未受影响。
  • 用户现象:docs-attestation staging gate run 1485 在 validate 的首步失败;Gitea 已枚举/压缩 3,246/2,895 个 shallow objects,但客户端传输降速后触发 curl 28 Operation too slowearly EOF。publish 被依赖关系跳过,自动 deploy 未触发;公网继续健康运行 e59f15d352787f3d05425ba8c459d092e9801a20
  • 触发条件:quality gate 的 exact-SHA --depth=1 fetch 只有单次调用,并把低速失败设为连续 30 秒低于 1024 B/s;当前 Gitea 链路在约 20 KiB/s 波动后短时低于阈值。
  • 根因:BUG-129 消除了 mutation workflow 的 Git object fetch,但 quality gate 自身仍必须取得待测源码;其 checkout 没有 bounded retry,且低速阈值对当前受限链路过严。旧测试只断言 exact SHA/clean tree,没有覆盖 checkout retry 与低速边界。
  • 修复:validate/publish 两处 exact-SHA checkout 均改为最多 3 次、每次 hard timeout 300 秒;保留 connect timeout 15 秒,将低速失败收紧为连续 60 秒低于 1 B/s。每次仍只抓 --depth=1 --no-tags origin "$GITEA_SHA",耗尽后明确 fail closed,不复用旧 artifact、不放宽 exact-SHA 或 clean-tree 校验。
  • 验证:过期基线 PR gate 1488、最新 controller PR gates 1519/1523、staging push gate 1525 均成功;最终 gate 对 exact SHA 6c1dcbe857006ec6ae7463b57b2b7d5947da4851 完成 validate/publishartifact ID 12 成功上传,deploy 1526 成功。
  • 防复发:质量门禁和 mutation controller 的网络边界分别测试;quality gate checkout 必须覆盖 attempt 数、hard timeout、低速阈值、exact-SHA refspec、最终错误和 clean-tree identity。
  • 相关记录:BUG-129、ERR-095、ERR-096
  • 复发自:无;属于同一 Gitea 链路在 quality-gate 阶段的独立缺口
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851(最终 staging 验收)

BUG-132 | 新增后台页面未同步能力审计精确路由集合

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources、Gitea staging quality gate;新增后台页面实现本身未由本记录改动。
  • 用户现象:并发主线 staging gate run 1489 的 Python quick gate 为 290 passed、1 skipped、1 failed;能力审计已扫描到 14 个新增后台页面,但测试仍精确断言旧的 7 路由集合,publish 被跳过,自动 deploy 未触发。
  • 触发条件:新增 administrators、audit logs、consultations、credit transactions、customers、feature flags、model releases、models、orders、products、roles、security、subscriptions、usage 页面后执行 capability audit 精确集合回归。
  • 根因:并发后台功能更新了真实 App Router 页面,却未同步能力审计的完整预期集合;这是 BUG-126 同类防复发模式在后台模块复发,说明新增页面的同变更门禁仍未统一执行。
  • 修复:保留精确集合比较,将实际新增的 14 个后台页面按排序加入预期列表;不删除既有个人报告页,不改成子集或数量下限,不修改并发后台业务实现。
  • 验证:本地 staging controller/contracts 35/35、完整 Gitea PR gates 1503/1505/1519/1523、最终 staging push gate 1525 与 deploy 1526 均成功。
  • 防复发:任何 frontend/src/app/**/page.tsx 新增或删除必须在同一提交更新 capability audit 精确路由集合,且 quality gate 失败不得通过放宽断言绕过。
  • 相关记录:BUG-126、BUG-131
  • 复发自:BUG-126
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851(最终 staging 验收)

BUG-133 | admin users Route Handler 重导出 runtime 导致 production build 失败

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:frontend/src/app/api/admin/users/route.ts、Next.js production build、Gitea staging quality gatecustomer handler 权限和业务逻辑未受改动。
  • 用户现象:PR gate run 1493 的 Python 路由回归和 frontend 1472/1472 均通过,但 next buildNext.js can't recognize the exported runtime field in route. It mustn't be reexportedpublish 被跳过,自动 deploy 未触发。
  • 触发条件:legacy /api/admin/users Route Handler 通过 export { ..., runtime } from "../customers/route" 同时重导出 handlers 和 route segment config。
  • 根因:Next.js 要求 runtime 等 route segment config 在当前 route 文件中可被静态解析,不允许从另一 Route Handler 重导出;既有精确合同测试反而固化了非法 re-export,且并发功能本地 production build 未闭环。
  • 修复:在 users route 本文件静态声明 export const runtime = "nodejs",只重导出 DELETE/GET/PATCH/POST/PUT handlers;不复制 handler、不修改权限或客户数据逻辑。合同测试改为强制本地 runtime 常量并拒绝 runtime re-export。
  • 验证:admin users 目标合同、tsc --noEmit、默认 Turbopack next buildnext build --webpack、全 route segment config re-export 扫描与 git diff --check 已通过;Gitea PR gate 1499 及最终 PR/push gates 1523/1525 均完成 production builddeploy 1526 成功。
  • 防复发:Route Handler 的 runtimedynamicrevalidate 等 segment config 必须本地静态声明;handler 可复用,但 segment config 不得 re-export。新增 alias route 必须经过 production build,而不只运行文本合同测试。
  • 相关记录:BUG-131、BUG-132
  • 复发自:无
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851(最终 staging 验收)

BUG-134 | staging admin origin selector 未配置导致 exact-SHA 自动部署失败

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:staging .env.staging 的公开 identity selector、自动 deploy run 1502;应用容器、业务数据库和 production 未被修改。
  • 用户现象:staging gate 1500 已成功验证并发布 SHA 9a3d0d440f43deab66c1f8a4a08cdbfc6f9d73eb 的 immutable artifact,但自动 deploy 1502 在远端 env 校验时报 invalid staging selector: ADMIN_USER_ORIGIN 并 fail closed;公网继续健康运行旧 SHA e59f15d352787f3d05425ba8c459d092e9801a20
  • 触发条件:包含双 host self-hosted identity validator 的 controller 部署到现有 staging host,而 .env.staging 尚未包含精确且唯一的 ADMIN_USER_ORIGIN=https://admin.staging.jyotisha.chat
  • 根因:并发 admin rollout 将 admin host origin 加入应用和 validator 合同,但 staging host-managed env 未在发布前同步新增的非密钥 selectorquality gate 验证仓库合同,不读取主机 secret/env,因此直到 mutation 前远端校验才暴露漂移。
  • 修复:已在共享 staging mutation lock 下,仅向原文件原子补入公开 ADMIN_USER_ORIGIN selector,保留全部既有内容、deploy:deploy owner 和 0600 mode;未输出、复制或重写其他 secret 值。随后完整 validator 暴露独立的 service runtime 漂移,转由 BUG-135 处理;仍待 exact-SHA artifact 重新部署。
  • 验证:脱敏只读检查先确认 .env.stagingdeploy:deploy 0600AUTH_USER_ORIGIN 精确且唯一、ADMIN_USER_ORIGIN 计数为 0;原子修复后 ADMIN_USER_ORIGIN 精确且唯一,正式 validators 与 deploy 1526 通过。最终 user host admin paths 为 404admin host /admin 为 307 /login、session API 为 401;容器 restart count 均为 0。
  • 防复发:任何新增 staging host-managed selector 必须在同一 rollout runbook 中包含 deploy 前 presence/exact-value 检查;quality gate 成功不能替代 host env validation。env 修复必须共享 mutation lock、原子替换并保持 owner/mode,严禁打印 raw env。
  • 相关记录:BUG-128、BUG-133、ERR-093、ERR-097
  • 复发自:无
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851staging host selector 与双 host 验收)

BUG-135 | staging service runtime 缺失且 admin runtime 错误继承 BYPASSRLS 角色

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:staging 私有 PostgreSQL runtime roles、.env.staging.env.staging.database、个人报告 service client 与后台最小权限;production 未受影响。
  • 用户现象:补齐 ADMIN_USER_ORIGIN 后,正式 validator 继续报 invalid staging identity setting: SERVICE_DATABASE_URL。脱敏检查确认应用 env 缺少 SERVICE_DATABASE_URL、数据库 env 缺少 SERVICE_RUNTIME_PASSWORD、PostgreSQL 缺少 service_runtime login role;同时旧 admin_runtime 意外继承了带 BYPASSRLS 的 service_role。当前旧 web 容器同样没有 service URL,因此无法安全恢复旧明文。
  • 触发条件:在早期初始化的 staging 数据卷上部署依赖独立 service client 和最新 admin RBAC 的应用;bootstrap 脚本只在空数据卷初始化时执行,现有 host env/roles 未随 reviewed compatibility contract 对齐。
  • 根因:staging host bootstrap 漂移。数据库保留了 service_role、identity/app/admin runtime roles,但没有后来合同要求的 service_runtime;旧 admin runtime membership 又违反当前 bootstrap 和 RBAC 的明确 revoke 边界。quality gate 不读取 host env 或运行时 role catalog,因此直到远端部署前校验与现场权限审计才暴露。
  • 修复:在共享 mutation lock 下生成独立 staging-only 随机凭据,通过 PostgreSQL stdin 创建/设置 service_runtime,授予 service_role membership 和数据库 CONNECT;将 raw password 仅原子写入 .env.staging.databasepercent-encoded URL 仅原子写入 .env.staging,两文件保持 deploy:deploy 0600。随后撤销 admin_runtimeservice_role membership;未重启容器、未输出凭据、未改 production。已应用的 20260806000000_personal_reports.sql checksum 与仓库一致,保持历史迁移不可变。
  • 验证:两个正式 env validator 均通过;service_runtime 真实密码登录、service_role membership 和 CONNECT 均通过;最终审计再次确认 service_membership=trueservice_connect=trueadmin_service_membership=falseadmin_bypassrls=falseservice_can_login=true。两份 env 均为 deploy:deploy 0600migration 1516、push gate 1525 和 deploy 1526 成功。
  • 防复发:非空数据卷不能依赖 /docker-entrypoint-initdb.d 自动重放;每次新增 runtime role 或 host-managed URL 都必须有兼容性 role repair、脱敏 pre-deploy presence 检查和真实登录/role-membership smoke。admin_runtime 永不得继承 service_roleservice writes 只能使用独立 SERVICE_DATABASE_URL。已应用迁移不得为修正文案而改 checksum。
  • 相关记录:BUG-128、BUG-134、ERR-093、ERR-098
  • 复发自:无
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851staging role/env 与 exact-SHA 验收)

BUG-136 | staging quality gate frontend build 无界卡住并耗尽 45 分钟 job

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:Gitea Staging Backend Quality Gate validate job、staging artifact publication;应用代码、staging host 和 production 未被本次失败修改。
  • 用户现象:push gate 1507 对 reviewed SHA 6fd22921197715e065d0d137fbd7ea5a82a188e4 完成 frontend 1472/1472、ESLint 0 errorNext.js 输出 Compiled successfully in 38.2s 后约 44 分钟无 further output45 分钟 job 超时,publish 被跳过;公网继续运行旧健康 SHA e59f15d352787f3d05425ba8c459d092e9801a20
  • 触发条件:质量门禁执行 npm run build --prefix frontend 没有命令级 bounded timeoutTurbopack 在编译后静态生成/收尾阶段无输出卡住时只能等待 job-level timeout。
  • 根因:quality gate 只有 45 分钟 job 上限,缺少针对生产构建步骤的 fail-closed deadline;此前 PR gate 1503/1505 同一代码完整 build 通过,说明本次是 runner/build hang,不是已观测的业务编译错误。
  • 修复:在 Gitea validate 中将 frontend production build 包在 timeout 600 内,超时输出明确事实并以非零状态失败;不跳过 build、不降低测试、不发布旧 artifact。新增 workflow contract 锁定该 bounded timeout。
  • 验证:本地 workflow contracts 35/35PR gates 1511/1519/1523 和 staging push gates 1514/1521/1525 均在 600 秒 command deadline 内完成 production build;最终 gate 1525 publish 与 deploy 1526 成功。
  • 防复发:所有可能长时间静默的编译、镜像构建和外部网络步骤都必须有命令级 deadline,且 deadline 失败必须 fail closed;保留 job-level timeout 作为第二层上限,不把 timeout 当成功。
  • 相关记录:BUG-129、BUG-131、ERR-096、ERR-099
  • 复发自:无
  • 修复版本:8dc61e3135d8afb96b6683714f41a1716761164e;最终验收 6c1dcbe857006ec6ae7463b57b2b7d5947da4851

BUG-137 | staging deploy 在公网 upstream 尚未收敛时用旧 SHA 立即判失败

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:Gitea Deploy staging 最终公网验证、自动回滚;数据库迁移已成功,production 未受影响。
  • 用户现象:exact-SHA gate 1514 与 migration 1516 成功后,deploy 15171518 均启动目标 web/API image 并达到容器 healthy,却在约 3 秒后的公网 verification 返回非零,随后成功恢复旧 web/worker;公网和 .state/deployed-revision 均保持旧 SHA e59f15d352787f3d05425ba8c459d092e9801a20
  • 触发条件:Compose 切换到目标容器后,旧 Caddy upstream 在短暂收敛窗口仍可让 /login 返回 200;脚本只轮询 /login,然后对公网 health SHA 和其余 predicate 仅检查一次,读取旧 SHA 时立即触发回滚。
  • 根因:发布验证把“login 可达”和“公网已路由到 exact SHA”拆成了不对称检查;容器健康与代理 upstream 收敛不是同一时刻,单次 SHA 检查形成确定性 race。目标 image 隔离 probe 已确认注入的 GITHUB_SHA 为目标 SHA。
  • 修复:在原 60 秒总预算内,每 5 秒原子重查 login、admin 未登录重定向、admin API/account 401、公网 health exact SHA、私有 API health 和 Swiss Ephemeris;仅当所有 predicate 同轮满足才成功。预算耗尽仍 fail closed 并只输出状态码、observed SHA、health 状态等脱敏摘要,不输出正文、env 或凭据。
  • 验证:合同测试 35/35deploy 1522 的脱敏摘要证明 bounded verifier 等待到目标 public SHA 后才报告独立 admin-host 问题;后续 PR gate 1523、push gate 1525、artifact ID 12 和 deploy 1526 均成功,最终公网/host state/main/staging 完整 SHA 一致。
  • 防复发:发布验证必须等待最终外部路由 identity,而不能把单个 readiness endpoint 当作代理收敛证明;所有重试必须有总上限,失败记录仅含非敏感 predicate 状态并保持自动回滚。
  • 相关记录:BUG-129、BUG-136、ERR-099、ERR-100
  • 复发自:无
  • 修复版本:28f4207f295276968427686a21971fd53abb42ec;最终验收 6c1dcbe857006ec6ae7463b57b2b7d5947da4851

BUG-138 | staging Caddy 保留旧单文件 bind inode 且 admin 验证误走用户域名

  • 状态:resolved
  • 首次发现:2026-08-06
  • 最近更新:2026-08-06
  • 影响面:staging Caddy 双 host 路由、admin TLS、Deploy staging 未登录边界验证;production 未受影响。
  • 用户现象:修复公网 SHA 收敛后,deploy 1522 明确观测目标 SHA、私有 API 和 Swiss Ephemeris 均正常,但在用户域名上得到 /admin -> 307 //api/admin/session -> 403;同时 admin.staging.jyotisha.chat TLS 握手失败。workflow 在 60 秒后自动恢复旧应用,未写入新 deployed-revision。
  • 触发条件:controller 通过原子目录同步替换 deploy/Caddyfile.staging,但长期运行的 Caddy 容器仍持有旧单文件 bind mount inode;随后 checker 又把 admin 页面/API 请求错误发送到 STAGING_URL 而不是 ADMIN_USER_ORIGIN
  • 根因:host 文件与 Caddy 容器 mount inode 漂移。只读现场证据显示 host Caddyfile 含 admin host、运行容器内文件不含,inode/size/mtime 均不同;staging VPS 从两台权威 nameserver 查询 admin A 记录均为 118.26.111.127,排除 DNS 缺失。用户域名按新 Caddy 合同本应对 admin paths 404,因此旧 checker 的 307/401 期待也违反双 host 边界。
  • 修复:应用 Compose 切换后显式 --force-recreate --no-deps caddy,使其重新挂载 gate-attested Caddyfile;完整 convergence 同轮要求用户域名 admin page/API 均 404、admin origin page 307 到 /login、admin API 401,并继续要求 exact public SHA、account 401、私有 API/Swiss 健康。失败仍 bounded、fail closed 并自动恢复旧应用。
  • 验证:shell/workflow contracts 35/35、PR gate 1523、push gate 1525、artifact ID 12 和 deploy 1526 成功。Caddy 被 force-recreate,容器内配置包含 admin host;公网 user host /admin/api/admin/session 均 404admin host / 为 308 /admin/admin 为 307 /login、session API 为 401Caddy restart count 为 0。
  • 防复发:原子替换单文件 bind mount 后必须 recreate/reload 长期运行服务;部署 smoke 必须分别使用各自主机 origin,不能在 user host 上测试 admin host 合同。保留 authority DNS、mount inode 和 TLS 检查作为脱敏现场证据。
  • 相关记录:BUG-134、BUG-137、ERR-097、ERR-100、ERR-101
  • 复发自:无
  • 修复版本:6c1dcbe857006ec6ae7463b57b2b7d5947da4851

BUG-139 | staging 后台缺失初始 Owner 且拒绝重定向与 Caddy 形成无限循环

  • 状态:resolvedlocal candidatepending review/deployment
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:admin.staging.jyotisha.chat 后台入口、self-hosted admin RBAC 初始 Owner 恢复;production 未受影响。
  • 用户现象:用户完成后台域名登录后访问 /,浏览器报 ERR_TOO_MANY_REDIRECTS;未认证公开链仍正常表现为 / 308 到 /admin、再 307 到 /login、最终 200。
  • 触发条件:已认证 self-hosted 用户通过身份 session,但 admin_permission_keys 没有返回 admin.access,后台 gate 产生 403;历史同域 fallback 将所有非 401 授权错误重定向到 /,而独立后台 Caddy 又将 / 永久重定向到 /admin
  • 根因:第一层是双 host 发布后仍保留 BUG-123 的同域 403/503 -> / 行为,与 BUG-138 的后台根路径 308 -> /admin 组合成确定性循环。第二层是 20260806010000_admin_rbac.sql 的一次性 bootstrap 只捕获迁移执行当时已经是 identity admin 的用户;staging 脱敏聚合显示 identity_admins=1auth_users=1bootstrap_eligible_admins=1,但 active_admin_users=0owner_assignments=0identity_admins_missing_rbac=1,因此当前唯一 active identity admin 没有 RBAC Owner,真实授权结果为 403,而不是 cookie/host 隔离或数据库不可用。
  • 修复:后台 layout 对 401 仍转 /login403 使用 Next.js forbidden interrupt 返回明确 403 页面;503 以 307 转到 admin layout 外的独立 /admin-unavailable route,由该 route 最终返回 503 和 cache-control: no-store。后台根 route 对 403/503 直接返回对应状态和 no-store 文本响应,所有拒绝路径都不再导向 /。授权边界现将读取 self-hosted 配置、Host 判定、Better Auth/session、identity DB 与 RBAC DB 查询置于同一异常边界:既有 AdminAuthorizationError 原样保留,identity 401/403 继续映射为脱敏 401/403,Host 不匹配仍为 403,其余未知基础设施异常统一转换为 后台服务暂时不可用 503API adminErrorResponse 保留该最终状态。Owner 恢复 migration 保留在 frontend/db/migrations,但在任何表查询前用 to_regclass/to_regprocedure 检查 identity/auth 表、RBAC 表与关键函数;identity-only MIGRATIONS_DIRECTORY=db/migrations 缺少 RBAC 时安全 no-op 并正常记账,full staging 合并两目录后按文件名排序,在 20260806010000_admin_rbac.sql 之后执行恢复,而 Supabase-only/production migration 集合不包含该文件,不污染 production Supabase ledger。恢复语义仍为:已有 active Owner no-op;真正空库 no-op;只有恰好一个当前可登录(未封禁,或封禁截止时间已过)、已同步 auth.users、持久 identity role 包含 admin,且 admin_users 不存在或尚未 revoked 的候选才恢复 Owner。历史 revoked admin 明确排除,admin_users 冲突使用 do nothing,不得清除 revoked_at/revoked_by;零个或多个候选均以约束错误 fail closed。运行时授权继续只依赖数据库 RBAC,不读取 ADMIN_EMAILS,也不批量授权所有 identity admin。
  • 验证:重定向合同覆盖 401 -> /login、403 forbidden、嵌套页面 503 只转 /admin-unavailable 以及 Caddy / -> /admin 不成环;直接执行独立 route handler 验证最终响应为 503、no-store、无 Location。可执行授权单元测试覆盖配置 reader、Better Auth/identity reader 与 RBAC query 未知故障均脱敏为 503,既有授权异常与 identity 401/403 不变,并直接验证 adminErrorResponse 最终返回 503。真实 PostgreSQL fixture 验证 db-only identity migration 在 RBAC 缺失时安全 no-op、full 两目录流程执行恢复,以及空库 no-op、已过期封禁和带空格角色的单一同步候选可恢复、未同步或仍封禁账号不可恢复、已有 Owner 时第二个 identity admin 不获授权、revoked 历史管理员不会复活且撤销字段保持不变、两个候选和零候选均 fail closed。聚焦与完整测试、lint、TypeScript、构建结果见本次候选提交验证记录。
  • 防复发:独立后台 host 的拒绝路径不得使用相对 / 作为逃生路由;401、403、503 必须分别保留认证、授权和服务故障语义,layout 不能把 503 吞成 500,授权依赖的未知配置/provider/数据库错误也不得泄露或退化为 500。跨 identity 与 RBAC 的恢复 migration 必须留在 DB migration ledger,并以显式 schema/function 前置检查兼容 identity-only no-op;不能放入 production 使用的 Supabase-only ledger。一次性 RBAC bootstrap 后新增的初始管理员必须通过受约束向前 migration 或显式角色管理进入权限图;历史撤销是安全边界,禁止自动清除,也禁止用邮箱 allowlist 或“所有 identity admin”兜底。
  • 相关记录:BUG-123、BUG-134、BUG-138
  • 复发自:BUG-123
  • 修复版本:本次 staging admin redirect/RBAC recovery 候选提交

BUG-140 | 首页入口卡片点击后持续显示灰色交互态

  • 状态:resolved
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:首页 starter-theme-carddaily-starlanguage-card 与共享 product-entrypoint-card 交互反馈
  • 用户现象:用户点击首页主题或产品入口卡片后,卡片背景持续处于灰色/选中样式,没有在松开指针后恢复。
  • 触发条件:在触摸设备点击卡片,或在桌面端点击后让指针继续停留在卡片上。
  • 根因:卡片把铺满表面的背景反馈绑定到无设备能力边界的 :hover;移动浏览器可能在点击后保留模拟 hover,桌面指针停留也会让一次点击看起来像永久选中。hover、短暂按压和真实 disabled 三种状态因此在视觉上混在一起。
  • 修复:将铺灰背景收敛到 :active,只在按压期间显示并于释放后恢复;桌面 @media (hover: hover) 只保留轻量边框、光泽和箭头位移,不再改变卡片底色;键盘继续使用现有 focus-visible 轮廓,真实 disabled 透明度规则保持不变。
  • 验证:frontend/tests/consultation-entrypoint.test.ts 新增 sticky-hover 回归;与 frontend/tests/starter-questions.test.ts 精确运行 40/40 通过;目标 ESLint 与 git diff --check 通过。全量前端尝试中 1457 项通过,16 项因本机缺少 Docker 或 python 可执行命令而失败,与本次 CSS 修改无关。
  • 防复发:首页可点击卡片的表面底色只能由短暂 :active 或真实业务 disabled 状态改变;hover 视觉必须限制在支持 hover 的设备,且回归测试禁止重新为这些卡片的 hover surface 添加背景色。
  • 相关记录:无
  • 复发自:无
  • 修复版本:待提交

BUG-141 | self-hosted PostgreSQL DATE 行导致 consultation 503

  • 状态:resolvedlocal candidate
  • 首次发现:2026-08-07
  • 影响面:self-hosted staging 的 profile/consultation 读取;production 未受影响。
  • 根因:本地 PostgreSQL adapter 将 DATE 查询结果保留为 JavaScript Date,下游业务合同要求无时区的 YYYY-MM-DD
  • 修复:按列类型将查询结果中的 DATE 归一化为本地日历字符串,timestamp 仍保持 Date;同步删除 legacy rectification 后的 account stale test。
  • 验证:focused 回归 37/37、完整前端 1017/1017、local quick quality gate 291 passed/1 skippedTypeScript、lint0 error)、production build 与 git diff --check 通过。
  • 修复版本:本次 staging-only 集成候选

BUG-142 | Gitea staging publish job Run 1540 post-job timeout

  • 状态:resolvedlocal candidate
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:Gitea Staging Backend Quality Gate publish jobvalidate、exact-SHA 与安全校验未修改,production 未涉及。
  • 用户现象:Run 1540 在发布步骤完成后进入 post-job timeout,质量门禁未能正常收口。
  • 触发条件:publish job 的 45 分钟 job timeout 不足以覆盖发布后的收尾阶段。
  • 根因:publish job 与 validate job 共用 45 分钟上限,未为发布收尾保留独立余量。
  • 修复:仅将 .gitea/workflows/backend-quality-gate.yml 的 publish timeout 从 45 分钟调整为 60 分钟;validate 仍为 45 分钟,安全检查与 exact-SHA 行为保持不变。
  • 验证:新增合同断言区分 validate=45 与 publish=60;聚焦 staging workflow test、YAML 解析和 git diff --check 通过;未 push、deploy 或触碰 production。
  • 防复发:合同测试必须同时锁定 validate 与 publish 的 job timeout,避免发布收尾预算被误改。
  • 相关记录:BUG-136
  • 复发自:无
  • 修复版本:本次提交(staging-only

BUG-143 | 新增 membership 页面未同步能力审计精确路由集合

  • 状态:resolvedlocal candidate,远端 gate 待 follow-up 更新)
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources、Gitea staging quality gate;会员页实现本身未由本记录改动。
  • 用户现象:staging gate run 1549 中 290 passed、1 skipped、1 failed;能力审计已扫描到新 membership 页面(位于 login 之后、reports/[reportId] 之前的排序位置),但测试仍精确断言旧集合,publish 被跳过,自动 deploy 未发生。
  • 触发条件:新增 frontend/src/app/membership/page.tsx 后运行 capability audit 精确集合回归。
  • 根因:这是 BUG-126reports 页面)、BUG-132(后台 14 页面)同类精确路由集合防复发模式第三次复发。旧防线失效的直接原因是会员页本地交付矩阵只运行了 frontend testsmembership-page/sidebar/starter/epay 等静态合同),没有把 Python 侧 test_capability_audit_scans_registry_and_local_sources 纳入同变更验证,因此真实 App Router 路由集合与测试期望再次分叉;该测试由 staging quality gate 动态扫描 frontend/src/app 才能捕获。
  • 修复:在预期排序位置 login 之后、reports/[reportId] 之前加入 membership,保留精确完整集合,不放宽为子集/包含断言。
  • 验证:本地目标节点 /Users/jesse/Documents/Jyotisha/.venv/bin/python -m pytest tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources -q 通过(1 passed);git diff --check 通过;远端 staging gate 需在 follow-up 提交/推送后更新确认,本记录不提前声称远端收口。
  • 防复发:新增或删除任何 frontend/src/app/**/page.tsx 页面时,必须在本变更中同步运行 test_capability_audit_scans_registry_and_local_sources(或完整 test_api_server_security.py 聚焦切片),并在本地验证通过后才进入推送门禁;前端本地矩阵不能替代该 Python 能力审计节点。
  • 相关记录:BUG-126、BUG-132
  • 复发自:BUG-126(模式:新增页面未同步能力审计精确路由集合)
  • 修复版本:待 follow-up commit / gate

BUG-144 | db/migrations 副本依赖业务 schema 导致 identity-only fixture 迁移失败

  • 状态:resolvedlocal candidate,远端 gate 待 follow-up 更新)
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:frontend/db/migrations/20260807020000_redeem_security.sql(已删除)、frontend/tests/redeem-orders-contract.test.tsfrontend/tests/database-redeem-security.test.tsstaging quality gate run 1552 frontend 1054 passed / 2 failedPython quick 291 passed / 1 skippedpublish / deploy 均未发生。
  • 用户现象:gate run 1552 数据库真实失败,publish 被跳过,自动 deploy 未发生。
  • 触发条件:新增依赖业务 schemapublic.redemption_codespublic.credit_transactions)的 redemption 安全迁移时,同时在 frontend/db/migrationsfrontend/supabase/migrations 各放一份;database-local-business 聚合迁移(两目录全量)通过,而只应用 frontend/db/migrations 的 identity-only fixture 中业务 schema 尚不存在。
  • 根因:database-self-hosted-identityidentity-auth-integration 只应用 frontend/db/migrationsidentity foundation),新的 db 副本假设 public.redemption_codes 已由 supabase compatibility 迁移创建;identity-only 序列因此 migration failed,与 BUG-127 同类(新增业务表必须精确选择迁移位置/全迁移),但不是 BUG-127 或 BUG-143 的复发,属本变更独立根因。
  • 修复:删除 frontend/db/migrations/20260807020000_redeem_security.sql,仅保留 frontend/supabase/migrations/20260807030000_redeem_security.sqlcontract 测试只审该单一 migration,删除 byte-identical 双副本断言,并新增硬防线:断言 frontend/db/migrations/20260807020000_redeem_security.sql 不存在(existsSync === false);DB security 测试 aggregate runner 只期望 applied 20260807030000_redeem_security.sql,不再期待 070200,并断言 stdout 不含 20260807020000_redeem_securitySQL 内容未作任何变更。
  • 验证:本机无 Docker,无法执行 identity-only 与 aggregate DB 实测;运行 npx tsx --test tests/redeem-orders-contract.test.ts7 passed,原 6 项 + 新增 existsSync 防复发守卫 1 项)、tsc --noEmitclean)与 git diff --check 通过;目标 identity DB 实测(database-self-hosted-identityidentity-auth-integration)与 aggregate DB 测试(database-local-businessdatabase-redeem-security)留待远端 gate 确认,本记录不提前声称远端收口。
  • 防复发:frontend/db/migrations 只放 identity foundation 独立可执行迁移;依赖业务 schema(public.redemption_codespayment_orders 等)的迁移只进 frontend/supabase/migrations;任何新增/删除迁移必须在本变更中同时跑 identity-only 两测试(database-self-hosted-identityidentity-auth-integration)与 aggregate DB 测试(database-local-business 等)。
  • 相关记录:BUG-127、BUG-143
  • 复发自:无(独立根因,非 BUG-127 / BUG-143 复发)
  • 修复版本:待 follow-up commit / gate

BUG-145 | staging Web health 被空模型目录错误阻断

  • 状态:resolvedlocal candidate,远端 gate 待 follow-up 更新)
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:frontend/src/app/api/health/route.ts 的 Web 容器 healthcheck;数据库尚未发布默认模型时 staging Web 被错误判定为 unhealthy,真实基础设施故障的 503 语义不变。
  • 用户现象:staging 数据库没有 published default model 时,model catalog 只有 default_model_unavailabledatabase_model_catalog_empty,但 /api/health 返回 HTTP 503,导致 Web bootstrap/Compose healthcheck 互相等待,页面无法进入稳定 healthy 状态。
  • 根因:health route 将所有非 ok 聚合状态统一映射为 503;同时把可恢复的模型目录缺少默认模型状态标成 blocked,混淆了业务配置未就绪与模型目录数据库基础设施不可用。上一轮放宽为 degraded -> 200 后,又会把 Jyotish API 非 2xx 误报为 healthy。
  • 修复:模型目录缺少 published default model 时报告 degradeddatabase_model_catalog_unavailable 与 Jyotish API 非 2xx 保持 blocked。整体 health 仅在存在 blocked 检查时返回 503,模型目录缺省配置仍返回 200;未恢复 legacy model env vars,也未改变 consult/report/onboarding 的 fail-closed 模型解析路径。
  • 验证:npx tsx --test tests/health-deployment.test.ts12 passed);npx eslint src/app/api/health/route.ts tests/health-deployment.test.ts(通过);git diff --check(通过)。
  • 防复发:health contract 必须同时覆盖“无 published default model -> modelCatalog=degraded、HTTP 200”、“Jyotish API 非 2xx -> blocked”和“任意 blocked -> HTTP 503”,不得让真实基础设施故障落入 degraded
  • 相关记录:BUG-145
  • 复发自:无(staging bootstrap deadlock 的独立 health contract 根因)
  • 修复版本:待 follow-up commit / gate

BUG-146 | staging 后台写请求把 Caddy 上游协议误当公开来源

  • 状态:resolved
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:所有经 requireAdminMutation 的后台写接口、admin.staging.jyotisha.chat 模型管理写操作;普通 staging 用户域名、其他后台功能的通用 ReasonActionModal 与 production 未改动。
  • 户现象:管理员在独立后台域名提交 POST /api/admin/models 时,浏览器 Origin 为 HTTPS 公开后台域名,但 Caddy 转发后的 Route Handler 请求 URL 使用上游 HTTP 协议,旧校验因此返回 403 请求来源不可信
  • 触发条件:请求经 staging Caddy reverse_proxy web:3000 进入 Next.js,公开 origin 与上游 request.url 协议不同;旧 isSameOriginAdminMutation 只比较这两个 origin,未核对 Caddy 提供的公开 Host/Proto 头。
  • 根因:共享后台 mutation guard 把应用上游 URL 当作浏览器公开来源真值,没有结合既有 ADMIN_USER_ORIGIN、原始 Host 与 Caddy 的 X-Forwarded-Host / X-Forwarded-Proto;因此合法后台请求被拒绝,同时也不能安全地仅信任任意 forwarded host。
  • 修复:requireAdminMutation 统一调用可测试的共享来源策略;配置 ADMIN_USER_ORIGIN 时要求浏览器 Origin 精确匹配、Host 与规范化后的 forwarded host 一致、公开 host/proto 精确匹配后台 origin,缺失、歧义、畸形或冲突的 forwarded 值全部 fail closed;未配置后台 origin 的既有直连环境继续使用严格 same-origin fallback。复用 identity host 规范化 helper,未新增同义 envstaging Caddy 继续在用户域名对 /admin*/api/admin/* 返回 404。模型管理同时删除 saveProvider/saveDraft/publish/rollback 的客户端“操作原因”字段与交互,服务端分别注入固定中文审计说明后继续传给原 DB procedure 的非空 reason 参数;通用 ReasonActionModal 未改动。
  • 验证:npx tsx --test tests/admin-http-origin.test.ts tests/admin-model-management-ui-contract.test.ts tests/identity-host-routing.test.ts tests/admin-reauth.test.ts tests/health-deployment.test.ts34 passed);相关 ESLint、git diff --check 与最终差异审查见本提交验证记录。
  • 防复发:后台 mutation 来源测试必须同时覆盖合法 admin Origin + 公开 host/proto、错误 Origin、普通 staging host、Host/forwarded host 冲突、逗号多值、畸形 host、缺失 proto 与非法 ADMIN_USER_ORIGIN;模型管理合同必须拒绝客户端 reason,并确认四个固定审计说明仍传入现有 procedure。
  • 相关记录:BUG-134、BUG-138、BUG-139
  • 复发自:无
  • 修复版本:本地候选提交(未 push / deploy

BUG-147 | staging 模型供应商保存成功但列表为空

  • 状态:resolvedlocal candidatestaging migration/deploy 待验证)
  • 首次发现:2026-08-08
  • 最近更新:2026-08-08
  • 现象:POST 保存成功但 GET providers 为空。
  • 触发:self-hosted admin_runtime 直查五张 model 表:model_providersmodel_configsmodel_config_versionsmodel_publish_eventsmodel_connection_test_evidence
  • 根因:表启用 RLS 且有 SELECT grant,但缺少 admin_runtime SELECT policySECURITY DEFINER 写成功、读被静默过滤。
  • 修复:20260808020000 migration 为 model_providersmodel_configsmodel_config_versionsmodel_publish_eventsmodel_connection_test_evidence 增加仅 SELECT policy。
  • 验证:focused 7/7full frontend 1071/1071lint 0 errors、3 warningsbuild 通过;git diff --check 通过。
  • 防复发:模型管理数据库回归测试同时覆盖 admin_runtime 对五张 model 表的直接读取,迁移需保持 SELECT grant 与 SELECT policy 成对存在。
  • 相关记录:BUG-124、BUG-135、BUG-145、BUG-146
  • 复发自:无
  • 修复版本:待本次 staging SHA

BUG-148 | Node 22/24 all-address lookup 使模型发现 DNS pinning 失效

  • 状态:resolved
  • 首次发现:2026-08-08
  • 最近更新:2026-08-08
  • 影响面:共享 requestAllowedModelProvider 的固定 DNS HTTPS 请求;OpenAI-compatible 供应商模型发现无法到达已通过公网 SSRF 校验的上游。支付网关、SSRF 边界、重定向拒绝、超时与响应大小限制未放宽。
  • 用户现象:staging Web 容器内使用同一 DNS pinning/request 链路请求模型列表时失败,脱敏错误为 ERR_INVALID_IP_ADDRESS,已固定的地址族为 IPv4。
  • 触发条件:Node 22/24 的 https.request / net.connectlookup 选项 all=true 调用自定义 DNS callback;旧实现无条件调用 callback(null, address, family),而 all-address 契约要求第二参数为 [{ address, family }]
  • 根因:共享 HTTPS helper 只实现了旧的单地址 lookup callback 形态,没有根据 Node 传入的 options.all 切换返回值;因此安全校验和 DNS 解析均成功后,Node 在建连前把字符串结果按地址数组读取并抛出 ERR_INVALID_IP_ADDRESS。discover route 的 provider 读取、密钥解密、/models URL、响应解析和错误翻译均不是本次失败根因。
  • 修复:抽出 pinnedAddressLookup 供共享 HTTPS 请求使用;options.all=true 时返回单元素已验证地址数组,其他模式继续返回 address, family。仍只使用已通过完整公网校验的固定地址,不重新解析、不跟随重定向,也不删除任何 SSRF/信任边界校验。
  • 验证:npx tsx --test tests/model-provider-encrypted-credentials.test.ts 的真实本地 TCP 回归在修复前稳定失败为 ERR_INVALID_IP_ADDRESS4 passed / 1 failed),修复后对 autoSelectFamily=true 的 all-address 模式和 autoSelectFamily=false 的单地址模式均实际建连成功(5 passed / 0 failed)。
  • 防复发:DNS-pinned 请求测试必须通过 Node 网络栈实际调用自定义 lookup,并同时覆盖地址数组与单地址 callback 契约;不得用只匹配源码字符串的断言替代该回归。
  • 相关记录:BUG-146、BUG-147
  • 复发自:无
  • 修复版本:2026-08-08 staging 变更

BUG-149 | staging quality gate npm ci 缺少安装级 deadline 导致 45 分钟黑洞

  • 状态:resolvedlocal candidate,远端 gate/deploy 待本提交)
  • 首次发现:2026-08-09
  • 最近更新:2026-08-09
  • 影响面:Gitea staging quality gate 的依赖安装阶段、manman-linux runner 与 Gitea/runner 控制面可用性;测试、lint、build、exact-SHA checkout/publish/deploy 合同未改变。
  • 用户现象:Run 1618SHA ffbe505c)在 Python pip 完成后进入 digest-pinned Node 容器执行 npm ci;步骤无后续 npm 输出,直到 45 分钟 job timeoutpublish/deploy skipped。
  • 触发条件:self-hosted runner 在受限 Node 容器中执行 frontend npm ci,但安装命令本身没有 fail-closed deadlinenpm registry/fetch 也没有比 job-level 更短的诊断边界。
  • 根因:BUG-149 的宿主资源争用已由容器边界缓解,但剩余风险转移到容器内 npm ci 黑洞:安装命令缺少比 45 分钟 job-level timeout 更短的 hard deadline,且 npm fetch/retry 没有显式网络诊断边界;当前没有 OOM 或 PID 耗尽证据。
  • 修复:仍只将 npm ci 放入 digest-pinned Node 容器;保留 CPU 1.5、memory 2g、no swap、pids 256,并在容器内用 timeout --signal=TERM --kill-after=30s 900s 包住安装,避免外层 timeout 杀掉 docker CLI 后留下 orphan 容器。npm 增加 --fetch-timeout=60000、2 次重试和 1s/10s retry 上下限;超时或非零退出都输出明确诊断并 fail closed。测试、lint、build 与 exact-SHA 行为保持原样,不回退到宿主 npm。
  • 验证:本地 node --test frontend/tests/staging-backend-workflows.test.ts 32/32 passedgit diff --check 通过。远端 staging gate/deploy 待本提交后验证,本记录不提前声称远端收口。
  • 防复发:依赖安装必须维持 digest-pinned Node runtime、显式资源上限、命令级 hard timeout 与 npm 网络 timeout;合同测试需锁定只有 npm ci 在受限容器内执行,并持续确认测试、lint、build、exact-SHA checkout/publish/deploy 语义未漂移。
  • 相关记录:BUG-129、BUG-136、BUG-142
  • 复发自:无
  • 修复版本:待本次提交 / gate / deploy

BUG-150 | staging publish 的 Webpack 镜像构建耗尽共享 Gitea 资源

  • 状态:resolvedlocal candidate,远端 gate/deploy 待本提交)
  • 首次发现:2026-08-09
  • 最近更新:2026-08-09
  • 影响面:Gitea staging quality gate 的 publish 镜像构建、共享 Gitea/runner 可用性;production 未涉及。
  • 用户现象:Run 1634 的 validate 在约 12 分钟内成功,publish 随后在 Docker 内执行 next build --webpack 超过 25 分钟;期间 Gitea API 持续返回 502 和空 JSON,最终 job 中断。
  • 触发条件:frontend/package.json 将默认 build 改为 Webpack 后,Dockerfile 的 RUN npm run build 也继承该构建器;classic Docker builder 的慢 COPY 与资源受限 runner 进一步放大构建开销。
  • 根因:为非 push 校验引入的 Webpack 兼容参数错误地放进了全局 package script,使实际镜像发布也从已成功的 Turbopack 路径切换到高开销 WebpackGitea 与 runner 共享资源时因此拖垮控制面。直接重跑旧 revision 会重复相同故障。
  • 修复:恢复默认 npm run buildnext build,让 Docker publish 继续使用 Turbopack;仅在 PR/manual 的非 push validate 分支显式追加 -- --webpack。staging push 继续跳过重复 production build,由 publish 镜像构建唯一验证。
  • 验证:聚焦 workflow contract 锁定默认 Turbopack、非 push Webpack 与 staging push 单次镜像构建语义;本地测试和 git diff --check 通过。远端 gate、publish、deploy 与 exact-SHA smoke 待本提交后验证。
  • 防复发:不得把只用于隔离 worktree/非 push 校验的构建器参数写回全局 package script;发布构建器变化必须由 workflow contract 同时覆盖 Dockerfile 与事件分支。
  • 相关记录:BUG-142、BUG-149
  • 复发自:无
  • 修复版本:待本次提交 / gate / deploy

BUG-151 | 手工 Release Gate 被分配到缺少 Docker Compose v2 的 runner

  • 状态:mitigated
  • 首次发现:2026-08-09
  • 最近更新:2026-08-09
  • 影响面:Gitea 手工 release-quality-gate.yml Run 1638;旧生产、Supabase、DNS 和新生产应用均未被切换。
  • 用户现象:候选 SHA 9235ee66f6d889e6c27e4b85411448889dd7d37d 已通过 staging gate 并在公网 staging 运行,但手工 Release Gate 在 1113 个前端子测试中出现 17 个失败。失败均从 docker compose --project-namedocker compose --env-file 返回 unknown flag 开始。
  • 触发条件:完整 release profile 在 xiaoxin runner 执行 PostgreSQL/Compose 集成测试,而该 runner 只有 Docker CLI、没有可用的 Docker Compose v2 插件。
  • 根因:workflow 的工具链预检只执行 docker version,未验证 docker compose;同时 Release Gate 与已验证具备 Compose v2 的 staging/backend runner 分离,导致环境能力漂移直到完整测试阶段才暴露。
  • 修复:将手工 Release Gate 收敛到 manman-linux,并在依赖安装前强制 docker compose version --short 必须为 v2;增加 workflow 回归断言,防止重新绑定到无 Compose runner 或删除能力检查。
  • 验证:Run 1638 的脱敏日志确认 17 个失败均由 Compose 命令不可用触发;同日 manman-linux 的 backend gate Run 1636 已报告 Docker Compose 2.40.3 并成功完成质量门;本地聚焦 workflow 测试 4/4 通过。仍需新 SHA 的手工 Release Gate 成功后再视为完整关闭。
  • 防复发:任何执行 Compose 集成测试的 runner 必须在昂贵依赖安装和测试前显式验证 Compose v2Docker Engine 可用不能替代 Compose 能力证明。
  • 相关记录:BUG-128、BUG-129、BUG-136、ERR-095、ERR-099、ERR-103
  • 复发自:无
  • 修复版本:待新 SHA 的 Release Gate 验证

BUG-152 | 订单返回套餐后再次返回会重新进入订单页

  • 状态:resolvedlocal candidate
  • 首次发现:2026-08-10
  • 最近更新:2026-08-10
  • 影响面:/membership/membership/orders 的浏览器返回历史;支付、订单读取、兑换与 production 未改动。
  • 用户现象:从套餐与会员页进入订单记录,点击返回套餐页后,再点击套餐页返回,会再次进入订单记录页,形成往返循环。
  • 触发条件:套餐页用普通 Link 将订单页压入历史记录,订单页返回时又用普通 Link 将套餐页再次压入历史记录。
  • 根因:订单页的“返回套餐与会员”是返回语义,却使用了默认 push 导航,历史栈变成 套餐页 → 订单页 → 套餐页
  • 修复:订单页返回 Link 使用 Next.js 原生 replace,将当前订单页历史项替换为套餐页;套餐页再次返回时回到进入套餐前的原入口。
  • 验证:聚焦 membership 合同测试锁定返回 Link 的 replace 语义;前端生产构建 next build --webpack 通过;git diff --check 通过。未 push、deploy 或触碰 production。
  • 防复发:固定目标的“返回上级页”不能再次 push 当前上级页;会员页回归测试持续锁定订单页返回的 replace 语义。
  • 相关记录:无
  • 复发自:无
  • 修复版本:本地候选提交(未 push / deploy

BUG-153 | 个人报告页面无法下滑且命盘与打印版式失真

  • 状态:resolvedlocal candidate,待 review/deployment
  • 首次发现:2026-08-09
  • 最近更新:2026-08-09
  • 影响面:/reports/[reportId] 网页阅读、D1/D9/D10 命盘 SVG、浏览器打印/保存 PDF;聊天页保留既有全局滚动锁。
  • 用户现象:报告页只能通过缩小浏览器查看下方内容;打印或保存 PDF 时长文本、表格和分页缺少稳定版式;命盘内多个宫位的星体文字叠在左上宫格;当前版本还隐藏了已存在的 PDF 操作入口。
  • 触发条件:报告页运行在全局 html, body { overflow: hidden } 的聊天壳层中但自身没有纵向滚动边界;命盘为每个宫位绘制 occupants 时重复使用固定的局部坐标却未应用宫格偏移;打印样式继续保留屏幕表格的 min-width/横向滚动合同,并把可能超过一页的长摘要整体设为避免分页。
  • 根因:报告页面复用了聊天应用的根滚动策略,却没有为报告建立独立 100dvh + overflow-y:auto 阅读容器。PlanetList 的调用没有 translate(cell.x, cell.y),因此十二宫共享同一绘制原点,且未对 schema 允许的密集 occupants 做行数上限和裁切。打印 CSS 只有 A4 页面与少量 break-inside 规则,没有清理祖先高度/overflow、表格最小宽度、长单词换行与长叙事分页。PDF 操作此前被功能隐藏提交移除,并非底层打印 helper 缺失。
  • 修复:为 ready 报告建立独立 .personal-report-reader 滚动容器;将报告重构为 answer-first 的研究备忘录版式(封面快照、核心判断、D1 与关键证据双栏、主题解读、证据附录),使用既有中文正文字体、暖象牙纸张色和细分隔线,不引入卡片阴影或渐变。SVG 逐宫应用 transform 与 clipPath,密集星体最多显示七项加 +N 项。打印时恢复祖先自动高度和可见 overflow,重置表格 min-width/wrapper overflow,启用 fixed layout 与强制换行,并允许长主题自然分页;A4 @page 只随 ready 报告 route 注入,不再污染全站打印。恢复“打印 / 保存为 PDF”操作并保留不支持打印浏览器的能力保护;附录改用原生 details/summary,即使客户端 hydration 延迟也可展开,打印时始终显示内容。
  • 验证:个人报告聚焦测试覆盖 answer-first 顺序、十二宫坐标/裁切/密集数据、独立滚动边界、打印表格与分页、原生附录 disclosure、PDF 操作能力保护;浏览器在 1363×936 CSS viewport 实测报告容器 clientHeight=936scrollHeight=3466,滚动后 scrollTop=980 并可到达底部,附录展开后高度增至 5219;参考图与实现图完成同画布视觉比对。聚焦 ESLint、生产构建和 git diff --check 作为最终门禁重新执行。
  • 防复发:聊天根滚动锁与报告阅读滚动必须分层;报告回归测试不得删除 100dvh/overflow-y:auto 合同。任何命盘 occupants 改动必须验证至少两个不同宫位 transform、clipPath 与 12 项密集输入。打印回归必须覆盖长摘要、长主题、最小宽度表格和无滚动容器的 A4 输出;PDF 入口的隐藏/恢复必须同步更新明确的能力与 ready-state 合同。
  • 相关记录:BUG-126
  • 复发自:无
  • 修复版本:本地候选(未 push / deploy

BUG-154 | 个人报告错误绑定会话且创建请求同步阻塞

  • 状态:resolvedlocal candidate,待 staging gate/deployment
  • 首次发现:2026-08-09
  • 最近更新:2026-08-09
  • 影响面:个人报告入口、/reports 信息架构、POST/GET /api/reports、报告生成等待体验;报告证据合同、用户归属与每日限额不放宽。
  • 用户现象:只有进入一条已有消息的咨询 session 后才看得到“生成个人报告”,但实际报告使用的是账户出生资料而非该次对话;点击后 HTTP 请求会等待完整排盘与模型生成,用户必须停留并感知长时间阻塞,也没有集中查看历史报告的位置。
  • 触发条件:聊天页用 active session、消息数和临时 workflow receipt 控制报告 CTAPOST /api/reports 在插入 generating 记录后继续同步等待计算、模型生成、校验和 ready 写入。
  • 根因:产品入口沿用了最初的会话内 MVP,但生成主链实际构造 entryMode=direct_chart 并只读取认证用户的 canonical profile,会话只充当无意义的入口门槛。API 状态表已经支持 generating/ready/failed,却没有用响应后任务执行,也没有元数据列表端点与全局报告中心消费这些状态。
  • 修复:从聊天 header 删除 session-bound CTA,在全局侧栏加入“我的报告”并新增 /reports 报告中心;创建请求不再发送 sessionId,只携带报告身份字段。GET /api/reports 通过认证客户端/RLS 返回最近 20 条元数据,明确不返回正文与证据 hash。生产 POST 持久化 generating 后使用 Next.js 16 官方 after() 在响应后运行原有生成链并立即返回 202;完成/失败继续走原有 owner-scoped service 写入,意外异常也会落到稳定失败态。进程重启等中断留下的生成记录超过 15 分钟后会在下一次显式创建前安全回收为失败,避免永久占用单用户生成锁。中心与详情页每 3 秒轮询,用户可离开页面,现有 ready 报告可直接打开。
  • 验证:新增可执行回归证明 deferred 模式先返回 202 且行状态为 generating,执行任务后才转为 ready;个人报告、侧栏相关测试 185/185 通过;聚焦 ESLint、Next.js 生产构建(含 TypeScript、57 个静态页面、/reports 动态路由)和 git diff --check 通过。
  • 防复发:个人完整报告入口不得依赖 active session、消息数量或临时 workflow receipt;创建请求不得携带 chat text、出生明文或 sessionId。生产适配必须保留 response-after 调度与 202 合同,列表端点禁止返回 report_document、calculation/evidence hash。报告中心必须持续覆盖空、生成中、完成、失败和未登录状态。
  • 相关记录:BUG-126、BUG-153
  • 复发自:无
  • 修复版本:本地候选(待 staging push/gate

BUG-155 | 后台邮箱 OTP 登录后密码状态接口误报未登录

  • 状态:resolvedlocal candidate,待 staging gate/deployment
  • 首次发现:2026-08-10
  • 最近更新:2026-08-10
  • 影响面:独立后台域名的邮箱 OTP 登录、首次密码设置引导与 GET/POST /api/account/password;普通用户域名、未知 Host 拒绝和后台 RBAC 不放宽。
  • 用户现象:邮箱 OTP 校验成功并已创建 Better Auth session,但页面随后提示“暂时无法确认密码状态,请稍后再试”,密码状态接口返回 401“请先登录”。
  • 触发条件:浏览器在已配置的 admin origin 完成邮箱 OTP 登录后,用同一 host-only session Cookie 请求 /api/account/password
  • 根因:密码状态 route 在读取 session 前把身份 surface 硬限制为 user;admin host 虽然是已识别身份域名且持有有效 user session,仍被提前拒绝。前端把该非 2xx 响应映射成密码状态暂不可用。
  • 修复:密码状态 route 继续要求 self-hosted identity、已识别 Host 和有效 user session,但允许 useradmin 两个已配置 surface;未知 Host 仍返回 401Cookie 继续保持 host-only,不引入跨域会话共享。
  • 验证:身份集成回归新增 admin host OTP -> session -> GET /api/account/password,无密码账户必须返回 200 与 hasPassword=false;同一 Cookie 改投未知 Host 仍必须返回 401。
  • 防复发:共享 user identity session 的账户自助接口应校验“已识别身份 surface”,只有明确属于普通站的业务接口才限制 surface=user;任何 admin OTP 登录回归都必须继续检查登录后密码状态探测。
  • 相关记录:BUG-123、BUG-139
  • 复发自:无
  • 修复版本:本次后台 OTP 密码状态候选提交

BUG-156 | self-hosted 报告创建被未实现的数据库过滤器直接打断

  • 状态:resolvedlocal candidate,待 staging gate/deployment
  • 首次发现:2026-08-10
  • 最近更新:2026-08-10
  • 影响面:self-hosted staging 的 POST /api/reports;报告生成主链、用户归属、每日限额和 production 未放宽或改动。
  • 用户现象:合法的个人报告创建请求返回 {"error":"报告生成暂时不可用","code":"report_generation_failed"}
  • 触发条件:已登录用户调用报告创建接口,route 在进入报告创建核心逻辑前先回收超过 15 分钟的 generating 记录。
  • 根因:回收查询使用 Supabase 风格的 .lt("updated_at", staleBefore),但 self-hosted LocalPostgresQueryBuilder 只实现了现有的 .lte();运行时在构建查询时抛出 TypeError,被 route 的稳定错误边界统一映射为 report_generation_failed
  • 修复:复用本地 PostgreSQL 查询构建器已有的 .lte(),把边界定义为“更新时间小于或等于 staleBefore”;不新增过滤器 API。
  • 验证:个人报告 API 与入口聚焦测试 54/54 通过,静态回归锁定 route 使用 .lte();聚焦 ESLint 和 git diff --check 通过。
  • 防复发:self-hosted route 不得调用 LocalPostgresQueryBuilder 未实现的 Supabase 操作符;报告回收合同继续锁定 .lte(),数据库集成层已有 lte 查询覆盖。
  • 相关记录:BUG-154
  • 复发自:BUG-154staging 发布前遗漏 self-hosted 适配能力核对)
  • 修复版本:本次报告接口修复提交

BUG-157 | 侧栏“我的报告”按钮缺少 flex 布局导致图标与文字分离

  • 状态:resolvedlocal candidate,待 staging gate/deployment
  • 首次发现:2026-08-10
  • 最近更新:2026-08-10
  • 影响面:展开状态的全局侧栏“我的报告”入口;导航行为、折叠态 tooltip 与 accessibility 语义不变。
  • 用户现象:报告图标贴在按钮左上方,文字单独位于中间,整体未与上方“新对话”按钮对齐。
  • 触发条件:侧栏展开并渲染 .report-nav-button 的图标与文字。
  • 根因:样式设置了 justify-contentgap,但没有启用 flex formatting context,因此这些对齐属性不生效。
  • 修复:直接复用相邻“新对话”按钮的原生 CSS 布局,补齐 display:flex、水平/垂直居中和一致的水平内边距。
  • 验证:个人报告入口回归锁定 .report-nav-button 的 flex、垂直居中和水平居中;聚焦测试、ESLint 与截图检查通过。
  • 防复发:带图标和文字的侧栏按钮必须在同一 flex formatting context 中对齐;入口合同测试持续锁定关键布局属性。
  • 相关记录:BUG-154
  • 复发自:无
  • 修复版本:本次侧栏对齐修复提交

BUG-158 | 系统 Python 启动预检时重复使用不兼容解释器

  • 状态:resolved
  • 首次发现:2026-08-07
  • 最近更新:2026-08-07
  • 影响面:scripts/pre_work_check.py、开工预检、碎片扫描与聚焦治理测试
  • 用户现象:每次执行文档规定的 python3 scripts/pre_work_check.py ...,碎片扫描都会因 Python 3.9 不支持项目使用的 PEP 604 类型注解而退出,聚焦测试同时报 /usr/bin/python3: No module named pytest
  • 触发条件:macOS 的裸 python3 指向 /usr/bin/python3 3.9,而仓库 .venv 已安装 Python 3.11 与 pytest。
  • 根因:预检脚本把启动自身的 sys.executable 固定为全部子命令的解释器,没有验证项目声明的 Python >=3.11,也没有优先使用仓库虚拟环境;ERR-078 原防线只要求操作者手动改用 .venv,因此文档中的标准命令仍会稳定复发。
  • 修复:预检入口按确定顺序探测项目 .venv、当前解释器和可用的 Python 3.11+;只有同时满足 Python >=3.11 与 pytest 可用才执行全部 Python 子检查。报告显式记录选择结果和各候选探测信息;找不到兼容环境时失败闭合并给出安装指引。新增严格的 JYOTISH_PRE_WORK_PYTHON 覆盖入口,配置错误时不静默退回其他解释器。
  • 验证:tests/test_pre_work_check.py 覆盖虚拟环境优先、系统 3.9 跳过、严格 override 和 runtime failure 状态;使用裸 /usr/bin/python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 完整通过,报告选择仓库 .venv/bin/python 3.11 且 focused governance tests 通过。
  • 防复发:预检不得再直接用未经探测的 sys.executable 启动项目脚本或 pytest;标准文档命令必须纳入真实系统 Python 启动回归,且 JSON 报告必须保留 python_runtime_ok 和实际解释器信息。
  • 相关记录:ERR-011、ERR-014、ERR-078
  • 复发自:无
  • 修复版本:本次同步提交

BUG-159 | consultation 报告漏挂 Ashtakavarga/KP 且 VedAstro raw response 晚于证据包生成

  • 状态:resolvedlocal candidate,待 staging gate/deployment
  • 首次发现:2026-08-10
  • 最近更新:2026-08-10
  • 影响面:个人报告 consultation workflow、Technique Audit、VedAstro official evidence、modules.ashtakavargamodules.kp_cusps
  • 用户现象:Technique Audit 将 Ashtakavarga、KP cusp 和官方 VedAstro 标成 blocked;即使已配置 API key,当前报告仍提示官方外部引擎未验证或不可用。
  • 触发条件:报告通过 consultation workflow 生成 machine evidence packet;本地排盘已有 planets/ascendant,或 VedAstro gateway 本次返回 official raw response。
  • 根因:consultation 本地补充层只挂入 Varga、Arudha 与 Narayana,未调用仓库已有的 Ashtakavarga/KP 计算;同时 VedAstro gateway 默认未请求 official full snapshot,且在 machine evidence packet 冻结后才执行,导致即使 key/endpoint/network ready 也没有本次 raw response 可进入报告审计。API key 只满足凭据配置,不证明本次调用、响应与证据传播成功。
  • 修复:复用现有 _compute_ashtakavarga_normalized_planets_from_body_compute_kp,把结果挂入既有 modules;让 gateway 入口显式请求 official raw snapshot,并将 gateway 调用提前到 evidence packet 生成前;仅在 official_verified 且存在本次 raw response 时合并 official evidence,缺 raw 时继续失败闭合。
  • 验证:consultation consumer/API 聚焦回归锁定 Ashtakavarga SAV、12 个 KP houses、两项 audit 为 used,以及 gateway 无需额外环境开关也会请求并传播 raw response 至 official_verified;相关聚焦测试与开工预检通过。
  • 防复发:本地计算器存在不等于已进入报告 modulesmachine evidence packet 不得在 official gateway 完成本次调用前冻结;official_verified 必须同时具备 closure state 与 raw response。
  • 相关记录:ERR-020、ERR-021、ERR-022、ERR-024、ERR-025、ERR-026、ERR-104
  • 复发自:无
  • 修复版本:待提交

BUG-160 | 管理端商品权益枚举未本地化且登录、套餐与充值返回状态出现前端闪烁或陈旧状态

  • 状态:resolvedlocal candidate,待真实登录态浏览器验收)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:管理端商品与权益列表/编辑表单、主站登录跳转、首页 composer、套餐页兑换码弹窗、支付后首页点数余额
  • 用户现象:商品类型、计费周期、权益类型和重置周期直接显示英文枚举;未登录进入 jyotisha.chat 时登录页接管前短暂出现“暂时无法进入 Jyotisha”;首页 composer 展示过多状态提示;套餐页默认打开兑换码弹窗;充值成功返回首页后仍显示旧点数。
  • 触发条件:管理端读取数据库枚举;首页并行账户/会话请求返回 401;composer 业务流程写入 notice;账户菜单携带 redeem=1 进入套餐页;支付页返回已存在的首页历史记录且该记录未按普通 pageshow 刷新账户。
  • 根因:商品与权益 UI 直接渲染数据库英文值;401 跳转后继续抛出普通错误并被首页 bootstrap 写入 accountErrorcomposer footer 集中渲染所有 notice;套餐页用 URL 参数初始化 redeemOpen,首页账户菜单默认写入该参数;余额同步事件只在套餐页当前窗口发出,而首页已卸载,首页又只在 BFCache pageshow.persisted 时刷新。
  • 修复:在管理端为商品类型、计费周期、权益类型和重置周期增加中文显示映射,API 原始值保持不变;401 使用 location.replace 和专用跳转错误,bootstrap/账户刷新忽略该跳转并保持 loading;删除 composer notice 可见渲染;移除 membershipHref 的 redeem 参数和套餐页 URL 自动开窗逻辑,保留用户手动兑换按钮;首页每次 pageshow 都重新读取账户与点数。
  • 验证:商品/会员/首页入口 66 条聚焦测试全部通过;本次 7 个目标文件 ESLint 通过;git diff --check 通过。完整 tsc --noEmit 仍被本任务外既有问题阻塞,包括缺少本地 boring-avatars 安装及既有报告、生产迁移和 staging workflow 测试类型错误,未把这些错误计入本修复通过条件。
  • 防复发:数据库枚举必须在管理 UI 显示边界映射,不得修改 API 真值;认证跳转不得进入普通错误页状态;对离开页面期间可能变化的账户余额,页面恢复必须主动重新读取服务端;兑换弹窗只能由明确用户操作开启。
  • 相关记录:BUG-010、BUG-090、BUG-123
  • 复发自:无
  • 修复版本:本地 staging 候选(未 push / deploy

BUG-161 | 咨询状态读取 503 与前台排盘在模型调用前超时

  • 状态:resolvedlocal candidate,待迁移与部署验收)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:GET /api/consult/status、首页前台咨询生成;个人报告与高严谨工作流继续保留完整外部证据链。
  • 用户现象:咨询状态接口返回“暂时无法读取咨询状态”;已发布且启用的模型仍无法开始聊天,咨询请求最终取消且没有生成回复。
  • 触发条件:self-hosted Web 以 service_role 直查启用 RLS 的 consultation_requests;前台咨询缓存未命中新星盘时,同步执行 VedAstro overview/full snapshot/range scan 与末尾 gateway,累计耗时超过 Web 请求时限。
  • 根因:这是两个独立故障。状态接口所用 service_role 缺少 consultation_requests 的表级 SELECT;聊天失败发生在模型调用前,排盘主链把可选外部交叉验证当作前台同步必经步骤,多个外部调用超时累计后触发前端 workflow 超时。
  • 修复:向前迁移仅授予 service_roleconsultation_requests 的 SELECT,浏览器角色仍无表权限;只有 /api/consult 传入 foreground 模式,Python 前台路径跳过 VedAstro main-entry overview 与 gateway,返回明确的 foreground_optional_evidence_deferred 降级状态并继续使用本地 D1、分盘、Arudha、Narayana、Ashtakavarga 与 KP 证据。个人报告保持默认完整工作流,排盘缓存键区分是否跳过外部 overview,避免快速结果污染完整报告缓存。
  • 验证:Python 聚焦回归覆盖 foreground 标志传播、overview/gateway 不执行、本地结果可回答且外部供应商不可用不致命;前端合同覆盖只有聊天传 foreground、报告不传;数据库合同覆盖 service_role 有 SELECT 且 anon/authenticated 无 SELECT。最终本地测试结果见本次任务记录。
  • 防复发:服务器直查启用 RLS 的表必须同时验证运行角色、表权限与策略;用户前台请求不得同步串联多个可选外部证据调用;快速排盘与完整证据排盘必须使用不同缓存键。
  • 相关记录:BUG-159、ERR-020、ERR-021、ERR-022、ERR-024、ERR-025、ERR-026、ERR-104
  • 复发自:无
  • 修复版本:本地 staging 候选(未 push / deploy

BUG-162 | 咨询工作流由路由预执行,Agent 无法真实编排 Skill 与服务器排盘工具

  • 状态:resolvedstaging candidate
  • 首次发现:2026-08-11
  • 最近更新:2026-08-14
  • 影响面:POST /api/consult 的个人/一般咨询、Mastra Agent、浏览器流式活动状态、咨询消息执行回执与计费结算;production 默认路径不变。
  • 用户现象:回答可以生成,但服务端在 Agent stream 前已经完成咨询工作流,Agent 只是复述结果;Web 只能按“有无正文”猜测状态,无法证明 Agent 实际加载 Jyotish Skill 或调用排盘工具。
  • 触发条件:咨询进入旧 runtime;route 直接执行 runConsultationWorkflow(),再把大段结果塞入 prompt,且仅返回 text/plain
  • 根因:编排权在 Next.js route,不在 Agent;个人 Agent 未绑定服务器排盘工具,Skill/tool 执行合同和公开事件协议均不存在。若直接透传 fullStream,还会泄露 reasoning、工具参数/结果、出生资料或 Skill 内容。
  • 修复:增加 legacy|canary|enabled runtime 开关;新路径由带 Jyotish Skill 的 Mastra Agent 调用请求级 run-jyotish-consultation,工具参数只允许 question/theme,出生资料始终由服务器上下文绑定,并以请求内 Promise 保证重复调用只计算一次。服务端把 fullStream 清洗为 NDJSON,仅公开安全的 run/Skill/tool/activity/answer 事件;Skill 和主工具合同未完成时最多重试一次,仍失败则释放预留点数且不保存成功消息。Web 按 content-type 保留 legacy text/plain 回滚路径,新路径只累积 answer.delta,收到 run.completed 后保存 workflow/execution receipt,并用服务器 activity 驱动状态 UI。浏览器断线后服务端继续完成互斥结算。2026-08-14 进一步加入 ConsultationPlan v2:服务端在真实点数预留前用最终 profile mode、精度边界和单次成本上限校验 plan;同一个 pre-reserve plan 原样传给 Agent 工具与 legacy workflow,模型提供的 question/theme 不能重建或改变 plan。Python 仅接受服务端 route allowlist 的完整 metadatapartial metadata 不得通过删除 plan_version 降级为 legacy,且 plan 只能收紧、不能授予精确应期权限。ConsultationPlan 在创建时对对象和嵌套数组执行深冻结,确保扣点后的 workflow 不能改写边界。模型 evidence packet 不再接收任意对象后依赖 denylist 清洗,而是分别对 natal_foundationdomaintimingvalidationevidence_contract 执行显式业务字段 allowlist 投影;主题 evidence 的 details 只选择批准字段,不整体透传,从结构上阻断出生、位置、账务、身份、权限和工具 payload 的别名绕过。
  • 验证:聚焦回归覆盖动态 Skill 工具、General Agent 无个人排盘工具、服务器绑定参数、并发幂等、unverified precise timing blocked、私有 chunk 过滤、任意 NDJSON 边界、合同完成前正文阻塞、失败不保存、真实 activity UI、execution receipt 持久化、legacy 流与断线结算合同;ConsultationPlan v2 的 TypeScript 定向测试 49/49、Python 合同与 API dry-run 测试 27/27 通过;回归覆盖 plan 与嵌套数组冻结,以及 reported_timelngutc_offsetaccount_balanceentitlementsaccess_scopes 六类恶意嵌套别名不进入模型 packet,同时保留本命、Shadbala、Ashtakavarga、Dasha、Narayana 与 evidence status。目标 ESLint、Python py_compilegit diff --check 通过;全量 tsc --noEmit 仅保留与本任务无关的既有 migration fixture 字段和 staging workflow 正则 target 错误。
  • 防复发:个人咨询不得在 Agent stream 前直接执行主 workflow;不得把出生资料放入模型工具参数;公开流不得包含 reasoning、provider metadata、工具输入/结果或 Skill 正文;只有 run.completed 可进入成功消息持久化,步骤最多 32 个,计算与结算都必须请求内幂等。版本化 plan metadata 必须 all-or-none,扣点前校验的 plan 必须作为同一不可变对象进入实际 workflow;required_layers/claim_boundary 只表示 allowlist 合同,不能冒充 layer 已执行或 evidence gate 已通过。
  • 回滚:仅将 staging 的 CONSULTATION_AGENTIC_RUNTIME 设为 legacy;无需回滚数据库或修改 production。
  • 相关记录:BUG-161
  • 复发自:历史咨询 runtime
  • 修复版本:本次 staging 候选提交

BUG-163 | 生时校正入口没有服务端 Case 状态,复用第一条校正 Session 且并发会重复创建

  • 状态:resolvedlocal candidate;真实 PostgreSQL fixture 待远端 runner 执行)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:生时校正首页入口、侧边栏校正 Session 恢复、Case/Session 绑定、并发打开、旧 Direct Agentic 数据映射
  • 用户现象:点击首页“生时校正”时,只要历史存在任意校正 Session 就进入“继续”,不管它是进行中、已采用、已确认还是已关闭;侧边栏点击某条校正 Session 可能被复用逻辑带向另一条记录;双击或多标签并发点击可能创建多组 Case/Session;已完成校正 Session 还会阻止用户重新开始。
  • 触发条件:使用 hasRectificationSession + sessions.find(sessionType === "birth_time_rectification") 的客户端猜测路径;历史 Direct Agentic Session 没有 V9 Case 状态。
  • 根因:旧入口把“存在任意校正 Session”当作“可继续”,把客户端数组第一条校正 Session 当作权威;没有服务端 Case 状态机(resumable/terminal 由消息数、是否有候选推断),Session 与 Case 没有数据库级双向绑定与精确恢复,open/create 没有 requestId 幂等账本和同用户串行化,旧 Agentic 数据也没有一次性的 Case 回填与结果映射。
  • 修复(V9 Agentic Domain lane):
    1. 新增独立 Skill skills/jyotish-birth-time-rectificationSKILL.md ≤200 行 + 5 个 references),方法源只在 Skill,系统提示词不再复制完整方法。
    2. 新增 V9 领域 contractsCase 状态机(10 状态 + resumable/terminal 谓词 + 单向终态)、Evidence kind/date precision/append-only lineage、public receipt/activity allowlist、open request/response schemassupersedeActive=true 被 schema 与 RPC 双重禁止。
    3. 新增向前业务迁移 20260812010000_agentic_rectification_v9_runtime.sql(只进 frontend/supabase/migrations,不进 frontend/db/migrations):agentic_rectification_cases(每用户最多一个 resumable 由 partial unique index 强制;Case/Session 双向一致由触发器+RPC+唯一索引保证)、agentic_rectification_evidence(原文 grounding、日期精度、revision lineage、confirmed 只能由服务器确认路径产生)、agentic_rectification_turnspending/completed/failed/retryable,禁止 reasoning)、agentic_rectification_tool_receipts(只存 fingerprint/phase/tool/status)、agentic_rectification_open_ledgerrequestId 幂等);agentic_rectification_results 前向扩展 case_id/evidence_ledger_fingerprint/candidate_range_fingerprint/skill_version;所有新表 RLS + 仅 service_role 授权,全部 RPC security definer + search_path='' 且仅 service_role 可执行;浏览器不传 userId、出生快照、range 或权限决定。
    4. 一次性幂等 legacy backfill(迁移内执行,应用启动不隐式批量跑):engine_confirmed→confirmed、user_accepted only→candidate_accepted、有消息无选择→collecting_evidence/candidate_ready、重复空/旧→abandoned/superseded、每用户只保留最新实际 active(窗口函数+部分唯一索引),旧文本绝不生成 confirmed evidence;提供 backfill_agentic_rectification_legacy_cases() 与可复跑的 verify_agentic_rectification_backfill()
    5. 新增 Case Service + 五个 APIPOST /api/rectification/cases/openhomepage resume-or-create / session 精确恢复 / new 安全冲突)、GET entry-summary(首页 CTA 真值)、GET cases/[caseId](脱敏投影,绝不返回 baseline_birth_snapshot)、POST closePOST upgrade-skill;profile 不完整不建案;错误不泄露身份、出生资料或 DB 原文。
  • 验证:新增 rectification-v9-contracts.test.ts12)、rectification-v9-case-service.test.ts14)、rectification-v9-migration.test.ts(17 静态合同)本地共 43 项全部通过;rectification-v9-database.test.ts(真实 PostgreSQL 迁移 apply/re-apply、open 原子+幂等+resume、profile 门禁、所有权、terminal 只读、evidence 生命周期、backfill 状态分布/单 active/结果映射/幂等)在本机因无 Docker 按环境 skip(5 项),待远端完整 runner 执行;既有生时校正相关测试 60/60 通过;tsc --noEmit 对本 lane 文件零错误(全量 6 个既有错误全部位于未触碰文件);目标 ESLint 0 error 0 warninggit diff --check 通过;database-local-business.test.ts 精确 public 表清单同步新增 5 张 v9 表并断言新迁移 appliedBUG-127/144 防复发)。
  • 防复发:Case 状态、resumable/terminal、shouldStartOpening 必须只由服务端决定;侧边栏必须以精确 sessionId 恢复并校验所有权;open/create 必须走 requestId 幂等账本 + 同用户 advisory 串行化;新增 self-hosted 业务表必须同时更新全迁移 applied ledger 与精确 public 表集合,且只进 frontend/supabase/migrations;新页面/新 API 必须同步能力审计;浏览器任何入参都不允许携带 userId、出生资料、range 或权限决定。
  • 相关记录:BUG-004、BUG-027、BUG-033、BUG-067、BUG-068、BUG-069、BUG-072、BUG-074、BUG-085、BUG-086、BUG-095、BUG-112、BUG-113、BUG-114、BUG-115、BUG-116、BUG-117、BUG-118、BUG-119、BUG-120、BUG-121、BUG-127、BUG-143、BUG-144(编号存疑:BUG-033 无法判定指向原记录还是 BUG-236)
  • 复发自:无(新 Agentic Domain 主链;旧防线缺失的直接原因见下)
  • 修复版本:本地 staging 候选(未 push / deploy

后续推进(2026-08-13 · stream agent execution + entry routing lane

  • 状态:resolvedlocal candidate;真实 PostgreSQL fixture 待远端 runner 执行)
  • 最近更新:2026-08-13
  • 现象:旧 /api/rectification/agent 仍消费 textStream,客户端 history 可覆盖服务端历史,maxSteps=8 全局固定,billing identity 是 sessionId;旧工具让模型传 candidate_range/events[]confirmedGate 在进程内;UI 的“思考状态”不能证明 Skill/工具真的执行过,候选卡从 Agent 文本或隐藏 sentinel 解析。
  • 根因:第一轮 V9 runtime 落地了 Case/Evidence/Turn/Receipt 表与 open API,但 agent 执行层仍是旧 Direct Agentic 传话筒:没有 durable turnpending→completed)、没有 fullStream→allowlist NDJSON 映射、没有 Case-ref 工具、没有 Skill 真实加载证据、没有 DB 驱动的 runtime selector。
  • 修复:
    1. 重构 agentic-rectification.ts:系统提示词压缩为约 25 行高优先边界(不再复制 gate→scan→score→diagnostics);固定加载 skills/jyotish-birth-time-rectificationmaxSteps 按 action 有界(opening/read-only 6、evidence 8、rescore 12、accept/confirm 6+ 硬上限 16 + 重复工具调用检测(>3 次相同调用中止)。
    2. 新增十个 Case-ref 工具(rectification-v9-tools.ts):read-case / propose-evidence / confirm-evidence / revise-evidence / compare-candidates / read-diagnostics / offer-candidates / accept-candidate / confirm-birth-time / close-caseinput 只含 caseId/sourceTurnId/evidenceId/resultId/candidateId/quote/proposedKind 等最小引用,绝不接 userId/出生资料/range/events[]/分数/权限开关;每个工具走 RLS 仅 service_role 的 RPCevidence ledger、指纹缓存、receipt、幂等);acceptedconfirmed 由 RPC 分列,confirm 需要 confirmation gate + 用户原话 consent quote 原文匹配;candidate 相同 evidence/range/engine 指纹复用缓存。
    3. /api/rectification/agent 重写为 Case-ref API:请求仅 caseId/sessionId/requestId/action/message;服务端验证 Case↔Session exact binding 与 chat_sessions.agentic_rectification_case_id;客户端 history 不再进入上下文,每轮先从持久化 dossier 读取 turns/evidence;先原子 pending Turn,成功才 completed(半截文本永不成为 settled history);消费 result.fullStream 并按 allowlist 输出 NDJSONrun.started/skill.started/skill.loaded/case.loaded/evidence./candidates./diagnostics.completed/candidate.accepted/birth_time.confirmed/answer.delta/run.completed/run.failed),reasoning/raw/provider metadata/tool args/results/出生资料/评分/DB 错误永不透传;skill 加载证据来自真实框架(agent.getSkill 预校验 + fullStream skill tool-call/tool-result),首轮无 skill.started/loaded 允许一次受控重试,仍失败则 Turn failed/retryable 并释放用量;billing identity 绑定 rectification:case:{caseId}opening/read-only 不扣,首次实质运行预留一次,恢复/重试不重复;断线 abort 传播释放。
    4. 新增向前业务迁移 20260813010000_agentic_rectification_v9_agent_api.sqlcase dossier / case compute(含 baseline 但不向模型暴露)/ turn finalize / candidate persist(指纹缓存) / case-scoped accept / consent-gated confirm / guarded transition / needs_rebaseline profile guard trigger / agentic_rectification_run_phases(持久化 skill.started/loaded 等 phases/ turn receipt 读取;rectification_runtime_version feature flagpublished、100%config version=v9 legacy_mode=readonly),DB 驱动 selector,两个 runtime 不可能同时写 profile/扣费/confirm。
    5. 前端:page.tsx 删除 hasRectificationSessionsessions.find(sessionType==='birth_time_rectification');新增 openRectificationFromHomepage/openRectificationSession(sessionId)/startNewRectification,全部请求服务端 Case open API 并使用返回的 exact sessionId/caseId;首页 CTA 由 entry-summary 驱动(开始/继续上次/再次校正);侧边栏校正 Session 点击走 intent=session + 精确 sessionIdterminal 只读 + “再次校正”);shouldStartOpening 只来自服务端;聊天组件改为 caseId/sessionId/readonly/shouldStartOpening 初始化并从持久化 Turns 恢复,候选卡来自 Candidate Snapshot API,活动展示来自真实 NDJSON + 持久化 receipt(可折叠“本轮做了什么”,不显示 reasoning),删除本地 timer 模拟与隐藏 sentinel。
    6. 新增 GET cases/[caseId]?sessionId= 返回持久化 turns/evidence/latest_result/逐轮 receipt 支持刷新恢复;新增 POST cases/[caseId]/candidates/accept(UI 候选卡直连,不扣费、幂等、case-scoped)。
  • 验证:新增 rectification-v9-entry-routing.test.ts13)、rectification-v9-evidence.test.ts11)、rectification-v9-agent.test.ts12)、rectification-v9-stream.test.ts9)、rectification-v9-status-security.test.ts11);rectification-v9-migration.test.ts 扩 9 项静态合同;rectification-v9-database.test.ts 扩 agent API 迁移/flag/consent 测试(本机无 Docker 按环境 skip);重写 rectification-agentic-entry.test.ts(旧测试锁死了 hasRectificationSession + sessions.find 与 maxSteps=8、textStream、sessions.find 复用等错误行为,全部改为断言 V9 服务端路由);consultation-entrypoint.test.ts / application-billing-contract.test.ts 的 rectification 段同步为 caseId 绑定契约。本机运行:v9 聚焦 107 通过 / 0 失败 / 6 Docker skip;完整 frontend 套件 1231 通过 / 18 失败(全部为无 Docker/PostgreSQL 的环境类既有失败,基线 20 失败,本轮未新增环境失败);tsc --noEmit 仅剩 6 个既有测试文件错误(均未触碰);ESLint 0 errorgit diff --check 通过。
  • 防复发(补充):任何新 rectification 前端逻辑不得再根据消息数/候选存在/session 排序推断 Case 状态;/api/rectification/agent 只能消费 fullStream 并输出 allowlist NDJSON;工具输入 schema 必须 strict 且只含最小引用;首轮必须保留真实 skill 加载证据;billing request identity 只能绑定 caseIdDB 驱动 feature flag 是 runtime selector 的唯一来源。
  • 修复版本:本地 staging 候选(未 push / deploy

旧防线为何没拦住(hasRectificationSession + sessions.find()

  • hasRectificationSession 只断言“存在任意校正 Session”,不区分 draft / collecting / candidate_ready / candidate_accepted / confirmed / closed,因此已完成会话始终被当作可继续,且没有服务端 Case 状态可被测试断言。
  • sessions.find(sessionType === "birth_time_rectification") 取客户端数组第一条,既不保证精确 sessionId,也不校验 Case 绑定;排序、缓存或刷新差异都会改变打开哪条记录。
  • 旧测试只覆盖“能找到一条校正 Session”的客户端行为,没有服务端 Case 状态机、没有“点击指定 Session 必须精确恢复”的契约、没有并发/双击/多标签幂等断言,也没有 legacy→V9 一次性回填的数据库级验证,因此这些缺陷在回归中被遗漏。

BUG-164 | V9 红队审查:引擎适配器契约不匹配 + accept 重放幂等顺序错误

  • 状态:resolved(已修复;真实 PostgreSQL 17 与真实 Python 引擎双重实证)
  • 首次发现:2026-08-11(红队审查 60e2ce4f..724fb64c
  • 最近更新:2026-08-11
  • 影响面:V9 候选比较管线(compare-candidates / read-diagnostics)、候选采用重放幂等、确认重放返回字段
  • 现象:
    1. rectification-compare-candidates 在生产面对真实 Python 引擎必然失败:前端 engine-client.ts 假设 /api/rectification/v5/score 返回 rank/tied_minute_count/representative_time/confidence/margin_percent/selection_allowed/confirmation_allowed,实际引擎只返回 candidate_scores:[{time,score,supporting_event_ids,conflicting_event_ids}]scripts/rectification/api_service.py:score_candidates,已用真实 HTTP 请求实证);同时 V9 evidence kind/domaineducation_start/career_entry/promotion/relationship_commitment/finance_gain 等)直接透传给引擎,而引擎 SCOREABLE_EVENT_KINDS 只有 education_milestone/relocation/relationship_start|change/career_change/finance_change/self_health_event(health_pressure),几乎全部 400。结果:所有候选比较必然 engine_no_candidatesengine_http_errorV9 无法产出任何候选。
    2. accept_agentic_rectification_candidate_for_case 的幂等重放分支位于 profile 基线校验之后;第一次 accept 写入 active_birth_time 后,基线快照与 profile 必然分歧,重放/双击/断线重试返回 candidate_profile_changed 而不是 idempotent=true(旧 accept_agentic_rectification_candidate 是先重放后校验,语义回归)。
    3. confirm_agentic_rectification_birth_time 幂等分支在 v_result 载入前引用 v_result.idconfirmed 重放响应的 result_id 恒为 null。
  • 触发条件:任何进入 compare-candidates 的真实运行;accept 成功后同一候选再次 acceptconfirmed 后同一 confirm 重放。
  • 根因:
    • 新增 engine-client.ts 从未与真实引擎做契约测试(既有测试全部 mock runV9CandidateScore,未覆盖真实响应形状);V9 evidence 领域模型与引擎粗粒度评分词汇之间缺少 kind/domain 翻译层。
    • accept/confirm RPC 的重放语义被基线保护逻辑错误地前置/后置,未对齐旧实现的先重放后校验顺序。
  • 修复:
    1. engine-client.ts 对齐真实引擎契约:新增 toEngineScoreableEvent kind/domain 翻译(education_→education_milestone、career_→career_change、relationship_start/commitment→relationship_start、relationship_separation→relationship_change、relocation→relocation、finance_*→finance_change、self_health_event→health_pressurefamily/other 留在账本但不再进引擎);readCandidatestime+score 按分数降序推导 rank、同分 tied_minute_count、相对支持度归一化;representative_time=top1selection_allowed=有候选;confirmation_allowed 只来自引擎 can_confirm_exact_minute(真实引擎当前为 false,confirm 门诚实关闭);margin_percent 取自 diagnosticsconfidence 由 margin+retention 推导;无 scorable 事件/无候选时 fail-closedno_scorable_evidence/engine_no_candidates)。
    2. 20260813010000_agentic_rectification_v9_agent_api.sqlaccept 重放分支移到 profile 基线校验之前,重放分支校验 profile 与已采用时间一致(对齐旧语义);confirm 幂等分支补 select id into v_result.idresult_id 不再为 null。
    3. 移除 rectification-v9-tools.ts compare-candidates 中双分支同 throw 的死代码。
  • 验证(真实执行,非 mock):
    • 本机安装 PostgreSQL 17brew),按 deploy/postgres/001-bootstrap-roles.sh 建角色,从空库全量应用 95 个迁移 ×2second run 95 already applied--check exit 0),含两个 V9 迁移。
    • 真实 SQL 行为:open homepage create/resume、requestId 幂等、session 精确恢复、跨用户 404、new 冲突、profile_incomplete、evidence quote grounding/idempotent/confirm/revision lineage、terminal 只读、accept→accepted + 重放 idempotent=true(修复后)、confirm gate=false blocked + gate=true 且 consent 原文匹配 → confirmed + 重放 idempotent + result_id 非空、指纹缓存复用、profile 变更 → needs_rebaseline + 候选 invalidated、legacy backfillconfirmed/candidate_accepted/superseded/abandoned 分布、results_mapped=3、重跑 cases_created=0、verify 0 conflict/0 orphan)、RLS service_role-only 均实证通过。
    • 真实 Python 引擎:scripts/jyotish_api_server.py 启动后,修复后 engine-client 直连 /api/rectification/v5/score + /v5/diagnostics 成功产出候选(rank/relative_support/tied)、family_event 被排除、confirmation_allowed=false、diagnostics 键完整映射。
    • 新增 rectification-v9-engine-contract.test.ts(9 项,含真实引擎响应形状 fixture)与 migration 顺序静态回归 1 项;v9 聚焦 33+108 全部通过;全量 npm test 12561232 pass / 18 Docker ENOENT 环境失败,与基线 6d7a9a97 同因,worktree 实证);tsc --noEmit 仅剩 6 个既有未触碰测试文件错误;ESLint 0 errornext build 通过;git diff --check 通过。
  • 防复发:引擎适配器必须有真实响应形状的契约测试;新增任何映射层必须对照 scripts/rectification/contracts.py:SCOREABLE_EVENT_KINDS;RPC 重放/幂等语义以“先重放后校验、重放校验已落库状态”为唯一实现顺序;迁移修改必须在真实 PostgreSQL 上从空库全量应用并重跑。
  • 相关记录:BUG-163、BUG-112
  • 修复版本:本地 staging 候选(未 push / deploy

BUG-165 | 合并咨询 Agentic runtime 后生时校正执行步骤复用共享 activity 字段导致构建失败

  • 状态:resolvedlocal candidate,待 staging gate
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:RectificationAgenticChat、共享 ChatMessageView、Next.js production type-check
  • 用户现象:生时校正与最新 staging 的咨询 Agentic runtime 单独测试均通过,但合并后 next buildrectification-agentic-chat.tsxstring[] 不能赋给结构化 AgentActivityView
  • 触发条件:将 V9 生时校正分支合入包含咨询 Agentic runtime 的最新 origin/staging,再运行 production build。
  • 根因:并行开发期间,共享 ChatMessageView.activity 从通用状态扩展为咨询 runtime 使用的单个 { phase, label };生时校正把执行回执的多步骤 string[] 也命名为 activity。聚焦源码/运行测试没有跨两个 runtime 做 TypeScript 组合检查,只有 production build 暴露交叉类型冲突。
  • 修复:生时校正的持久化/流式回执步骤改为独立 receiptActivity: readonly string[],只用于折叠的“本轮做了什么”;共享 activity 继续由咨询 runtime 独占并保持 { phase, label } 合同,不改其 UI 与状态映射。
  • 验证:合并最新 staging 后,rectification-v9-streamrectification-agentic-entrychat-stream-layout 共 36/36 通过;目标 ESLint 通过;Next.js 16.2.10 production build 完成 type-check、60 个静态页面生成与 route 列表收口;git diff --check 通过。
  • 防复发:生时校正执行回执与咨询即时 activity 必须使用不同字段;任何同时修改 ChatMessageView 与专用聊天 surface 的分支,合并最新 staging 后必须运行共享 layout 测试和 production build,不能只依赖各自聚焦测试。
  • 相关记录:BUG-162、BUG-163、BUG-164
  • 复发自:无(并行 runtime 合并冲突)
  • 修复版本:本次 staging 候选

BUG-166 | V9 Docker DB 测试在 staging gate 失败:service 连接池未按 URL 关闭 + fixture 种子角色越权

  • 状态:resolved(本地 Docker PostgreSQL fixture 复跑通过,待 Gitea gate
  • 首次发现:2026-08-11Gitea staging gate run 1732
  • 最近更新:2026-08-11
  • 影响面:frontend/tests/rectification-v9-database.test.tsfrontend/src/lib/db/local-postgres-client-core.ts 的按 URL 连接池生命周期
  • 用户现象:staging gate 的 Docker DB 套件恰好三个 V9 测试失败。
  • 触发条件:在 CIDocker fixture)中运行 rectification-v9-database.test.ts
  • 根因(脱敏):
    1. 两个测试创建了 createLocalPostgresDataClient 的 service 客户端后没有关闭其全局按 URL 缓存的连接池;fixture.stop() 先销毁 PostgreSQL,池的异步终止随后触发 57P01terminating connection)类报错。不能全局关闭所有池(node:test 的 DB 用例可能并发),必须按各自 service URL 关闭,且任何情况下 fixture.stop() 都要执行。
    2. 最后一个 “v9 agent api migration…” 测试用 identity_runtime 角色向 public.profiles/chat_sessions/agentic_rectification_cases/agentic_rectification_turns 播种 fixture 行;生产最小权限正确拒绝了这些写入。该用例还重复插入由 identity→auth→profile 触发器已自动创建的 profile,把 JavaScript .repeat() 写进 SQL,并错误期待未 grounded 的 consent quote 进入候选查询。
  • 修复:
    1. 审查并保留新增的 closeLocalPostgresDataPool(connectionString):先按 key 从全局缓存删除再 pool.end(),未知 key 与重复关闭均为安全 no-op,删除后再注册同名 key 可创建新池,与全局 closeLocalPostgresDataPools 并发/先后调用无冲突(end 幂等)。
    2. rectification-v9-database.test.ts 中所有创建 service 客户端的测试(open / profile gating / evidence / backfill / agent api,共 5 个)在 finally 中先 await closeLocalPostgresDataPool(service_url)fixture.stop(),嵌套 try/finally 保证池关闭失败时 fixture 仍会停止;纯迁移测试无需关闭。
    3. 最后一个测试先经合法的 identity_runtime 播种 identity.users,再由 postgres admin 更新触发器自动创建的 profile 并播种其它 public.* 行;同时改用模板中的既有 fingerprint,并按 RPC 真实合同断言 consent grounding 失败。保留生产最小权限,不改任何 grant、不改迁移。
    4. 新增非 Docker 单测 tests/local-postgres-pool-close.test.ts4 项):未知 key no-op、按 key 关闭互不影响、关闭后同 key 可重建、全局关闭后再按 key 关闭 no-op。
  • 验证:local-postgres-pool-close.test.ts 4/4 通过;本地 Docker PostgreSQL fixture 的 rectification-v9-database.test.ts 6/6 通过;目标 ESLint 0 errorgit diff --check 通过。Gitea gate 仍需对最终提交复跑。
  • 防复发:任何测试创建本地数据客户端必须在 fixture.stop() 之前按自身 service URL 关闭连接池;测试不得用 identity_runtimepublic.* 播种 fixture 行(一律走 postgres admin 的 fixture.psql);禁止为测试放宽运行时 grant 或改迁移。
  • 相关记录:BUG-163、BUG-164、BUG-165
  • 修复版本:本地 staging 候选(未 push / deploy

BUG-167 | 管理端账号重置重复要求邮箱验证码

  • 状态:resolved(本地验证通过,待 staging 部署验收)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:管理端用户列表“重置资料与会话”弹窗与 POST /api/admin/customers/reset
  • 用户现象:已登录且具备账号管理权限的管理员执行账号重置时,仍需额外发送并输入邮箱验证码,增加不必要的操作步骤。
  • 触发条件:在用户列表打开重置弹窗并提交账号重置。
  • 根因:账号重置复用了面向角色变更、账务调整等操作的 requireHighRiskAdminMutationreauthPermission,把一次性邮箱复核错误扩展到了已有登录、权限、原因、可信来源及数据库审计保护的重置路径。
  • 修复:该路由改用既有 requireAdminMutation(request, "admin.users.manage_roles"),前端仅移除该弹窗的 reauthPermission;保留管理员会话、权限、可信 Origin、确认字面量、操作原因、数据库权限检查与审计日志。其他高风险管理员操作继续使用邮箱复核。
  • 验证:账号重置合同测试确认前后端均不再要求邮箱复核,同时管理员角色变更仍使用 requireHighRiskAdminMutation;管理端 Origin 与授权边界的既有测试继续覆盖未登录 401、缺少权限/不可信来源 403
  • 防复发:账号重置只允许使用普通管理员 mutation guard;共享高风险 guard 不做全局放松,并由合同测试锁定角色变更仍需邮箱复核。
  • 相关记录:无
  • 修复版本:本次 staging 候选

BUG-168 | 生时校正运行时开关被 RLS 静默隐藏并误报服务未开放

  • 状态:resolved(本地回归已通过,待 staging 迁移与真实接口验收)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:POST /api/rectification/agent、共享 loadRuntimeFeatureFlags 的运行时开关读取,以及管理端 feature flag 列表。
  • 用户现象:staging 的 V9 生时校正接口返回 503 rectification_runtime_disabled,即使数据库中的 rectification_runtime_version 已是 publishedenabled=truerollout_percentage=100
  • 触发条件:self-hosted Web 通过 ADMIN_DATABASE_URLadmin_runtime 查询启用了 RLS 的 public.feature_flags
  • 根因:20260806050000_operations_feature_flags.sqladmin_runtime 授予了表级 SELECT,但启用 RLS 后没有创建对应 SELECT policy。PostgreSQL 因此不报权限错误而是返回零行;loadRuntimeFeatureFlags 将缺失记录安全降级为 disabled,路由遂返回“生时校正服务暂未开放”。
  • 修复:新增向前迁移 20260811030000_feature_flags_admin_runtime_read_policy.sql,保留最小 SELECT grant,并为 admin_runtime 创建 feature_flags_admin_read RLS SELECT policy;不放宽匿名、普通用户或其它运行时角色权限。
  • 验证:Docker PostgreSQL 回归先在修复前稳定得到空结果,新增迁移后要求 admin_runtime 能读取 true:100:published;staging 还需验证迁移账本、角色可见性及真实 Agent 接口不再返回 runtime disabled。
  • 防复发:任何对启用 RLS 的表新增 runtime grant 时,必须同时测试对应运行时角色的真实可见行,而不能只断言 has_table_privilege=truefeature flag 种子测试必须以 Web 实际使用的 admin_runtime 读取。
  • 相关记录:BUG-151、BUG-166
  • 修复版本:待提交

BUG-169 | V9 新建生时校正 Session 未绑定模型导致 Agent 立即返回模型不可用

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:V9 open_agentic_rectification_case 新建会话、既有 model_id is null 的生时校正会话,以及 POST /api/rectification/agent 的模型解析。
  • 用户现象:运行时开关恢复后,原请求继续返回 409 模型暂不可用;请求体包含有效 modelId,管理端模型及供应商也均为 published/enabled。
  • 触发条件:V9 Open Case RPC 原子创建 birth_time_rectification Session 后立即发送 opening。
  • 根因:RPC 插入 chat_sessions 时没有写入 model_id;前端仅在内存中把目录默认模型显示为当前选择,而 Agent 路由按安全合同只解析服务端持久化的 chatSession.model_id/model_config_version,不会信任请求体覆盖会话模型。
  • 修复:新增向前迁移 20260811040000_rectification_session_default_model.sql;数据库触发器为缺少模型的生时校正 Session 绑定当前 published/enabled 默认模型,由既有 pin trigger 固定配置版本,并一次性回填同类历史 Session。普通咨询 Session 与已有明确模型选择均不改变。
  • 验证:V9 PostgreSQL fixture 在创建 Case 前播种默认模型,要求 RPC 新建 Session 后持久化为 v9-default-model:1;staging 还需核对迁移账本、原 Session 回填结果和真实 opening 请求。
  • 防复发:任何服务器端创建 birth_time_rectification Session 的路径都必须在同一事务内得到可解析的持久化模型与版本;前端显示的默认模型不能替代数据库绑定。
  • 相关记录:BUG-060、BUG-163、BUG-168
  • 修复版本:待提交

BUG-170 | V9 Agent 开场未收到 Case ID 导致工具调用失败

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:POST /api/rectification/agent 的 opening、message、read-only Agent 消息,以及所有要求 caseId 的 V9 rectification 工具调用。
  • 用户现象:接口已返回 200 application/x-ndjsonSkill 与 Case 均已加载,但回答正文报告 invalid_case_id,最后事件为 run.failed
  • 触发条件:Agent 按指令调用 rectification-read-case,但服务端构造的 Agent 消息没有提供当前 Case ID。
  • 根因:请求路由和 runV9AgentTurn 已验证 Case/Session 绑定,但 buildAgentMessages 只传时间与用户消息;模型只能猜测工具所需的 caseId
  • 修复:在共享 Agent 消息构造处加入服务端已验证的唯一 Case ID,并明确所有 rectification 工具必须原样使用;不放宽 UUID、所有权或 Case/Session 绑定校验。
  • 验证:新增回归测试捕获实际传给 Agent 的 opening 消息,要求包含精确服务端 Case ID;staging 需以原请求确认流以 run.completed 结束。
  • 防复发:任何由模型调用、但值由服务端拥有的工具引用,都必须在 Agent 上下文中显式提供,不能要求模型猜测。
  • 相关记录:BUG-163、BUG-169
  • 修复版本:待提交

BUG-171 | V9 免费开场生成成功后仍执行 usage settlement 导致 run.failed

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-11
  • 最近更新:2026-08-11
  • 影响面:POST /api/rectification/agentopeningread_only 免费 Turn,以及回答完成后的 Turn 最终状态与 assistant message 持久化。
  • 用户现象:Agent 已加载 Skill 与正确 Case,并输出完整回答,但 NDJSON 最终事件仍为 run.failed;数据库 Turn 为 retryable 且没有持久化 assistant message。
  • 触发条件:免费 openingread_only Turn 正常生成回答并进入成功收尾。
  • 根因:路由的 billing.reserve() 对免费 Turn 不创建 usage_reservations,但 billing.complete() 仍调用 complete_usage;数据库因找不到 reservation 返回 request_missingAgent runner 将已成功回答降级为 usage_settlement_failed
  • 修复:免费 Turn 在 settlement adapter 中直接成功返回;只有 message Turn 才执行 reservation 与 settlement,保留原有付费消息的计费、幂等与失败保护。
  • 验证:staging 数据库确认目标 Case 没有 usage reservation,直接调用同一结算函数稳定返回 request_missing;新增合同回归要求免费 Turn 同时绕过 reservation 与 settlement。部署后需以真实 opening 确认最终 run.completed、Turn completed 且 assistant message 已持久化。
  • 防复发:任何声明为免费的 Agent action 必须在授权和结算两个阶段保持同一策略,不能只跳过预授权而继续结算。
  • 相关记录:BUG-163、BUG-168、BUG-169、BUG-170
  • 修复版本:待提交

BUG-172 | V9 执行记录顺序与技法展示失真,Agent 缺少完整出生地时区上下文

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:V9 生时校正 Activity、SSE/Turn receipt、rectification-read-case、Profile baseline 与结果失效链。
  • 用户现象:Agent 消息下方出现无样式的白底黑字执行记录,并固定显示“开始本轮执行、正在加载专用方法、专用方法已加载、本轮完成”等泛化文案;界面没有展示引擎实际使用的分盘、大运或行运,也无法确认 Agent 是否收到生日、出生地、精确经纬度与时区。
  • 触发条件:打开或继续 V9 生时校正 Case,渲染已持久化 Turn receipt,或让 Agent 调用 rectification-read-case
  • 根因:前端从 receipt phases 重建固定步骤且 <details> 没有专用样式;read-case 只返回 Case/证据摘要,没有合并服务端 Profile compute context;引擎结果与公开 receipt 之间也没有安全、可验证的技法 provenance 字段。
  • 修复:Activity 移到对应 Agent 消息上方,使用现有颜色、间距、圆角与键盘焦点样式;已完成记录仅从成功工具 receipt 生成,并仅在引擎返回 allowlist provenance 时展示技法。服务端内部 read-case 增加出生日期、地点标签、经纬度、IANA 时区、UTC offset、填报/当前时间与候选范围;公开 Case GET、React props、receipt 与 SSE 不返回出生上下文或原始评分,只投影 allowlist 工具和技法。Profile 的地点标签或时区 ID 变化会使可恢复 Case needs_rebaseline 并失效活动结果。
  • 验证:新增 UI 静态合同、SSE 安全投影、内部 read-case、引擎 provenance、Profile 完整性与迁移排序/权限回归;本地聚焦测试、TypeScript、ESLint 与 PostgreSQL fixture 结果见本轮交付记录。
  • 防复发:明确区分“服务端内部 Agent 工具结果”和“公开 UI/API projection”;Activity 不得从泛化 phase 文案推导技法,不得公开思维链、Prompt、工具参数、出生资料、原始分数、权重、规则 ID 或 Provider metadata。
  • 相关记录:BUG-163、BUG-170、BUG-171
  • 修复版本:待提交

BUG-173 | 恢复生时校正会话时历史消息未随异步 Case GET 渲染

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:V9 生时校正恢复入口、历史 turns 和持久化 Activity 技法展示。
  • 用户现象:点击“继续上次校正”后只看到页面壳层,Case GET 已返回 assistant turn,但消息区为空。
  • 触发条件:恢复已有 Case;聊天组件先以空 initialTurns 挂载,Case GET 稍后返回历史 turns。
  • 根因:RectificationAgenticChat 仅在 useState 初始化时投影 initialTurns,未在异步 props 更新后同步;同时 page.tsx 映射持久化 receipt 时漏掉 methods
  • 修复:复用同一个 turns→messages 投影函数,并让父层在持久化 turns 到达时用最后一个 Turn ID 重挂载该局部聊天组件;补齐 receipt methods 的客户端类型和安全映射。
  • 验证:新增静态回归检查覆盖异步 hydration 和 methods 映射;部署后以真实登录浏览器确认历史 Agent 消息及 Activity 渲染。
  • 防复发:任何异步加载后传入的初始化数据不能只依赖子组件首次 state 初始化;公开 receipt 新字段必须贯通 API projection、page mapping 与组件类型。
  • 相关记录:BUG-172
  • 修复版本:待提交

BUG-174 | V9 当前用户事件无法绑定服务端 Turn,Agent 要求重复发送

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:V9 生时校正事件证据提出与最终出生时间确认的用户原话绑定。
  • 用户现象:用户输入“2016 年 9 月上大学”后,Agent 能识别事件并调用“整理事件证据”,但声称当前轮锚点没有接通,要求用户重复发送同一句话。
  • 触发条件:Agent 在当前消息轮调用 rectification-propose-evidence;工具 schema 要求模型回传 sourceTurnId,但消息上下文和 rectification-read-case 安全投影都不公开当前 Turn UUID。
  • 根因:服务端 runner 已在 Agent 执行前创建并持有可信 turnId,工具仍错误地把该内部引用交给模型提供,形成模型无法满足的参数合同;重复发送不会修复这一合同缺口。
  • 修复:rectification-propose-evidencerectification-confirm-birth-time 不再接受模型提供的 sourceTurnId,统一使用工具上下文中的服务端当前 turnId;保留数据库 Case ownership、Turn existence 与 quote/consent 原文匹配校验。
  • 验证:回归断言 propose/confirm schema 拒绝模型传入 Turn ID,并确认事件证据与最终确认 RPC 的 p_source_turn_id 均等于服务端当前 Turn;聚焦测试通过,staging 部署证据见本轮发布记录。
  • 防复发:当前请求已经由服务器掌握的内部 ID 不得再要求模型猜测或回传;原文真实性继续在数据库信任边界验证,不能以放宽 quote 校验规避绑定问题。
  • 相关记录:BUG-170、BUG-172、BUG-173
  • 修复版本:本次提交(staging 精确 SHA 以发布记录为准)

BUG-175 | V9 明确事件被强制要求额外二次确认

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:V9 生时校正事件证据写入、Agent 访谈连续性与候选评分输入。
  • 用户现象:用户已经明确说出“2016 年 9 月上大学”后,Agent 仍要求再回答一次“对/确认”,否则事件不进入评分账本。
  • 触发条件:当前轮包含日期、主体和事件语义均明确的新事件,Agent 完成 rectification-propose-evidence 后继续按旧 Prompt/Skill 等待下一轮确认。
  • 根因:Prompt、工具描述、Skill 文档与 TypeScript 状态机把“confirmed 只能由服务器确认路径产生”错误等同于“必须额外等待一轮用户同意”;同时 scorableEvidence() 又把 pending_confirmation 纳入正式评分,导致确认语义与评分边界不一致。数据库确认 RPC 实际已支持 draft -> confirmed
  • 修复:当前轮主动、明确、单一且无歧义的用户事件由 Agent 在同一个 run 内依次调用 propose 与服务器 confirm;只有模糊、冲突、修订或需要补充原文外信息时追问。评分输入统一只接受 confirmed 且有日期的证据,保留 quote grounding、Case/Turn ownership、幂等与 append-only 修订链。
  • 验证:回归测试覆盖同轮 propose -> confirm 工具顺序、draft -> confirmed 合法迁移、Prompt 不再要求重复确认,以及 pending/draft 不进入正式评分。
  • 防复发:服务器确认路径与额外对话轮次必须分开建模;任何 pending 状态不得隐式参与正式候选评分。
  • 相关记录:BUG-170、BUG-174
  • 修复版本:本次提交(staging 精确 SHA 以发布记录为准)

BUG-176 | V9 普通 Turn 的 Agent 回复在刷新后消失

  • 状态:resolved(本地候选)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:V9 生时校正历史消息、持久化 Activity 与 Agent 下一轮上下文。
  • 用户现象:事件提交后当轮可以看到 Agent 回复和 Activity,但刷新或重新进入 Case 后只剩用户事件;opening Agent 消息仍可显示。
  • 触发条件:一条物理 agentic_rectification_turns 记录同时保存非空 user_messageassistant_message
  • 根因:get_agentic_rectification_case_dossier() 把每条物理 Turn 只投影成一条逻辑消息,并用 coalesce(user_message, assistant_message) 优先返回用户文本,导致同一行已持久化的 Agent 回复和 receipt 关联在恢复 API 中丢失。
  • 修复:新增向前业务迁移,将每条物理 Turn 通过 lateral values 展开为按 user、assistant 排序的最多两条逻辑消息;保留同一真实 Turn ID,使 assistant 消息继续读取对应 Activity receipt。
  • 验证:迁移契约测试锁定双消息展开、空消息过滤、顺序、迁移唯一性和禁止复制到 identity migration treestaging 需继续验证 Case GET、页面刷新、下一轮上下文及计费不变量。
  • 防复发:持久化行与对话消息不是一对一时,恢复投影必须显式展开全部逻辑消息,不得用 coalesce 静默舍弃其中一侧。
  • 相关记录:BUG-173、BUG-174、BUG-175
  • 修复版本:本次提交(staging 精确 SHA 以发布记录为准)

BUG-177 | 首页生时校正被未完成 Session 强制劫持,无法新建独立校正

  • 状态:resolved(staging 发布与真实环境验收以本次发布记录为准)
  • 首次发现:2026-08-12
  • 最近更新:2026-08-12
  • 影响面:首页生时校正卡片、POST /api/rectification/cases/open、V9 Case 并发约束
  • 用户现象:账户存在任意未完成的生时校正时,从首页点击“生时校正”会直接回到旧 Session;用户无法保留旧记录并另开一段校正。
  • 触发条件:存在 draftcollecting_evidencecandidate_readycandidate_acceptedneeds_rebaselinepaused Case 后点击首页生时校正卡片。
  • 根因:V9 初始设计把首页 homepage intent 定义为 resume-or-create,并用 agentic_rectification_cases_one_resumable_per_user 部分唯一索引和 active_case_conflict 强制每用户最多一个 resumable Case;首页 UI 又据 entry summary 显示“继续上次校正”。该安全约束错误扩大成产品限制。
  • 修复:首页入口始终作为显式创建动作;新增向前迁移 20260813040000_allow_parallel_rectification_cases.sql,删除每用户单 resumable 唯一索引并重定义 open RPC,使 homepage/new 创建独立 Case + Sessionsession 仍按精确 Session 恢复;requestId 幂等与同用户 advisory lock 保留。首页有未完成记录时明确提示可从左侧历史继续,但主 CTA 仍是新建。
  • 验证:入口、Case service、迁移静态合同等聚焦测试共 119 项,113 通过、0 失败;6 项真实 PostgreSQL 测试因本机 Docker 不可用跳过(测试已覆盖不同 requestId 新建多个 resumable Case、同 requestId 幂等和精确 Session 恢复)。目标 ESLint 与 git diff --check 通过;全量 TypeScript 检查仅命中仓库既有的 dayjs 缺失及无关测试类型错误。
  • 防复发:首页“新建”和历史“继续”必须使用不同 intent;不得用“存在 resumable Case”改变首页主 CTA 或阻止新 Case;同一 requestId 重试只能返回同一 Case。
  • 相关记录:BUG-163
  • 复发自:BUG-163
  • 修复版本:本次提交(staging 精确 SHA 以发布记录为准)

BUG-178 | staging 发布控制器被默认 main 分支耦合,阻止测试分支独立演进

  • 状态:resolved(本地候选,待 staging gate/deploy 验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:Gitea staging quality gate、Deploy staging、Migrate Staging Databaseproduction 发布门禁未修改。
  • 用户现象:staging 作为测试分支需要领先或偏离 main 时,旧部署工作流仍要求 main 与 staging 同一 SHA,且 workflow_run 从默认 main 加载控制器,导致 staging-only 变更无法按自身已测试工作流发布。
  • 触发条件:staging 推送了尚未进入 main 的测试提交并完成 quality gate。
  • 根因:staging 发布把 production 的 reviewed-main 收敛约束复用到了测试环境,同时依赖默认分支的 workflow_run controller;即使删除 SHA 相等检查,旧 main controller 仍可能继续执行旧门禁。
  • 修复:将 staging gate 重命名为 Independent Staging Quality Gate,使默认 main 上遗留的 workflow_run 监听器不再匹配并抢占 staging-mutation 并发组;staging push gate 在发布同一 exact-SHA 的不可变镜像与 allowlisted controller bundle 后,显式从 refs/heads/staging dispatch Deploy staging,并传入源 gate run ID。deploy 等待并验证该 gate 最终成功,正常发布仍要求当前 staging HEAD,回滚仍要求当前 staging history 中的旧成功 gate SHA。staging migration 只要求当前 staging HEAD 和 exact-SHA gate artifact。main 与 production workflow 均不改动。
  • 验证:工作流契约测试锁定 staging-ref dispatch、源 gate run 证明、无 main 引用、当前 HEAD 防陈旧发布、同一 gate artifact 的 controller/digest 校验和回滚祖先限制;远端 gate/deploy 与运行时 SHA 待本次 staging 发布记录。
  • 防复发:测试环境的部署控制器必须来自被同一 quality gate 证明的 staging SHAstaging gate 名称不得恢复为默认 main 遗留监听器匹配的 Staging Backend Quality Gate,也不得重新引入 workflow_run 默认分支控制器或 staging/main 相等门禁。production 继续保持独立的 main/staging 收敛要求。
  • 修复版本:本次 staging workflow 提交

BUG-179 | 生时校正候选未显示卡片且回复被推荐问题与内部执行叙述干扰

  • 状态:resolved(代码已验证,staging 部署与真实环境验收待发布流程)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 生时校正候选结果展示、对话输入区、Agent 回复自然度与候选采用后的会话延续。
  • 用户现象:服务器已有候选快照时,候选时间仍被写进 Agent 正文而没有出现既有候选卡;输入框下方固定出现三条推荐问题;Agent 还会叙述读取 Skill、加载 Case、调用工具和读取诊断等内部步骤。用户采用候选或表示没有更多事件后,回复容易被迫继续追问或引导结束、暂停、保存进度。
  • 触发条件:Case API 返回已解析为公开 camelCase 字段的 latest_result,同时生时校正复用通用 Agent 回复解析器及其三条建议兜底,并且 Prompt/Skill 未明确零问题回复、静默工具执行和无更多事件的停止边界。
  • 根因:候选组件仍按数据库 snake_case 字段读取 Case API 的公开 camelCase 快照,因缺少 result_id 判定而丢弃结果;通用 parseAgentReply() 在模型未提供恰好三条建议时自动生成三条兜底建议,生时校正又持有并渲染 composer-suggestionsPrompt/Skill 只限制“最多一个问题”,未明确完整回复可以零问题、内部执行不得进入正文、用户没有更多事件时不得继续轮换领域,也未划清候选卡与 Agent 正文的内容所有权。
  • 修复:新增客户端候选快照解析模块,按 Case API camelCase 外层字段读取结果并规范化候选内部字段;候选卡独占时间、排名、相对支持度、采用动作和选中状态,近乎并列且未开放确认门时不标记“当前推荐”;生时校正改用只提取正文与标题的解析入口,移除建议状态、持久化和 composer-suggestionsPrompt、Skill 与会话策略明确工具静默、零或一个问题、没有更多事件时停止领域轮换、采用后不强制追问或关闭,Session 依现有机制自然保留。
  • 验证:候选解析、回复解析、V9 入口静态合同与 V9 合同聚焦测试共 48 项通过;V9 Agent 聚焦测试 14 项通过;目标 ESLint 与变更源文件定向 TypeScript 检查通过,git diff --check 通过。仓库全量 TypeScript 仍命中既有的无关测试类型错误,未在本修复中扩展处理。
  • 防复发:Case API 的公开 DTO 必须有单一解析边界并由合同测试锁定;特殊业务流不得继承通用推荐问题兜底;Agent 正文、Activity 和结构化业务卡片必须各自拥有唯一内容职责,不得重复呈现或把内部执行过程写进自然回复。
  • 相关记录:BUG-173、BUG-174、BUG-175、BUG-176
  • 修复版本:本次提交(staging 精确 SHA 以推送结果为准;尚未部署)

BUG-180 | 生时校正缺少跨轮安全上下文且单一事件契约破坏自然叙述

  • 状态:resolved(已验证,待本次 staging 发布完成)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 System Prompt、首次开场、rectification-read-case 安全投影、多轮承接与多事件证据记录。
  • 用户现象:Agent 容易把每条输入当成新事件;同一段中的多件事件被合并或要求逐条重发;“是的、那年、后来改了”等承接回答缺少上下文;用户拒答后可能被重复追问;首次开场像客服式流程介绍。
  • 根因:Prompt 缺少以理解真实经历和信息增益为导向的正向行为目标,并把输入限制为单一事件;buildAgentMessages 只提供当前轮消息,rectification-read-case 又只返回统计,没有受限的近期对话、事件摘要、当前修订目标和近期拒答目标。
  • 修复:整体替换 System Prompt 与首次开场指令;支持同轮多事件分别 propose/confirmrectification-read-case 从服务端 Dossier 派生有界的 evidence_contextconversation_context;允许当前 pending 用户轮立即关闭已拒绝的 active follow-up;同步 Skill 与 conversation strategy。
  • 验证:V9 Agent、status/security、evidence、contracts 聚焦行为与安全合同测试共 56 项通过;目标 ESLint 通过,git diff --check 通过。
  • 防复发:不得恢复单一事件问卷契约;跨轮承接必须通过服务端安全投影,不得传递 raw ledger;拒答不落成证据,也不得重新开启同一追问目标。
  • 相关记录:BUG-173、BUG-174、BUG-175、BUG-179
  • 修复版本:本次 staging 发布提交(精确 SHA 以提交与部署结果为准)

BUG-181 | 生时校正 Activity 写死状态且完成凭证抢在 Agent 正文之前

  • 状态:resolved(已验证,待本次 staging 发布与真实浏览器验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 生时校正 NDJSON Activity、运行中 Orbs、完成凭证、候选采用按钮与 Agent 执行静默。
  • 用户现象:Agent 运行时显示与真实工具不一致的“正在核对星盘信息”等写死文案;只有候选比较能在开始阶段出现公开状态,其他工具实时状态缺失;完成后的大块执行记录位于 Agent 正文之前,用户先看到系统日志而不是结论,技法又以不可点击的标签堆叠产生交互误导。
  • 根因:前端把运行中 Activity 与持久化执行凭证混在同一展示状态中,通用消息行在没有真实事件时虚构具体动作;服务端 stream mapping 只为少数 phase 映射开始状态,没有为所有公开校正工具投影统一的 started/completed/failed 生命周期;Prompt/Skill 也未明确禁止正文自行生成执行记录标题与 Activity 文案。
  • 修复:新增仅包含 allowlist 工具、状态与公开 executed_methodstool.activity 协议,并在 API 出口再次清理;所有十个公开工具均按真实 tool-call/tool-result/tool-error 映射 started/completed/failed。客户端拆分当前 Activity 与完成凭证,运行时只显示 Orbs 和当前真实动作,正文开始后清除状态;完成凭证移动到 Agent 正文之后,使用原生默认折叠 details/summary、普通文本分组和可见键盘焦点,不再使用大灰卡与胶囊标签。候选采用按钮内部改为“正在采用…”,不增加第二个 Orbs;文字流光支持 160ms 切换淡入和 reduced-motion。Prompt、Skill 与会话策略禁止正文生成“本轮做了什么”“执行步骤”“使用技法”或内部错误细节。
  • 验证:V9 Agent/status/evidence/contracts、Activity stream、入口和布局聚焦回归共 98 项全部通过;目标 ESLint 与定向 TypeScript 检查通过;Next.js webpack 生产构建完成 TypeScript、60 个静态页面及全部路由生成;git diff --check 通过。默认 Turbopack 仅因隔离 worktree 的外部 node_modules 符号链接触发环境性 panic,非本次代码错误。真实键盘、读屏器、移动端状态切换和登录业务流仍需浏览器验收。
  • 防复发:Activity 只能来自服务端清理后的真实工具生命周期,不得从 Agent 正文、未来步骤或前端猜测生成;工具参数、出生资料、评分、权限、Provider metadata 与内部错误不得进入公开事件;Agent 正文、运行状态、完成凭证和候选卡必须保持单一内容所有权。
  • 相关记录:BUG-172、BUG-173、BUG-179、BUG-180
  • 修复版本:本次 staging 发布提交(精确 SHA 以提交与部署结果为准)

BUG-182 | 候选卡允许“改选”但数据库把不同分钟误报为“该时间已采用”

  • 状态:resolved(已验证,待 staging 发布与登录态业务验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 生时校正候选卡的 改选为此时间 操作、Case/Profile/Result 三方采用状态一致性。
  • 用户现象:一个候选分钟已经采用后,点击同一候选结果中的另一分钟,界面返回“该时间已采用”;卡片明明显示“改选为此时间”,实际却无法切换。
  • 触发条件:agentic_rectification_results.selected_time 已有值,随后以同一个有效 result_id 请求另一个候选分钟。
  • 根因:accept_agentic_rectification_candidate_for_case 只实现了首次采用和同分钟幂等重放;只要请求分钟不同,就直接抛出 agentic_rectification_candidate_already_selected,没有实现前端合同所承诺的安全改选分支。
  • 修复:新增前向业务迁移,保留同分钟幂等;仅允许 candidate_accepted、未 confirmed、同一有效未过期结果、旧采用时间与 Profile/Case/Result 完全一致时改选。改选原子更新 Profile、Result 与 Case,并强制保持 user_accepted / accepted / candidate_accepted,不绕过显式确认门;终态、已确认、资料漂移、更新结果或非候选时间仍拒绝。
  • 验证:新增迁移合同回归覆盖顺序、事务、幂等、改选门、Profile 旧值校验、accepted 写入、confirmed/终态不可变及业务迁移隔离;新增 PostgreSQL 集成场景覆盖首次采用、跨分钟改选、三方状态落库与新分钟幂等重放。部署后仍需在 staging 登录态点击候选卡完成真实业务验收。
  • 防复发:候选卡 CTA 与数据库状态机必须共享同一行为合同;任何“改选”文案都必须有跨分钟成功路径测试,不能只测试按钮未禁用或同值幂等。
  • 相关记录:BUG-127、BUG-144、BUG-179、BUG-181
  • 修复版本:本次 staging 发布提交(精确 SHA 以提交与部署结果为准)

BUG-183 | 首页主标题展示字体被重构为粗黑体

  • 状态:resolved(本地候选,待 staging 部署与视觉验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:首页欢迎主标题与两个产品入口卡片标题。
  • 用户现象:首页原有的宋体/衬线标题变成黑体加粗,整体视觉层级和既有编辑感丢失。
  • 触发条件:加载 starter-heroproduct-entrypoint 首页区域。
  • 根因:前端视觉清理提交将 .starter-hero h1.product-entrypoint-copy h2 从既有 var(--font-display) / font-weight: 400 改为 var(--font-body) / font-weight: 650-680;中文因此回退到 PingFang SC 等无衬线字体并明显加粗。全局展示字体变量本身并未删除。
  • 修复:仅恢复两个首页标题使用现有 var(--font-display) 和常规字重 400,保留当前重构后的字号、行高与布局,不新增字体依赖或平台硬编码。
  • 验证:新增首页字体合同测试,分别锁定欢迎主标题和产品入口标题使用 font-displayfont-weight: 400,并拒绝回退到 font-body;聚焦测试、目标 ESLint、生产构建、git diff --check 与 staging 视觉验收按发布结果记录。
  • 防复发:首页编辑型主标题与产品入口标题必须继续使用设计系统的展示字体 token;通用去风格化或排版重构不得把展示标题批量替换成正文无衬线字体。
  • 相关记录:BUG-026
  • 修复版本:本次 staging 发布提交(精确 SHA 以提交与部署结果为准)

BUG-184 | 生时校正完成凭证显示在 Agent 正文下方

  • 状态:resolved(本地候选,待 staging 部署与登录态视觉验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 生时校正每轮完成后的“本轮完成 · 查看详情”执行凭证与 Agent 正文阅读顺序。
  • 用户现象:执行凭证显示在整段 Agent 回复之后,与运行中 Activity 位于 Agent 消息上方的空间关系不一致;用户需要读完正文后才看到本轮执行状态。
  • 触发条件:生时校正消息完成且持久化了公开 completedReceipt
  • 根因:消息容器的 JSX 顺序固定为 ChatMessageRow 后渲染 CompletedActivityReceipt,并由静态合同测试锁定了错误的下方顺序。
  • 修复:在同一消息容器内先渲染 settled 状态的 CompletedActivityReceipt,再渲染 ChatMessageRow;运行中的真实 Activity 仍由消息行自身在正文上方展示,不改变公开事件协议、折叠行为或凭证内容。
  • 验证:更新 Agentic rectification DOM 合同测试,明确要求 CompletedActivityReceipt 位于 ChatMessageRow 之前;聚焦测试、目标 ESLint、生产构建、git diff --check 与 staging 精确 SHA 验证按发布结果记录。
  • 防复发:生时校正的运行中状态与完成凭证都必须位于对应 Agent 正文上方;不得仅通过 CSS order 视觉重排而保留错误的 DOM/读屏顺序。
  • 相关记录:BUG-172、BUG-181
  • 修复版本:本次 staging 发布提交(精确 SHA 以提交与部署结果为准)

BUG-185 | 生时校正工具重试成功后仍显示失败,且 V9 消息操作栏丢失

  • 状态:resolved(本地候选,待浏览器与 staging 验收)
  • 首次发现:2026-08-13
  • 最近更新:2026-08-13
  • 影响面:V9 生时校正 Activity 完成凭证,以及 Agent 正文下方的赞、踩、复制和重新生成操作。
  • 用户现象:同一证据工具首次失败、同轮自动重试成功且最终回复正常时,界面仍显示“未完成,当前进度已保留”;同时此前已有的消息操作栏在 V9 页面中消失。
  • 触发条件:同一公开校正工具在一个 NDJSON run 中出现 failed → started → completed;或查看任意已完成的 V9 Assistant 消息。
  • 根因:客户端 Activity 聚合只保留“曾经失败”的集合,后续成功没有覆盖同工具旧失败;旧版生时校正运行时退役时删除了消息操作 JSX、状态和 handler,而 V9 入口没有迁入,虽然对应 CSS 仍被保留。
  • 修复:Activity 改为按工具记录最终终态,后续 completed 清除同工具旧失败,只有最终仍为 failed 才显示失败凭证。V9 恢复赞、踩、复制与重新生成操作栏,并保持 完成凭证 → Agent 正文 → 操作栏 的 DOM 顺序。重新生成使用独立只读 Jyotisha Agent,仅可读取当前 Case,免费且原位替换最新 completed Assistant 正文;不新增 Turn、不写证据或候选、不修改 Case 状态、不调用计费。
  • 验证:新增 Activity reducer、重新生成 runner、公开 stream、UI DOM 与数据库迁移合同回归;聚焦测试、目标 ESLint、TypeScript 与 git diff --check 结果以本次本地验证记录为准。首次 staging quality gate 还发现全库 schema 快照漏列新表 agentic_rectification_turn_regenerations,现已同步更新并纳入全门禁。真实剪贴板权限、键盘焦点和登录态重新生成仍需浏览器验收。
  • 防复发:Activity 必须以每个工具的最终终态为准,不能把历史瞬时失败永久化;V9 消息动作不得依赖已退役组件。任何“重新生成”都必须是最新回复的只读原位替换,严禁复用普通 message 发送链导致重复证据、重复 Turn 或重复计费;新增迁移表时必须同步全库 schema 快照测试。
  • 相关记录:BUG-049、BUG-050、BUG-181、BUG-184

BUG-186 | 生时校正运行合同仅靠 Prompt,失败 attempt 与长会话焦点缺少服务器隔离

  • 状态:resolved(本地候选,待 staging migration、部署与登录态业务验收)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-14
  • 影响面:V9/V10 生时校正每轮 Skill/Case 门禁、自动重试、计费完成凭证、证据写入、承接回答、长会话摘要与历史 Skill 身份。
  • 用户现象:模型可能在真正加载绑定 Skill 或读取 Case 前生成正文;失败 attempt 已产生的半句话、Activity、usage 或工具记录可能混入成功重试;“是的、不是、不记得、换个方向”等承接词依赖正则和上一条 Assistant 文本猜测目标;长会话只靠 recent turns 截断,单条消息中的多件事件又缺少独立批量持久化结果;历史 Case 还可能受当前全局 active Skill 版本变化影响。
  • 触发条件:首个 stream attempt 在输出部分正文后以 empty_streamstream_abortedstream_unfinished 失败并自动重试;用户对 active question 作简短承接回复;会话超过近期窗口;一条消息包含多件明确经历;或注册表 active Skill 版本在 Case 创建后升级。
  • 根因:旧运行器把 skill.boundcase.loaded 主要写在 Prompt 约定中,没有 durable attempt ownership 和成功 attempt 投影;ConversationFocus 由 Assistant 文本正则倒推,没有服务器持久化的 target evidence/domain/kindCase dossier 缺少 durable conversation summary 和批量 evidence 幂等合同;运行时按全局 active Skill 解析,而不是严格使用 Case 已绑定的 immutable package identity。
  • 修复:新增 additive V10 migration,建立 agentic_rectification_run_attemptsagentic_rectification_conversation_focusesagentic_rectification_case_conversation_summaries,并把 phase/tool receipt 绑定 attempt。每轮先锁定 Case/Turn 并取得 attempt 执行权;只有完成 skill.bound → case.loaded → intent.classified 后才能提交答案,失败 attempt 的正文、Activity、usage 与 receipt 不进入成功 Turn。completed attempt 必须同时具备 billing.settledrun.completed 和与 Case 精确 Skill name/version/SHA/source commit 匹配的 immutable run receipt。Turn 的 request_id 进入数据库唯一幂等边界;同请求只返回原 Turn,不同正文/模型拒绝复用。V10 migration 同时撤销 service_role 对旧无 request-id append 与旧无 attempt 所有权 Turn finalizer 的执行权,避免兼容 overload 绕过;terminal Case 仍阻止新 attempt,但允许幂等取回已存在 attempt 完成收口。新增 focus set/resolve 与批量 evidence RPC,批量项目保留独立 quote、kind、domain、date precision 和幂等键,并且只有唯一匹配的 accepted evidence 才能解决 focus。Dossier 同时返回受限 recent turns 与 durable summary;开场只传服务器 brief,由 Skill 的 OpeningPolicy 生成自然措辞。terminal Case 拒绝新 attempt、focus、evidence 与 confirmation/revision 写入。
  • 验证:V10 migration 静态合同 55/55 通过;migration 与 stream 隔离合跑 77/77 通过;PR-3 聚焦门禁 199 passed、0 failed、7 skippedDocker 场景由独立真实数据库测试覆盖);真实 Docker PostgreSQL 从空库应用全部 migration 并完成业务测试 1/1,覆盖 request replay/mismatch、legacy RPC 撤权、terminal attempt 取回与新 attempt 拒绝、completed Turn 单调性及 superseded attempt;目标 ESLint 0 error/0 warninggit diff --check 通过。全库 tsc --noEmit 的本 PR 新增错误已清零,仅剩 production-data-migration.test.ts 两处 fixture 字段缺失和 staging-backend-workflows.test.ts 三处低 target 正则 flag,共 5 个既有无关错误。pre-work governance 的 Python、fragment scan、外部引擎诊断与远端可见性均通过,focused governance tests 仍被既有 fragment 计数断言 candidate_count 4 < workspace_residue_count 9 阻塞,不属于本 PR 文件。
  • 防复发:不得把 Skill/Case read gate、focus 目标、attempt 成功归属或 Skill 身份降级为 Prompt 约定;公开正文、usage、Activity 与 receipt 必须只来自 successful_attempt_id;失败/重试不得重复扣费或重复 evidencefocus resolve 必须引用服务器持久化 focus/evidence,不能解析 Agent prose;历史 Case 必须继续绑定创建时可核验的 immutable Skill package。
  • 相关记录:BUG-173、BUG-174、BUG-175、BUG-179、BUG-181、BUG-185
  • 修复版本:本次功能分支提交(精确 SHA 以提交与远程分支核对结果为准;未合并 staging,未部署)

BUG-187 | 生时校正详细事件被压平,候选采用与精确确认缺少版本化服务器门禁

  • 状态:resolved(本地候选,未部署)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-14
  • 影响面:V9/V10 生时校正事件评分输入、候选排序与展示、候选结果持久化、候选采用/精确确认 RPC、Activity 技法凭证。
  • 用户现象:教育、事业、关系、迁居和财务事件在进入 Python 评分前被压成粗粒度类型;Web 根据 margin、候选数量和事件 domain 自行判断置信度、采用权限与已执行技法;候选采用可能在代表分钟上隐式升级为 confirmed;所谓 candidate ID 实际是调用方提交的 HH:MM,不能证明来自服务器保存的候选结果。
  • 触发条件:提交 career_entry/career_exitrelationship_commitment/relationship_separation 等生命周期事件进行候选评分;展示候选卡或 Activity;采用代表候选;或调用旧的 time-based candidate RPC。
  • 根因:Python 事件合同只接受少量粗粒度 kindTypeScript adapter 承担了有损映射、排序/tie/置信度/permission 和 technique 推断;结果表与 RPC 只保存调用方提供的布尔值和候选 JSON,没有版本化 decision receipt、真实 execution ledger 与服务器 candidate UUID 边界;历史 accept RPC 仍可把 accepted 与 confirmed 混合。
  • 修复:引入 Event Contract v2 与 Decision Policy v2,由 Python 返回版本化 candidate_decisionsdecision_receipt 和真实 execution_ledger,日期精度进入服务器评分;Web 只做严格安全投影,不再自行推断 rank、tie、margin confidence、selection gate 或已执行技法。forward-only migration 保存政策与执行凭证,以服务器生成的 candidate UUID 绑定 result,并将 representative candidate 重写为持久化 UUIDaccepted 与 confirmed 使用独立 RPC 和状态门禁,精确确认必须引用已接受候选、有效 consent/source turn,旧 time-based RPC 对 service_role 撤权。Case/Dossier 恢复路径同时投影完整 v2 receipt、ledger、selected candidate 与 display gate。
  • 验证:Python 服务回归 36 passedTypeScript 核心合同 49 passed、0 failedmigration 与 PR-4 真实 PostgreSQL 场景 75 passed、0 failedV9/V10/candidate 全量聚焦门禁 219 passed、0 failed,覆盖 request replay、payload conflict、候选 UUID 归属、representative UUID 缓存一致性、accepted/confirmed 原子分离、consent/source-turn 精确确认门禁、legacy RPC 撤权与 Case/Dossier 恢复。目标 ESLint 0 error/0 warningPython Ruff 与 py_compile 通过,git diff --check 通过。全库 tsc --noEmit 仅剩 5 个既有无关测试错误:production-data-migration.test.ts 两处 fixture 字段缺失、staging-backend-workflows.test.ts 三处低 target 正则 flag。
  • 防复发:详细事件不得在 adapter 层压平;“有候选”不得等于“允许采用”;Web 不得自行推断执行技法或业务 tie;采用候选永远不能隐式确认精确分钟;candidate action 必须引用服务器持久化 result 中的 candidate UUID。
  • 相关记录:BUG-172、BUG-179、BUG-184、BUG-186
  • 修复版本:本次功能分支提交(精确 SHA 以提交与远程分支核对结果为准;未合并 staging,未部署)

BUG-188 | 个人报告按 general 单次计算且 v1 证据包无法闭合主题结论

  • 状态:resolved(本地候选,未部署)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-14
  • 影响面:个人完整/专题报告的主题计算编排、报告 Agent 输入、出生时间政策、主题证据引用与报告生成状态。
  • 用户现象:完整报告即使请求多个主题也只执行一次 general,专题报告只使用首个主题;写作 Agent 只能获得缺少 Claim Card、Blocked Section 和执行账本的 ReportEvidencePacket v1,因此内容短、主题证据不足,且 accepted 时间曾通过零宽候选区间表达。
  • 触发条件:创建包含多个 requested themes 的 personal_full 报告;请求 wealth 等需要专题分盘/技法但证据未闭合的报告;或使用 accepted 出生时间生成报告。
  • 根因:报告路由把所有完整报告折叠到单次 general workflow,旧 packet 仅提供窄化盘面与技法状态,没有服务器持有的主题 Claim Graph、最低证据计划、blocked coverage、execution receipt 与规范化 hash;Agent 又被禁止自行推算未提供事实。
  • 修复:新增 ReportEvidenceBundle v2、主题最低证据计划、规范化 hash 与强引用校验;路由按 requested themes 分别运行 workflow,并把缺失专题证据表示为 Blocked Section,而不是伪造已执行技法或让部分 blocked 主题拖垮整份报告。服务器从 Bundle 生成确定性的 section plan,writer 只能填写计划允许且引用闭合的章节,再由服务器 guard 规范化为 ReportDocument v2;新建报告固定写入 v2,读取端保持 v1/v2 双读。生产创建路径不再依赖 Next.js after(),改为由数据库原子创建的持久化 job 与 lease worker 执行,覆盖 heartbeat、bounded retry、historical generating backfill、expired-lease recovery、幂等 request identity 和进程重启续跑;job migration 使用唯一版本 20260814040000。ready 与 failed 两种终态都通过 exact-live-lease RPC 在同一事务更新 report/job,且 complete/fail 在取得 job 行锁后才读取 lease 校验时间;final-attempt lease 过期会原子收敛为 report=failed/job=failed 并释放用户生成槽位。scheduleRetry() 耗尽预算时保留 live lease 交给失败 RPC 收敛;若 report 已 ready 而 job ready 对账遇到临时存储错误,则返回 reconcile_deferred 并保持 report ready、job running,绝不把 ready 报告降级为 failed。authenticated 对 job 保持 select-only,完成 RPC 仅授予 service_rolewriter 与单次 repair retry 均接收 worker AbortSignal。accepted 状态改为 accepted_directional_onlyBundle 不生成 candidateRange;缺失 D2/D11 等证据只能生成 blocked + executed=false receipt。Web renderer 继续只消费结构化文档和真实 chart data,并使用浏览器原生打印,不接受模型生成 HTML/CSS/SVG。
  • 验证:个人报告合同、planner/writer、API、entry、job state/service/worker、renderer/export、migration 与 Skill registry 完整聚焦回归 247 passed、0 failedPython 报告合同与 Skill package 41 passed、0 failed;目标 ESLint 0 error/0 warninggit diff --check 通过。真实本地 PostgreSQL 已成功应用两个 PR-6 migration,聚焦 smoke 1 passed、0 failed,覆盖 historical generating backfill、authenticated owner 删除拒绝、错误 lease 对 ready/failed 均不写入、正确 lease 原子完成 ready/failed、final-attempt 历史 ready/job-running split recovery、final-attempt generating/running 原子 failed/failed,以及失败后可创建新的 generating/queued 报告。migration 唯一性检查只剩基础分支既有的 20260806010000_admin_rbac.sql / 20260806010000_personal_reports.sql 冲突;全库数据库业务测试仍在既有 Rectification V10 场景因 service_role 无权执行 accept_agentic_rectification_candidate 失败,该权限问题不属于本条报告修复。全库 tsc --noEmit 仅剩 5 个既有无关测试错误:production-data-migration.test.ts 两处 fixture 字段缺失、staging-backend-workflows.test.ts 三处低 target 正则 flag。pre-work 仅剩既有 fragment candidate 计数断言 4 >= 0 + 23 失败。
  • 防复发:每个 requested theme 必须且只能由 Claim Card 或 Blocked Section 覆盖;未执行或 blocked/partial 技法不得升级为 verified/consensusAgent 不得接收 raw workflow、坐标、内部路径、secret、聊天历史或工具轨迹;Bundle hash 必须由服务器对规范化且排除自身 hash 的内容计算。报告文档、job 状态和日志不得写入用户资料、密钥、内部 URL 或模型自由生成的 HTML/CSS/SVG;不得把本地测试结果虚构为 staging 或生产验收。
  • 相关记录:BUG-152、BUG-159
  • 修复版本:本次功能分支提交(精确 SHA 以提交与远程分支核对结果为准;未合并 staging,未部署)

BUG-189 | 普通咨询域与独立产品域漂移,导致多域请求、专题证据与 UI 状态越界

  • 状态:resolved(本地候选,未部署)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-14
  • 影响面:普通咨询入口与会话持久化、Mastra 多域计划、Python 咨询编排与专题报告、chat_sessions.theme 约束、生时校正 Activity/候选文案,以及个人报告、合盘、生时校正独立产品 API。
  • 用户现象:同一个自然语言问题涉及多个主题时只能落入单一 theme;前端、API、Python 与数据库允许域不一致,新增普通咨询域可能在写会话或专题执行时失败;不具备原生专题证据的域可能被静默当成 general;候选采用曾显示成“已确认”,流式正文到达后会清除真实运行中 Activity,失败回合的服务端 receipt 也可能丢失;独立产品入口与 API 缺少统一的服务端产品开关。
  • 触发条件:请求 education/migration/family/annual 等旧数据库或旧专题枚举未覆盖的普通咨询;Agent 从同一问题规划多个咨询域;采用但未确认生时候选;工具运行中先收到 answer.delta 或工具失败;关闭独立产品但直接调用其写入/计算 API。
  • 根因:咨询域在多个客户端与服务端枚举中重复维护,单 theme 合同无法表达多域;专题报告只认识旧主题枚举;UI 把 accepted 与 confirmed、正文与真实执行状态混合;独立产品没有与普通咨询 taxonomy 分离的 registry 和 fail-closed 服务端 gate。
  • 修复:建立十个 canonical consultation domain 的 TypeScript/Python 单一 registry、alias 规范化与最多六域的有序去重计划,Agent 按域逐一执行并由服务器聚合真实 domains receipt;数据库前向迁移同步 chat_sessions.theme 约束。career/marriage/wealth/health 使用原生专题报告,其余 canonical 域返回明确 degraded/blocked adapter,未知域和独立产品域 fail closedannual 普通咨询不等于 annual_report。UI 保留 answer delta 期间的真实 Activity 与失败 receipt,只从服务端 event/receipt 渲染执行状态,并严格区分“已采用(未确认)”和“已确认”。另建独立 product registry 与 feature flags;个人报告、合盘、生时校正写入/计算 API 在鉴权后执行 fail-closed 产品 gate,报告中心同时保留既有环境变量开关。
  • 验证:TypeScript focused 矩阵 178 passed、0 failedPython consultation/thematic/API focused 70 passed、0 failedpy_compile 通过;全部 PR-7 TypeScript/TSX 目标 ESLint 与 git diff --check 通过。全库 tsc --noEmit 仍只有 5 个既有无关测试错误:production-data-migration.test.ts 两处 fixture 字段缺失、staging-backend-workflows.test.ts 三处低 target 正则 flag。全量 Ruff 会报告 legacy Python 文件既有存量问题,因此未将其声明为 PR-7 通过门禁;未应用任何远端数据库迁移,未部署。
  • 防复发:普通咨询只能持久化 canonical consultation domain,独立产品 ID 永不进入 chat_sessions.theme;多域执行必须保序、去重、逐域产生真实 receipt,未执行能力不得伪装为 general 或 verifiedaccepted 不得升级为 confirmedActivity 不得从 Agent 正文推导;所有独立产品写入/计算 API 必须执行服务端 fail-closed gate,数据库迁移保持前向兼容且默认关闭未发布产品。
  • 相关记录:BUG-159、BUG-181、BUG-185、BUG-188
  • 修复版本:本次功能分支提交(精确 SHA 以提交与远程分支核对结果为准;未合并 main/staging,未部署)

BUG-190 | Agent 评测缺少完整去标识化场景,观测日志无严格非 PII 合同

  • 状态:resolved(本地候选,未部署)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-14
  • 影响面:普通咨询、生时校正、个人报告与安全边界的 Agent 回归评测;普通咨询 Agent 运行时日志、用量与结算观测。
  • 用户现象:仓库缺少一套按多轮业务场景统一组织的 Agent golden dataset,无法确定性证明 Skill/工具合同、证据引用闭环、主题覆盖、精确时间边界与校时焦点;自然度等模型评审项也容易被误写成事实门禁。普通咨询运行日志使用拼接字符串且字段零散,没有统一的严格 allowlist,后续增加正文、异常消息、出生资料、密钥或内部路径时缺少 fail-closed 保护。
  • 触发条件:新增或修改普通咨询、校时、报告、安全行为但只运行局部单元测试;把模型评审结果当成事实通过条件;向 Agent 日志添加任意字段或直接记录原始异常消息。
  • 根因:没有版本化、去标识化、多轮 golden dataset 与通用 deterministic scorer;评测事实门禁、模型评审和性能统计未分层。运行观测沿用 route 内自由拼接日志,没有闭合 schema、受控错误码和未知字段拒绝机制。
  • 修复:新增 agent_golden_dataset.v1,以 intent code/context tags 表达 34 个 synthetic 多轮场景,完整覆盖五个核心咨询主题、多主题/改问/无出生分钟/accepted-confirmed 边界,校时多事件与不同精度、更正/拒答/跳过/承接/长会话/候选采用确认,报告完整/部分/冲突证据与 accepted 时间、多主题,以及 prompt、凭据、内部路径、高风险确定性请求和伪造出生资料/candidate ID。新增确定性 scorers,分别检查 Skill/工具合同、引用集合闭包、canonical 主题覆盖、规则型 unsupported facts、精确时间违规、校时焦点、工具经济性与延迟/成本统计;引用闭环只接受本轮 availableEvidenceIds/producedEvidenceIds 中且属于 case catalog 的证据,fact/timing 不得用 requiresEvidence=false 绕过,acceptedconfirmed minute 显式分离且后者只在明确 gate/consent case 放行。自然度、重复性、follow-up relevance 和模型型 unsupported-fact review 只产生显式 pending 输入。新增严格非 PII Agent observability schema/logger,未知或禁止字段 fail closed、sink 失败不影响业务、异常只映射为受控错误码,并将普通咨询现有 logRun/usage/结算接入结构化日志;取消结算重试耗尽时记录 failed/settlement_failed,不得伪记为 cancelled
  • 验证:PR-8 combined focused TypeScript 矩阵 97 passed、0 failed;目标文件 ESLint 与 git diff --check 通过;fixture 隐私扫描未发现姓名、邮箱、真实出生日期/时间/地点、凭据、内部绝对路径或完整用户正文。全库 tsc --noEmit 仍只有 5 个既有无关测试错误:production-data-migration.test.ts 两处 fixture 字段缺失、staging-backend-workflows.test.ts 三处低 target 正则 flag。未运行 quick/browser/accuracy/release 或 staging canary,未部署。
  • 防复发:golden fixture 只能保存 synthetic intent code/context tags,不得保存真实用户正文或出生资料;事实、权限、证据、状态与精确时间边界只能由确定性门禁判定,模型评审必须保持 pending 直到真实执行;catalog membership 不能替代本轮 evidence availabilityrequiresEvidence 不能关闭事实/时间证据规则,accepted 不得升级为 confirmedAgent observability 不得加入自由格式 metadata、正文、prompt、messages、出生资料、身份信息、secret/API key、provider payload、stack 或内部路径,结算失败不得降级为 cancelled,公开 NDJSON 不得扩展为内部 telemetry。
  • 相关记录:BUG-181、BUG-186、BUG-187、BUG-188、BUG-189
  • 修复版本:本次功能分支提交(精确 SHA 以提交与远程分支核对结果为准;未合并 main/staging,未部署)

BUG-191 | PR-8 全量发布门禁被过期测试合同与本地解释器假设阻断

  • 状态:resolved(本地候选,待 staging 精确 SHA 发布验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:PR-8 全量前端测试、TypeScript 编译、本地 PostgreSQL 业务迁移测试、staging workflow YAML 合同与质量门禁。
  • 用户现象:PR-1 至 PR-8 功能提交本身已完成,但全量前端门禁仍有 5 个失败:starter questions 继续锁死旧四主题并扫描派生 wrapper 的重复文案;数据库业务测试继续调用已撤权的 legacy 校时候选接受 RPC;生产迁移 fixture 缺少新增列元数据;staging workflow 测试使用 ES2018 /s flag 且假设 PATH 中存在可导入 PyYAML 的 python。因此不能安全提交、推送或发布 staging。
  • 触发条件:在 PR-7 十域 registry、PR-4 V2 candidate decision contract、生产迁移列模型与独立 staging workflow 合同合并后运行全量 tsx --test tests/*.test.tstsc --noEmit 或 Python quality gate。
  • 根因:测试仍复制旧业务常量和旧 RPC 调用方式,没有跟随新的单一真源与服务端 UUID 合同;测试 fixture 未补齐列模型新增字段;Node 启动的 YAML 检查未继承质量门禁实际使用的 Python 解释器,并包含依赖特定 worktree 深度的临时 fallback。
  • 修复:starter tests 改为验证全部 10 个 canonical domain、label/prompt/evidence/claim 投影及按 domain 归属的 D10、D9、Ashtakavarga、negative holdout gatebilling 测试分别锁定 reserve/complete/release 的免费 turn 短路与付费调用;数据库测试保留 legacy RPC 撤权断言,并恢复 persist_agentic_rectification_candidate_v2accept_agentic_rectification_candidate_for_case_v2 的真实纵向链路,覆盖服务端 candidate UUID、首次接受、幂等重放、切换候选、profile 落库、reported time 保留及基线变化后的 expired 拒绝;生产迁移 fixture 补齐 identity/data type 元数据;YAML 检查改用 PYTHONVIRTUAL_ENV、仓库 .venv 与 PATH fallbackquality gate 通过 os.environ.setdefault("PYTHON", sys.executable) 向前端测试传递解释器,并移除 worktree 层级假设。
  • 验证:聚焦非数据库测试 70 passed、0 failed;本地 PostgreSQL 全迁移与业务链路 1 passed、0 failed;全量前端 1532 passed、0 failedtsc --noEmit 通过;ESLint 0 errors、4 个既有 warningsNext 16 webpack production build 与静态生成通过;git diff --check 通过。四档质量门禁中的 quick、browser、accuracy 已完整通过。release profile 在 Playwright POC 前实际执行的公开发布隐私扫描通过(3212 files、0 findings);随后 report_renderer_isolation_poc.py --strict 因本机 Playwright Chromium 不可用/持续闪退而阻断,并按用户明确要求停止,未将完整 release profile 宣称为 passed,后续不再通过 Playwright 调用 Chrome。停止该 profile 后,另以独立非浏览器命令完成三引擎 parity validator 与 golden cases3/3 passed);业务比对仍如实记录为 92 行中 32 match、60 mismatch、无缺失引擎或高严谨 section,未启用 --require-external-parity,不得解释为外部公式完全一致。staging 仍需以正式 Gitea 独立质量门禁、精确 SHA 部署与非 Playwright HTTP/API smoke 完成验收。
  • 防复发:测试必须读取 canonical registry,不得重新硬编码派生业务真源;正式 V2 RPC 替代 legacy RPC 时必须保留完整成功、幂等、切换、失效和持久化业务覆盖,不能以“旧入口被拒绝”替代纵向链路;跨语言测试必须显式传递当前解释器,不得依赖 PATH 别名或 worktree 深度;类型 fixture 必须跟随共享列模型演进。
  • 相关记录:BUG-178、BUG-181、BUG-189、BUG-190
  • 修复版本:本次功能分支提交(精确 SHA 以提交、远程分支和 staging 发布核对结果为准)

BUG-192 | 外层 .gitignore 漏提交上游 .agentsclean staging package identity 不完整

  • 状态:resolved(本地候选,待 staging 精确 SHA 发布验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:jyotish-vedic-astrology@6.9.14 上游研究快照完整性、immutable package identity、Agent 模块导入与 Gitea 独立 staging 质量门禁。
  • 用户现象:本地全量前端测试通过,但同一提交在 Linux clean checkout 上有 5 个 Agent/registry 测试连锁失败;registry 期望 d3d6d05c…runner 计算为 ec528fc8…,因此 Agent 在 import-time fail closed。
  • 触发条件:版本化 RishiAI 上游参考仓包含 .agents/rules、14 个 .agents/skills 和 14 个 .agents/workflows 文件,但仓库根 .gitignore 的非锚定 .agents/ 规则也匹配该嵌套上游目录,使文件留在本地快照却没有进入 Git tree。
  • 根因:package hash 正确地覆盖完整磁盘树,但外层 ignore 规则把上游 README 明确列为仓库组成部分的 29 个文件静默漏提交。核对结果为本地 1175 files、tracked/clean archive 1146 files;差集正好是 references/open_source_sources/rishi-ai-mcp/.agents/ 的 29 个上游文件。本地完整快照 hash 为 registry 已登记的 d3d6d05c1da25bb684af3be591687f0321b9a3a69a9896454ea3e25b57b6071b,残缺 clean tree 才产生 ec528fc873aca4b971ca8e537d076fefdec47825a11be9572158f1274004d6d6
  • 修复:保留严格的完整 package hash 算法与既有 d3d6… identity;在根 .gitignore 中只为该精确版本化 RishiAI 路径增加反向例外,并提交 29 个上游 .agents 文件。没有全局放开 .agents,也没有把普通未知文件或上游源码排除在 identity 外。
  • 验证:检查 git ls-files 与本地 package traversal 均为 1175 files;当前树和由候选提交生成的 clean Git archive 必须得到同一 d3d6… hash。聚焦 Agent/registry 测试、全量前端测试、TypeScript、ESLint、quick gate 与正式 staging gate 均不得调用 Playwright/Chrome。
  • 防复发:版本化第三方研究快照必须检查外层 ignore 规则导致的漏文件;registry hash 必须从完整、可由 clean checkout 重建的 Git tree 生成,不能通过放宽 hash traversal 掩盖缺失源码。
  • 相关记录:BUG-190、BUG-191
  • 修复版本:本次功能分支提交(精确 SHA 以提交、远程分支和 staging 发布核对结果为准)

BUG-193 | staging revision 状态文件存在但不可读时部署提前失败

  • 状态:resolved(待 staging 精确 SHA 发布验收)
  • 首次发现:2026-08-14
  • 最近更新:2026-08-15
  • 影响面:staging 部署和数据库迁移的 forward-only revision 检查。
  • 用户现象:.state/deployed-revision 已存在但部署用户无法读取时,发布流程在比较旧、新 revision 之前以 Permission denied 退出,无法使用运行中容器的 GITHUB_SHA 完成回退发现。
  • 触发条件:状态文件由不同权限上下文写入,文件存在但当前执行用户没有读取权限。
  • 根因:GitHub staging workflow、run-staging-deploy.shrun-staging-migration.sh 只用 -f 判断文件存在;存在性不代表可读性,因此错误地进入直接读取分支。
  • 修复:将三处判断收紧为 -r;状态文件不可读时按既有协议从当前 Web 容器发现 GITHUB_SHA。Gitea staging deploy workflow 已有相同的可读性判断,保持不变。
  • 验证:新增部署合同测试锁定 workflow 与两个 runner 必须使用 -r;聚焦部署测试、Shell 语法检查、正式 staging gate、精确 SHA 部署与非 Playwright HTTP 健康检查分别记录。
  • 防复发:任何 revision state 快路径都必须验证可读性,并保留容器镜像 revision 的只读回退,不得仅以路径存在作为可消费条件。
  • 相关记录:BUG-083、BUG-192
  • 修复版本:本次 staging 集成提交(精确 SHA 以远端 staging 与部署结果为准)

BUG-194 | 移动端出生资料空状态无法滚动且主题问题被压成窄列

  • 状态:resolved(待 staging 精确 SHA 验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:首页空会话出生资料填写、移动端内部滚动容器、首页主题问题入口。
  • 用户现象:手机上填写出生资料时无法继续向下滚动,底部字段与“继续”按钮不能进入视口;首页十个主题问题在桌面横向挤成窄条,中文几乎逐字换行。
  • 触发条件:宽度不超过 767px 的空会话首页内容高于可视区域;或桌面显示完整主题问题集合。
  • 根因:早期移动端 .conversation.is-empty { display: block; } 声明位于后续桌面 .conversation.is-empty { display: grid; place-items: center; } 之前,被级联顺序覆盖;外层页面又固定为隐藏溢出,只能依赖该内部容器滚动。主题区同时使用单排 flex 并让所有问题共享整体边框,十项内容被压缩到不可读宽度。
  • 修复:把移动端空状态覆盖移动到桌面 Grid 规则之后,恢复独立纵向滚动、iOS 惯性滚动,并为动态视口高度增加 100vh 回退和 100dvh;主题入口改为带独立边框和圆角的响应式 Grid,桌面、平板、手机分别为 3、2、1 列,同时沿用按压态底色与仅桌面 hover 箭头反馈。
  • 验证:移动端滚动与主题布局契约测试、首页入口回归、目标 ESLint 和 git diff --check 通过;真实浏览器在 1440、820、390 宽度下无水平或文字溢出,390×844 与模拟键盘高度 390×500 均可滚动到底部并显示“继续”。
  • 防复发:移动端覆盖必须位于桌面空状态 Grid 之后;测试锁定滚动容器、vh/dvh 顺序、iOS 惯性滚动和主题区 3/2/1 列断点,并禁止恢复为单排 flex 压缩布局。
  • 相关记录:BUG-042、BUG-048、BUG-140
  • 复发自:无
  • 修复版本:本次 staging 集成提交(精确 SHA 以远端 staging 核对结果为准)

BUG-195 | 生时校正焦点已写入却被前端误报为未完成

  • 状态:resolved(本地候选,待 forward migration、staging 发布与登录态业务验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:V10 生时校正 opening/message 回合的 conversation focus 持久化、运行完成状态、刷新后的 Activity receipt。
  • 用户现象:Agent 已正常提出首个校正问题,但界面同时显示“设置对话焦点未完成,当前进度已保留”;刷新后失败步骤又可能从执行凭证中消失。
  • 触发条件:set_agentic_rectification_conversation_focus 成功插入记录后返回精简字段,而 TypeScript 工具层按完整 focus row 解析;或 set-focus 真失败后,runner 仍允许已有回答文本进入 completed 路径。
  • 根因:已部署 RPC 的创建返回值只有 focus_id/status/idempotent,与 parseConversationFocus 要求的 id/case_id/question_id/intent/... 合同不一致,导致成功写入后抛出 invalid_focus。同时 turn completion 没有把 set-focus 的最终失败状态作为阻断条件,旧 receipt 也只聚合 completed tools,造成实时与刷新后的失败状态不一致。
  • 修复:新增 forward-only migration,使创建和幂等分支统一返回完整 focus row 与 idempotentset-focus 最终失败时 turn fail closed 为 retryable、不得发送完成回答或结算;turn receipt 按选定 attempt 聚合每个工具最新 terminal 状态并公开安全的 tool_activities,客户端使用同一 reducer 恢复 failed tool 与 methods。
  • 验证:聚焦回归覆盖 RPC 返回合同、失败后不得 completed、同 attempt 重试成功、receipt attempt 隔离、failed tool 持久化及刷新前后 Activity 一致性;与初始化、Case 和 migration 回归合并运行 203 passed、0 failed。远端 migration 尚未应用,仍需 staging 登录态验证最终 NDJSON、持久化 turn/receipt 与免费 opening 计费不变量。
  • 防复发:数据库 RPC 返回结构必须与 TypeScript parser 共用合同测试;提出用户可见主问题前必须成功持久化对应 focus;完成凭证不得只记录成功工具或从 Agent 文本反推执行状态。
  • 相关记录:BUG-176、BUG-181、BUG-185、BUG-186
  • 修复版本:本次功能分支提交(精确 SHA 以提交、远程分支与 staging 发布结果为准)

BUG-196 | 新建生时校正错误继承已采用时间和用户误差范围

  • 状态:resolved(本地候选,待 staging 发布与登录态业务验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:初始化出生时间采集、homepage/new 生时校正 Case 基线与候选搜索范围、历史 Session 恢复。
  • 用户现象:用户再次新建校正时,系统可能以上一次采用的分钟而不是最初填报时间为中心,并继续继承旧的前后误差;只有大致时段或完全未知声明的 Profile 也可能被错误当成可创建精确分钟扫描的基线。
  • 触发条件:Profile 同时存在 reported_birth_time、历史 active_birth_time 和 uncertainty,或只有 period/unknown 声明时创建 fresh Case。
  • 根因:fresh Case 的范围推导优先使用 active_birth_time,再直接读取 Profile uncertainty;初始化模型把用户声明误差和引擎搜索窗口混为同一字段,并允许用 period 或全天范围代替具体初始时间。
  • 修复:新填报的准确时间保存为 reported + 0/0,不自动宣称引擎 confirmed;初始化仍允许用户如实声明大致时段或完全未知,但这些声明不能作为 fresh 精确分钟扫描的基线。homepage/new 只查询和使用合法 reported_birth_time,忽略历史 active minute、uncertainty 与 period;没有合法 reported time 时在调用创建 RPC 前以 profile_incomplete fail closed。Case 扫描所需的可移动窗口改为独立的服务器执行策略,目前以填报时间为中心使用前后 15 分钟,不再伪装成用户声明;intent=session 继续恢复历史 Case 自身冻结的 baseline/range。
  • 验证:回归覆盖准确时间 0/0 持久化但不确认、旧 uncertainty 不影响 fresh range、旧 active minute 不进入 baseline、period/unknown/无具体时间 legacy profile 不得新建、失败前不调用 RPC,以及 session 恢复不读取当前 Profile;初始化入口回归另由 BUG-197 锁定。与 Focus、receipt 和 migration 回归合并运行 203 passed、0 failed。
  • 防复发:reported_birth_time 是 fresh Case 唯一用户时间基线;active_birth_time 只表示已采用的当前排盘时间,不能反向改写新校正起点;用户声明字段、服务器搜索策略与最终 confirmed truth 必须保持分层。
  • 相关记录:BUG-127、BUG-177、BUG-187、BUG-197
  • 修复版本:本次功能分支提交(精确 SHA 以提交、远程分支与 staging 发布结果为准)

BUG-197 | 精确分钟校正前置条件误删不确定和未知出生时间入口

  • 状态:resolved
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:初始化“出生日期与时间”表单、移动端资料填写、无准确出生时间用户的普通产品入口。
  • 用户现象:表单只显示“我知道准确出生时间”,原有“我不确定准确时间”、大致时段、补充描述和“完全不清楚,跳过出生时间”全部消失,无法准确填写分钟的用户无法继续。
  • 触发条件:staging 包含 075c62e5 后打开未确认出生时间的初始化表单。
  • 根因:BUG-196 修复把“fresh 生时校正 Case 必须有合法 reported_birth_time”错误扩大为“初始化表单只能接受具体时间”,同时测试也被改成明确禁止不确定/未知入口,导致业务回归被质量门禁当成正确结果。
  • 修复:恢复“我知道准确出生时间 / 我不确定准确时间”两条一级选择;不确定路径恢复大致时段、可选描述和完全未知跳过入口,未知状态允许返回描述范围。保留 BUG-196 的服务端边界:准确时间继续保存为 reported + 0/0 且不自动 confirmedperiod/unknown 只能完成资料声明和使用无需分钟的功能,不能创建 fresh 精确分钟扫描。
  • 验证:先把两个错误测试合同改回用户路径合同并确认旧实现稳定失败,再恢复实现后通过;目标回归锁定 family_exact + period_only 两个一级选项、period/unknown 的真实组件分支、跳过入口、reported 状态及 consultation 的 minute-free 行为。390×844 Chrome 真实点击验证两个一级选项、时段表单、完全未知跳过和返回范围按钮均可见;滚动容器 overflow-y: auto,可从 scrollTop=414 滚到 718 并到达底部,继续按钮可进入视口。预览模式的三个 401 来自无登录态的只读背景接口,不影响本表单交互;仍需完成 staging 远端 SHA 验收。
  • 防复发:资料声明完整性和精确分钟校正可启动性必须是两个独立条件;任何 fresh Case 前置条件调整不得删除 period/unknown 资料入口。UI 合同测试必须正向断言两个一级选择、时段选择和跳过路径存在,禁止再用负向断言把产品能力删除写成门禁。
  • 相关记录:BUG-127、BUG-196
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支核验结果为准)

BUG-198 | 不确定或未知出生时间被错误拒绝创建生时校正 Case

  • 状态:resolved(本地候选,待 staging 发布与登录态业务验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:/api/rectification/cases/open、首页和新建生时校正入口、只有大致时段或完全未知出生时间的用户。
  • 用户现象:用户已选择“上午/下午/晚上/深夜”等出生时段,或明确选择“完全不清楚”,资料保存成功,但开始生时校正时仍返回 HTTP 422 profile_incomplete,无法进入 Case。
  • 触发条件:Profile 的 reported_birth_time 为空,且 birth_time_sourceperiod_onlyunknown 时,以 homepage/new intent 创建 fresh Case。
  • 根因:BUG-196 将“没有具体分钟不能直接使用 ±15 分钟精细扫描”错误实现为“没有具体分钟不能创建 Case”;case-service.ts 的 fresh 候选范围只接受 reported_birth_time,没有恢复资料模型已经支持的时段范围和全天范围。对应测试也把该错误边界锁定为预期行为。
  • 修复:继续保持 fresh Case 不继承历史 active_birth_time 和 uncertainty;有合法 reported_birth_time 时仍使用服务器控制的前后 15 分钟范围。period_only 改为使用用户已选择的服务器映射时段,包含 late_night 的跨午夜 23:0003:59unknown 使用 00:0023:59。缺少出生日期、地点、时区,或选择 period_only 却没有合法时段等真正不完整组合,仍在调用创建 RPC 前 fail closed。
  • 验证:回归测试先证明旧实现对 period/unknown 稳定抛出 profile_incomplete,修复后锁定 morning 08:0011:59、late-night 23:0003:59、unknown 00:0023:59 均能传入 open_agentic_rectification_case_v2;非法缺时段/缺具体时间组合仍不调用 RPC。既有引擎范围判断支持跨午夜,工具合同已覆盖全天宽范围不伪造中午分钟。
  • 防复发:资料完整性、Case 可创建性和是否可以立即执行分钟级扫描必须分层;宽范围应先通过事件问题逐步缩小,不得以 profile_incomplete 阻止用户进入,也不得生成虚假具体出生时间。
  • 相关记录:BUG-127、BUG-196、BUG-197
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支与 staging health 验收结果为准)

BUG-199 | Case 创建把可解析的空时区偏移误判为出生资料不完整

  • 状态:resolved(本地候选,待 staging 发布与登录态业务验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:/api/rectification/cases/open、保存了 IANA 时区但 timezone_offset 为空的全球出生地点资料,尤其是 period_only / unknown 用户。
  • 用户现象:用户已选择“晚上”等合法出生时段,页面也认为出生资料完整,但开始生时校正仍返回 HTTP 422 profile_incomplete
  • 触发条件:Profile 已有出生日期、地点标签、坐标、timezone_id 和合法时间声明,但缓存字段 timezone_offsetnull
  • 根因:资料表单和账户保存合同允许用 IANA timezone_id 表达完整地点,既有普通咨询与 Journey 链路也会按出生日期和参考时间动态解析历史 offset;V9 Case 服务却在调用同一解析器之前直接强制 timezone_offset !== null,把可恢复的派生字段缺失误判成用户资料缺失。
  • 修复:V9 Profile 读取后先调用共享 resolveMissingBirthTimezoneOffset;具体时间使用填报分钟,时段声明使用服务器定义的时段参考时刻,未知时间使用中午参考时刻,只用于解析该日期的历史 UTC offset,不会生成或确认具体出生分钟。解析成功后再执行原有完整性和候选范围校验;解析服务异常映射为 profile_unavailable,不再冒充 profile_incomplete
  • 验证:新增真实服务边界回归,先证明 period_only + evening + timezone_id + timezone_offset null 在 RPC 前稳定抛出 profile_incomplete,修复后确认调用历史时区接口、候选范围仍为 18:0022:59、baseline 使用解析得到的 offset 并成功创建 Case。聚焦测试、Lint、TypeScript、远端 SHA 与 staging 业务结果按本次发布记录补充。
  • 防复发:IANA 时区是地点真相,timezone_offset 是依赖出生日期与参考时刻的派生值;所有需要 offset 的服务必须先走共享解析边界,再区分真正资料不完整与下游服务异常。
  • 相关记录:BUG-127、BUG-198
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支与 staging health 验收结果为准)

BUG-200 | 无准确出生分钟时首页主题退化为第三方占星百科问题

  • 状态:resolved(本地候选,待 staging 发布与视觉验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:首页“每日运势”、十个主题问题、Onboarding Agent 生成的事业/关系/时运建议,以及已有 onboarding 缓存。
  • 用户现象:用户进入首页后看到“印度占星一般如何……”“通常会看哪些因素”“包含哪些证据层”等教学式问题;卡片在介绍占星方法,而不是帮助用户直接询问自己的每日运势、事业、关系或未来一年重点。
  • 触发条件:Profile 没有可用于个人星盘的准确出生分钟,首页选择 generalGuidedJyotishTopics;或 Onboarding Agent 生成未使用第一人称的客观式问题。
  • 根因:无分钟降级主题被写成“不依赖个人出生分钟”的占星知识入口,错误地把真实性边界实现成百科模式;同时 Onboarding Agent 只被要求介绍产品能力,服务端也未校验问题是否以用户本人为中心,因此模型输出和缓存都可能继续保存第三方视角文案。
  • 修复:把每日运势和十个无分钟主题统一改成“请帮我……”的任务式请求;首页明确提示出生时间不足的部分会说明限制,但仍允许用户从自己的问题开始。Onboarding Prompt 强制每个建议包含“我”并禁止百科式句型,服务器解析层再次拒绝客观教学文案并回退到安全的第一人称问题;缓存版本升级到 ayanam-onboarding-v4,使旧问题重新生成。
  • 验证:新增回归测试锁定无分钟主题全部为用户视角,并证明客观式 Agent 输出会被拒绝且使用第一人称 fallback;聚焦测试 25/25 通过,目标 ESLint 与 TypeScript --noEmit 通过。真实 staging 视觉验收待发布后完成。
  • 防复发:缺少准确出生分钟只限制分钟敏感的个性化结论,不得把用户入口改写成占星教学。首页卡片和 Agent 推荐问题必须直接表达用户要解决的事;Prompt 约束之外必须保留服务端输出校验和版本化缓存失效。
  • 相关记录:BUG-194、BUG-197、BUG-198
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支与 staging health 验收结果为准)

BUG-201 | 咨询完成后的账户刷新重复请求每日星语接口

  • 状态:resolved(staging 质量门禁修复候选,待部署验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:首页每日星语卡片、咨询完成后的账户与积分刷新、POST /api/daily-starlanguage
  • 用户现象:每完成一次普通咨询,浏览器都会再次请求每日星语接口;即使账户返回的标准化出生资料没有任何变化,也会重复生成同一张每日卡片。
  • 触发条件:咨询流成功完成后调用 refreshAccount();账户接口返回与当前状态值完全相同但引用不同的 Profile 对象。
  • 根因:readProfile() 每次都会创建新的标准化对象,refreshAccount() 又无条件用该对象替换 Profile state;每日星语 effect 需要跟踪完整 Profile,因此依赖对象引用并在引用变化后重新执行。问题不在咨询结算,也不能通过移除账户刷新或缩减 Profile 依赖来规避。
  • 修复:保留咨询完成后的账户与积分刷新;新增浅等值引用保持 helper。账户刷新得到的新 Profile 与当前 Profile 所有标准化字段等值时继续使用当前引用,只有真实字段变化时才替换 state,从而避免无意义地重跑每日星语及其他 Profile 对象 effect。
  • 验证:先添加回归测试并确认因 helper 尚不存在而失败;修复后 Profile 引用行为与 refreshAccount() 集成测试 3/3 通过。相关 account、consultation entrypoint、starter questions 聚焦测试合计 53/53 通过;目标 ESLint 与 TypeScript --noEmit 通过。首次独立 staging gate Run 1856 暴露既有 Agentic 测试仍硬编码 setProfile(nextProfile);该测试已改为验证等价的新函数式更新语义,同时继续禁止覆盖 setProfileDraft,避免把正确的引用保持修复误判为回归。git diff --check 按本次本地验收执行。
  • 防复发:服务器资料刷新不得把“值相同”转化为无意义的状态引用变化;依赖完整 Profile 的 effect 必须在真实资料变化时执行,不能为消除重复请求而遗漏依赖字段。
  • 相关记录:BUG-200
  • 修复版本:本次 staging 质量门禁修复提交(精确 SHA 以远端分支与 staging health 验收结果为准)

BUG-202 | 无出生分钟的“每日运势”仍被 General Agent 整段拒绝

  • 状态:resolved(staging 修复候选,待质量门禁与业务验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:首页“每日运势 / 深入看今日”入口、普通咨询请求 schema、服务端咨询路由、无出生分钟 General Agent,以及公共 Panchanga 证据注入。
  • 用户现象:用户已经在初始化资料中如实选择“晚上”等出生时段,但从首页点击“深入看今日”后,Agent 仍回复“今天运势这个请求,我无法在这个模式下回答”,并把用户引导到占星百科问题或生时校正,无法获得首页承诺的“适合推进什么、需要注意什么”。
  • 触发条件:服务端 Profile 的 birth_time_sourceperiod_only 或其他没有具体分钟的状态,首页以 general_no_birth_time 发起 daily_starlanguage 请求。
  • 根因:BUG-200 只修正了首页任务式文案,没有闭环服务端能力合同:前端无分钟请求主动丢弃 daily_starlanguage entrypointGeneral Agent 又只允许百科知识,并把所有 forecast 一律拒绝。初始化保存的出生时段不能安全替代具体分钟,因此也不能直接走个人命盘日运链路。
  • 修复:无分钟请求保留受限的 daily_starlanguage entrypoint,并在服务端确认最终咨询模式后将其展开为“公共日历趋势”问题。新增服务器公共 Panchanga 客户端,仅向 /api/panchanga_range 发送当天日期和已保存地点的经纬度、时区偏移;将经过结构校验的 Vara、Tithi、Nakshatra、Yoga、整体质量、条件标签与计算策略作为 <public-daily-panchanga> 证据注入 Agent。General Agent 只在存在该服务端证据时回答今日整体趋势、适合推进事项、注意事项和一个立即行动;普通无证据的个人预测仍按原边界拒绝。公共数据不可用或字段不完整时 fail closed,不编造答案,并由既有外层流程取消结算。
  • 验证:在最新 origin/staging 基线上,相关 TypeScript 聚焦测试 82/82 通过,覆盖无分钟 daily prompt、请求保留 entrypoint、公共 API 不携带出生分钟、不完整证据拒绝、period_only 不生成个人 serverChart、地点参考来自服务端 Profile、输出 guard 继续拦截个人星盘断言,并兼容既有 Profile 引用保持与 Agentic 生时校正契约;tsc --noEmit 通过,目标 ESLint 通过,Python Panchanga endpoint 测试 2/2 通过。staging 登录态点击、最终流事件、持久化回合与结算不变量仍待发布后验收。
  • 防复发:出生时段必须继续按 period_only 诚实保存,不能转换成时段中点、00:00 或任何候选分钟。无分钟“每日运势”只能使用服务器公共 Panchanga,必须明确它不是个人命盘日运;不得声称个人上升点、宫位、分盘、大运、本命过境叠加、确定事件或精确时间。
  • 相关记录:BUG-127、BUG-198、BUG-200
  • 修复版本:本次 staging 修复提交(精确 SHA 以远端分支与 staging 质量门禁结果为准)

BUG-203 | 生产恢复点因 .state/mutation.lock 所有权漂移无法创建

  • 状态:resolved(待重新通过 staging、release gate 与生产恢复验收)
  • 首次发现:2026-08-15
  • 最近更新:2026-08-15
  • 影响面:Create Production Recovery Point、生产 schema migration 前置恢复门禁,以及后续 production deploy。
  • 用户现象:mainstaging 已同步且 release gate 成功,但生产恢复 workflow Run 1865 在创建备份前失败,日志为 /opt/jyotisha-production/.state/mutation.lock: Permission denied。首次修复后的 Run 1869 仍在 pg_dump 前失败,准确日志为 sudo: a password is required。切换到受限 helper 后,Run 1873 又在同一备份前阶段报告 chown: /opt/jyotisha-production/.state/mutation.lock: Permission denied。改为 child-first 后的 Run 1877 仍在 pg_dump 前报告同一错误;迁移和部署因此持续阻断,生产运行版本未改变。
  • 根因:生产 bootstrap 遗留的 .state 路径或既有 lock 仍为非 deploy 所有。原恢复脚本只执行 install -d -m 700;对已经存在的目录该命令不会恢复所有权,随后由 deploy 打开共享锁即被内核拒绝。首次修复又错误假设主机已为 deploy 配置独立的免密 chown,但真实生产 sudo 边界只允许已审查的 Docker 命令,因此 sudo -n chown 立即失败。第二次修复的 helper 只保留 CHOWN capability,却按“父目录在前、lock 子文件在后”的顺序处理;Run 1873 已先把 .state 改成 deploy:deploy 0700,再因没有 DAC_OVERRIDE 无法遍历到仍未修复的 lock,留下半修复状态。Run 1877 虽改为 child-first,但 helper 启动时父目录已经是不可遍历的 0700 deploy,所以仍无法通过原宿主路径到达 lock。四次失败都发生在 pg_dump 前,没有生成可用恢复证明,也没有执行 schema migration。
  • 修复:恢复脚本先对 .statebackups 和既有 mutation.lock 做类型与非符号链接校验,取得当前运行 PostgreSQL 容器的不可变本地 image ID,再复用既有 sudo -n docker 边界启动一次性所有权修复容器:--pull never、无网络、只读根文件系统、no-new-privileges、删除全部 capability 后只保留 CHOWN。除绑定 .statebackups 外,把已经验证为普通非 symlink 文件的 lock inode 直接绑定到容器 /mutation.lock,先通过该直接 mount 恢复 lock,再恢复两个目录 mount point 到当前 deploy UID/GID。这样无需遍历半修复的 mode-0700 父目录,也不需要增加 DAC_OVERRIDE;不递归改动历史备份、不删除或替换 lock inode,随后仍用同一个 flock -n fail-closed 获取共享 host lock。同步更新生产 runbook 和静态安全契约测试,明确不得扩大主机 sudoers 或 capability。
  • 验证:本地 bash -n、YAML parse、聚焦 workflow contract 与 git diff --check 必须通过;远端必须重新完成 staging quality/deploy、exact-SHA release gate、真实 production dump + disposable restore + off-site artifact,再允许 migration/deploy。
  • 防复发:生产私有状态目录必须保持 deploy:deploy 0700,共享 lock 必须是普通非 symlink 文件且不可通过删除重建来“修复”;任何恢复流程失败都不得手填 restore_verified=true 或跳过恢复门禁。
  • 相关记录:生产迁移 runbook、Run 1865、Run 1869、Run 1873、Run 1877
  • 修复版本:待提交(精确 SHA 以重新发布后的远端分支与 production health 为准)

BUG-204 | 生产发布后报告页因 Next.js chunk 版本偏斜落入通用错误页

  • 状态:resolved(待 staging 与 production 精确 SHA 发布验收)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:发布切换期间已打开旧页面的用户进行客户端导航时,包括 /reports 等动态页面。
  • 用户现象:生产 /reports 显示 This page couldnt load,只能点击 Reload 或手工刷新;同一登录会话刷新后报告列表恢复正常。
  • 根因:生产 Next.js Web 构建未设置 deploymentId。旧标签页仍运行上一发布的客户端 runtime,在新镜像切换后进行客户端导航时请求了当前发布无法匹配的静态 chunk,触发 ChunkLoadError 并落入 Next.js 通用错误页。报告 API、登录态和报告数据本身没有失败。
  • 修复:Web Docker build stage 接收并设置 NEXT_DEPLOYMENT_IDGitea 主发布链与 GitHub fallback 都把各自完整 commit SHA 作为 build argument 注入。Next.js 因而在构建产物中写入 deployment marker,并为静态资源请求附加 deployment query,使跨发布的客户端版本不一致能够触发完整导航,而不是继续加载不匹配的 chunk。新增 workflow 合同测试,锁定 Dockerfile 和两个构建入口都不能丢失该参数。
  • 验证:聚焦 workflow 合同测试 36/36 通过;使用固定 40 位测试 SHA 的真实 Next.js production build 成功,生成 HTML 含 data-dpl-idJS/CSS URL 含同一 ?dpl= 参数。远端仍需完成 staging quality/deploy、release gate、生产恢复点与 restore drill、migration gate、production deploy,以及登录态 /reports 浏览器验收。
  • 防复发:所有可发布 Web 镜像必须在 next build 阶段注入与镜像/发布清单相同的完整 Git SHA;仅设置容器运行时变量无效。发布验收必须覆盖已登录页面和静态资源 deployment marker,不能只看 /api/health
  • 相关记录:deploy/README.mddeploy/railway-web.Dockerfile、staging/production exact-SHA release workflows
  • 修复版本:待提交(精确 SHA 以重新发布后的远端分支与 production health 为准)

BUG-205 | 普通咨询参数重试复用已拒绝计算缓存并误报运行合同未完成

  • 状态:resolved(本地修复,未提交、未发布)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:普通咨询 Mastra Agent 在同一 Agent 执行尝试内对 run-jyotish-consultation 的参数纠正、服务端运行合同补跑与计算 Promise 去重。
  • 用户现象:普通咨询运行较长时间后返回“Agent 未完成必要的方法与计算步骤,本次不会扣点”,实际主咨询 workflow 一次也没有执行。
  • 触发条件:模型首次同时传入 domains 与兼容字段 theme,触发 invalid_consultation_domain_plan;随后模型或服务端合同补跑改用合法 theme: timing 再次调用同一上下文绑定工具。
  • 根因:createConsultationTools()canonicalDomainPlan(input, ctx) 参数校验前就创建并缓存 calculation Promise。首次非法调用产生的 rejected Promise 被永久保留;后续合法调用命中缓存后直接复用旧拒绝,导致 workflow 调用数保持为零,最终由既有合同门禁判定 runtime_contract_incomplete
  • 修复:在读取或写入计算缓存前同步完成域计划校验,非法参数不启动、不计数也不污染缓存;实际计算 Promise 拒绝时仅清除仍指向该 Promise 的缓存,使后续调用可以重新执行。成功 Promise 继续保留,合法并发调用仍共享同一次服务器计算。未移除运行合同门禁,未伪造 workflow receipt 或成功状态。
  • 验证:新增 invalid domains + theme → valid theme: timing 回归,确认首次 workflow 调用数和工具调用计数均为零、第二次合法调用执行一次并完成合同;新增 workflow Promise 首次拒绝后后续调用不复用旧拒绝的回归;既有合法并发调用只计算一次测试继续通过。frontend/tests/consultation-agentic-runtime.test.ts 共 15/15 通过。
  • 防复发:所有可复用的请求级 Promise 必须先完成同步输入合同校验再缓存;rejected Promise 不得长期占用幂等缓存。并发去重测试必须同时覆盖合法并发、非法后合法重试和真实异步拒绝后的缓存释放。
  • 相关记录:BUG-186、BUG-189
  • 修复版本:本地未提交候选

BUG-206 | 新用户完成初始化后首页生时校正打开失败被静默吞掉

  • 状态:resolved(本地修复,未提交、未发布)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:新用户完成出生资料初始化后,从首页“生时校正”卡片打开 V9 Agentic Rectification Case 的入口错误处理。
  • 用户现象:用户完成初始化资料后点击首页“生时校正”,页面没有切换,也没有显示任何错误,看起来像按钮没有反应。
  • 根因:POST /api/rectification/cases/open 返回 profile_incomplete 时,客户端仅在本地 missingProfileStep(profile) 非空时处理;新用户本地资料已被判断完整时该分支不更新任何 UI 状态便直接返回。网络异常同样只写入未渲染的 composer notice,导致 Case 未打开时没有可见反馈。
  • 修复:服务端 profile_incomplete 统一进入既有资料重新确认流程;invalid_open_request 与网络异常写入可见的 rectification error;首页入口附近增加 role="alert" 错误提示。成功响应后的 Case/Session 合并与校正界面切换逻辑保持不变。
  • 验证:新增修复前失败的首页 open 错误回归,锁定 profile incomplete、invalid request、网络异常和可见 alert;修复后聚焦入口测试 69/69 通过,git diff --check 通过。
  • 防复发:首页业务入口不得把 API 失败只写入未渲染状态;本地资料完整与服务端资料不一致时必须提供可见错误和恢复动作,不能静默返回。
  • 相关记录:BUG-177、BUG-198、BUG-199
  • 修复版本:本地未提交候选

BUG-207 | 管理端业务操作重复要求邮箱验证码、手工原因与二次确认弹窗

  • 状态:resolved(补充修复已完成,待提交与发布)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:管理端兑换码生成/编辑/撤销、管理员角色变更、账务与订阅调整、商品保存/发布、功能开关发布、模型发布、易支付设置等写操作。
  • 用户现象:管理员已经登录后台并具备对应权限,执行批量生成兑换码等日常操作时仍需发送邮箱验证码;首轮删除邮箱验证后,又先填写“操作原因”,提交真实业务表单后还出现额外的“确定”弹窗,形成连续二次确认。
  • 根因:多个管理写入被统一接入 operation-level requireHighRiskAdminMutation,公共 ReasonActionModal 内置权限级邮箱 OTP challenge/proof;后续只把它替换成 ConfirmActionModal,仍保留了多余的操作级确认层,没有让真实的数据录入表单直接执行。
  • 修复:所有管理业务写入统一使用 requireAdminMutation,继续强制管理员会话、对应权限与可信 Origin;删除 /api/admin/reauth、high-risk challenge/proof cookie、ReasonActionModalConfirmActionModal、相关 Popconfirm / Modal.confirm 与 pending-confirm 状态。真实的数据录入 Modal 继续保留,但点击表单的“生成 / 保存 / 发布”等主按钮即直接执行;无额外参数的撤销、重试与开关动作由原按钮直接执行。客户端不再提交手工 reason,服务端按动作注入固定审计标识并继续传给数据库 RPC 的非空审计字段。
  • 安全边界:保留 request ID、数据库 actor 身份校验、领域 RPC、细粒度权限、可信 Origin、append-only 审计和最后一位 Owner 保护;保留账户级 Better Auth TOTP MFA 与一次性恢复码;普通用户登录、注册和找回密码的邮箱 OTP 不受影响。
  • 验证:全局源码扫描确认管理业务中不存在 ConfirmActionModalPopconfirmModal.confirmreason-action-modal 或“操作原因”;管理端权限、业务直提交流程、兑换码/账务合同、账户级 MFA 等 6 个聚焦测试文件共 52/52 通过;tsc --noEmit、改动 TS/TSX 文件 ESLint 与 git diff --check 通过。
  • 防复发:新增管理业务时只能在登录、权限、Origin、服务端审计标识和数据库审计边界内扩展;不得把邮箱 OTP、手工原因或通用二次确认弹窗放回日常管理操作。只有真实的数据录入/选择表单可以使用 Modal,账户级 MFA 与普通用户身份验证必须保持独立。
  • 相关记录:BUG-155、BUG-156、BUG-209
  • 修复版本:本地未提交候选(首轮邮箱复核移除:4f6cf5782d3871a28e531a4dff9bcc6a2633ce09

BUG-209 | self-hosted 管理端生成兑换码先返回通用 500,随后合法管理员被权限链拒绝

  • 状态:resolved(本地修复,待提交、迁移与发布)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:self-hosted runtime 的兑换码列表、批量生成、编辑与撤销;普通用户兑换和 production 未在本次修复中验证。
  • 用户现象:第一次提交合法点数、数量、到期时间和备注时,POST /api/admin/codes 返回 500 {"error":"后台服务暂时不可用"};修正参数序列化后,已登录且能进入后台的管理员再次提交无 reason 请求,返回 {"error":"无权执行此操作"}
  • 根因:存在两个独立问题。第一,runCodeRpc() 将 JavaScript 对象数组直接作为 $5::jsonb 参数交给 node-postgres,被编码成 PostgreSQL array 文本而不是 JSON 数组。第二,兑换码页面、Refine access-control、API 与 PostgreSQL wrapper 使用了不一致且过窄的 billing.adjustments.write;部分合法后台角色只有所有管理员共有的 admin.access,因此请求在 UI、API 或数据库任一层都可能被拒绝。
  • 修复:调用 public.admin_create_redemption_codes 前显式执行 JSON.stringify(input.p_codes)。兑换码列表、创建、编辑、撤销的 UI 可写判断、Refine 资源读写权限、GET/POST/PATCH/DELETE 路由及三个 PostgreSQL wrapper 全部统一为 admin.access。新增向前迁移重建 wrapper 与审计 trigger,把兑换码审计行的 permission_used 统一写成 admin.access;保留服务端固定审计标识、原 RPC 签名、request ID、明文码仅单次返回和数据库 actor 身份校验。
  • 验证:本地使用项目实际 pg serializer 对比确认显式序列化后保持合法 JSON 数组文本;源码合同锁定兑换码 UI、Refine、四个 API 方法、三个数据库 wrapper 与审计权限一致,且不再依赖客户端 reason 或操作确认弹窗。相关 6 个聚焦测试文件共 52/52 通过;tsc --noEmit、改动 TS/TSX 文件 ESLint 与 git diff --check 通过。
  • 发布要求:必须先把 20260816010000_admin_redemption_admin_access.sql 应用到 staging 数据库,再部署同一精确 SHA;只部署应用代码仍会被旧 PostgreSQL wrapper 按 billing.adjustments.write 拒绝。
  • 防复发:self-hosted pgjsonb 参数必须显式 JSON 序列化;一个管理资源的列表、UI access-control、API guard、数据库 permission check 与审计 permission_used 必须使用同一权限语义,不能只改前端或 API。
  • 相关记录:BUG-155、BUG-207
  • 修复版本:本地未提交候选

BUG-208 | 生时校正 opening 首步依赖模型主动加载 Skill,失败时只返回 run.started → run.failed

  • 状态:resolved(staging 发布候选,待质量门禁与业务验收)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:POST /api/rectification/agent 的 V9 Agentic Rectification opening/普通 turn、Skill 绑定收据、首步 Case 读取与公开 NDJSON 事件。
  • 用户现象:已通过鉴权、Case/Session 绑定和模型校验的 opening 请求,只收到 run.started 后紧接 run.failed,没有可见的 Skill、Case 或回答事件。
  • 触发条件:服务端已经通过 agent.getSkill() 加载并核验 Case 绑定的不可变 Skill,但首个 provider step 仍使用自动工具选择;模型直接回答,或先调用 rectification-read-case 而没有先主动调用框架 skill 工具时,运行器按 skill_not_loaded / skill_not_bound fail closed。attempt 内的活动与文本在成功前统一缓冲,因此该合同错误在公开流中折叠成只有 run.started → run.failed
  • 根因:Skill 的真实性与版本已经由服务器加载和校验,但运行合同仍把“是否完成绑定”交给模型是否主动选择 skill 工具,形成服务器事实与模型行为之间的不一致;首步 Case 读取同样没有由服务器强制。该缺陷可确定性复现用户现象,但在缺少 staging 运行日志时不据此断言某个具体 provider 一定返回了直接文本或特定工具序列。
  • 修复:要求 agent.getSkill() 返回非空指令,并将其作为本 attempt 的服务器 system bootstrap 注入;在 provider 执行前持久化唯一 Skill receipt 和 skill.bound phase,并将 Skill 标记为已绑定。通过 Mastra prepareStep 把 step 0 的可用工具缩减为 rectification-read-case 且强制调用;重试提示一并放入 bootstrap,不再覆盖 stream instructions。模型若冗余调用 skill 不会重复写入收据,其他校正工具在 case.loaded 前仍继续 fail closed。
  • 验证:新增回归覆盖服务器 Skill 指令注入、首步强制 rectification-read-case、无需模型调用 skill 即可完成、Skill receipt 只写一次,以及 getSkill() 缺失时 provider stream 不得启动。Rectification Agent/stream/Skill registry 聚焦测试 53/53 通过;目标 ESLint、TypeScript --noEmitgit diff --check 通过。部署同构 Docker build target 成功,镜像内 @mastra/core1.50.1
  • 防复发:服务器已经确定的 Skill 身份、指令和首个事实读取步骤不得再依赖模型自动选工具;所有 provider 调用前必须完成可审计的 Skill 绑定,首步工具面保持最小化,并继续以最终 run.completed、持久化 Turn 和计费不变量作为部署后验收标准。
  • 相关记录:BUG-177、BUG-198、BUG-206
  • 修复版本:本次 staging 发布候选(精确 SHA 以远端 staging 与健康检查验收为准)

BUG-247 | 用户选择准确出生时间后仍停留 reported,个人报告固定返回 birth_time_not_usable

  • 状态:resolved(本地候选,待 staging 迁移、精确 SHA 发布与登录态报告验收)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:初始化出生资料保存、账户资料编辑、POST /api/reports 出生时间可用性门槛、既有准确时间 Profile。
  • 用户现象:用户在初始资料明确选择“我知道准确出生时间”并填写具体分钟,资料与地点均完整,但生成个人报告仍返回 422 birth_time_not_usable
  • 触发条件:Profile 保存为 birth_time_source=family_exact、前后误差均为 0 且有合法 reported_birth_time,但 active_birth_time 仍为空、birth_time_status 仍为 reported;报告接口正确要求 accepted/confirmed + active_birth_time,因此请求必然被拒绝。
  • 根因:账户资料写入逻辑把所有非引擎确认的出生时间声明统一降为 reported + active null,没有表达“用户明确采用自己提供的准确分钟”这一独立状态。初始化资料声明与报告事实门槛各自符合旧合同,但组合后准确时间永远无法成为报告可用时间。
  • 修复:账户资料应用层只对 family_exact + 0/0 + 合法分钟 写入 active_birth_time=reported_birth_timebirth_time_status=accepted,继续保留原始 reported_birth_time,绝不伪装为 confirmed;同一准确声明重新保存可修复既有 reported,修改已采用的准确分钟会同步新的 active time。带 10/15 分钟误差的 family 声明、approximate、period-only、unknown 仍保持 reported + active null,普通资料编辑仍不得覆盖 confirmed。新增 forward-only 业务迁移,仅回填无校正 Case、active 为空、状态为 reported 的严格 0/0 family-exact 记录;该迁移只进入 frontend/supabase/migrations,不污染 identity-only frontend/db/migrations
  • 验证:账户回归覆盖新建、既有 reported 原样重存、已 accepted 分钟修改、confirmed/legacy confirmed 保护及所有非严格准确来源,13/13 通过;账户、出生时间 intake、报告 API 与报告入口聚焦测试 82/82 通过。PostgreSQL 全业务迁移测试实际执行新增 migration,验证严格 0/0 记录得到 05:00:05:00:accepted10 分钟误差记录保持 active null + reported1/1 通过;TypeScript --noEmit、目标 ESLint 与 git diff --check 通过。
  • 防复发:reported 表示用户声明但尚未采用,accepted 表示用户明确采用为当前排盘输入,confirmed 只表示引擎或校正流程确认;任何初始化来源语义变更必须同时覆盖 Profile 持久化、历史回填、报告服务端门槛和客户端入口,不得通过放宽报告接口读取未采用的 reported_birth_time 绕过事实边界。
  • 相关记录:BUG-125、BUG-196、BUG-197
  • 修复版本:本地未提交候选

BUG-210 | 用户填报具体出生分钟被生时校正状态错误阻断精确应期

  • 状态:resolved(本次 staging 发布候选)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:普通咨询 unverified_birth_time 模式的 Consultation Plan、Python workflow 精度边界、Agent/legacy 回答 receipt 与确定性日期输出过滤。
  • 用户现象:用户已经明确填报到具体分钟并使用个人星盘咨询,回答仍以生时未校正为由拒绝精确应期,真实大运或阶段日期被替换成 [具体时间已省略];生时校正因此被错误实现成查看精确日期的付费前置条件。
  • 触发条件:服务端 Profile 含合法 reported_birth_time,咨询模式解析为 unverified_birth_time,且 workflow 原始证据本可允许精确应期。
  • 根因:TypeScript Consultation Plan 把除 verified_chart 外的所有模式统一投影为 precise_timing_blockedworkflow 返回后 applyBirthTimeModeToWorkflowContext() 又无条件把 can_answer_precise_timing 改为 false。最终输出 guard 根据被强制阻断的 receipt 删除年月日,而不是根据计算证据是否完整决定。
  • 修复:有具体分钟的 verified_chartunverified_birth_time 统一使用 server_evidence_required,精确应期权限由服务器计算证据决定;未校正模式继续保留 birth_time_confidence=unverified_reported_timecandidate_is_confirmed=false,但不再覆盖 workflow 的精度许可。无出生分钟的 general_no_birth_time 继续使用 precise_timing_blocked,证据确实不足时仍保留原确定性 guard。输出事件、receipt 字段和回答结构未改变。
  • 验证:回归测试先稳定复现 unverified plan/receipt 被强制 blocked 和日期脱敏,修复后确认具体填报分钟投影为 server_evidence_required、证据允许时 receipt 为 allowed、真实起止日期保持原文,同时证据阻断和无分钟模式仍继续过滤不允许的精确日期。
  • 防复发:生时校正状态只能作为出生时间来源与置信度元数据,不得充当普通咨询功能 entitlement;精确应期许可必须由服务端证据完整性决定。Plan、workflow context、receipt 与输出 guard 的回归必须同时覆盖 verified、reported-minute 和 no-minute 三种模式。
  • 相关记录:BUG-194、BUG-198、BUG-200
  • 修复版本:本次 staging 发布候选(精确 SHA 以远端 staging 为准)

BUG-211 | 多领域咨询 receipt 可生成但无法写入聊天记录

  • 状态:resolved(本次 staging 发布候选)
  • 首次发现:2026-08-16
  • 最近更新:2026-08-16
  • 影响面:普通咨询首轮完成后的整段聊天记录 PATCH、立即追问流程及 workflowReceipt.domains 持久化。
  • 用户现象:首轮多领域回答正常完成,用户立即追问时返回 HTTP 400 聊天记录格式不正确,问题被放回输入框,第二次 /api/consult 未开始。
  • 触发条件:assistant message 同时包含公开 workflowReceipt.domainsagentExecutionReceipt.workflow.domains,前端在下一轮生成前 PATCH 完整消息数组。
  • 根因:公开 Agent 事件使用的 canonical workflowReceiptSchema 已允许可选 domains,聊天写入合同却重复维护了一套 .strict() 旧 schema,导致 messages[].workflowReceipt.domains 被 Zod 判定为 unrecognized_keys;嵌套 execution receipt 使用新版 schema,因此同一业务对象在两个位置具有不同合法字段。
  • 修复:聊天写入合同直接复用 consultation-agent-events.ts 导出的 workflowReceiptSchemaWorkflowReceipt 类型,删除重复字段定义;API、NDJSON、消息和 receipt 输出结构不变。
  • 验证:新增与真实失败 payload 同形的回归,assistant message 同时携带顶层与 execution receipt 的 domains=[general,timing]chatSessionWriteSchema 成功解析;既有 same-origin PATCH、错误重试和所有权边界测试继续通过。
  • 防复发:跨响应、UI 状态和持久化边界共享的 receipt 必须只有一个 canonical schema;禁止在写入合同中复制 .strict() 子结构。新增字段必须以包含完整真实消息形状的 round-trip 回归验证。
  • 相关记录:BUG-186、BUG-189
  • 修复版本:本次 staging 发布候选(精确 SHA 以远端 staging 为准)

BUG-212 | 管理端新生成兑换码复制为 [object Object]

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:管理端批量生成兑换码后的“完整兑换码(仅显示本次)”弹窗。
  • 用户现象:完整兑换码在页面上显示正常,但点击复制图标后,剪贴板内容是 [object Object],无法直接发送或兑换。
  • 根因:Ant Design Typography.Paragraph 开启了布尔值 copyable,其直接子节点却是嵌套的 React Typography.Text 元素;组件默认复制子节点时把 React 元素对象字符串化,因而写入 [object Object],而不是业务字段中的明文兑换码。
  • 修复:为每个新生成兑换码的 copyable 显式指定 text: record.code ?? "";显示仍使用 code 样式,完整明文仍只存在于本次创建响应与当前弹窗,不改变列表脱敏和服务端存储边界。
  • 验证:兑换码管理源码合同锁定复制源必须是 record.code,禁止再次依赖嵌套 React 子节点的默认字符串转换;运行对应聚焦合同测试及 git diff --check
  • 防复发:只要可复制 UI 的 children 不是直接字符串,就必须显式提供 copyable text;一次性秘密值不得从 mask、ReactNode 或对象隐式转换。
  • 相关记录:BUG-207、BUG-209
  • 修复版本:本地未提交候选

BUG-213 | 会员页显示预置套餐但管理端商品列表为空

  • 状态:resolved(本地修复,待提交、迁移与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:管理端商品列表与详情;会员页公开套餐读取的数据来源说明。
  • 用户现象:会员页显示“体验卡 / 标准月卡 / 标准年卡”,但 GET /api/admin/products 返回 {"data":[],"total":0},看起来像套餐被前端写死且无法在后台管理。
  • 根因:套餐不是前端常量,而是 20260806020000_billing_products_subscriptions.sql 预置到 billing_products / product_entitlements 的默认发布商品。公开 /api/payment/packages 通过 service_runtime 能读取这些记录;管理 API 通过受限 admin_runtime 查询。原迁移虽然授予 admin_runtime 表级 SELECT,但两张表已启用 RLS,且只创建了 anon/authenticated 公开策略,遗漏 admin_runtime SELECT policy,导致合法管理查询被 RLS 静默过滤为零行。
  • 修复:新增向前迁移,为 admin_runtime 重新授予 billing_productsproduct_entitlements SELECT,并分别创建 using (true) 的管理员只读策略。API 仍先执行 billing.products.read 权限校验;未给 admin_runtime 增加 service_role 成员关系,也未放开直接写表,保存与发布继续只能经过既有审计 RPC。
  • 验证:数据库回归以真实 admin_runtime 连接读取预置 standard_monthly 商品及其 3 条权益;同时保留无法 set role service_role 与敏感 Profile 列不可读断言。发布后还需确认管理商品接口不再为空,并与公开套餐接口中的商品 ID/版本一致。
  • 防复发:对启用 RLS 的管理资源,table grant 与 RLS policy 必须成对验证;后台列表测试必须使用 admin_runtime 真实角色,不能只用 schema owner 绕过 RLS。
  • 相关记录:BUG-156、BUG-209
  • 修复版本:本地未提交候选

BUG-214 | 计算重试成功后仍误报运行合同未完成并丢弃已算出的星盘

  • 状态:resolved(本地修复,未提交、未发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/api/consult 个人咨询在 run-jyotish-consultation 出现任何一次瞬时失败后的全部后续行为。
  • 用户现象:staging 事业类咨询运行约一分钟后返回 run.failed / runtime_contract_incomplete,提示“Agent 未完成必要的方法与计算步骤,本次不会扣点”。事件流显示 Skill 已加载、星盘实际已计算成功,用户却拿不到任何回答文本。
  • 触发条件:run-jyotish-consultation 首次调用失败,模型据此重试,并在后续某次调用中成功。观测到的事件序列为两次 tool.failed calculation_failed、一次 tool.completed(20278ms),随后补跑命中缓存再次 tool.completed55ms)。
  • 根因:合同门禁 contractReady() 要求 consultationToolCallCount === 1,而该计数在 createConsultationTools() 中对每次未命中缓存的执行递增,失败尝试同样计入。BUG-205 的修复让被拒绝的计算缓存可以释放、从而允许重试,但门禁仍按总尝试次数判定,导致只要发生一次瞬时失败,计数就永久大于 1,之后无论计算是否成功都不可能满足合同。服务端补跑无法降低计数,因此纯属浪费,最终以 runtime_contract_incomplete 结束并丢弃已经算出的结果。
  • 修复:新增 consultationToolSuccessCount,仅在工作流真正成功时递增;门禁改为判定成功次数为 1。失败尝试继续记入 consultationToolCallCount 供观测使用,但不再影响合同。未放宽单次计算边界:请求级缓存保留成功 Promise,后续调用一律复用,因此每个请求仍最多执行一次计费计算;两次真实成功计算依然判定为违约。
  • 验证:新增修复前失败的回归,复现“两次失败 + 一次成功 + 补跑命中缓存”序列并断言 run.completed 且回答正常输出;新增“两次成功仍然违约”边界回归以锁定单次计算约束;工具层补充断言失败重试后 consultationToolCallCount=2consultationToolSuccessCount=1。回退门禁到旧实现可确认新回归失败。frontend/tests/consultation-agentic-runtime.test.ts 17/17 通过。
  • 防复发:运行合同门禁只能依据成功语义的计数,不得用包含失败尝试的总调用次数;任何允许重试的缓存改动,必须同步检查下游门禁是否仍按尝试次数判定。合同类回归必须覆盖“失败后恢复”与“重复成功”两个方向。
  • 相关记录:BUG-205、BUG-186、BUG-189
  • 修复版本:本地未提交候选

待跟进

前两次 calculation_failed 的服务端原因尚未定位(工作流超时为 90s,两次失败均在 20s 内,可排除超时)。本条修复只保证瞬时失败可恢复,不替代对失败本身的排查。

排查所需的可观测性已随本批补齐:calculation_failedsafeToolError() 的兜底码,除中止与超时外的一切失败都会被压成它,而上游真实错误文本属于 provider payload,按 agent-observability.ts 的封闭契约不得进入日志。因此改为按封闭机器码分类:runConsultationWorkflow 抛出带 codeConsultationWorkflowError,按 HTTP 状态区分 workflow_rate_limited(429)、workflow_bad_request(400)、workflow_queue_full(503)、workflow_server_error(5xx) 等,并单独标识 workflow_contract_invalidHTTP 通过但响应未过 consultationWorkflowResponseSchema,此种情况 Python 侧日志显示成功,仅凭访问日志无法发现)。该码记入运行步骤的 failureCode,经 agentObservabilityToolCallSchema 的新增受控可选字段进入观测日志。同时把 request_id 透传给 Python API,用于与其访问日志交叉对齐;此前两侧无任何关联标识,只能靠时间戳猜测。公开回执改由 publicConsultationRuntimeSteps() 按白名单构建,内部 failureCode 不出现在对外契约中——executionStepSchema 是 strict,若直接透出会让成功运行在解析回执时报错。

BUG-215 | CSS 契约测试取错规则块,假阳性阻断 staging 发布

  • 状态:resolved(本地修复,未提交、未发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:backend-quality-gate 的前端契约测试;间接影响该 gate 上所有等待发布的改动。
  • 用户现象:quality gate 以 makes SidebarContent the only sidebar scroll owner 失败,报 [data-sidebar="content"] 缺少 min-height: 0,但该声明实际存在且未被改动。staging 因此停留在 7050f7eebced1b9d 与其后两批后端修复均无法部署。
  • 根因:cssBlock()globalStyles.indexOf(selector + " {")首个匹配规则。bced1b9d 在媒体查询中新增了一条同名规则 [data-sidebar="content"] { -webkit-overflow-scrolling: touch; },位置早于第 333 行的基础规则,helper 因而返回媒体查询块。该 helper 在 sidebar-contractmembership-page 两个文件各有一份副本,且自身没有任何测试。修复过程中又暴露两个同源缺陷:CSS 注释写在规则上方时会被计入捕获的选择器文本(.membership-page 因此找不到);调用方会把整个选择器组当作 key 传入(".membership-plan-card, .membership-credit-card"),旧实现仅靠字面量子串匹配碰巧生效。
  • 修复:抽出共享 tests/css-contract-test-support.tscssDeclarations(),先剥离注释再逐条规则解析,按逗号拆分并归一化空白后做精确或后代组合匹配,支持以整组作为查询,并返回所有命中规则声明的并集而非首个。并集使 assert.match 语义为“任一规则声明即可”、assert.doesNotMatch 为“任何规则都不得声明”,后者比原实现更严格,也更贴近“唯一滚动容器”这类断言的本意。未放宽任何既有断言,未改动 globals.css
  • 验证:新增 9 个 helper 回归,覆盖媒体查询先于基础规则、注释引入的规则、跨行选择器、选择器组查询、后代组合、以及选择器缺失时必须响亮报错;sidebar-contractmembership-page 合计 78/78 通过;全量非数据库套件 1577/1578,唯一失败为需要真实 Postgres 的 v9 迁移测试。
  • 防复发:被多处断言复用的测试辅助函数必须有自己的回归,不得以副本形式散落在各测试文件。以字符串匹配近似 CSS 语义时,必须按规则解析并覆盖同名选择器的全部声明;indexOf 式首个匹配不可用于可能重复出现的选择器。
  • 相关记录:BUG-214
  • 修复版本:本地未提交候选

BUG-216 | 咨询中断、取消、归档等 44 条提示文案计算后从未显示

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页的断线恢复、取消回答、模型下线切换、会话重命名与删除失败等全部提示通路。
  • 用户现象:网络中断、刷新后恢复、点击停止、删除会话失败时,界面只有一个转圈或静默无反应,用户无法知道回答仍在后台生成、是否已取消、失败原因是什么。
  • 根因:frontend/src/app/page.tsx 将提示状态声明为 const [, setComposerNotice] = useState(""),解构时丢弃了状态值,JSX 中也没有任何渲染点;44 处 setComposerNotice(...) 调用的文案全部写入一个永不读取的 state。恢复轮询逻辑本身正确,缺的只是出口。
  • 修复:新增 frontend/src/lib/chat-notice.ts,把提示按语义分派到既有 sonner toast.success / toast.error / toast;根布局早已挂载 <Toaster />,无需改动。以固定 id 复用同一条 toast,避免 1750ms 恢复轮询把同一句话堆成几十条;空字符串走 dismiss 而不弹空 toast。44 处调用点与全部中文文案逐字未改。
  • 验证:新增 frontend/tests/chat-notice-and-scroll-contract.test.ts 锁定状态不再被丢弃、提示进入 toast、空串不弹窗、轮询防刷屏与分级语义;tsc --noEmiteslint 清洁;与改动文件相关的 43 个测试文件共 386 条断言全绿。
  • 防复发:禁止以 const [, setX] 形式声明用户可见文案状态;任何面向用户的提示必须有可断言的渲染出口,合同测试需覆盖“文案确实可达 UI”而不仅是“文案存在”。
  • 相关记录:BUG-211
  • 修复版本:本地未提交候选

BUG-217 | 个人报告页轮询 120 秒后静默停止,界面仍显示“生成完成后页面会自动显示”

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/reports/[reportId] 报告详情页的生成等待态。
  • 用户现象:报告生成超过两分钟后,页面永远停在“报告正在生成中,请稍候…”的转圈上,即使报告已经在后台完成也不会刷新;页面同时声称“生成完成后页面会自动显示”,与实际行为矛盾。
  • 根因:personal-report-page.tsxMAX_POLLS = 40 × POLL_INTERVAL_MS = 3000 限制轮询次数,达到上限后 effect 直接 return 停止轮询,但 state.phase 仍保持 "generating",UI 继续渲染等待分支,没有任何超时态或失败态承接。
  • 修复:在 ReportLoadState 中新增客户端专用的 timed-out 相位(classifyReportEnvelope 不产出该值,服务端语义未变)。轮询改为 8 分钟墙钟预算配阶梯退避(首分钟 3s,之后 6s/10s/15s,总请求数由 160 降到约 48)。预算耗尽后进入 timed-out 屏:说明生成仍在后台继续,提供“继续等待”重置时钟并立即重取,保留“返回报告中心”。等待中与超时后均显示“已等待 X 分 Y 秒”。
  • 验证:新增 frontend/tests/report-polling-contract.test.ts,除文本合同外还对导出的 pollIntervalForElapsed / formatWaitedDuration 做真实单元断言(退避边界、单调性、请求数上限);既有 personal-report-* 测试全部通过。
  • 防复发:任何有次数或时间上限的轮询,达到上限时必须切换到显式终态并给出用户可执行的下一步;等待文案承诺“自动刷新”时,必须由测试保证该承诺在整个等待窗口内成立。
  • 相关记录:BUG-217 无前序同类记录
  • 修复版本:本地未提交候选

BUG-218 | 流式回答期间强制滚到底部,用户无法向上翻阅历史

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页的会话滚动容器。
  • 用户现象:长回答生成过程中向上滚动查看此前内容,会被立即拽回底部,无法停留;也没有任何“回到最新”的入口。
  • 根因:page.tsx 的自动滚动 effect 依赖数组包含 activeStreamingText,每个流式 token 都会触发一次无条件 scrollTo(scrollHeight),未判断用户当前是否已在底部附近。
  • 修复:新增 frontend/src/hooks/use-conversation-scroll-anchor.ts,以 rAF 合并的 passive 滚动监听维护锚定状态:用户向上越过约 96px 阈值即解除锚定,回到阈值内自动恢复;仅在锚定时执行自动滚动。切换会话与用户自己发送消息仍强制滚到底部。解除锚定时显示“跳到最新”按钮(真实 <button>aria-label、可见焦点环、44×44 触控区),点击后滚到底并恢复锚定。沿用既有 prefers-reduced-motion 处理。
  • 验证:同 BUG-216 的合同测试覆盖锚定守卫、有意跳转与无障碍跳转控件;tsceslint 清洁。浏览器内的视觉位置未经人工目视确认。
  • 防复发:聊天类自动滚动必须做底部锚定判断,禁止把流式文本直接作为无条件滚动的依赖项。
  • 相关记录:BUG-218 无前序同类记录
  • 修复版本:本地未提交候选

BUG-219 | 应用根级缺少错误与 404 边界,渲染崩溃时落到 Next.js 默认页

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面://membership/login/admin/* 等除 /reports/[reportId] 外的全部路由,以及所有未知 URL。
  • 用户现象:主对话页等发生渲染异常时,用户看到的是 Next.js 默认错误界面,无中文说明、无重试入口、无返回路径;访问不存在的地址得到默认 404,与产品界面割裂。
  • 根因:src/app/ 下从未创建 error.tsxglobal-error.tsxnot-found.tsx,全应用唯一的错误边界位于 src/app/reports/[reportId]/
  • 修复:新增三个根级边界。error.tsx 为客户端边界,提供“重试”调用 reset() 与返回入口,并以克制方式展示 error.digest 供用户报障引用,不暴露堆栈。global-error.tsx 自带 <html lang="zh-CN"><body>,零依赖并使用内联样式配 var(--color-*, 字面回退),因为它替换根布局时 globals.css 不可达。not-found.tsx 为服务端组件,文案兼容 login/page.tsx 在未识别域名下主动 notFound() 的既有行为。
  • 验证:新增 frontend/tests/root-error-boundaries-contract.test.ts 6 条断言,覆盖文件存在、客户端指令、global-error 自带文档骨架、role="alert"、标题层级与中文文案;tsceslint 清洁。未做浏览器目视验证。
  • 防复发:新增顶层路由段时必须同步确认错误与未找到边界覆盖;global-error 不得依赖根布局引入的全局样式。
  • 相关记录:BUG-219 无前序同类记录
  • 修复版本:本地未提交候选

BUG-220 | 管理端 antd 缺少 SSR 样式提取与 React 19 适配,首屏闪烁无样式内容

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/admin/** 全部 18 个后台页面;以及所有用户端路由的首屏 CSS 体积。
  • 用户现象:进入后台页面时先闪现一屏无样式内容再套上 antd 样式;同时聊天、登录、会员等用户端页面也要下载解析只有后台会用到的 antd 覆盖样式。
  • 根因:其一,admin 是 "use client" 子树并使用 antd v5 CSS-in-JS,但项目从未接入 AntdRegistry / @ant-design/cssinjs 服务端样式提取,也未应用 antd v5 + React 19 的官方渲染适配,服务端输出 0 字节 antd 样式。其二,globals.css 由根布局引入,却在尾部包含 37 行 .admin-app-shell .ant-* 后台专用规则。
  • 修复:新增 frontend/src/components/admin/admin-antd-registry.tsx,在 admin 子树内完成按请求的样式提取(createCache + extractStyle + useServerInsertedHTML,输出 data-rc-order="prepend" 保证服务端样式先于客户端注入),并用 antd 5.29.3 公开导出的 unstableSetRender 完成 React 19 适配,不新增 registry / patch 包。仅显式声明已随 antd 安装的 @ant-design/cssinjs@^1.24.0(版本未变、锁文件仅增 1 行)。后台 37 行样式移入 frontend/src/app/admin/admin.css 由 admin 布局引入;移除与 antd reset 重复的 refine reset。另在 next.config.ts 增加 experimental.optimizePackageImports,既有 outputFileTracingIncludes 等设置逐项保留。
  • 验证:npx next build 退出码 0;构建产物显示用户端 CSS chunk 184,968 字节且不含任何后台规则,后台规则独立成 5,358 字节 chunk 且仅被 .next/server/app/admin/ 下 18 个 client-reference-manifest 引用。以 react-dom/serverServerInsertedHTMLContext 实测 registry 服务端输出 90,132 字节含主题色 #85432f<style id="antd-cssinjs">(修复前为 0)。npm ci --dry-run 报告锁文件同步。standalone 产物仍包含 Python skill 资产。未能以真实管理员会话做端到端 HTTP 验证(需 Postgres 与登录态)。
  • 防复发:引入 CSS-in-JS UI 库时必须同时接入 SSR 样式提取;路由段专用样式不得写入根布局引入的全局样式表。
  • 相关记录:BUG-213
  • 修复版本:本地未提交候选

BUG-248 | 七个设计 token 类名编译不出任何 CSS,报告页文字颜色长期未生效

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:text-inktext-ink-secondarytext-ink-tertiarytext-dangertext-warningbg-canvashover:bg-canvas-muted 共 7 个类名,30 处调用点,分布在 personal-report-page.tsx17 处)、reports/[reportId]/ 的 error、loading、not-found 页、generate-personal-report-button.tsxapp/page.tsx
  • 用户现象:个人报告页与报告子页的次级说明文字、错误提示、警示文字全部沿用正文墨色,本该更浅或本该是红色、琥珀色的层次完全没有出现。因为页面并未报错也没有明显错版,问题从未被当成 Bug 报上来。
  • 根因:Tailwind v4 只从 @theme 构建工具类命名空间。globals.css 把 32 个 --color-* 设计 token 定义在普通 :root 里,而顶部的 @theme inline 块只暴露了 --color-background--color-foreground--color-muted-foreground 等另一套 shadcn 别名。于是 text-ink 这类按 token 名书写的类名不生成任何规则,静默失效——类名拼写正确、编辑器不报错、构建也不报错。
  • 修复:在既有 @theme inline 块内追加 32 行,把全部 :root 调色板 token 以同名别名暴露出来(--color-ink: var(--color-ink);)。未改动任何 token 的取值,未新增或删除 :root 声明,未改动任何 .tsx。沿用 inline 而非新开裸 @theme:块内既有的 --color-background: var(--color-canvas) 依赖 inline 才能转发到调色板而不是被 Tailwind 接管,拆成两个块只会多出一套结构。
  • 验证:以 @tailwindcss/postcss 编译真实样式表前后对比,产物 216,466 → 218,532 字节、311 → 318 条工具类规则,diff -u 显示新增 57 行、删除 0 行,新增的正是上述 7 个类且无一遗漏,既有 text-primarytext-muted-foregroundbg-background 输出逐字未变。另以 headless Chrome 取 getComputedStyle 实测:修复前 13 个探针有 9 个取到错误值(四个文字类都落回正文墨色 rgb(29,29,31),三个背景类落到透明),修复后全部为预期值。新增 frontend/tests/design-token-contract.test.ts 遍历 src/ 下所有颜色工具类并与 @theme 命名空间交叉核对,已在修复前的样式表上确认该测试会失败。tsc --noEmit 清洁;读取 globals.css 的 25 个测试文件共 253 条断言全绿。
  • 防复发:新增设计 token 必须同时进入 @theme,否则按 token 名书写的工具类会静默失效。合同测试须交叉核对"src/ 中实际使用的工具类"与"@theme 暴露的命名空间",仅断言类名字符串存在无法发现这类缺陷。
  • 相关记录:BUG-215 同属"断言了文本却没断言真实产物"的一类
  • 修复版本:本地未提交候选

BUG-249 | 每敲一个字重渲染 2723 行组件,且刷新丢失未发送的问题

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页输入框,聊天场景中最高频的交互路径。
  • 用户现象:在输入框中打字时界面卡顿,长会话下尤其明显;此外刷新页面或误触返回会丢失已经写了一半的问题,没有任何恢复入口。
  • 根因:draft(输入框文本)是 Home() 的顶层 useState,而 Home() 单个函数体就有 2723 行、56 个 useState、18 个 useEffect、47 个内部函数、向子组件传下 62 个函数 prop,且全文件 useCallbackuseMemo 各为 0React Compiler 也未启用。因此每次击键都会重渲染整个组件、重建全部 47 个内部函数、给所有子组件换一批新的函数引用。草稿仅存在于 React state,没有任何持久化。
  • 修复:新增 frontend/src/lib/composer-draft.ts 作为模块级草稿 store,新增 frontend/src/components/chat-composer.tsx 通过 useSyncExternalStore 订阅它。Home()draft state 移除,setDraft / setDraftTheme / setDraftEntrypoint 保留为同名函数(前者转发到 store,后两者写 ref),十余处既有调用点逐字未改。选用外部 store 而非 useImperativeHandle:输入框在生时校正面板与引导表单打开时会真的卸载,而 selectSessionopenRectificationCase 恰好在这些窗口里调用 setDraft(""),ref 句柄此时为 null 会让清空静默失效、旧文本在重新挂载后复活。草稿以 jyotisha.composer-draft 写入 sessionStorage(沿用 jyotisha.pending-consultation 命名约定),发送成功即删除键,读取时校验类型、拒绝未来时间戳、丢弃超过 24 小时或超出 500 字上限的值,sessionStorage 抛错(隐私模式、配额)时降级为纯内存,绝不向打字路径抛出异常。输入框保持受控,isComposing 中文输入法守卫原样保留。
  • 验证:tsc --noEmiteslint(未新增任何 disable 注释)、npx next build 均通过;读取 page.tsx 的 24 个既有测试文件 268 条断言无需修改任何一条即全部通过;新增 frontend/tests/composer-isolation-contract.test.ts 5 条。逐项确认发送后清空、停止后恢复原问题、推荐问题填入、咨询恢复回填、发送按钮空态禁用五个行为均未变。未做的验证:未在真实浏览器中实测中文输入法,也未用 React Profiler 量化重渲染减少量——重渲染的消除是结构性的(击键不再改变 Home() 中任何 state),但没有实测数字。
  • 防复发:高频输入状态必须下沉到实际使用它的叶子组件,不得放在大型容器组件的顶层;跨组件读写的输入状态优先用外部 store 而非 ref 句柄,因为句柄在组件卸载期间不可达。
  • 相关记录:BUG-249 无前序同类记录
  • 修复版本:本地未提交候选

BUG-250 | 首屏强制下载动画引擎与完整 Markdown 管线,多出 73 KB

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页首屏 JavaScript 体积,影响所有用户尤其是移动端与弱网首次访问。
  • 用户现象:打开对话页时需要先下载并解析动画引擎和完整 Markdown 渲染管线,而这两者在用户发出第一条消息、收到第一条回答之前都用不上,首屏可交互时间被拖长。
  • 根因:chat-message-row.tsx 静态导入 gsap@gsap/reactthinking-orbschat-message-content.tsx 静态导入 react-markdownremark-gfm,两者又都被 page.tsx 静态导入。全代码库此前只有 1 处 next/dynamic,其余全部静态。
  • 修复:Markdown 抽到 chat-markdown-view.tsx,经 import() 加模块级缓存加载——刻意不用 React.lazy + Suspense,因为 lazy 的 promise 落定后仍需一次重渲染,可能露出一帧 fallback;模块级缓存则保证解析完成后每次渲染都同步出 Markdown,整个生命周期只切换一次。加载前的兜底仅在文本不含任何 Markdown 语义字符时渲染纯段落(此时输出与 CommonMark + GFM 逐字一致),含语法则渲染空而非原文,确保用户永远不会看到裸露的 **bold**。GSAP 改为 import("gsap"),且只在模块已解析时才执行入场动画——若在首屏后补放会先绘制可见再拉回 autoAlpha: 0 淡入,那是比不动画更糟的闪烁;prefers-reduced-motion 守卫保留并强化(该类用户现在完全不下载 gsap)。移除 @gsap/react(它会静态导入 gsap,留着就白拆了),useGSAP 替换为等价的 isomorphic layout effectgsap.matchMedia() / motion.revert() 结构不变。thinking-orbs 用 next/dynamic + ssr: false,占位符预留精确的 20×20 盒子避免抖动。三个 chunk 在首屏绘制后经 requestIdleCallbackSafari 回退 setTimeout(300))预取。
  • 验证:next build 前后实测。Next 16.2.10 的 Turbopack 已不再打印 First Load JS 列,故改为从预渲染的 .next/server/app/index.html 汇总其引用的全部 /_next/static/**.js 并按 gzip -9 计算,两次口径一致:549.5 KB → 476.3 KB gzip73.2 KB,−13.3%),raw 1819.4 KB → 1601.4 KB(−12.0%)。两次构建的 page.tsx sha256 相同,19 个首屏 chunk 中 17 个逐字节相同,三个新 lazy chunk 合计 74,006 字节 gzip,占降幅的 98.7%,可归因。以最小化产物特征串(gsap 的 GreenSock、markdown 的 micromark、orbs 的 ribbon)确认三者已不在任何首屏脚本中。tsceslint 清洁;新增 frontend/tests/chat-bundle-splitting-contract.test.ts 5 条(含对"绝不渲染裸 Markdown"规则的真实单元断言,覆盖 **##、表格、反引号、裸 URL 等);相关既有测试 38 条全绿。未做的验证:无法在真实浏览器中目视确认无闪烁,无闪烁的结论来自代码路径与合同测试而非实际渲染。
  • 防复发:仅在首次交互后才需要的重依赖不得静态导入进首屏组件树;延迟加载必须同时给出不产生布局抖动、不闪烁、不泄漏源码语法的兜底,做不到就不要延迟。
  • 相关记录:BUG-250 无前序同类记录
  • 修复版本:本地未提交候选

BUG-251 | 查看报告与充值走整页跳转,返回后对话状态全部丢失

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页跳往 /reports 与会员充值页的 5 个入口。
  • 用户现象:从对话中点开报告列表或余额、充值入口时整页刷新,返回对话后需要重新加载全部会话与账户数据;点击充值时刚输入的问题也一并丢失。
  • 根因:page.tsx 有 11 处 window.location 跳转、useRouter 使用为 0,文件从未引入 next/navigation。每一次跳转都丢弃整棵 React 树、已加载的 JS 与全部客户端状态,再从头启动应用。
  • 修复:逐处判断后改软导航 5 处(/reports 侧栏入口、会员入口、余额按钮、以及发送前余额守卫与 /api/consult 返回 402 两处充值跳转),改为 router.push。其中发送前的余额守卫在清空输入框之前执行,软导航因此顺带保住了用户刚输入的问题。刻意保留硬跳转 6 处5 处 /login 认证重定向(401 时客户端持有服务端已拒绝的会话,整页加载是可靠的清除方式;signOut() 后尤其如此,会话已在服务端销毁,软跳转会让整棵树带着已登出用户的数据继续存活),与 1 处启动失败页的重试按钮(失败数据全部来自挂载时 useEffect 内的客户端 fetchrouter.refresh() 只重取服务端组件、不会重新挂载客户端树,改了按钮反而会静默失效)。此判断与本分支 membership/page.tsx 保留认证硬跳转的处理一致。
  • 验证:tsceslint 清洁;next build 通过;新增 frontend/tests/chat-navigation-a11y-contract.test.ts 13 条,同时锁定"哪些是软导航"与"认证重定向是有意保持硬跳转",未来有人机械改写会被测试拦下。修改既有测试 1 个:personal-report-entry.test.ts 原断言 window.location.assign("/reports"),随代码更新为 router.push追加 assert.doesNotMatch 反向断言,强于原状。全部非数据库套件 199 个文件 1592 条断言全绿。未做的验证:未在运行中的应用里实测软导航的实际体感。
  • 防复发:应用内同源路由必须走 router.push;认证重定向与启动失败恢复例外,且例外必须在合同测试中显式声明为有意,不能只是"还没改"。
  • 相关记录:BUG-251 无前序同类记录
  • 修复版本:本地未提交候选

BUG-252 | 回答完成对屏幕阅读器完全无提示,且开始提示很可能也未被念出

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页依赖屏幕阅读器的用户的全部问答流程。
  • 用户现象:提问后只能听到"Jyotisha 正在回答"(且这一条很可能也没被念出),此后没有任何提示——不知道回答何时结束、是否可以开始阅读、内容在哪里。
  • 根因:其一,page.tsx 只有开始态的 live region,完成态没有任何播报出口。其二(排查中发现的更严重问题),该 region 位于 .message-list 内部,而后者带 aria-busy={isLoading}aria-busy="true" 的语义正是让辅助技术暂缓呈现该子树的变化,因此连"正在回答"很可能都从未被念出。其三,该 region 会随会话首条消息重新挂载,而挂载时即带内容的 live region 本身就不可靠。
  • 修复:新增 frontend/src/lib/chat-reply-announcement.ts,把回答生命周期归为六个阶段并为每个阶段指定唯一播报出口,避免重复播报:generatingcompleted 走新的状态区域,recovering / stopped 交给既有 sonner(已确认其容器带 aria-live="polite" aria-relevant="additions text"BUG-216 接入的 44 条提示本就会被念出),failed 交给既有 role="alert"。完成播报含序号(「第 N 条回答已显示在对话区末尾」),因为连续两次内容完全相同的播报常被屏幕阅读器抑制。状态区域移出 aria-busy 子树,改挂在 main.chat-app 下并跨分支常驻。流式文本本身绝不进入 live region——逐字更新会让屏幕阅读器持续重复播报,完全不可用;合同测试断言播报模块不引用任何流式文本变量。.message-listaria-busy 保留不动,它同时在抑制 agent-activity-status 那个按阶段变化的 role="status" 标签,去掉会造成串读。启动失败页从 <main> 上的 aria-live="assertive" 改为内层 role="alert",同等紧急度但不再覆盖 main 地标,全文件不再出现 aria-live="assertive"
  • 验证:tsceslint 清洁;next build 通过;新增合同测试 13 条(与 BUG-251 同一文件);全部非数据库套件 199 个文件 1592 条断言全绿。同时确认 BUG-217 新增的"跳到最新"控件键盘可达:真实 <button>、无 tabIndex、标签与 aria-label 一致、min-h-11 min-w-11pointer-events-auto。未做的验证:无浏览器与屏幕阅读器,全部无障碍行为均为依据 ARIA 规范推理并以源码合同测试固定,没有任何一条经过实听。特别未经证实的有:NVDA / JAWS / VoiceOver 是否真的念出完成播报、序号是否足以避开重复文本抑制、aria-busy 在各家实现中抑制的程度。
  • 待跟进:「跳到最新」按钮在跳转成功后立即卸载,焦点会掉回 <body>,下一次 Tab 从文档顶部重新开始。修复需要给滚动容器一个可编程聚焦目标,会牵动 BUG-217 刚落地的合同测试,另开一轮处理。
  • 防复发:aria-live 区域不得放在任何可能带 aria-busy="true" 的子树内,也不得随内容分支重新挂载;流式内容只播报状态迁移,绝不播报增量文本;每个事件必须有且只有一个播报出口,静默必须是显式声明的选择而非遗漏。
  • 相关记录:BUG-216、BUG-217
  • 修复版本:本地未提交候选

BUG-253 | 27 个 BUG 编号被两条不同记录共用,交叉引用体系失效

  • 状态:resolved(编号唯一性已修复;文档更深层的错位问题见待跟进)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:docs/BUG_HISTORY.md 全文的交叉引用体系,以及 AGENTS.md 第 7 节的可执行性。
  • 用户现象:无终端用户可见现象。对维护者与 AI agent 而言,相关记录:BUG-209 这类引用无法确定指向哪一条记录,文档最有价值的部分(247 条记录中有 224 条填写了 相关记录)因此不可靠。
  • 根因:BUG-018BUG-043 连续 26 个编号加上 BUG-209,共 27 个编号各被两条不同记录占用。成因是并行分支各自追加记录、合并时两侧都保留。该失效模式仍在持续:仅在准备本批提交的一小时内,staging 就两次抢占了本地待提交记录的编号(先 BUG-214BUG-215),迫使本地记录两轮顺延。
  • 修复:保留每对中较早的记录编号,为后加入的一条分配 BUG-221BUG-247。逐条审计了 78 行指向受影响编号的引用:其中 10 行凭"合并保留了两侧副本、两份仅编号不同"直接判定,13 行凭语义判定(如 复发自:BUG-019 出现在「收到具体经历后仍重复泛问」中,按保留编号读是「原问题交接租约过期」全然无关,按顺延编号读是「自然语言真实事件被静默丢弃」同属一条提取链),14 行属自引用的全球地点集群,5 行属合并后的模板追问流;36 行确认本就正确未动。另在文档头部 使用流程 增补一条:追加记录前必须先检索当前最大编号。AGENTS.md 第 7 节把「必须完整读取并搜索」改为「必须用报错原文、接口路径、状态码、模块名和用户操作强制检索,并完整读完命中的记录」,并明写「检索不是可选步骤」——该文件已达 3690 行约 150 KB,通读一次约耗 5 万 token,原措辞在实践中不可执行;六条执行要求逐条未改。同步更新 tests/test_bug_history_workflow.py 的断言以匹配新措辞并追加「检索不是可选步骤」一条,保持约束强度。
  • 验证:重复编号 0;记录总数 247 条不变;编号 001–247 无空缺;无悬空引用;无冲突标记;docs/BUG_HISTORY.md 仅因新增的头部说明 +1 行,AGENTS.md 行数不变;逐行归类确认改动仅落在 27 个标题行、38 个 相关记录 行、8 个 复发自 行,状态/根因/修复/验证/防复发 等正文字段零改动。tests/test_bug_history_workflow.py 两条断言通过。
  • 待跟进:审计中发现一个更早、更严重的缺陷——BUG-020BUG-041 区间内记录正文与标题整体错位一位,部分标题下只剩 3 行结尾字段而没有 状态/根因;且 BUG-221BUG-234 本质是 BUG-020BUG-035 的重复记录,编号已唯一但内容仍冗余。两者都需要移动正文内容,属内容编辑,未在本轮处理,已由 BUG-254 完成。另有 7 行属"混合编号"(此前有人部分修正过),证据不足以判定,已就地加 (编号存疑:…) 标注而未改数字。
  • 防复发:BUG 编号必须唯一;追加记录前先检索当前最大编号;出现重复时保留较早记录的编号并同步修正指向它的 相关记录复发自。强制性文档约束的措辞必须与文档实际体量相称,否则会被普遍忽略而失去约束力。
  • 相关记录:BUG-253 无前序同类记录
  • 修复版本:本地未提交候选

BUG-254 | 22 条记录的正文挂在别人的标题下,其中一条正文被压在另一条记录末尾

  • 状态:resolved
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:docs/BUG_HISTORY.mdBUG-020BUG-043BUG-221BUG-240 区间的 22 条记录。
  • 用户现象:无终端用户可见现象。对维护者与 AI agent 而言,按标题检索到的记录读出来是另一个 bug 的现象、根因与修复——比查不到更危险,因为它看起来完全正常。BUG-253 已修复编号唯一性,但当时判断"需要移动正文内容"而留作待跟进。
  • 触发条件:按 BUG-036BUG-041BUG-235BUG-240 任一编号检索并阅读其正文。
  • 根因:三个缺陷叠在一起,都源自 2026-07-25 的合并提交 3ca30ed7。其一,该提交自身的标题序列与正文序列就已错开一位,从 BUG-036 起每份正文实际属于前一个标题,末尾 BUG-240 的正文则被整段追加到 BUG-043 之后,使 BUG-043 带了两份完整正文。其二,两条分支各自保留了同一批记录的结尾三行(相关记录/复发自/修复版本),多出来的副本落在下一个标题之下,形成 11 处"有标题无正文、只有三行结尾"的空壳。其三,BUG-221BUG-234BUG-020BUG-035 标题逐字相同、共用同一份正文,本就是同一个 bug 的两份记录,BUG-253 为消除编号冲突给后者另分了编号,反而把"重复"固化成了 14 条独立记录。
  • 修复:按内容而非位置重新绑定。11 处只剩结尾三行的空壳先行删除——逐条比对确认其中 7 处与所属记录的结尾逐字同义(仅编号体系不同),另 4 处是所属记录结尾的真子集,差集恰为 BUG-253 标注过的 BUG-018/BUG-019 存疑残留,删除不丢信息。随后把错位区间内 11 份正文各回退一个标题,BUG-240 的正文从 BUG-043 末尾取回;每一次归属都以正文中的 用户现象/根因 与标题语义逐条核对,不依赖行号。14 对重复记录合并为一条,保留较早编号,BUG-221BUG-234 退役成空号,23 处指向退役编号的引用回指合并后的编号。BUG-235BUG-2403ca30ed7 中就缺失的 状态/首次发现/最近更新 三行按同批次记录补记,并在 状态 行内标明"非原文",不冒充原始内容。
  • 验证:记录数 253 → 239(退役 14 个重复编号);结构不完整的记录 22 → 0;重复编号 0;悬空引用 0;冲突标记 0。以"改动前每一行正文必须在改动后仍然存在"为口径做了全量比对,34 行差异全部落在被重写的引用行与被删除的 11 处重复结尾上,无正文丢失。tests/test_bug_history_workflow.py 通过。
  • 防复发:docs/BUG_HISTORY.md 出现合并冲突时,只消除冲突标记不算解决——必须逐条确认标题与其下正文仍然对应,判据是正文的 用户现象/根因 能否解释标题。两条分支各自追加记录时,标题逐字相同的两条必须合并为一条而不是分配新编号;分配新编号只应对"不同的 bug 恰好撞号"。
  • 待跟进:BUG-163状态/最近更新/根因/验证/修复版本 各出现两次,疑为同一记录两轮更新直接拼接。该缺陷早于本轮、不属正文错位,未处理。
  • 相关记录:BUG-253
  • 修复版本:本地未提交候选

BUG-255 | 模型把步数预算耗在无效工具参数上,个人咨询只返回兜底文案

  • 状态:resolved(本地修复,未提交、未发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/api/consult 个人咨询的 Agent 步数预算、run-jyotish-consultation 的模型可见参数契约,以及模型运行结束原因的可观测性。
  • 用户现象:staging 事业类咨询运行至星盘计算成功,但模型没有产出任何回答文本,用户只看到 ensureFinalResponseText() 的兜底句“本次计算已完成,但暂时没有生成可展示的回答”。事件流显示 Skill 已加载、四次 run-jyotish-consultation(三次失败、一次在 20278ms 后成功),此后再无 answer.delta
  • 触发条件:模型在同一次运行内多次调用排盘工具,其中至少两次同时传入 domains 与兼容字段 theme;叠加渐进式披露的 Skill 参考读取后,maxSteps: 6 在写回答之前耗尽。
  • 根因:三层叠加。其一,consultationToolInputSchemadomainstheme 声明为两个彼此独立的可选字段,互斥关系只在 canonicalDomainPlan() 里以 invalid_consultation_domain_plan 运行期抛出,工具 description 也从未提到该约束;更直接的是 jyotishInstructions 明确写着“Use the legacy theme field only for a single-domain compatibility retry”,等于主动引导模型去用一个会被拒绝的组合。其二,这类无效调用发生在步骤记录 try/catch 之前,既不产生 chart-calculation 活动也不追加运行步骤,因此每次都白耗一个模型步骤且在回执里不留痕迹。其三,maxSteps: 6AbortSignal.timeout(110_000) 约束同一次运行却分别硬编码:1 次 Skill 加载 + 4 次工具调用已占 5 步,skill_read / skill_search 这类渐进式披露工具未映射进公开事件流,第 6 步一旦被一次不可见的参考读取拿走,运行就在没有任何回答的情况下结束。finishReason 在整个仓库中没有任何记录点,因此“步数耗尽”只能靠事后数事件推断,无法证实。
  • 修复:把互斥关系改为不可表达而非运行期拒绝——模型可见的 inputSchema 只保留 questiondomainstheme 从模型契约中移除,.strict() 保持不变,使 theme 在进入工具体之前即被 Mastra 的入参校验拒绝;description 补齐“只用一个有序 domains 数组,省略即接受服务端已选领域,出生资料服务端绑定”的显式契约;jyotishInstructions 同步删除引导模型使用 theme 的那句。canonicalDomainPlan() 继续处理单值 theme 形态并保留 invalid_consultation_domain_plan,导出后由直接单元测试覆盖,供不经模型 schema 构造计划的调用方使用。步数预算与时钟预算改为相邻声明的 AGENT_MAX_STEPS = 8AGENT_TIMEOUT_MS = 110_000,并注明二者约束同一次运行、必须一起考虑;运行步骤记录预算随之对齐,避免耗尽步数的运行同时截断自身证据。新增受控观测字段 modelFinishReason(封闭枚举,未知取值一律归一为 unknown)与 modelStepCount,在流中按 step-finish 计数、以终止 finish 携带的步骤列表为准,跨重试累计。未放宽单次计算边界,未放宽运行合同门禁,未把任何模型原文或 provider payload 写入日志。
  • 验证:新增修复前失败的回归 6 项——模型可见 schema 必须拒绝 theme(单独出现与与 domains 同时出现)、被拒调用不得推进任何运行状态(consultationToolStarted / consultationToolCallCount / steps 全部不变)、完成运行必须记录 finishReason 与权威步数、以 tool-calls 结束且无回答的运行必须记下耗尽的步数、重试必须累计步数并归一化未识别的 provider 取值、步数与时钟预算必须成对声明。回退任一源改动可确认对应回归失败。另新增公开回执守卫:agentExecutionReceiptSchema 是 strictmodelFinishReason / modelStepCount 一旦透出会让成功运行在序列化自身回答时报错,故断言按白名单构建的回执不含这两个字段、直接透出则必须抛错。canonicalDomainPlan() 补 8 项直接断言,锁定“单值 theme 不得覆盖路由选定领域”。BUG-205 的缓存不被污染性质改由 schema 合法但注册表非法的输入(domains: ["career", "unknown"])复验,因其原始触发条件已不可达。全量非数据库套件 1586/1586 通过,npx tsc --noEmit 0 错误,改动文件 npx eslint 0 错误。
  • 待跟进:Mastra 的 createTool 会在调用业务 execute 之前完成入参校验,校验失败时返回错误对象而不是抛出,因此模型若仍误传 theme,本次运行仍会消耗一个步骤,只是拿到的是明确可纠正的提示,而不再是不透明的 invalid_consultation_domain_plan,且不会进入计算缓存。该类校验失败同样不追加运行步骤,回执中依旧看不到;是否为“入参被 schema 拒绝”单独记一条受控失败码,留待与 failureCode 分类一并评估。另记 maxSteps 取 8 而非更大值的依据是时钟而非步数:单次排盘约 20s,路由 maxDuration 为 120s、Agent 超时 110s,三次失败计算即会先耗尽时钟;8 步刚好覆盖最长有用形态——Skill 加载、两次渐进式披露参考读取、一次计算加一次重试、一次写回答,继续放大只会在注定失败的运行上多花 token,不会换来更多计算机会。
  • 防复发:模型可见的工具参数不得存在两个语义重叠的字段,互斥关系必须由 schema 表达而不是运行期抛出;任何在模型契约中被移除的字段,必须同时从 Agent instructions 中删除,否则提示词会继续引导模型踩坑。步数预算与时钟预算必须相邻声明并在同一处说明彼此关系,不得分散硬编码。凡以“模型没写回答”为现象的问题,必须先能读到 finishReason 与实际步数再下结论;新增观测字段只能是封闭枚举或计数,且必须同时验证其不会进入 strict 的对外回执。
  • 相关记录:BUG-214、BUG-205、BUG-186
  • 修复版本:本地未提交候选

BUG-256 | 多领域排盘把回答契约整块藏进 consultations,模型无据可依只能不说话

  • 状态:resolved(本地修复,未提交、未发布)
  • 影响面:/api/consult 个人咨询中模型提交两个及以上领域的全部运行;单领域运行不受影响。
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 用户现象:staging 综合类咨询(问题“未来两年哪些阶段值得把握”)工具执行成功,回执 route: "multi-domain"domains: ["timing","career","wealth"]status: "ready"missingLayers: []durationMs: 62909,模型却产出零回答文本,用户只看到 ensureFinalResponseText() 的兜底句“本次计算已完成,但暂时没有生成可展示的回答”。与 BUG-255 不同,本次步数预算 {planned:10, used:3, remaining:7} 远未耗尽,不是步数问题。
  • 触发条件:模型在一次 run-jyotish-consultation 调用中提交多于一个领域,且运行成功。单领域调用(Run 1,事业类)在同一批数据上正常输出完整回答。
  • 根因:toModelDomainPlanContext() 对单领域与多领域返回两种结构不同的形状。单领域走 { ...consultations[0], domains, consultations },证据包被摊平到顶层,evidence_contractclaim_cardsrectificationroutestatusquestionpacket_version 全部可达;多领域只返回 { success, domains, consultations },顶层仅此三键。而 jyotishInstructions 的硬性输出契约全部以顶层路径表述——evidence_contract.answer_policy 作为硬约束、hard_blockers 非空才可声称计算失败、rectification.boundary=not_auto_rectified 视为终态、把回答政策当作权威依据。多领域形状下这些路径一律解析不到,叠加“服务端证据不支持的内容一律不得陈述”的总政策,模型手里没有任何授权它开口的契约,沉默是它唯一符合提示词的选择。证据并未丢失,只是嵌在 consultations[] 里,而 instructions 从未提到该路径。反向不对称同时存在:success 只在多领域形状里有,单领域形状根本没有该键,因为证据包 schema 里没有这个字段。附带一处冗余:projectNatalFoundation() 对每个领域产出完全相同的本命投影,三领域载荷把同一大块本命数据重复三份,零信息增量。
  • 修复:为整个领域计划给出一份顶层回答契约,形状与单领域逐字一致,使 instructions 引用的每条路径在两种形状下都能解析。合并一律取最严:status 取 ready > degraded > blocked 中最差的一档;hard_blockersmissing_route_layers 取并集;answer_policycan_answer_* 一类许可布尔必须每个执行领域都为 true 才为 true,should_lead_with_limitations 一类限制布尔任一领域为 true 即为 true,数组字段取并集,其余字段仅在所有领域取值一致时保留;出现无法合并的分歧时不选边,记入 unresolved_policy_fields 并强制 should_lead_with_limitations = trueavailable_layers 是唯一取并集的许可类字段——某层只要为任一领域真实算出就确实存在,否认它等于否认真实证据,真正约束回答的是缺失与阻断的并集。rectification.boundary 只要有一个领域报 not_auto_rectified 就整体沿用该边界。本命投影在各领域逐字相同时上提为顶层单份并从各领域移除,不同时保持每领域各自携带,不挑一份充当共享。单领域形状继续走摊平分支,逐字不变,另补 successomitted_domains 两键消除反向不对称。每领域细节仍留在 consultations[],未做删减。
  • 验证:新增修复前失败的回归 4 项——多领域结果必须暴露与单领域相同的顶层契约路径(packet_version / question / route / status / evidence_contract / claim_cards / rectification);一个领域禁止精确时机即强制合并政策同样禁止,且 status 取最差、缺失层与阻断项取并集;mergeConsultationAnswerPolicies() 的直接单元断言锁定“合并只能收紧,不能放宽”,含冲突字段不选边;合并契约必须暴露单领域暴露的每一个政策字段,防止今后新增字段被静默丢弃。第 4 项另覆盖本命投影上提与“领域不一致时不上提”。把多领域分支回退成 { success, domains, consultations } 可确认这 4 项全部失败。
  • 待跟进:projectEvidenceContract() 只投影 available_layers / missing_route_layers / hard_blockers / answer_policy / user_facing_limitation 五项,Python 侧在 answer_policy 顶层给出的 deterministic_claims_forbidden_for 并不在其中,因此 instructions 里“把 answer_policy.deterministic_claims_forbidden_for 当作硬性禁止”这句在单领域形状下同样解析不到——这是与本条同源的对称缺口,但属于投影层而非合并层,本轮未改投影范围,仅让合并逻辑对该类禁止列表按并集处理,字段一旦被投影即自动生效。
  • 防复发:同一个工具结果不得对不同输入返回结构不同的顶层形状;提示词以顶层路径表述硬性契约时,每种可能的返回形状都必须让这些路径解析得到,否则模型会以沉默满足“无证据不得陈述”。跨领域聚合只允许收紧,任何许可类字段取并集前必须能说清“它为何不是放宽”;无法合并的分歧必须显式暴露并倒向限制,不得择一。以“模型没写回答”为现象的问题,先核对提示词引用的每条路径在实际载荷中是否存在,再怀疑步数或时钟。
  • 相关记录:BUG-255、BUG-214、BUG-215
  • 修复版本:本地未提交候选

BUG-257 | 领域上限允许提交注定超时的计划,六领域计划在时钟上从不可能完成

  • 状态:resolved(本地修复,未提交、未发布)
  • 影响面:/api/consult 个人咨询的领域计划上限、run-jyotish-consultation 的模型可见参数契约与工具描述,以及领域循环的时钟纪律。
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 用户现象:staging 综合类咨询(问题“请综合说明我当前最值得关注的主题”)在一次 tool.startedchart-calculation 活动之后直接 tool.failed code=calculation_failed,没有 evidence-validation 活动,说明失败发生在领域循环内部;服务端补跑的第二轮模型循环既无工具调用也无文本,最终 run.failed code=runtime_contract_incomplete
  • 触发条件:模型提交的领域数乘以单领域实际耗时超过 Agent 级 abort 信号剩余时间。观测口径为单领域 20936ms、三领域 62909ms,约 21s/领域,证实领域循环串行且延迟随领域数线性增长。
  • 根因:MAX_CONSULTATION_DOMAINS = 6,工具描述也照此宣称“up to six allowlisted domains”,但 for (const domain of domains) 逐个 await 一次 Python 调用,每次约 21s,且所有调用共用同一个 AbortSignal.timeout(AGENT_TIMEOUT_MS)110s)——该 deadline 对整轮运行是累计的,runConsultationWorkflowAbortSignal.any 叠加的 90s 才是每次调用各自的。因此六领域约 126s 永不可能完成,四领域约 84s 也几乎不给模型留下写回答的时间。schema 允许表达一个注定失败的计划,且失败时序(循环内抛出、无 evidence-validation)与该推断一致。并发不是出路:Python API 是单进程 ThreadingHTTPServer,核心计算受 GIL 约束,/api/consultation_workflow 为同步处理,异步作业另有 JYOTISH_ASYNC_JOB_WORKERS=2JYOTISH_ASYNC_JOB_QUEUE_SIZE=8 的有界队列,满载即以 HTTP 503 ERR_JOB_QUEUE_FULL 回绝(前端 workflow_queue_full 即由此映射)。并行只会把串行等待换成排队与 GIL 争抢,不会缩短总时长,故不并行。
  • 修复:上限改为由时钟推导而非选定,与它约束的同一轮预算相邻声明:CONSULTATION_DOMAIN_DURATION_MS = 21_000staging 实测)、CONSULTATION_ANSWER_RESERVE_MS = 45_000(三领域运行在 110s 内实际留给写回答的余量口径)、CONSULTATION_DOMAIN_WALL_CLOCK_MS = 110_000 - 45_000 = 65_000MAX_CONSULTATION_DOMAINS = floor(65_000 / 21_000) = 3。模型可见的 domains 数组上界随之收为 3,使超预算计划不可表达——Mastra 在进入工具体之前即拒绝,不会启动任何计算、不推进任何运行状态。绕过模型 schema 的内部调用方走 executableDomainPlan():截断到上限、把余下领域记为 omittedDomains,宁降级不整体失败。循环内另加 domainFitsRunBudget(),按已执行领域的真实耗时外推下一个领域是否还装得进循环份额,装不下就停在此处并把剩余领域计入 omittedDomains;第一个领域始终执行,否则无从作答。任何截断都会把顶层 status 压到至少 degraded、强制 should_lead_with_limitations = true,并通过 omitted_domains 与回执的 omittedDomains 同时对模型和调用方披露,使部分回答不可能被当作完整回答呈现。工具描述与 jyotishInstructions 同步改写为真实上限、串行执行、单领域时钟成本与截断披露语义。未提高 AGENT_TIMEOUT_MS:路由 maxDuration 为 120110s 已贴近上限。
  • 验证:新增修复前失败的回归 4 项——上限必须等于时钟能支付的领域数(同时断言 AGENT_TIMEOUT_MS、循环份额与 executableDomainPlan() / domainFitsRunBudget() 的边界取值,并显式记录旧上限 6 在 110s 内不可能完成);超上限计划必须不可表达且一次计算都不启动(consultationToolStartedsteps 均不变);每领域 40s 的慢运行必须在第一个领域后停止、披露丢弃的领域、status 降为 degraded,且被截断的结果仍须携带完整顶层契约;工具描述宣称的上限必须与执行的上限一致,且不得再出现 “up to six”。把上限回退为 6 并让预算判定恒真,可确认前三项失败;描述一致性那项针对修复前的字面描述文本失败。
  • 防复发:任何领域级并行提议必须先证明后端能承接并发,判据是 Python 侧的进程模型、GIL 约束与有界队列,而非前端看起来能不能同时发请求。串行循环的规模上限必须由时钟推导并与预算常量相邻声明,不得独立选定;超出上限的计划优先“执行装得下的部分并披露丢弃项”,其次才是拒绝,且披露必须同时到达模型与回执,并强制降级状态,使部分结果无法被呈现为完整结果。宣称上限的文案与强制上限必须由同一常量插值,禁止在描述里写死数字或数词。
  • 相关记录:BUG-255、BUG-256、BUG-214
  • 修复版本:本地未提交候选

BUG-258 | 运行失败时回执不随事件返回,最需要解释的运行反而只剩一个错误码

  • 状态:resolved(本地修复,未提交、未发布)
  • 影响面:/api/consult 所有以 run.failed 结束的运行的对外诊断信息,以及在流开始之前就失败的 agentic 运行的服务端观测日志。
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 用户现象:无终端用户可见文案变化。对调用方与排查者而言,run.completed 携带完整回执,run.failed 只有 code 与一句提示,于是 BUG-255 刚补齐的每步 durationMs、步数预算、工作流路由在运行失败时一概拿不到——恰好是最需要它们的时刻。
  • 触发条件:其一,任何走到 streamAgentResponse catch 分支的运行;其二,agentic 运行在 streamAgentResponse 建立之前失败(计划装配、服务端星盘真值缺失、工具构造等),此时请求级 catch 只调用 cancel(),永远到不到 onError 里的 settle-and-log 入口。
  • 根因:run.failed 事件 schema 从设计上就没有 receipt 字段,catch 分支也从未尝试构建回执;而 agentExecutionReceiptSchema 是 strict,内部字段不能直接透出,agent-observability.ts 又是刻意封闭的非 PII 契约(无自由格式 metadata、无原文、无 provider payload),所以“把内部诊断塞进对外事件”这条路本就不通,最初便被整体放弃,连白名单可透出的部分也一并放弃了。服务端侧则是入口位置问题:settle-and-log 只挂在 streamAgentResponseonError 上,更早的失败没有任何路径抵达它,观测事件因此对失败最重的那类运行完全缺席。
  • 修复:run.failed 增加可选 receipt,内容用既有白名单助手 publicConsultationRuntimeSteps() 构建,与 run.completed 走同一条边界,因此每步 durationMs、步数预算与工作流路由到达调用方,而内部 failureCodemodelFinishReasonmodelStepCount 仍留在服务端。构建回执本身被包在 try 内:回执构建失败不得把失败事件替换成一次静默关闭,此时照旧发出不带 receiptrun.failed。服务端侧由 agentic 装配把 settle-and-log 入口发布为 agenticFailure.report,请求级 catch 优先经它上报(内部按 toAgentObservabilityErrorCode() 归一化错误码),仅在该入口尚未发布时退回裸 cancel(),从而保证每条 agentic 失败路径都留下一条封闭观测事件。未放宽任何 strict schema,未新增自由格式字段。
  • 验证:新增修复前失败的回归 3 项——失败运行必须携带与成功运行同构的白名单回执(断言两步的 durationMsstepBudget.used,并断言序列化结果中不出现内部分类与模型循环诊断);回执构建抛错时仍须恰好发出一次不带 receiptrun.failed;源级契约断言请求级 catch 必须经 agenticFailure.report 而非裸 cancel() 上报。删除 receipt 透出可确认第一项失败;agenticFailure 在修复前不存在,第三项对修复前的源文件必然失败。
  • 防复发:失败路径的诊断价值必须与成功路径持平,二者共用同一个白名单构建入口;对外 schema 是 strict 不能作为放弃全部诊断的理由,只能作为“哪些字段留在服务端”的划线依据。诊断信息的构建不得成为失败事件本身的前置条件。凡新增 settle-and-log 类入口,必须确认它覆盖到最早的失败点,否则失败越早、可观测性越差。
  • 相关记录:BUG-255、BUG-214、BUG-256、BUG-257
  • 修复版本:本地未提交候选

BUG-259 | 两套路由各说各话:问题文本选路与声明领域不一致时,多领域计划里除一个领域外全部 400

  • 状态:resolved(本地修复,未提交、未发布)
  • 影响面:POST /api/consultation_workflow 携带 plan_version 的全部产品运行,即 /api/consult 个人咨询的每一次领域调用;不带计划元数据的研究 / MCP 调用方(mcp_server.pystrict_workflowscripts/consultation_workflow_service.py)行为不变。
  • 用户现象:staging 综合类咨询(问题“我的事业和财运接下来会怎么走,两者之间该怎么取舍”,模型选择 career + wealth 两个领域)连续四次 tool.failed code=calculation_failed,四次完全相同,重试无一次成功;用户只看到兜底文案。同批次里问题文本直接含“事业”而只选事业单领域的运行成功(20936ms,完整回答)。
  • 触发条件:一次运行提交两个及以上领域,且问题文本的关键词命中的领域不等于其中某个领域声明的路由。因为每个领域各发一次 Python 调用却共用同一段问题文本,文本只能选出一个路由,所以多领域计划里至多一个领域能对上,其余每个都必然 400;判定完全确定,故重试逐次复现同一结果。
  • 根因:服务端存在两个互不知情的路由器。UnifiedConsultationOrchestrator.resolve_route() 以文本优先:先查显式应期词,再按 domain_tokens 关键词匹配,只有文本一无所获时才回落到 themes 参数。而 validate_consultation_plan_contract() 要求文本推出的路由必须落在 RouteContract.resolved_routes 内,每条契约只允许一个路由,不等即抛 ConsultationPlanContractError,在 execute_consultation_workflow() 里被包成 BadRequest → HTTP 400,前端映射为 workflow_bad_request,再被 safeToolError() 压成 calculation_failed。本次现象的落点是词表不对称:“事业”在 career 词表里,“财运”却不在 wealth 词表(财务/财富/投资/房产/收入)中,于是 career 与 wealth 两次调用都被文本判成 careerwealth 那次声明 wealth,必然 400。补齐词表只会把矛盾推到下一个问法上——只要一次运行发出多个领域调用,文本选路与声明路由就在结构上不可能同时满足。frontend/src/lib/consultation-workflow-request.tstheme === "timing" 时给问题加前缀“应期与阶段问题:”,正是为了把“应期”这个词塞进文本让文本路由同意声明路由,是本 bug 只对一个领域打过的绕行补丁;同批 timing 运行成功恰恰因为它带着这个前缀。
  • 修复:让服务端自己签发的声明路由成为权威,两套路由不再可能互相矛盾。consultation_plan_contract 新增 declared_workflow_route():只在计划元数据完整、版本受支持、路由在服务端白名单内时返回该路由,缺一即抛;无任何计划元数据时返回 Noneexecute_consultation_workflow() 先取声明路由,再以 resolve_route(question, themes, declared_route=...) 解析,声明存在即直接返回该路由定义,声明为 None 时文本启发式逐字不变——这保证遗留调用方行为不变。路由包新增 route_sourcedeclared_plan / question_text),使响应能自证由哪套路由决定;routing 在前端是 z.record + passthrough,新增键不破坏契约。执行面同步正确:question_type / primary_theme / focus_techniques 都取声明领域的 RouteDefinition,因此 runtime_planner 的同步步骤、consumer_context.route 的必需层、machine_evidence_packetreal_case_calibration 以及前端据 routing.primary_theme 选取 thematic_report.themes[primary_theme] 的领域证据,全部落在声明领域上,不会出现“声明 wealth 却拿到 career 证据”。契约检查保持 fail-closed 且一处未放宽:白名单外的路由在 declared_workflow_route() 就被拒(unsupported consultation workflow route),根本进不到执行;themes 与契约不一致、必需层 / claim boundary / requested domains / 证据类别 / depth / horizon / precision boundary 任一不符仍逐项拒绝;原先那条 resolved_routes 检查保留,语义从“两套路由仲裁”变为“断言执行路由确实等于声明路由”。既然声明路由已经权威,timing 前缀所修的 bug 不再存在,故删除:它会把一句合成前缀塞进模型据以作答的问题文本,而它对路由不再有任何作用;核对过前缀不影响 _build_consumer_context 的任何领域正则与 precise_timing_requested 判定(应期 不在这些正则里,timing 路由本身已置该标志),也不影响 prashna 之外的 question_text 用途。
  • 验证:新增修复前失败的回归——(1) Python 端复现 run 4:声明 strict_workflow_route: "wealth"、问题文本文本路由到 career 时必须成功且执行 wealth 路由(修复前抛 BadRequest: consultation plan route mismatch,即线上那个 400);(2) tests/test_consultation_workflow_domains.py 补齐它一直缺的计划元数据——十个规范领域各带完整计划、共用同一段文本路由到 career 的问题,逐个断言 routing.question_type / primary_theme / consumer_context.route 等于声明领域,且 thematic_report.themes[primary_theme] 就是该领域证据(修复前除 career 外九个全部 400,这正是此前 bug 从未被测试发现的原因:该文件原本不带计划元数据);(3) 编排器直接断言声明路由压过关键词、无声明时文本路由逐字不变、每个规范领域都可作为声明路由执行、未知声明路由必须拒绝而不是静默回落文本;(4) declared_workflow_route() 的白名单与完整性断言,含 strict_workflow_route: "free_script" 与篡改必需层经 API 仍为 BadRequest,确认计划无法夹带不受支持的路由。前端回归改为断言问题文本逐字送达(前缀存在时必然失败)。测试结果:tests/test_consultation_plan_contract.pytests/test_consultation_workflow_domains.pytests/test_unified_consultation_orchestrator.pytests/test_consultation_consumer_context.pytests/test_api_server_security.pytests/test_mcp_strict_workflow_career.pytests/test_runtime_import_boundaries.pytests/test_historical_event_backtest.py 共 275 项通过;scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime 通过,ruff 门禁文件全通过(改动文件相对修复前无新增告警),py_compilecommercial_privacy_artifact_scanfindings 0)、python -m build 均通过;前端 npx tsc --noEmit 0 错误、改动文件 npx eslint 0 错误、非数据库套件 1664 项中 1660 通过,4 项失败全部是本机并发 Postgres 容器争抢(另有工作树同时在跑数据库测试,本机同时存在 11 个测试用 Postgres 容器),逐个单独重跑后 onboarding-routerectification-v9-databaseadmin-databaseidentity-auth-integration 均通过,model-configuration-securitydatabase ... 一项单独重跑仍以 Connection terminated unexpectedly 失败,该文件不引用本轮任何改动模块。
  • 待跟进:run 2(问题“请综合说明我当前最值得关注的主题”的那次 calculation_failed)不由本机制解释——该文本不含任何领域关键词,每个领域都会回落到自己的 themes 并对上,本轮未能找到独立证据说明它为何失败,不认领。同一问题文本的一次失败已在 BUG-257 记为领域数乘单领域耗时超出时钟预算,但本轮没有该次运行的领域数与耗时证据可核对,因此既不视为已解释也不视为复发。另记:领域循环里任一领域抛出即让整次工具调用失败,BUG-257 已有 omitted_domains 这条降级披露通道,把单领域失败也接入该通道属独立改动,本轮未做——本次修复消除的是那个确定性的失败源。
  • 防复发:同一个决定必须只有一个权威来源。凡服务端自己签发的受控元数据已经声明了执行参数,就不得再由请求文本的启发式重新推导一遍并要求两者相等——这类“契约允许了服务端不接受的东西”的自伤矛盾在 fail-closed 门禁下必然表现为确定性 400。文本关键词只能作为没有声明时的兜底,且必须在返回值里标明本次由哪套规则决定。为了让文本路由同意声明路由而改写用户问题(如注入关键词前缀)不是修复而是绕行:它只覆盖被打补丁的那一个领域,还会污染模型据以作答的输入,一旦声明路由成为权威必须删除。凡是“一次运行对同一文本发出多次不同领域调用”的形态,测试必须带上产品真实发送的计划元数据并逐领域断言执行路由等于声明领域,否则测试会用一段刚好自洽的文本掩盖矛盾(本 bug 正是如此漏过)。
  • 相关记录:BUG-257、BUG-256、BUG-255

BUG-260 | React Compiler 无法接管 page.tsxHome:编译器静默拒编 2730 行组件且不报任何错

  • 状态:won't fix(本轮不修;Next 已升到 16.3.1 并保留,编译器配置已回滚,原因见下)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页 frontend/src/app/page.tsx 的重渲染性能,以及后续任何「靠 React Compiler 免除手写记忆化」的计划。
  • 用户现象:无终端用户可见现象。对维护者而言的现象是:next.config.tsreactCompiler: true 开启后构建打印 ✓ turbopackRustReactCompiler、退出码 0、测试全绿,看起来完全成功,但 Home 一个函数都没被优化。这个失败不产生任何警告、错误或日志,只看构建输出无法察觉。
  • 根因:本条所有行号与槽数均测于交付基点 e8d201ddpage.tsx 此后仍在演进(交付时远端已改到 3719 行),复现时应按函数名而非行号定位。page.tsx 当时有 3738 行,其中 export default function Home() 单个函数占 2730 行(10023738),带 24 个 useState、18 个 useEffect、0 个手写 useCallback/useMemo。Next 16.3.1 的 Rust 版 React Compiler 会正常编译同一文件里的其他函数,却拒编 Home:默认 infer 模式下全项目 44 个函数拿到缓存槽(app-sidebar 112 槽、use-birth-time-guided-journey 84 槽、sidebar-session-row 73 槽等),page.tsx 内部两个小组件(原始行 760、815)也拿到了 11 和 24 槽,唯独 Home 的生成代码仍以裸 useState 序列开头、没有 _c(N) 前导。把 compilationMode 设为 all(绕过组件识别启发式、强制编译每个函数)后 page.tsx 被编译函数从 2 涨到 47Home 依然不在其中——既然 all 模式下不存在「未被识别为组件」,只剩一种解释:编译器尝试了 Home 并失败,然后按 panicThreshold 默认值 none(官方文档原话 "skips components which cannot be compiled")静默跳过。具体原因经授权后已定位(panicThreshold: "all_errors" 在 Rust 版下形同虚设,设了也不报错,因此改用临时安装 babel-plugin-react-compiler 并挂 logger 钩子的方式取诊断,诊断完已卸载):Home 的失败全部来自编译器自身未实现的语法与内部断言失败,没有一条是本仓库代码写错。 稳定版 babel-plugin-react-compiler@1.0.0Home 报 24 条错误,去重后三类,全部带 Todo: 前缀(React Compiler 用 Todo: 标记「该语法尚未实现」):13 条 Todo: (BuildHIR::lowerStatement) Handle TryStatement with a finalizer ('finally') clause、9 条 Todo: (BuildHIR::lowerStatement) Support ThrowStatement inside of try/catch、2 条 Todo: (BuildHIR::node.lowerReorderableExpression) Expression type MemberExpression cannot be safely reordered。即编译器当时还不支持 try/finallytry/catch 内的 throw,而 Home 有 13 个 finally 和 9 个这样的 throw。这 13 个 finally 做的全是 finally 该做的事——window.clearTimeout(bootstrapTimeout)polling = falserectificationOpenInFlight.current = falsecancellationRequests.current.delete(requestId),以及 8 处 setProfileSaving(false) / setAvatarSaving(false) / setCreatingSession(false) / setBirthTimeAssessmentPhase(null) 之类的加载态复位;删掉任何一个,try 块抛错时 UI 就永久卡在加载态,是拿真 bug 换假优化。另在更新的 0.0.0-experimental-a1856f3-20260507 上复测:那 22 条 try/finallythrow 错误已被上游修好,但 Home 随即撞上编译器内部断言失败 Invariant: Expected all references to a variable to be consistently local or context referencespage.tsx:2666catch (error) 的绑定同时被直接使用和被 setRequestError((current) => ...) 的闭包捕获);在仓库外的副本上把这一处改掉后,又冒出下一个 Invariant: [PruneHoistedContexts] Unexpected hoisted functionpage.tsx:1837refreshAccount,被 1626 行的 useEffect 提前引用)。Invariant: 在 React Compiler 的分类里是编译器 bug 而非用户代码违规。Home 共 50 个函数声明,其中 6 个被声明前引用(refreshAccount 1837/1626、editDeclaredBirthTimeDetails 2280/1098、completeGuidedBirthTime 2308/1097、openRectificationFromHomepage 2515/2373、openRectificationSession 2519/1982、handleRectificationProfileIncomplete 2531/2457),要满足编译器就得在 2700 行的组件里跨千行重排这 6 个定义,且照上述规律修完还会有下一个内部 panic。诊断顺带交叉验证了产物取证的正确性:Babel 版报告成功编译的两个函数是 BirthLocationFields @ 760ProfileFields @ 815,与先前从 Rust 版构建产物 source map 反查出的两个缓存槽(行 760/815,槽 11/24)逐一对上,两条独立证据互相印证。
  • 修复:未修复。next.config.tsreactCompiler: trueexperimental.turbopackRustReactCompiler: true 已回滚到与 origin/staging 逐字节一致;Next 16.2.10 → 16.3.1 的升级保留(该升级本身独立验证通过,是 Rust 版编译器的前置条件)。本轮共试六种配置全部失败:reactCompiler: true、加 panicThreshold: "all_errors"、再加文件顶部 "use memo"compilationMode: "all""all" + all_errorscompilationMode: "annotation" + Home 体内 "use memo"。其中 compilationMode: "all" 还会直接把构建搞坏:它会编译模块作用域的普通回调,birth-time-intake.tsx:22Array.from({length:24}, (_, index) => ...) 被插入 useMemoCache,预渲染 / 时抛 TypeError: Cannot read properties of null (reading 'useMemoCache')。注解模式(领导指定的退路)反而最差:全项目 0 个缓存槽,连原本能编的 44 个也停了。
  • 验证:npx tsc --noEmit 无输出;npx eslint 0 error4 个既有 warning 在未改动文件里);npx next build 退出码 0;非数据库套件 199 个文件 1592 条全绿、fail 0 skipped 0 todo 0(与开工基线一致)。取证方式:Home 在产物里被压缩改名,function Home( 搜不到,改用 source map 反查——解 .next/server/chunks/ssr/*.js.map 的 VLQ mappings,把每个缓存槽 _c(N)(压缩后真实形态是 (0,X.c)(N))归属回原始文件与行号。反向验证:只加/删 page.tsx 顶部一行 "use no memo"page.tsx 被编译函数数在 2 与 0 之间可见切换,且切换只影响 page.tsx、其他文件槽数不变,证明取证方法不是恒为真;两种状态下都判定 Home 未编译。构建代价(各测 4 次取最快,同机噪声大):开启前 36.65s / 关闭后 35.11s,差异落在噪声内,Rust 版没有可测量的构建变慢;/ 首屏 JS gzip 470.1 KB → 481.9 KB+12031 B / +2.50%,低于 5% 阈值。
  • 待跟进:其一(已完成,结论见根因):定位 Home 的失败原因已获授权并完成,答案是上游编译器缺陷,不是本仓库代码问题。同时用同一套诊断量化了「换编译器版本能否提高覆盖率」这个问题,答案是不能:对 frontend/src 全部 373 个文件跑批量诊断,稳定版 1.0.0 与 experimental 版编译成功的函数数完全相同,都是 134 个,失败文件也都是同样的 21 个,只是报错事件从 74 降到 48——被上游修掉的那些错误类别,所在函数都还有别的拦路错误,所以一个函数都没多编译出来。因此换 Babel 版或换更新版本都买不到任何东西,babel-plugin-react-compiler 诊断完即卸载(npm ci 从锁文件权威还原,git diff 为空),不进交付。复现诊断的方法:临时 npm i -D babel-plugin-react-compiler,用 @babel/coretransformSyncparserOpts.plugins = ["jsx", ["typescript", {isTSX:true}]] 单独跑该文件,给插件传 logger: { logEvent(file, event) {} }event.kindCompileError 的即为 bailoutevent.detail.message 是原因。其二,是否为了那 44 个能编的组件而保留 reactCompiler: true:它们大多在 / 上渲染(侧边栏、会话行、日期选择器、三个引导 hook),收益真实但本轮未测量,代价是首屏 +2.5%;本轮按止损条款选择回滚,这个取舍留给决策。其三,若要让 Home 真正受益,最可能的路是把它拆成若干个小组件——但那是业务代码重构,本轮明令禁止碰 frontend/src/**。诊断结果还把这条路的性质说清了:为迎合编译器去改 Home(删 finally、重排 6 个提升函数、逐个绕内部断言)是被编译器 bug 牵着走的打地鼠,每一步都拿确定的正确性换不确定的记忆化收益,不该做;真正值得做的是按职责把 Home 拆小,那样每个小组件天然落在编译器能处理的范围内,同时也解决可维护性问题。
  • 防复发:「配置开启 + 构建绿」绝不等于「React Compiler 生效」。编译器放弃某个组件时不报错、不警告、不留日志,这是它的默认行为(panicThreshold: "none")而非缺陷。任何启用 React Compiler 的改动都必须在构建产物里定位目标组件、确认存在编译器注入的缓存槽,并用 "use no memo" 做一次反向验证证明取证方法会随编译状态变化;只贴构建退出码等于没验证。另外 "use memo" / "use no memo" 是函数体内的指令,写在文件顶部时 "use no memo" 可作整文件退出、但 "use memo" 不构成整文件加入。
  • 相关记录:BUG-260 无前序同类记录。本记录三次改号:初次写作取 255;第一次 rebase 到 origin/stagingc8d9ec64)时远端已占 254255,改 256;第二次 rebase 到 e1db5762 时远端又占到 259,改 260。每次都按 BUG-254 的防复发要求处理——标题与远端各记录均不同,故另分新号而非合并。BUG-253 所记的抢号失效模式在同一轮交付里连续复现两次,暴露出「追加前检索最大编号」这条措施的边界:它只在写记录那一刻成立,防不住推送前远端继续前进。可靠做法是把定号推迟到推送前最后一次 rebase 之后。
  • 修复版本:本地未提交候选

BUG-261 | staging web 镜像把 skills/ 拷两遍,符号链接撞上被解引用的同名目录

  • 状态:resolved
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:deploy/railway-web.Dockerfile 的镜像构建,即 staging 与生产的 web 镜像产出。构建期缺陷,不影响已在运行的实例。
  • 用户现象:无终端用户可见现象。对维护者而言:docker build -f deploy/railway-web.Dockerfile 在最后一步失败,ERROR: failed to build: failed to solve: cannot replace to directory /var/lib/docker/buildkit/containerd-overlayfs/cachemounts/<id>/app/skills/jyotish-vedic-astrology/assets with file,指向 COPY --from=build /app/skills /app/skills。危险之处在于它依赖 BuildKit 实现才会暴露Gitea/GitHub runner 上一直构建成功,本地 Docker 29.1.3containerd-overlayfs 快照器)必定失败,因此这是一颗按 runner 环境触发的定时炸弹,而不是一个稳定可见的错误。
  • 触发条件:用一个不允许「以文件覆盖已存在目录」的 BuildKit 快照器构建该 Dockerfile。与 Next 版本、CPU 架构均无关:在升级前的基线提交(next 16.2.10)与 amd64/arm64 两种平台上都能复现同一条错误。
  • 根因:同一份 skills/ 以两种互不兼容的形态进了最终阶段。skills/jyotish-vedic-astrology/assets 是仓库里 git 跟踪的符号链接(-> ../../assets)。frontend/src/lib/skill-package-registry.ts 存在动态文件访问,Next 构建期打印 Dynamic filesystem access ... causes tracing of the whole project,于是文件追踪把 skills/ 整棵树带进 .next/standalone/,并在拷贝时把符号链接解引用成真目录。最终阶段第一步 COPY --from=build /app/frontend/.next/standalone /app 先把这份解引用产物落到 /app/skills,随后第 36 行 COPY --from=build /app/skills /app/skills 又要把源码树里的符号链接放到同一路径上——用文件覆盖目录。两行 COPY 相隔 5 行、意图并不冲突,冲突完全由中间那层不可见的文件追踪行为造成。
  • 修复:在构建阶段 npm run build 之后加一行 RUN rm -rf /app/frontend/.next/standalone/skills,删掉追踪产物里的那份,让显式 COPY skills 确定性地独占该路径。没有改动两行 COPY 的顺序或语义,也没有改 outputFileTracingIncludes/app/assets 仍由 standalone 提供,而它是 outputFileTracingIncludes/api/consult 显式声明的(../assets/**/*),不是追踪的附带产物,因此符号链接照旧可解析。
  • 验证:修复后完整构建镜像成功(naming to docker.io/library/jyotisha-web:fixed done)。进镜像逐项核对:/app/skills 下 1208 个文件、assets 仍是 lrwxrwxrwx ... -> ../../assetsls 能列出其中文件、/app/frontend/server.js 存在。运行时冒烟:容器启动打印 ▲ Next.js 16.3.1GET / 返回 200(日志中的 SupabaseConfigurationError 是未注入环境变量所致,属预期)。对照实验确认与本批 Next 升级无关——在升级前的 3371bacanext 16.2.10)上用同一条命令构建,在同一步报同一条错误。
  • 待跟进:本条没有自动化回归测试,与 BUG_HISTORY 工作流对 resolved 的要求存在缺口,此处如实标注而非掩盖。原因是复现该缺陷必须真的构建镜像(本机约 16 分钟),放不进 npm test。可行的折中是在 tests/health-deployment.test.ts 加一条结构断言:若 Dockerfile 同时存在「拷贝 standalone 到 /app」与「显式拷贝 skills」两行,则必须存在删除追踪副本的一行。这是结构守卫而非行为回归,能防住这一行被顺手删掉,未在本轮加入。更彻底的方向是消除 skill-package-registry.ts 的动态文件访问以恢复精确追踪,那属业务代码改动,另开一轮。
  • 防复发:Next 的输出文件追踪会把项目文件带进 .next/standalone/把符号链接解引用成真目录;任何 Dockerfile 若既拷 standalone 又显式拷同名目录,两者就在争同一路径,必须显式指定谁赢,不能靠 COPY 顺序碰运气。仓库内 git 跟踪的符号链接(如 skills/*/assetsskills/*/referencesskills/*/scripts)是这类冲突的固定诱因。更普遍的教训:镜像构建在 CI 上绿不等于 Dockerfile 正确,凡涉及符号链接与目录同名的 COPY,判定必须落到「换一个 BuildKit 快照器是否仍然成立」。
  • 相关记录:BUG-261 无前序同类记录。与 BUG-260 同批交付但成因无关,是为验证 BUG-260 的 Next 16.3.1 升级而首次在本地构建 staging 镜像时暴露出来的。编号顺延过程见 BUG-260 的相关记录。
  • 修复版本:本地未提交候选

BUG-262 | staging 镜像内 posthog-node 的 Node 引擎要求高于基础镜像实际版本

  • 状态:investigating
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:staging 与生产 web 镜像内的 posthog-node@5.41.0(产品分析上报)。
  • 用户现象:目前无任何可见现象。仅在镜像构建日志里表现为一条 npm 警告:npm warn EBADENGINE package: 'posthog-node@5.41.0', required: { node: '^20.20.0 || >=22.22.0' }, current: { node: 'v22.15.0', npm: '10.9.2' }
  • 触发条件:构建 deploy/railway-web.Dockerfilenode:22-alpine 当前解析到 Node v22.15.0,而 posthog-node@5.41.0 声明需要 ^20.20.0 || >=22.22.0v22.15.0 落在两个区间之外。
  • 根因:尚未确认实际影响面。已确认的事实只有两点:一是版本区间确实不满足,二是与本批 Next 16.3.1 升级无关——git diff 3371baca 37938187 -- frontend/package-lock.json 中没有任何 posthog 相关改动,且该警告在升级前的基线构建日志里就已出现。尚未确认的是 posthog-node 是否真的用到了 Node 22.22 才有的 APIEBADENGINE 只是警告、不阻断安装,npm 不会因此拒绝装包,所以风险形态是「构建期无声、运行期在某个具体调用上抛错」,而不是构建失败。
  • 修复:未修复。两条候选路径的代价都超出本轮范围:升基础镜像到 node:24-alpine 会引入一整片新的验证面(原生模块需重编、sharp 的 musl 二进制需重新确认);降 posthog-node 需确认低版本是否仍满足现有调用。
  • 验证:不适用(未修复)。已核实的事实见根因。
  • 待跟进:先确定这是真风险还是纯噪声——读 posthog-node@5.41.0 的 changelog/引擎声明来源,确认它把下限提到 >=22.22.0 是因为用了新 API 还是仅仅是维护者的支持策略。若属后者,本条可降级为「已知噪声」并就地记录,不必改依赖。
  • 防复发:镜像构建日志里的 EBADENGINE 不得当作噪声跳过——它意味着依赖声明的运行环境与镜像实际提供的不一致,而 npm 不会阻止这种安装,因此它是少数「构建期唯一一次提示、之后只会在运行期爆发」的信号。基础镜像用浮动 tag(node:22-alpine)时,实际 Node 版本会随上游重建漂移,依赖的引擎下限也会随升级上移,两者相向移动,这类不匹配会反复出现。
  • 相关记录:BUG-262 无前序同类记录。与 BUG-261 同为首次本地构建 staging 镜像时暴露的构建期问题。
  • 修复版本:未修复

BUG-263 | “跳到最新”按钮悬在输入框上方约 160px,压住正文而不是贴着输入框

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 主对话页在用户向上翻阅历史时出现的“跳到最新”控件。
  • 用户现象:按钮既没有贴着下方输入框,也没有落在空白处,而是悬在正文中间挡住一整行回答文字;视觉上不像输入框的附属控件,更像漂在内容上的异物。
  • 触发条件:会话已有消息,用户向上滚动超过 96px 锚定阈值。桌面端偏移约 160px,移动端约 128px。
  • 根因:按钮此前是 .conversation(滚动容器)的 position: sticky; bottom: 12px 子元素,而 .conversationpadding-bottom: var(--composer-reserve)(桌面 148px / 移动 116px)。sticky 元素被钉在视口底部之前,会先被自己的包含块——也就是滚动容器的 内容盒——夹住,而内容盒底边正好比容器可视底边高出这一段 padding。于是 bottom: 12px 从未生效,按钮实际停在 padding + 12px 处。这段 padding 是早期输入框覆盖式布局的遗留:现在 .chat-panelgrid-template-rows: 68px minmax(0,1fr) auto.composer-wrap 已是独立行、不再覆盖对话区。
  • 修复:把按钮从滚动容器移到 .composer-wrap 内部,改用 absolute inset-x-0 bottom-full + pb-3,让它挂在输入框区域自己的上边缘。.composer-wrap 本就是 position: sticky(定位元素)且 z-index: 2,可直接作为包含块,无需新增定位上下文。这样偏移不再依赖 --composer-reserve,输入框长高、推荐问题行出现或消失时按钮都跟着走。按钮自身的可访问性属性(真实 <button>aria-label、焦点环、44×44 触控区、pointer-events-auto)原样保留。
  • 验证:新增合同测试固定新位置(断言控件位于 composer 块内、使用 bottom-full、不再出现 sticky),并把 BUG-218 / BUG-252 两个文件里按缩进切片的定位锚点改成与缩进无关;两文件 23 条断言全绿。几何结论这次经过浏览器实测:用一份复刻 .chat-panel / .conversation / .composer-wrap 真实规则的静态页面同时渲染新旧两种写法,量得旧写法距输入框 160px、新写法 11px,与线上截图相符;量完即删,未留在仓库。tsceslint 清洁。未做的验证:没有在真实登录会话里目视确认(需要长对话与账户),复刻页面只覆盖了本条涉及的布局规则。
  • 防复发:滚动容器留了 padding-bottom 时,它的 position: sticky 子元素永远无法贴到容器可视底边——sticky 受包含块内容盒夹持,调 bottom 偏移不解决问题。悬浮在输入框上方的控件应挂在输入框容器上(bottom: 100%),而不是挂在滚动容器里,这样才不依赖任何预留高度常量。另:BUG-218 当时已写明“浏览器内的视觉位置未经人工目视确认”,本条正是那句话对应的实际后果——纯源码合同测试能固定 DOM 与属性,固定不了几何位置,涉及定位的改动必须实测。
  • 相关记录:BUG-218(引入该按钮与锚定逻辑)、BUG-252(曾把它记作 BUG-217 新增,并留下焦点丢失的待跟进项,本轮未处理)
  • 修复版本:本地未提交候选

BUG-264 | 初始化填报准确出生时间后,第一条咨询必定 409:客户端按提交的声明选路,服务端已把它升级为 accepted

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 初始化流程的出生时间与出生地点保存、PATCH /api/accountPOST /api/consult 的出生真值校验。
  • 用户现象:新用户在初始化里选择“我知道准确出生时间”并填到分钟,走完地点一步进入首页,发出第一条咨询即被拒绝,提示“出生时间状态已经变化 / 请刷新后重新选择使用填报时间、一般咨询或先完成校正”。刷新页面后同一条问题可以正常发出。
  • 触发条件:声明为 family_exact 且误差为 0,经账户接口保存后不刷新页面直接发第一条咨询。
  • 根因:账户写入会在服务端派生出生真值——零误差的准确时间被直接采用为 active_birth_time 且状态升级为 accepted——但 PATCH /api/account 只回 {ok:true},页面又用提交的草稿覆盖 profile,草稿里状态仍是 reportedtime 为空。于是客户端按未确认分钟选 unverified_birth_time,而咨询接口的未确认分支明确拒绝 accepted,在扣点前抛出 mode_changed 返回 409。派生规则写在服务端、客户端却各自推断同一件事,是这次不一致的入口。
  • 修复:PATCH /api/account 随写入成功返回它派生的出生真值(状态与当前排盘分钟),新增 resolveAppliedAccountBirthTime 负责这一派生;persistProfile 返回按该真值对账后的档案,初始化出生时间、初始化地点、账户弹窗保存和默认星盘切换四条保存路径统一采用返回值,不再沿用本地草稿。服务端的真值校验保持严格,不为客户端的过期视图放宽。
  • 验证:account-api.test.ts 锁定派生结果(零误差准确时间→accepted+分钟;改为时段声明→回落 reported;已确认与 legacy 分钟不被覆盖;纯改名不产生状态);birth-time-intake.test.ts 锁定客户端对账(含 HH:mm:ss、未知状态和缺字段时不动草稿);profile-persistence.test.ts 断言每条保存路径都采用返回档案,禁止回退到 setProfile(profileDraft)npm test 1710 项中 1709 通过,唯一失败是并发跑 database-* postgres fixture 的既有抖动,单独串行复跑通过;tsc --noEmit 与目标文件 ESLint 清洁。未做的验证:没有在 staging 真实新用户流程里目视复跑一遍,需要一个未初始化的账户。
  • 防复发:出生时间状态与当前排盘分钟由服务端唯一派生,客户端只能采用接口返回值;任何新增的档案保存路径都必须消费 persistProfile 的返回档案,源码合同测试会拦住用本地草稿覆盖 profile 的写法。咨询选路不得从未落库的草稿推导。
  • 相关记录:BUG-018(首次保存未写入档案状态,同一类真值不一致)、BUG-017(mode_changed 的另一入口)
  • 复发自:无
  • 修复版本:本地未提交候选

BUG-265 | 首页“今日星语”号称个人化,实际是 4 张写死卡片轮换

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-17
  • 最近更新:2026-08-17
  • 影响面:/ 首页在出生资料完整时展示的“今日星语”入口卡片。
  • 用户现象:卡片标题是“今日星语”、位置紧挨生时校正入口,读起来像是基于本人星盘的当日解读,但同一个人换个日期、不同人同一天,看到的往往是同几句话。
  • 触发条件:任何完成资料的账号打开首页。
  • 根因:/api/daily-starlanguage 从来没有接过模型。顺利时走 transitBackedCard,它确实调了 /api/chart/api/transit,但只把 total_triggers 填进一句固定模板,actioncaution 是常量;引擎 2.5 秒超时或资料不全就走 pickCard,用「日期+生日+省市」的字符码之和对 4 取模选一张写死卡片。page.tsx 还把同样的 4 张卡抄了一份作为客户端兜底,因此即便接口整个挂掉,界面也照样显示得像有内容。
  • 修复:改成 Agent 生成。新增 getDailyStarlanguageAgentfrontend/src/mastra/index.ts)与纯函数模块 frontend/src/lib/daily-starlanguage.ts(证据摘要、模型输出解析、缓存键)。路由先取 /api/chart,再并行取 /api/dashaVimshottari 与 Narayana 双轨)、/api/varga_fullD9/D10)、/api/transit,把结果压成一份紧凑证据交给 Agent,只接受完整的 {trend, action, caution} JSON。生成前要求已登录会话(避免匿名烧 token),按「账号+出生载荷+日期」在进程内缓存当天结果,并用 in-flight Map 合并并发请求。前端与路由里那 4 张写死卡片全部删除。
  • 验证:新增 frontend/tests/daily-starlanguage.test.ts 7 条:证据抽取覆盖 Vimshottari 当前 MD/AD、按今天选中的 Narayana 期、D9/D10、过境触发与功能吉凶星;引擎层缺失时必须显式列进 missingLayersstatus: blocked 的功能吉凶层不得进入证据;未确认的出生时间必须写成硬约束进提示词;模型输出缺字段或过短一律拒收;缓存键不得跨账号、跨资料、跨日期复用。另有源码合同断言鉴权、缓存键、双轨 Dasha 调用与「不留写死卡片」。tsceslint 清洁,next build 通过,非数据库套件 1630 条中仅 rectification-pr4-database 因本机 Docker 环境失败(与本次无关)。未做的验证:没有真实跑通一次线上生成——本机没有 Python 引擎与模型密钥,Agent 实际产出的措辞、耗时与失败率均未观测。
  • 待跟进:内存缓存随进程重启失效、且多实例不共享;若上线后发现重复生成成本明显,需要改成落库(要新迁移,staging 迁移是手动工作流)。另外 /api/daily-starlanguage 现在最长可能跑满 60 秒,首页首屏会看到“正在结合你的星盘写今天的星语”的等待态,是否需要预生成尚未决定。
  • 防复发:任何号称个人化的界面文案,其数据来源必须能追到当次计算或当次生成;用哈希在固定文案池里取模不是个人化,只是伪装成个人化。降级路径不得复制一份“看起来正常”的内容——生成失败时必须让用户看出没有生成成功,本次改成明确的一句话降级文案而不是通用建议。
  • 相关记录:BUG-265 无前序同类记录。与 BUG-263 同批,均为首页可见问题。
  • 修复版本:本地未提交候选

BUG-266 | staging 连续 5 个提交发不出去:质量门跑在跳板机上,把那台机器的磁盘占满,PostgreSQL fixture 全线起不来

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:.gitea/workflows/ 全部 12 个 job 的 runner 归属、staging 质量门的数据库集成测试、publishDeploy staging 的放行,以及跳板机上与本项目无关的 Nacos/MySQL/Redis/xxl-job 服务。
  • 用户现象:推到 staging 的提交一个都没上线。https://staging.jyotisha.chat/api/health 长时间停在 561010f2,而 origin/staging 已经前进了 4 个提交;Gitea 上 validate 连续 5 次 failure、publish 连续 5 次 skipped。
  • 触发条件:向 staging 推任何提交。与提交内容无关。
  • 根因:两层。其一,manman-linux runner 以 manman-linux:host 注册在跳板机上,job 直接跑在该机的宿主文件系统里,而质量门每次 push 都在本地 build 两个按 SHA 打标的镜像(api-<sha>web-<sha>)且全流程没有任何镜像与构建缓存回收,累积到 1453 个镜像、根分区 99G 用满 94G、Avail 归零,于是 docker compose up -d --wait postgres 阶段 initdb 直接 No space left on device,23 个数据库测试级联失败。这台机器同时还跑着与本项目无关的生产服务,等于用别人的磁盘做构建。其二,当初把门禁从 xiaoxin 搬到跳板机的理由(ERR-103「xiaoxin 缺少 Docker Compose v2」)是误诊:xiaoxin 的 Docker Engine 一直正常,真正的原因是 /root/.docker/cli-plugins/docker-compose 有一个 2026-08-05 建立的零字节文件,用户级插件目录优先级高于系统目录,把系统里正常的 compose 插件遮蔽成 exec format error。误诊导致门禁被搬到一台不该承担构建的机器上,磁盘耗尽只是时间问题。
  • 修复:删掉 xiaoxin 上那个零字节插件占位文件并补装 docker-compose-v22.40.3),确认 docker compose version --short--project-name 两项门禁能力检查通过;把 .gitea/workflows/ 里 8 个仍指向 manman-linux 的 job 全部改为 xiaoxin(该机 20 核、61G 内存、877G 空闲);新增 deploy/reclaim-runner-disk.sh,在 validatepublish 的 checkout 之后、拉取 Node 工具之前回收磁盘,并在空间仍不足 10 GiB 时带着原因 fail-closed,而不是让 23 个数据库测试去暴露磁盘问题。回收只针对没有任何容器持有的资源(container prune --filter until=6hname=^jyotisha-postgres-dangling=true 的卷、network prune --filter until=6h、悬空镜像、buildx 缓存,以及除当次 SHA 外的历史 api-/web- 标签),因此不会掀掉同一台 runner 上并发 job 的 fixture。
  • 验证:staging-backend-workflows.test.ts 38 项通过,其中新增两项——遍历 .gitea/workflows/*.yml 断言每个 runs-on 都是 xiaoxin(并禁止 manman-linux 重新出现),以及断言两个 job 都在 Node 工具之前调用回收脚本、脚本只回收未被持有的资源且低于阈值时 fail-closed;回收脚本纳入既有 shell 语法校验。tsc --noEmit 与该测试文件 ESLint 清洁。在 xiaoxin 上实测:compose 2.40.3 通过门禁的两项能力检查,且到 Gitea、ACR 镜像仓库、staging 主机 22 端口、npm/pypi/ECR/SWR 镜像源的出网全部可达。未做的验证:回收脚本没有在真实 runner 上跑过一次(当前 xiaoxin 有 877G 空闲,回收逻辑与阈值分支要等首次门禁运行才被真正执行);跳板机上那 94G 与仍在运行的 act_runner 尚未清理下线。
  • 防复发:构建与测试不得跑在承载其他服务的机器上,也不得以 :host 模式共用其文件系统。质量门在开跑前自己保证磁盘余量,并把「磁盘不够」报成一句明确的失败,而不是让下游 fixture 去替它失败。runner 能力缺失必须定位到具体原因再决定搬迁:docker compose 不可用时要先查 docker info 的 client plugins 与用户级 ~/.docker/cli-plugins 遮蔽,不能直接判定整台机器不支持 Compose——ERR-103 正是漏了这一步,代价是把门禁搬到错误的机器上并最终堵死发布。
  • 相关记录:ERR-103docs/research/pre_work_error_ledger.md,同一 Compose 现象的误诊,本次给出真实根因)、ERR-105(同一台跳板机磁盘耗尽的基础设施记录)、BUG-264(本次被卡住无法发布的修复)
  • 复发自:无
  • 修复版本:本地未提交候选

BUG-267 | 咨询证据门的三处判据都没接到权威来源:婚姻/财富的必需层从未被检查,领域边界靠猜关键词,精确应期无条件放行

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:scripts/jyotish_api_server.py_build_consumer_context(),即所有产品咨询(web /api/consult)与 MCP/研究路径共用的回答真相合同。直接影响 10 条路由中 7 条的证据门、6 个领域的解读边界,以及全部路由的精确应期许可。
  • 用户现象:手测 staging 时,一次婚姻方向的咨询回执报 status: "ready"missingLayers: []preciseTiming: "allowed",看上去证据齐备;但同一次回答里既没有 Upapada(UL)层的任何结论,也没有性别解读边界。回执的「齐备」与回答的内容互相矛盾,而回执本身不含任何可据以追问的线索。
  • 触发条件:任何走 marriagewealthhealtheducationmigrationfamilyannual 路由的咨询(即除 careertiminggeneral 外的全部路由);以及任何问题文本没落在七条关键词正则里的领域提问。
  • 根因:同一个函数里三处判据各自接错了来源。其一,route_requirements 的键写成 relationshipfinance,而 _ROUTE_DEFINITIONS 里的路由名是 marriagewealth.get(route, general) 于是静默把这两条路由降级成 general 的门(D1/D9/dasha_boundaries),婚姻的 UL 与财富的 D2 从未进入检查;另有 5 条路由(health/education/migration/family/annual)压根没有条目,同样落到 generalmissingLayers: [] 因此不表示证据齐备,只表示没检查过。其二,7 个领域上下文层与解读边界由 7 块 re.search 匹配问题文本决定,而领域此时已经由模型声明并写进了 route_packet——用文本再猜一遍既是重复,又只能覆盖关键词表里的写法:本次实测中一句明显是情感取向的问题因为写作「情感」而非表里的「感情」,在 marriage 路由上完全没拿到性别解读边界。其三,timing_layers_ready 判断 dasha_boundariesnarayana_dasha 是否就绪时读的是 missing_route_layers,而该列表只包含本路由要求的层,于是对任何不要求 narayana_dasha 的路由恒为真,can_answer_precise_timing 在该层根本没算出来时也照样放行。三处的共同形态是:判据没有落在权威来源(路由表、路由声明、证据 section)上,而是落在错键、文本猜测和一个恰好为空的列表上,因此全部失败方向都是「静默放宽」。
  • 修复:把三处判据都接回权威来源。_ROUTE_REQUIRED_LAYERS 提为模块级常量并对 _ROUTE_DEFINITIONS 的 10 条路由逐条显式列出,不再依赖 general 兜底;表里每个层名都限定为证据包真实构建的 section(因此 wealth 只要求 D2 而不要求引擎当前不产出的 D11,migration 要求确实产出的 D4),避免把状态整体推成 degraded。领域上下文层与边界改为 _ROUTE_DOMAIN_CONTEXT 按 route 查表,7 块正则整体删除。出生时间不确定边界改从矫正闸门的真实状态派生(effective_accuracy 不在精确档位,或 lagna_boundary.is_sensitive),不再等用户把「出生时间不准」说出来;档位缺失按不确定处理——不知道精度不等于已确认精度。timing_layers_ready 改为直接读 sectionsstatus,与路由要不要求该层无关。唯一保留文本探测的是 precise_timing_requested:「用户有没有要一个具体时间」是服务端没有权威来源的信号,因此改成 timing/annual 路由结构性携带、文本探测仅作叠加,一次措辞漏判不再能把信号清零。
  • 验证:tests/test_consultation_consumer_context.py 新增 8 条并全绿(该文件共 16 条通过)。8 条覆盖:遍历 _ROUTE_DEFINITIONS 断言每条路由都有自己的证据门条目(这是本该拦住本 bug 的守卫),且表里每个层名都能在真实证据包的 sections 里找到;marriage 缺 UL 必须报 degradedmissingLayers == ['UL']wealth 缺 D2 同理;性别边界在不含任何关键词的措辞下仍随路由挂上,且不串入其他领域的边界;出生时间边界在「问题完全没提出生时间但精度为 1hour」时出现、在「精度 minute 且 Lagna 不敏感」时不出现;精度档位缺失按不确定处理;minute 档位下 Lagna 敏感仍保留边界;marriage 路由在 narayana_dasha 缺失时 can_answer_precise_timing 必须为 false(此时 missingLayers 仍为 []、状态仍为 ready,正是旧代码放行的那个组合)。已逐条验证这 8 条在旧代码上会失败:旧表下 marriage/wealth 的门确为 ['D1','D9','dasha_boundaries'](UL、D2 均不在内),旧正则对该措辞返回 False,旧 timing_layers_readynarayana_dasha 缺失时仍为 True。未做的验证:没有在 staging 上真实跑一次婚姻类咨询复看回执,因此「回执与回答不再矛盾」只有单测证据,线上措辞与耗时未观测。
  • 待跟进:其一,birth_time_rectifier.get_effective_accuracy() 会返回 '5min'(声明 minute + 家人清楚记得),而 ACCURACY_MATRIX 没有 '5min' 这一行,get_enabled_vargas() 于是落到 unknown 档——比声明 15min 更差。本轮只让 '5min' 在边界判定上按不确定处理(方向正确),没有补这一行矩阵,分盘可用性仍被低估。其二,jyotish_api_server.py 约 7293 行处(穆胡尔塔领域选择)仍有一处同形态的关键词匹配,本轮未动。其三,projectEvidenceContract() 的投影范围仍不含 deterministic_claims_forbidden_for(见 BUG-256 待跟进),本轮未改投影层。
  • 防复发:查表取合同时不得用 .get(key, 默认值) 静默兜底——本次三处缺陷里有两处都是「取不到就用一个更宽松的值」,而更宽松的失败方向不会有任何人报错。凡是按路由/领域分派的表,必须有一条测试遍历权威路由集合断言逐条覆盖,并断言表里引用的层名在运行时真实存在;键名与权威定义分处两个文件时,这条测试是唯一能发现键名漂移的机制。已经由模型声明的语义(领域、路由)不得在服务端用文本正则重新推断一遍:重复推断不会更准,只会多出一处静默失效点。判断「某层是否就绪」必须直接读该层的状态,不得借道任何按条件裁剪过的列表——missing_route_layers 这类列表为空既可能是齐备也可能是没检查,两者不可区分。
  • 相关记录:BUG-259(同一函数上游的路由分歧,本条是「路由定了以后合同没跟上」)、BUG-256(同为回答契约投影/合并层的缺口,其待跟进项与本条同源)、BUG-268(本条的可观测性对照:回执里同样查不到模型有没有读方法)、BUG-270(修完本条核对同一张表时发现门仍比产品声明松三处)
  • 复发自:无
  • 修复版本:10ae149cstaging

BUG-268 | 模型有没有真的翻开方法文档,运行结束后无处可查:计数器数完就丢

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/api/consult 的公开回执 AgentExecutionReceipt 与服务端可观测事件;影响「web 输出为何不如本地 Agent」这一类问题的可诊断性。
  • 用户现象:用户对比本地 Agent 与 web 的输出质量,web 明显更弱。但两份成功回执里能看到的只有 skill.loaded: truesteps: [skill, tool]stepBudget.used: 2,无法回答「模型到底有没有读过方法文档」——而这正是两条链路最可能的差异所在。
  • 触发条件:任何一次咨询运行结束后试图回溯模型的方法使用情况。
  • 根因:Mastra 的 skill 工具返回 SKILL.md 的全文指令加上 references/scripts/assets 的文件名清单,真正打开某份参考文档要另调 skill_readcreateConsultationRuntimeHooks 确实在 afterToolCall 里数了 skillReferenceReadCount,但这个计数器既没进公开回执,也没进 agentObservabilityEventSchema,数完即丢。同时 skill_read 按设计不记成 runtime step(只有 skill/tool/validation 三类会记),所以 stepsstepBudget.used 天然看不见它;服务端日志里唯一能间接反映的是 modelStepCount。结果是:唯一能直接回答该问题的数字被算出来后丢弃,只留下一个需要推断的替代量。
  • 修复:把 referenceReads 加进回执的 skill 对象,并设为必填而非可选——正是「可选且没人填」让这个数字消失的,必填能让将来任何一处新的回执构造点无法再省掉它。同时把 skillReferenceReads 加进 consultationModelStepTelemetry(),它已被展开进可观测日志,因此服务端日志自动与 modelStepCount 并列拿到该值。两个构造点(工具可选路径与强制工具路径)都改为从 state.skillReferenceReadCount 读取。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 37 项通过,其中新增 1 项:走真实 hooks,断言加载 skill 后计数仍为 0(说明「已加载」不等于「读过方法」)、两次成功的参考读取记为 2、失败的读取不计数、参考读取不出现在 steps 里(因此 steps 无法替代该字段),最后断言回执解析后 skill.referenceReads === 2。既有断言同步收紧:consultationModelStepTelemetry() 的期望值现在包含 skillReferenceReadstsc --noEmit 清洁。未做的验证:没有在 staging 上取一次真实回执,因此线上那两次运行的 referenceReads 究竟是 0 还是别的值仍未观测——这正是本条要让它可观测的那个数。
  • 待跟进:线上真实值已取到2026-08-18 手测三次,10ae149c):两次单领域 career 均为 2,一次多领域 career+wealth 为 0。所以不是「从不读」,而是多领域路径下模型一份方法文档都没打开——恰是最需要方法的那条路径。单样本,尚不能断定必然。下一步应先复跑几次多领域确认是否稳定为 0;若稳定,方向是让方法在多领域计划下仍可达(该路径的工具结果体积大得多,可能挤掉了模型继续读文档的动机),而不是继续加服务端约束。
  • 防复发:被数出来的诊断量必须有一个出口(回执或可观测日志),否则等于没数。当某个字段的作用正是「证明某件事发生过或没发生过」时,它在 schema 里应当必填:可选字段缺失与「值为 0」在下游无法区分,而这里 0 恰恰是最需要被看见的答案。另外,不要用 steps / stepBudget.used 推断模型的全部动作——这两者只记录被显式登记的三类步骤,模型的其余工具调用在其中不可见。
  • 相关记录:BUG-267(同一批手测暴露的另一处静默缺口)、BUG-255(步数预算被浪费,当时也依赖 modelStepCount 这一间接量定位)、BUG-258(同为「失败/结束时回执信息不足」的形态)
  • 复发自:无
  • 修复版本:10ae149cstaging

BUG-269 | “今日星语”永久停在“正在结合你的星盘写今天的星语”:请求被一个反向的出生时间守卫拦住,从未发出

  • 状态:resolved(已发布 staging
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/ 首页“今日星语”卡片、首页 hero 的问候与标题、/api/daily-starlanguage 的失败归因与超时预算、Onboarding Agent 生成的欢迎语。
  • 后续修正:本记录「Agent 欢迎语落到 hero 说明行」的处置已被 BUG-272 推翻——该说明行经用户评审判定为累赘并删除,onboarding.greeting 因此重新回到无渲染点状态,处理见 BUG-272。
  • 用户现象:staging 上出生资料完整的账号打开首页,“今日星语”卡片一直显示“正在结合你的星盘写今天的星语。”,永远不出现 Agent 写的文案。同一次反馈里还问到:首页那三个问题是不是写死的,以及首页标题“今天想先理清什么?”为什么没换成登录后按时段问候的形式。
  • 触发条件:birth_time_statuscandidateacceptedconfirmed 且已有可用出生时间的任何账号打开首页。也就是说,越是资料完整的账号越必然命中。
  • 根因:三处独立问题,都在同一屏上。
    1. 每日星语 effect 的守卫是 if (!hydrated || !profileComplete || birthTimeDisplayState(profile)) return;birthTimeDisplayState 恰好在出生时间可用(candidate/accepted/confirmed)时返回非 null,因此条件的实际含义是「星盘可用时不要请求」,与卡片的渲染条件 personalChartAvailable 完全相反,请求从未发出,state 永远停在 pending。这个守卫在 BUG-265 之前就存在,但那时客户端还有 buildDailyStarlanguageCard 写死兜底把空状态遮住了;BUG-265 删掉兜底、保留守卫,于是暴露成永久等待态。
    2. /api/daily-starlanguage/api/chart 是唯一没有 .catch() 的引擎调用(其余四层都有),且 engineTimeoutMs 只有 8 秒、agentTimeoutMs 只有 30 秒。线上实测冷路径端到端 30.6 秒,紧贴 30 秒上限;首次调用在 9.5 秒就返回 agent_generation_failed,正是 8 秒引擎超时被当成模型失败上报。失败原因被压成同一个字符串,无法区分是引擎、模型目录还是模型输出。
    3. 首页同时存在三套问候实现:page.tsxgreetingForHourhero 第一行)、starter-prompt.ts 的 8 条静态随机池(hero 的 h1)、onboarding-client.tscreateStartGreeting(按时段+称呼,只用在 onboarding 气泡)。用户要求的按时段问候在第三套里,而 hero 标题读的是第二套,所以「改了却没生效」。同时 /api/onboarding 返回的 Agent 欢迎语在客户端被 greeting: createStartGreeting(presentationName) 覆盖,而 onboarding.greeting 在整个页面里没有任何渲染点,等于 Agent 每次都白写一句欢迎语。
  • 修复:守卫改成 !hydrated || !profileComplete || !personalChartAvailable,与卡片渲染个人内容的条件对齐,并在服务端报 unavailable 时延迟 5 秒重试一次后才落到失败态,卸载时清理定时器。路由给 /api/chart.catch(),把生成结果改成判别联合,失败原因区分 chart_unavailable / model_unavailable / agent_generation_failedengineTimeoutMs 提到 20 秒、agentTimeoutMs 提到 45 秒,仍在 maxDuration = 60 之内。问候收敛成一套:createStartGreeting 拆出 createStartGreetingParts,返回 {salutation, question}hero 第一行用 salutation、h1 用 question,选中变体在离开首页时重抽;删除 starter-prompt.tsgreetingForHour。客户端不再覆盖 Agent 欢迎语,onboarding.greeting 落到 hero 说明行并保留原静态文案作为兜底。
  • 验证:线上先证明后端是好的——带登录 Cookie 直接调 staging /api/daily-starlanguage,冷路径 30.6 秒返回真实 {trend, action, caution},第二次 1.6 秒命中 agent_cache/api/health 确认部署 SHA 就是 origin/staging 头部,jyotishApi 检查为 ok,因此排除部署落后与引擎不可用。新增 4 条回归:每日星语请求条件必须与 personalChartAvailable 一致且源码中不得再出现 birthTimeDisplayState(profile)、失败必须重试一次且清理定时器、/api/chart 必须带 catch 且三种失败原因各自可辨、两个超时预算有下限断言;hero 断言 salutation/question 拆分与 Agent 欢迎语落点,并禁止 starterPrompt/createStarterPrompt/greetingForHour 复活。前端非数据库套件 1711/1724 通过,13 个失败全部是本机 Docker/PostgreSQL fixture(与 BUG-265 同一类环境失败,与本次无关);tsc --noEmit 与改动文件 ESLint 清洁。未做的验证:没有在浏览器里看过修好后的首页——本机没有 Python 引擎与模型密钥,无法起完整栈,卡片从 pendingready 的实际观感、重试是否够用、以及 hero 换行后的排版都要等 staging 发布后确认。
  • 防复发:一个界面元素的「取数条件」必须和它的「渲染条件」写成同一个表达式,不能一边用 personalChartAvailable 渲染、一边用另一个语义相反的谓词决定是否请求。删除兜底文案时必须回头检查被兜底遮住的空状态路径是否本来就是坏的——BUG-265 删兜底是对的,但没有验证删掉之后真实账号能不能拿到内容,代价是上线即空转。同一个概念(这里是「登录后的问候」)不允许存在多套并行实现,否则改动必然落在没被渲染的那一套上。Agent 生成的字段如果没有渲染点,就不要生成,更不能在客户端覆盖后还继续消耗 token。
  • 相关记录:BUG-265(本次修复的直接前序:Agent 化改造正确但守卫未同步,且其「待跟进」已经预告了首屏等待态问题)、BUG-201(每日星语 effect 依赖完整 Profile 对象的既有决定,本次沿用其引用保持策略,未改依赖形状)、BUG-200(首页文案第一人称与真实性边界)、BUG-272(本次 hero 说明行处置的后续推翻)
  • 复发自:无
  • 修复版本:a12f5797staging

BUG-270 | 证据门比产品自己向用户预告的层更松:两份声明分处两端且没有任何机制保证一致

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:scripts/jyotish_api_server.py_ROUTE_REQUIRED_LAYERSfrontend/src/lib/consultation-domain-registry.tsrequiredLayers。涉及 marriagewealthgeneral 三条路由的证据门,其中 general 是最常走的兜底路由。
  • 用户现象:暂无可见现象。与 BUG-267 同一形态——门比声明松,失败方向是静默放宽,健康运行里看不出差别。
  • 触发条件:任何走 marriagewealthgeneral 路由的咨询,且该路由声明要用的某一层实际缺失。
  • 根因:修完 BUG-267 后核对时发现,前端领域注册表早就为每个领域声明了 requiredLayers,键名正确(marriage/wealth),并且这份列表会驱动界面上的 evidencePreview——也就是产品明确告诉用户「这次会用这些证据」。但服务端的门是另写的一份,两份用不同词汇描述同一个合同,彼此没有任何链接。对照下来门少查三处:marriage 声明了 A7Darapada)而门只要求 ULwealth 声明了 Ashtakavarga 而门只要求 D2general 声明了 D10 与 D2 而门只要求 D1/D9。三者实测在真实排盘中都是 used,所以补进去不会把状态推成 degraded。这正是 BUG-267 里键名漂移能发生两个月的同一片土壤:合同有两份,没有一份是权威。
  • 修复:_ROUTE_REQUIRED_LAYERS 补上 marriage 的 A7、wealth 的 ashtakavarga、general 的 D10 与 D2。新增一条测试解析前端注册表,断言其中每个指向真实 section 的条目都出现在服务端的门里。之所以只比对真实 section:注册表里同时有人看的标签(7th house/lordnegative holdout gate)和引擎压根不产出的 D11,机械全量对齐会把每条路由钉死在 degraded。该测试对解析失败采取 fail-closed——先断言 10 条路由全部解析到且列表非空,否则一次正则失配就会让它无声通过。另外把 tests/test_consultation_consumer_context.py 加进 run_quality_gate.pyCORE_PYTEST_TARGETS:核对时发现该文件不在任何 CI 档位里BUG-267 的 8 条与本条的 1 条此前都只在本地手动执行过,staging 门(--profile quick)从不运行它们。防漂移的钉子本身没人跑,等于没钉。
  • 验证:tests/test_consultation_consumer_context.py 17 条通过(新增 1 条)。已验证这条钉子是真守卫而非恰好通过:分别从 marriage 去掉 A7、wealth 去掉 ashtakavarga、general 去掉 D10,三次都报出对应路由与缺失层,恢复后通过。另用真实排盘跑了 general/marriage/wealth/career/timing/health/annual 七条路由,收紧后 core_status 仍为 readymissing_route_layers 仍为 [],确认没有把既有运行推成 degraded。未做的验证:没有在 staging 上真实跑一次复看回执,线上表现未观测。
  • 待跟进:D11 是另一个方向的口径不一致——前端向用户预告 wealth 会用 D11,而引擎从不构建这个 section,属于「承诺了不存在的证据」,与本条(门比承诺松)反向。本轮未处理。更彻底的方向是让两端读同一份声明而不是靠测试比对,但两套词汇(机器 section 名 vs 人看的标签)先得想清楚归一到哪一套。
  • 防复发:同一个合同不得有两份声明而没有一份权威。当暂时无法归一时,必须有一条测试把两份的交集钉住,并且该测试要 fail-closed——跨文件、跨语言的比对里,解析失配导致的「无声通过」比比对失败更危险。补门之前先用真实数据确认该层稳定产出:把引擎不产出的层写进必需集会把状态恒推成 degraded,这与放太松同样是错的,只是方向相反。写完回归测试后必须回头确认它在 CI 的哪个档位里真的会被执行:run_quality_gate.py 有多组 pytest 目标,staging 门只跑 CORE_PYTEST_TARGETS,写进 RUNTIME_TRUTH_PYTEST_TARGETS 或压根不进列表的文件在 staging push 上一次都不会跑。「本地全绿」不等于「门会拦住它」。
  • 相关记录:BUG-267(本条是修完它以后核对同一张表时发现的,同源同形态)、BUG-259(同为路由与合同之间的口径不一致)
  • 复发自:无
  • 修复版本:5caf47a4staging

BUG-271 | 工具调用在进入工具体之前被拒时,回执里查不到它失败过:步没记、调用没计、原因塌成兜底码

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/api/consult 的公开回执与服务端可观测日志。凡是模型给出的工具参数不被工具 inputSchema 接受的运行都受影响。
  • 用户现象:staging 手测 run 951a841e10ae149c)第一次工具调用发出 tool.failed / calculation_failed,重试成功并给出了完整回答。但 run.completed 的回执里 steps 只有 2 条(skill + 那次成功的 tool)、stepBudget.used: 2失败的那次完全不存在tool.failed 的 code 是兜底值,服务端日志同样只有兜底值。也就是说:客户端看见「失败过一次」,回执却说没有,而且两边都查不到为什么。
  • 触发条件:模型对 run-jyotish-consultation 传出不被 consultationToolInputSchema.strict(),只接受 questiondomains)接受的参数。
  • 根因:三处叠加。其一,schema 拒绝发生在 Mastra 调用 execute 之前,所以工具体内的一切都没跑——consultationToolCallCount 不增、appendConsultationRuntimeStep 不执行、连 chart-calculation 活动事件都没发出(这也正是本次能定位的证据:失败那次调用没有任何 activity,重试那次有)。工具自己无法记录一次它从未收到的调用。其二,safeToolError() 把一切非 AbortError/TimeoutError 的错误塌成 calculation_failed,公开事件因此不携带任何可区分信息。其三,即使失败落进了工具体的 catchconsultationWorkflowFailureCode() 对非 ConsultationWorkflowError 返回 undefined,而 append 处写的是 ...(failureCode ? { failureCode } : {})——于是「最需要解释的那条记录」恰好是唯一没有原因的记录。
  • 修复:在流层补记。流是唯一能观测到全部工具失败的位置(无论失败发生在 schema 这一侧还是 execute 那一侧),且它持有 startedAt 计时因而能给出时长。tool-error 分支现在比对「流已见的工具错误数」与「state 里已有的失败 tool 步数」,仅在前者更多时补一条 status: "failed"failureCode: "tool_call_rejected" 的步——工具仍然记录它能看见的失败(带它独有的时长与真因),流只填空档,不重复计。另新增 consultationToolFailureCode(),令每个错误都解析出一个码(工作流传输故障沿用原码、领域计划被拒为 invalid_domain_plan、其余为 unexpected_error),工具体 catch 改用它,failureCode 不再可能缺失。刻意未改failureCode 仍不进公开回执——publicConsultationRuntimeSteps() 的白名单与名为「the public receipt never carries the internal failure classification」的现存测试是刻意约束,workflow_rate_limited 这类后端内情不应上线到客户端。原因走可观测日志的 toolCalls[].failureCode(该通道本就是内部诊断专用,machineCodeSchema 允许新码)。客户端现在能看到「有一步失败了」,运维能在日志里看到为什么。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 40 项通过(新增 3 项)。新增:(1) 只喂 tool-call + tool-error 两个 chunk(模拟 execute 从未运行),断言 state 里出现带 tool_call_rejected 的失败 tool 步、stepBudget.used 变为 2、公开回执的 steps 状态序列为 ["completed","failed"],且序列化后不含该内部码;(2) 工具已记录过的失败不得被流重复记一遍,且原有的 workflow_queue_full 不被覆盖;(3) consultationToolFailureCode() 对工作流错误、计划错误、普通 Error 与非 Error 值逐个断言有码。已验证第 (1) 条在修复前失败(fail 1),修复后 40 全通过。非数据库前端套件 1675/1675 通过,tsc --noEmit 与改动文件 eslint 清洁。未做的验证:没有在 staging 上复现一次 schema 拒绝来复看回执——本次修复后线上是否真的出现带失败步的回执尚未观测,且触发它需要模型再犯一次同样的参数错误,无法主动构造。
  • 待跟进:模型当时到底传了什么参数仍然不知道——正因为没被记下来。修复后若再次出现,日志会给出 tool_call_rejected,但具体是哪个字段不合法仍不可见;若该情况反复出现,下一步是在服务端日志里补一个「被拒字段名」的封闭枚举(只记字段名,不记值,避免把模型输出当日志内容)。另外 consultationToolCallCount 在这条路径上仍然不增(工具体没跑),本轮刻意未动:改它会破掉「never starts a calculation」与「invalid model input does not poison a later valid contract retry」两条现存的刻意断言,而失败已经能从 steps 看见,计数本身不再是唯一线索。
  • 防复发:把「某件事发生过」的记录放在能观测到它的那一层。工具无法记录一次它从未收到的调用,因此这类记录必须由流(或同等的外层观察点)兜底;判断「哪一层能看见」的可靠证据是活动事件的有无,而不是错误码。失败记录的原因字段不得可选省略:...(code ? {code} : {}) 这种写法会让分类器覆盖不到的错误静默变成无原因记录,而那恰好是最需要原因的一类。公开与内部两个通道要分别满足:客户端需要知道「失败过」,运维需要知道「为什么」,把后者塞进前者会泄漏后端内情,把前者省掉会让回执与客户端亲眼所见互相矛盾。
  • 相关记录:BUG-268(同为「诊断量算出来就丢」,本条是「诊断量压根没被创建」)、BUG-258(同为失败时回执信息不足)、BUG-255(同为模型参数被拒白扔步数,当时的修法是把互斥字段从 schema 里删掉)、BUG-278(补正本条对触发路径的归因,并接手枚举化之后新的拒绝路径)
  • 复发自:无
  • 修复版本:b5bcbaedstaging
  • 生产验证(2026-08-18 补记):staging run a5f4409e2605f34a)出现连续两次 tool.failed,回执 steps 里如实带上两条 status: "failed"39ms / 56ms)、stepBudget.used: 4。这正是本条修复前会丢掉的那两条记录,「无法主动构造」的验证点由手测撞上。同时补正本条根因里的一处归因:实际观测到的触发路径不是 inputSchema 拒绝,而是 execute 已进入、但 canonicalDomainPlan() 在任何状态写入之前就抛(领域不在注册表里),因此工具体同样没能记录任何东西。「失败发生在工具记录任何东西之前」这个机制是对的,落到 inputSchema 这一层的具体归因是错的——真正的 inputSchema 拒绝路径见 BUG-278Mastra 对它 resolve 而不抛,压根不会走到 tool-error 分支。

BUG-272 | 首页 hero 第三行重复问候被删除,真实性边界随之迁移;onboarding 契约去掉无渲染点的欢迎语并扩展到全部十个主题

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/ 首页 hero 文案层级、主题区小标题承载的出生时间边界声明、Onboarding 起点加载文案、/api/onboarding 的 payload 契约与缓存版本、onboarding 生成超时预算、首页十张主题卡的文案来源。
  • 用户现象:BUG-269 把 Agent 欢迎语放进 hero 说明行后,用户评审首页时指出这一行「有点累赘」,要求删掉整段 starter-hero-note。同一次反馈里还指出「首页可不是三个问题啊,有好多问题」,并追问首页标题是否由 Agent 生成(答:不是,来自 onboarding-client.ts 的本地时段变体表,5 个时段 × 3 条问句)。
  • 触发条件:任何账号打开首页;hero 连续三行都在做同一件事(称呼、提问、再一次欢迎并再问一次「想从哪里开始」)。
  • 根因:两处独立问题。
    1. hero 的信息层级在 BUG-269 之后变成三行同义内容。starter-greeting 已经完成称呼、h1 已经完成提问,Agent 欢迎语在句式上又重复了这两件事,因此第三行没有新增信息。但这一行同时是「无可用出生分钟」账号的真实性边界声明所在(BUG-200 约束,starter-questions.test.ts 有断言钉住),直接整段删除会连边界声明一起删掉。
    2. 首页主题卡渲染的是 consultationDomainRegistry 的全部十个域,而 /api/onboardingsuggestionscareer/marriage/timing 的固定三元组。页面按主题 id 逐个 find,找不到就回落到 registry 的静态 prompt,因此其余七个域(wealth、health、education、migration、family、annual、general)恒定是写死文案,个性化只覆盖三分之一的入口,而界面上十张卡看起来完全同级,用户无法分辨哪些是为自己生成的。加载态文案「根据你的资料整理三个起点。」也与实际渲染的十张卡不符。
  • 修复:分两步。
    1. 界面收敛:删除 starter-hero-note 元素及其两处 CSS 规则,hero 收敛为称呼加提问两行。出生时间边界声明迁到主题区小标题,按 personalChartAvailable 分支——读者正要挑主题时才看到这句限制,位置比 hero 更贴近实际动作。加载态文案改为「根据你的资料整理今天的起点。」,不再声明具体条数。
    2. 契约收敛(用户评审后决定,同一分支内完成):onboarding.greeting 从 Agent 契约里彻底移除——schema、fallback、prompt、客户端响应校验与 OnboardingContent 类型全部不再有这个字段,不再为无渲染点的内容付费。suggestionscareer/marriage/timing 的固定三元组改成按 consultationDomainIds 顺序覆盖全部十个域,校验用 refine 钉住「长度与顺序都必须与 registry 一致」,于是首页十张卡全部是 Agent 写的,静态 prompt 退回纯兜底角色。fallback 直接由 generalGuidedJyotishTopics 派生,避免手写十条又与 registry 漂移。生成十条比三条显著更慢,预算随之上调:路由 maxDuration 30→60 秒、服务端生成超时 18→45 秒、客户端单次请求超时 25→50 秒。缓存版本 ayanam-onboarding-v4v5,让所有存量 v4 payload 重新生成一次。
  • 验证:onboarding-presentation.test.ts 的 hero 断言改为同时检查 page.tsxglobals.css 中不再出现 starter-hero-note,避免只删元素留下死样式;starter-questions.test.ts 的边界声明断言从「文件里存在这句话」收紧为「这句话出现在主题区小标题且受 personalChartAvailable 分支控制」,防止下一次挪动文案时悄悄丢掉。契约部分新增 5 条回归:只覆盖三个域的旧形态响应必须整体拒绝并落到覆盖全域的兜底、主题顺序被交换必须拒绝(否则问题会挂到错误的主题标签下)、fallback 自身必须能通过 payload schemaregistry 里的 prompt 一旦不再第一人称就会被这条抓住)、prompt 与 payload/client 源码中不得再出现 greeting 字段、路由必须把 registry 主题列表发给 Agent。客户端请求超时断言从 25 秒下限改到 45 秒下限,与服务端生成预算对齐。全量套件 1720/1729 通过,9 个失败全部是本机 Docker/PostgreSQL fixture(与 BUG-265、BUG-269 同一类环境失败);tsc --noEmit 清洁,ESLint 仅存量 4 条 warningnext build 成功。未做的验证:没有在浏览器里看过改动后的首页,也没有真实调用过新契约的生成——本机没有模型密钥,十条问题的实际生成耗时是否落在 45 秒预算内、十张卡的文案是否彼此重复、以及 hero 删掉第三行后的留白与边界声明在窄屏的折行,都要等 staging 发布后确认。若线上出现大量 fallback,第一嫌疑就是 45 秒预算仍然不够。
  • 防复发:删除一个 UI 元素前必须先确认它有没有在承载与自身样式无关的合规或真实性文案——starter-hero-note 表面是装饰性说明行,实际是 BUG-200 边界声明的唯一落点。删元素时同步删样式,并用测试同时钉住两个文件,否则死 CSS 会在下一次改版时被误当作现有设计复用。声明数量的文案(「三个起点」)不要写死在与数据源无关的地方,数量必须跟着 registry 走。Agent 契约里的字段数量变化必须同步三件事:缓存版本、生成超时预算、客户端请求超时;只改 schema 不改预算的结果是全量用户静默落到 fallback,而 fallback 看起来是「正常内容」,不会报错。契约收紧时要用 refine 校验「集合与顺序」而不是只校验单项,否则模型少写几项或错位映射都能通过。
  • 相关记录:BUG-269(本次推翻其 hero 说明行处置)、BUG-200(首页真实性边界声明的原始约束,本次迁移未削弱)、BUG-273(同一轮评审的下一项删除)
  • 复发自:无
  • 修复版本:本地未提交候选

BUG-273 | 回答后输入框上方的三条推荐问题实际使用率极低且从未由 Agent 生成,整条链路删除

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:会话内 composer-suggestions 追问按钮、/api/consult 写入的 assistant 消息字段、consultation-reply-metadataagent-reply 的解析契约、会话写入 schema、composer-wrap 相关样式与 DESIGN.md 的对应条目。
  • 用户现象:用户实际测试后反馈「Agent 回答后用户输入框上方的三个推荐问题」使用频率很少,要求删除,并要求 Agent 也不再生成这三个问题。
  • 触发条件:任何咨询会话收到 assistant 回复之后,输入框上方固定出现三条按钮。
  • 根因:这里有一个与用户预期不同的事实——这三条问题从来不是 Agent 生成的createConsultationReplyMetadata 按会话主题从写死的十主题三元组里取一组,服务端总是把它塞进 parseAgentReply 的 metadata 参数,而 metadata 分支优先级高于模型输出(metadata?.suggestions ?? …),因此模型即便真的输出了 AYANAM_SUGGESTIONS 也会被覆盖;Prompt 本身还明确禁止模型产出隐藏元数据块。同一份三元组表被完整复制在 agent-reply.tsconsultation-reply-metadata.ts 两处。也就是说,界面上看起来「个性化」的追问入口,实际是按主题查表的十组固定文案,与用户的具体问题、星盘证据都无关——这正是使用率低的合理解释。附带发现:chooseConversationSuggestion 里针对「先完成生时校正」这一条的生时校正跳转分支在当前链路下不可达,因为服务端 metadata 恒定覆盖,三元组里从不含这条文案。
  • 修复:删除渲染点(composer-suggestions 块)、派生状态 activeSuggestions、点击处理 chooseConversationSuggestion 与常量 rectifyBeforeConsultationSuggestion、客户端 readSuggestions 与三处 suggestions 写入(send、本地预览、预览会话种子)。服务端 consultationReplyMetadataSchema 收缩为只有 title.strict()createConsultationReplyMetadata 不再需要 theme 入参,两份 fallbackSuggestions 表全部删除。parseAgentReplyparseAgentReplyBody 在失去 suggestions 后返回形状完全相同,合并为单一 parseAgentReply(value, metadata?),生时校正改调这一个入口。ChatMessage 去掉 suggestions 字段;但会话写入 schema 的 suggestions 有意保留为可选——该 schema 是 .strict() 的,发布瞬间仍在运行旧 bundle 的客户端会继续带上这个字段,拒绝整个写入等于丢掉用户那条消息,代价远大于留一个被忽略的字段。stripAgentReplyMetadata 同样保留剥离 AYANAM_SUGGESTIONS 注释的能力,因为删除前写入的历史回答里仍带着这个块,不剥离会把原始 HTML 注释显示给用户。CSS 删除 8 处 .composer-suggestions 规则(含混合选择器中的片段与两个媒体查询内的规则),Prompt 里「follow-up suggestions 由服务端生成」改为只提标题,DESIGN.md 对应条目改写为「回答不提供追问建议」并记录原因。
  • 验证:新增/改写回归共 6 条:会话内不得再出现 composer-suggestionsactiveSuggestionschooseConversationSuggestion 且 CSS 同步无残留、metadata schema 必须拒绝 suggestions 字段(防止 chips 从元数据侧回流)、两个库文件与 consult 路由中不得再出现主题三元组或 suggestions 写入、历史遗留的 AYANAM_SUGGESTIONS 块必须被剥离且不得复活成字段、输入框与正文之间不得再有会改变高度的兄弟节点(原 chip 行会在流式过程中撑高 composer)、生时校正入口在失去 chip 跳转后仍可从首页卡片进入。全量 1716/1721 通过,5 个失败全部是本机 Docker/PostgreSQL migration fixturetsc --noEmit 清洁,ESLint 仅存量 4 条 warningnext build 成功。未做的验证:没有在浏览器里确认删掉 chip 行后会话底部的留白与滚动锚点表现,尤其是 BUG-263 里「跳到最新」按钮的定位曾依赖 chip 行带来的高度变化,需要发布后目视确认按钮位置仍然合理。
  • 防复发:不要把查表得到的固定文案摆在会让用户以为是个性化生成的位置——这类「假个性化」入口既消耗界面空间又必然低使用率,而且因为看起来正常,不会有人报 bug。同一份兜底数据不允许在两个模块各存一份副本,否则删除时必然漏掉一处。删除一个可选字段时要区分「输出契约」与「输入契约」:输出侧应当立刻停止产出,输入侧在旧客户端与历史数据仍可能带上它时必须继续宽容接收,否则发布窗口内会丢用户数据。当两个函数因为字段删除而返回形状相同时应当合并,但要意识到其中一个可能是某个历史 Bug(此处 BUG-179)刻意建立的隔离措施,合并前必须确认该措施防的问题已经在结构上不可能发生。
  • 相关记录:BUG-179(曾因通用解析器的三条建议兜底污染生时校正,本次删除使其隔离措施不再必要)、BUG-263(「跳到最新」按钮定位曾受 chip 行高度影响)、BUG-249(草稿隔离验证里包含推荐问题填入路径)、BUG-272(同一轮评审的上一项删除)
  • 复发自:无
  • 修复版本:本地未提交候选

BUG-274 | 个人报告中心与报告正文在移动端无法下拉

  • 状态:resolved(已推入 staging,待部署)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/reports 个人报告中心、/reports/[reportId] 报告阅读容器;聊天根滚动锁不放宽。
  • 用户现象:移动端打开个人报告页后无法向下滑动阅读。
  • 触发条件:全局 html, body { height: 100%; overflow: hidden } 仍然锁死根滚动。报告中心只有 min-height: 100dvhoverflow-x: hidden,没有被视口卡住的纵向滚动容器。报告正文虽然有独立滚动层,但高度写成 100dvh:在移动浏览器里它可能比 body100% 更高,多出来的部分被根滚动锁裁掉,容器自己却还没溢出,所以也滑不动。
  • 根因:BUG-153 只给 ready 报告加了 100dvh + overflow-y: auto,没有覆盖报告中心,也没有按「父级已经是 height: 100%」来锁高度。overflow-x: hidden 不会让一个随内容长高的块变成可滚动层。
  • 修复:.report-center-shell.personal-report-reader 都改为 height: 100%; overflow-y: auto,并补上 -webkit-overflow-scrolling: touch。报告正文另加 touch-action: pan-y,避免命盘 SVG 把纵向滑动吃掉。
  • 验证:报告入口与阅读合同测试锁定两处容器都是 height: 100% + overflow-y: auto,且不得再写 min-height: 100dvh / height: 100dvh
  • 防复发:任何跑在聊天根滚动锁里的独立页面,必须有「相对父级视口封顶的高度 + overflow-y: auto」。禁止只用 min-height: 100dvh 冒充滚动容器;也不要在 html, body 已是 height: 100% 时把子层写成未封顶的 100dvh
  • 相关记录:BUG-153
  • 复发自:BUG-153
  • 修复版本:已推入 staging,待部署

BUG-275 | 后台已发布套餐无法修改,也没有删除/下架入口

  • 状态:resolved(已推入 staging,待部署)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:POST /api/admin/productsadmin_save_product_draft、商品管理页;已有订单/订阅的硬删除限制不放宽。
  • 用户现象:编辑已发布套餐保存时返回「提交内容不符合业务约束」;列表里没有删除或下架。
  • 触发条件:对已发布种子套餐(如 standard_monthly)调用 action=save 并带上该商品 id;或在后台寻找删除按钮。
  • 根因:保存函数只更新 status='draft' 的行,已发布 id 会抛 product_draft_not_foundPostgreSQL 22023),被统一映射成「提交内容不符合业务约束」。即便命中草稿,审计 after_value 仍写入禁止字段 code,触发 admin_audit_logs_after_value_check23514),同样被折叠成这句话。界面把所有行都标成「编辑草稿」,也没有 delete/retire API。已发布商品因订单/订阅外键不能硬删除,正确动作是下架。
  • 修复:保存已发布/已下架商品时,复用该 code 的现有草稿或创建下一版本草稿,不再改正在售行。新增 admin_delete_product:草稿硬删除,已发布改为 retiredenabled=false。商品审计字段改为 productCode,避免踩兑换码审计的密钥字段禁令。管理页区分修改/发布/删除/下架,并把上述 SQL 异常映射成可读错误。
  • 验证:数据库回归用与线上失败请求同形的权益 JSON 保存已发布月卡,必须得到新草稿且原在售行不变;再次保存更新同一草稿;删除草稿后行消失;删除已发布年卡后变为下架。API/UI 合同覆盖 action=delete、下架文案与错误映射。
  • 防复发:后台商品写路径必须区分草稿与已发布。已发布商品的保存不得要求调用方先手建草稿;删除已发布商品只能下架,不能绕过订单/订阅外键做硬删除。22023/23514 的商品异常不得再折叠成笼统「业务约束」。写入 audit.admin_audit_logs 的 JSON 不得包含 code/token/secret/key 键。
  • 相关记录:无
  • 复发自:无
  • 修复版本:已推入 staging,待部署

BUG-276 | 发送后刷新草稿被正确清空,但咨询请求从未落地,Agent 无反应

  • 状态:resolved(已推入 staging,待部署)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:/ 主对话发送、POST /api/consultGET /api/consult/status、tab 内 jyotisha.pending-consultation 恢复。
  • 用户现象:正常发出一条咨询后立刻刷新。输入框草稿已清空,不会把刚发出的问题填回来;对话里也看不到后台任务,Agent 没有后续反应。GET /api/consult/status 对这次 requestId 返回「咨询请求不存在」。
  • 触发条件:点击发送后、服务端 reserve_consultation_usage 写入之前刷新页面。包括 2.5 秒撤回窗口内、问题持久化过程中,以及咨询 POST 已发出但还在选路/扣点、尚未插入 consultation_requests 时。
  • 根因:发送在点击当下就清草稿、写入 pending requestId 并展示用户气泡,但真正的咨询占位发生在撤回窗口和 persistSession 之后的 POST /api/consult 里。刷新会中断这次 fetch。服务端只有在流开始之后才会 continueAfterDisconnect;占位之前断开则行不存在。刷新后恢复链路只轮询 status:把首次 404 当成「可能还在启动」假装 reserved,连打三次 404 后放弃,并且从未用同一 requestId 重放 POST。pending 存储也只留了 session/request id,撤回窗口内刷新时问题正文既不在会话里也不在草稿里。
  • 修复:pending 存储同步写入问题原文、主题和入口;刷新恢复时用这份原文补回用户气泡。status 首次 404 仍等待一次确认,第二次 404 则跳过撤回窗口、不重复追加用户消息,用原 requestId 重放 POST /api/consult。若重放撞上 request_conflict(原请求稍后占位成功),改回 status 轮询而不是当成失败解锁。三次以上确认仍 404 才停止恢复。草稿仍在发送时清空,避免已发出的问题被填回输入框。
  • 验证:frontend/tests/consultation-recovery.test.ts 覆盖 pending 带问题正文、404 后重放而非放弃、request_conflict 留在恢复态;既有草稿隔离合同继续要求发送清空存储键。
  • 防复发:刷新恢复不得把「status 404」直接解释成用户已取消或请求已结束;在占位完成前断开必须能用同一 requestId 重放生成。pending 存储必须包含重放所需的问题原文。已发出的问题不得靠草稿复活,必须出现在会话里并由 Agent 继续。
  • 相关记录:BUG-249(草稿持久化清空已发送问题,本条保留该行为)、咨询流恢复迁移 20260808030000_consultation_stream_recovery.sql(断线后续跑只覆盖已占位的 reserved 请求)
  • 复发自:无
  • 修复版本:已推入 staging,待部署

BUG-277 | 模型写给自己的过程叙述被当成回答交付给用户:契约未绿的文本没有丢弃,而是攒起来晚点一次性放出

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/lib/stream-agent-response.tsoutputText。所有 /api/consult 运行都经过它,但只有「契约变绿之前模型输出过文本」的运行会显形。
  • 用户现象:staging 手测 run a5f4409e2605f34a)最终回答里没有任何占星结论,整段是模型在向用户解释自己的工具调用出错以及打算怎么改参数(大意:域名单有误、我改用另一组域、两次都不被接受、我改为不指定域),然后就结束了。用户问的问题(「大概哪一年」)没有得到任何回答。三句自述装在同一个 answer.delta 里——这就是一次性 flush 的指纹。回执显示计算其实成功了:4 步、两次失败 tool 之后一次成功 tool。
  • 触发条件:模型在契约未绿(skill 未加载完或工具尚无一次成功)之前输出过文本,且此后契约变绿。失败重试的运行几乎必然满足。
  • 根因:守卫写成了「先累加再判断」。held += text 无条件执行,紧随其后的 if (!held || !contractReady(options)) return 只是「暂不发送」。设计意图(原测试名写着 holds answer text until the contract completes)是「计算未验证前不要把文本流给用户」,但实现选的是攒着晚点发而不是丢掉。契约一变绿,下一次 outputText 就把攒下的内容整段 flushemitted 随之置真,ensureFinalResponseText() 的空回答兜底因此也不会触发,用户既拿不到回答也拿不到「本次没能生成回答」的提示。要命的地方在于这段文本的内容与运行是否顺利强相关:顺利的运行里它是无害的开场语,失败重试的运行里它正好是模型在复述后端错误——而失败重试恰恰是它唯一会变长的场合。换句话说,这个缓冲在最需要它闭嘴的时候话最多。
  • 修复:契约未绿时直接丢弃,不进 heldattemptOutput 仍然无条件累加,所以「模型说过话但始终没给出回答」(runtime_contract_incomplete)与「模型全程沉默」(empty_answer)的诊断区分不受影响。随之 first.held 恒为空串,已连同 consumeAttempt() 的该返回字段一起删除:留一个恒假的判断在最终结算处,会让后来人以为 held 仍然跨越契约边界携带意义。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 43 项通过。原有的 holds-answer-text 断言按新意图改写并改名(契约前文本不得以任何形式到达客户端、契约后文本原样送出,并断言序列化后的事件流里搜不到那段自述);新增一条按 run a5f4409e 形状构造的回归:模型只有自述、契约变绿后再无文本,断言用户拿到的是空回答兜底文案而不是那段自述。已确认改写后的断言在修复前失败。tsc --noEmit 与改动文件 eslint 清洁;与 consult/流相关的 8 个套件 65 项全通过(tests/rectification-v9-database.test.ts 在本机因缺少数据库预先就失败,与本改动无关,已用 stash 对照确认)。
  • 待跟进:丢弃是一刀切的,合法的开场寒暄也会被丢。目前判断可接受(寒暄没有信息量,回答本体一定在工具结果之后产生),但如果将来要有意展示模型的推理过程,必须走独立事件类型而不是放宽这里——本条正是「把模型的中间输出混进 answer 通道」的后果。未做的验证:没有在 staging 上复现一次失败重试来复看回答。
  • 防复发:不要用「暂存」实现「不该展示」。缓冲意味着数据仍在管道里,只是等一个条件;一旦那个条件与内容的性质无关(这里是「计算是否成功」与「这段文本是不是回答」无关),它迟早会在错误的时刻把错误的内容放出来。真正的判据是内容属于哪个通道,而不是时机到没到。同一个通道不得同时承载「给用户的回答」和「模型的过程自述」:一旦混流,任何「先攒着」的实现都会在失败路径上把两者对调。
  • 相关记录:BUG-278(同一次 staging 运行暴露,且是本条的上游:域词汇不对导致失败重试,失败重试才让自述变长)、BUG-214(同为契约门与可见输出之间的耦合)
  • 复发自:无
  • 修复版本:待提交

BUG-278 | 领域词汇在模型能看到的地方从未被声明:schema 收任意字符串,skill 又教了一套不存在的名字,模型照 skill 传参白扔工具调用

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/mastra/consultation-tools.tsconsultationToolInputSchema.domainsfrontend/src/lib/consultation-domain-registry.ts、Agent 指令,以及 skill 侧的 SKILL.md 第 223 行与 references/strict-workflow-router.md
  • 用户现象:staging 手测 run a5f4409e2605f34a)连续两次 tool.failed,模型在回答里自述 "event-timing-strict" 不在服务端支持列表。同一批另一次成功运行的回答开头,模型自称「按 wealth-timing-strict + career 两域来算」——把 skill 的检查单标签当成服务端域名在向用户复述。两次失败各白吃一步预算,并间接造成 BUG-277(自述变长后被当成回答交付)。
  • 触发条件:模型需要自己挑领域,且它遵循 skill 的方法文档来命名。
  • 根因:合同有两份,没有一份权威——与 BUG-267、BUG-270 同一片土壤,只是这次两份分别是「不说话的」和「说错话的」。其一,domains 声明为 z.string().trim().min(1).max(64),于是模型收到的 JSON schema 里 items 只是一个 type: "string"完全不陈述合法词汇;真正的白名单藏在 execute 背后的 validateConsultationDomainPlan() 里,只有调用失败之后模型才能知道它的存在。其二,模型手上的 SKILL.md 第 223 行明确要求「先判断问题类型,再自动选择 career-timing-strict / relationship-timing-strict / wealth-timing-strict / event-timing-strict / event-verification-strict」,被它引用的 references/strict-workflow-router.md 里这套名字出现 10 次,而 skill.referenceReads 证明模型确实在读这些文件。这些名字在方法学里是技法检查单的标签,不是任何工具参数,但没有任何一处告诉模型这个区别。模型于是老实照方法文档选名,schema 照收不误,一路走到注册表查表才被拒。
  • 修复:把 domains 的元素改为枚举。枚举取「规范 id + 全部既有别名」共 37 个值(新增 consultationDomainPlanValues / consultationDomainPlanValueSchema),而不是只取 10 个规范 id:模型可以传别名、由服务端归一,这是 Agent 指令里的明文承诺,也有既有测试钉住(["career","finance","home"] → career/wealth/migration),枚举化不应顺手收紧它。已实测该枚举确实进入模型收到的 JSON schemaitems.enum 37 项),否则整个改动等于没做。同时在 Agent 指令里点明检查单标签不是域 id:指令层不受 skill 包哈希约束,且按本项目设计其优先级高于 skill 内容。刻意未改 SKILL.md:根 SKILL.mdskills/jyotish-vedic-astrology/versions/6.9.14/SKILL.md 之间有逐字节相等校验(frontend/src/mastra/index.ts),包目录整树的 sha256 钉在 skills/skill-package-registry.json,改内容的正确做法是升版本;但 6.9.14 同时是 pyproject.tomlCHANGELOG.mdREADME.md 与 SKILL.md 自身版本横幅的发布身份,为一行澄清升版本会造出「skill 6.9.15 / Python 包 6.9.14」这类新的不一致,而就地改钉住的版本会让同一个版本号在历史上指两份内容。枚举本身已经是权威约束,文档澄清留待下一次真实的 skill 发布搭车。
  • 附带发现(本轮实测,此前无记录):Mastra 的 createTool 校验 inputSchema,但校验失败时不抛异常,而是正常 resolve 一个 { error: true, message, validationErrors } 信封(信封的 message 里会列出枚举值,这也是模型自我纠正的唯一线索)。这直接改变了枚举化的风险面:被拒调用从「进入 execute 后抛错」变成「resolve 一个值」,流层原本会照常把它当 tool-result 发出 tool.completed——把一次从未执行的计算报告成完成,比枚举化之前更误导。因此在 tool-result 分支按信封的结构error === true 且带 validationErrors 对象)识别拒绝,而不是匹配上游的英文文案,改发 tool.failed 并沿用 BUG-271 的补记逻辑记一条 tool_call_rejected 步。同时更正 BUG-271 对触发路径的归因,详见该条的生产验证补记。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 43 项通过(本条新增 1 项、改写 3 项)。新增「schema 陈述它接受的词汇」:断言 event-timing-strict / wealth-timing-strict 被拒、规范 id 与别名(finance感情)被收,并断言枚举值可从 schema 结构上读出(shape.domains.unwrap().element.options)——这一条专门防「换成 z.preprocess 之类的包装后,校验仍然通过但 JSON schema 里的枚举没了」这种静默失效。改写:原先钉住「注册表抛错」的两条断言改为钉住信封(机制变了,意图不变:不得启动计算、不得污染请求级缓存),并额外断言信封 message 里列出了合法 id。另新增「被 schema 拒绝的调用不得报告为 completed」,断言事件流里没有 tool.completed、有 tool.failed、state 里落下 tool_call_rejected 步,且内部码不泄漏到客户端。tsc --noEmit、改动文件 eslinttests/test_consultation_consumer_context.py 17 项均清洁。未做的验证:没有在 staging 上观测模型见到枚举后是否不再传检查单标签。
  • 待跟进:SKILL.md 第 223 行与 references/strict-workflow-router.md 里的 10 处仍在教这套词汇,须随下一次 skill 版本发布一并澄清。另外 37 项枚举里含中文别名,若日后确认模型只用规范 id,可考虑收窄到 10 项以减少 schema 噪声——但那是收紧合同,需要单独一轮并同步 Agent 指令里的别名承诺。
  • 防复发:模型必须遵守的词汇,要写在模型读得到的地方,也就是 schema,而不是只写在校验它的代码里。z.string() 这类「结构上合法、语义上无约束」的字段等于把合同留在服务端自言自语:模型只能靠试错发现规则,而每次试错都是一次真实的调用预算。给模型的工具 schema 里出现自由字符串时,先问一句「合法值是有限集吗」——如果是,就必须枚举出来。枚举化之后要实测生成的 JSON schema,不能假设 zod 的包装层会把枚举透传。还要顺带检查「被拒」这条路径在框架里是抛还是返回:抛与返回会落到流层完全不同的分支上,误判会把失败报告成成功。
  • 相关记录:BUG-271(本条更正了它对触发路径的归因,并接手枚举化之后新出现的拒绝路径)、BUG-277(本条是它的上游)、BUG-267 与 BUG-270(同为「同一合同两份声明、没有一份权威」)、BUG-255(同为模型参数被 schema 拒白扔步数)
  • 复发自:无
  • 修复版本:待提交

BUG-279 | 模型手上从来没有副运边界:证据包读的是引擎从未写过的键,而精确应期的许可由另一个同名对象签发

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/mastra/consultation-workflow.tslocal_layersprojectTimingEvidencescripts/jyotish_api_server.py_attach_local_consultation_layers_build_consumer_contextscripts/unified_consultation_orchestrator.py 的证据分节表。所有 /api/consult 个人盘运行。
  • 用户现象:staging 手测中两次成功运行(「未来一年」「财运」)的回答都出现「副运细节与行运触发未在本轮完整计算」,只能给到大运级别的方向;而同一次运行的回执写着 preciseTiming: "allowed"。用户直接问了这两句为什么对不上,以及副运为什么没算。
  • 触发条件:任何依赖应期的回答。恒定发生。
  • 根因:一个名字指着两个对象。模型侧 local_layers.dasha_boundarieschart.modules.dasha_boundaries,而这个键在整个仓库里从未被赋值过——_attach_local_consultation_layers 只挂 varga_full / arudha_padas / narayana_dasha / ashtakavarga / kp_cusps——所以该字段恒为 undefined,投影时整段消失,模型手上只剩 chart.dasha.periods,也就是 6 到 20 年一段的大运列表。证据门里同名的分节 dasha_boundaries 读的却是 modules.dasha(大运),它当然是 used,于是 timing_layers_ready 成立、can_answer_precise_timing 为真。回执因此签发了「能给到月份」的许可,而模型连一条副运边界都没有,只能在回答里如实说副运没算全。两侧各自自洽:读的一侧永远拿不到数据,判的一侧永远看得见数据,中间没有任何断言比较过这两个名字是不是同一个对象。BUG-268 让 skillReferenceReads 可见时曾怀疑是模型没翻方法文档,本条说明缺的是数据本身。
  • 修复:三处。其一,服务端真的算出副运并挂到 modules.dasha_sub_periods:从 chart.dasha.periods 里取出覆盖参考日的那一段大运,用 dasha_analyzer.build_antardasha 按比例细分为 9 段。刻意不复用现成的 _compute_vimshottari_analysis_layer(它从月亮黄经另建一条时间线):两条线出自同一引擎、数值几乎一样,但「几乎」意味着模型手上会出现两套大运日期且无从判断该引用哪套;从包里已经展示给模型的 periods 上切,副运边界必然落在模型看得见的大运边界之内。其二,证据包新增独立分节 dasha_sub_periods(源路径写明 modules.dasha_sub_periods),精确应期改为同时要求 dasha_boundaries + dasha_sub_periods + narayana_dasha——大运列表只能定位十年,本就不该单独签发月份级许可。其三,模型侧字段随之改名为 local_layers.dasha_sub_periods,与服务端实际写入的键对齐;Agent 指令补一句:该层在场就用它的边界、不得再说副运没算,缺席则一句话说明。
  • 验证:真实排盘端到端跑通(1990-05-12 北京,参考日 2026-08-18):current.mahadashachart.dasha.periods 里的 Sun 段逐字段相同(2024-07-03→2030-07-03),当前副运为 Jupiter 2026-07-21→2027-05-099 段副运首尾正好贴合大运首尾,local_consultation_layers.diagnostics 为空,分节 usedcan_answer_precise_timing 为真。tests/test_consultation_consumer_context.py 21 项通过(新增 4 项:副运边界必须切自包里展示的 periods 且覆盖参考日;跨语言键名守卫;只缺副运时精确应期必须被拒;副运在场时放行)。测试基准盘原先只写了 {'current_md': 'Sun'},已改为用 compute_vimshottari_timeline 派生真实 periods,否则新层在 fixture 上根本不会被执行。前端除数据库套件(本机无 Postgres)外 1633 项通过,tsc --noEmiteslint 清洁。
  • 待跟进:Pratyantardasha(第三层)仍未计算,所以「哪一周」仍只能答到副运加 90 天慢行星触发窗,不能声称精确到日。_compute_vimshottari_analysis_layer 那条从月亮黄经另建的时间线仍留在 dasha 接口路径上,未与本层合并。行运触发窗已由 BUG-302 接入咨询包。未做的验证:没有在 staging 上复看真实回答是否不再出现「副运没算全」。
  • 防复发:跨语言的字段名不能靠人眼对齐。一端读 modules.X 而另一端从不写 X,两侧测试都不会失败——读的一侧只看到 undefined 被投影掉,写的一侧根本不知道有人在读。已加的守卫直接比较两端:把 local_layers 里所有 modules.<key> 读法抽出来(先剥注释,否则解释性注释里的旧键名会被算成读法),与服务端真实挂载后的 chart['modules'] 键集合求差,非空即失败。另一条教训是分节名要说清自己是什么:dasha_boundaries 既能读成「大运边界」也能读成「所有周期边界」,正是这层歧义让门以为自己检查过副运。
  • 相关记录:BUG-267(同一段代码里另一处「判据没接到权威来源」,精确应期的空判是那轮修的)、BUG-270(同为两端声明不一致且缺跨端断言)、BUG-268(同一批可观测性问题,本条是它排除掉的另一种解释)
  • 复发自:无
  • 修复版本:待提交

BUG-280 | 计算成功而模型没写回答时,用户被扣点并只收到一句「没有可展示的回答」

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/lib/stream-agent-response.ts 的最终结算段、frontend/src/app/api/consult/route.ts 的重试接法。两条 agentic 路径(个人盘与无出生分钟)都在内。
  • 用户现象:staging 手测一次多域运行 run.completed,回答全文只有「本次计算已完成,但暂时没有生成可展示的回答。请换一个角度提问……」;回执显示工具 63.5 秒成功、状态 ready。用户问「为什么这次就生成失败了,也没有 run failed」——问的正是这个矛盾:运行报成功、点数照扣、回答没有。
  • 触发条件:契约已绿(skill 已加载且恰好一次成功计算),而模型此后未输出任何非空文本。
  • 根因:兜底文案被当成回答交付并结算。ensureFinalResponseText() 在契约绿且输出为空时返回一句固定道歉,随后 fullOutput 被替换、emitted 置真、onComplete 照常调用 complete_consultation_response——用户为一句「没有回答」付了费。同时它让下方以「输出为空」为条件的分支全部变成死代码:兜底一定先把 fullOutput 填满,所以既有的 empty_answer 失败码从未有机会发出,run.failed 也就永远不会出现,这正是用户看到「没有 run failed」的原因。至于模型为何不写,本轮无法定案:最接近的证据是既有回归里同形状运行的 modelFinishReasontool-calls(还想调工具却用尽了 AGENT_MAX_STEPS = 8),且已确认那次不是超时(工具 63.5 秒成功,110 秒预算尚余约 46 秒)。修复因此不押注单一根因,而是覆盖「计算成功、模型没写」这一整类。
  • 修复:两步。其一,先再问一次:契约已绿而输出为空时,用同一份 baseMessages 追加一句「上一轮没有输出任何回答文本,请直接给出回答」重跑一轮模型循环,并记一条 answer-retry 校验步。这轮重试不关闭工具——请求级缓存在 execute 之前就短路返回(if (calculation) return calculation,不计数、不记步、不重算,因此单次计算边界不受影响),模型再调一次工具即可原样取回同一份证据;反过来若用 toolChoice: "none",模型手上没有任何计算结果,只能凭空写。新一轮也拿到全新的 maxSteps 预算,这正是「上一轮因步数耗尽而沉默」所需要的。其二,重试后仍为空则以 empty_answer 失败:不再交付兜底文案,run.failed 带回执与「不会扣点」的文案,账务走 cancel。固定道歉文案与 ensureFinalResponseText() 一并删除。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 44 项通过。新增两项:计算成功但无文本时触发一次 answer-retry 并交付重试写出的回答(末步为 answer-retry);重试仍沉默则 run.failed / empty_answeronComplete 不触发、文案含「不会扣点」。改写两项:BUG-277 那条「只有自述」的回归改为断言既不交付也不结算;步数耗尽那条(modelFinishReasontool-calls 的生产形状)不再断言兜底被结算。计费契约测试改为钉住 4 次 usages.push(retried.totalUsage) 与 2 处 retryForAnswer(两条路径各有契约重试与回答重试,四次模型调用的 token 都必须计量)。前端除数据库套件外 1633 项通过,tsc --noEmiteslint 清洁。未做的验证:没有在 staging 上复现一次空回答来观测重试是否救回。
  • 待跟进:仍未取到那次运行的 [agent-observability] 日志,modelFinishReason 未定案;若日后确认多域运行普遍在第 8 步耗尽,应当调预算而不是靠重试兜。回答重试没有独立时间预算:若上一轮已把 110 秒用尽,它会立即被 abort 并以 calculation_failed 结束(同样不扣点),代价是失败码不如 empty_answer 精确。
  • 防复发:兜底文案不能既当回答又当结算依据。「保证客户端总能收到点东西」和「这次咨询算完成」是两件事,用同一段文本表达两者,必然出现「没回答也扣点」。空回答的正确出口是失败码加不扣点,而不是一句道歉。另一条:一旦某个兜底把最终变量填满,它下游所有以「该变量为空」为条件的分支都成了死代码——empty_answer 就这样被埋了整轮。加兜底时要顺手确认它没有吃掉下游的诊断分支。
  • 相关记录:BUG-277(同一段结算逻辑;它删掉的缓冲正是兜底此前不触发的原因之一)、BUG-214(同为契约门与可见输出的耦合)、BUG-271(同一批「失败在回执里查不到」)
  • 复发自:无
  • 修复版本:待提交

BUG-281 | staging 质量门 1731/1735xiaoxin 上 Docker 地址池耗尽,4 个 PostgreSQL fixture 起不来

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:Gitea backend-quality-gate.ymlnpm test --prefix frontendfrontend/tests/helpers/postgres-fixture.tsdeploy/reclaim-runner-disk.sh、xiaoxin runner 上的 Docker user-defined networks
  • 用户现象:927bdd7a 的 validate 在 1735 项前端测试里红 4 项,日志末尾是两条已通过的 truth-source 合同。失败原文都是 docker compose up -d --wait postgres 创建 jyotisha-postgres-*-app 网络时返回 all predefined address pools have been fully subnetted
  • 触发条件:向 staging 推送后,xiaoxin20 核)上 tsx --testos.availableParallelism() 并行跑全部 frontend/tests/*.test.ts。约 20 个文件会各自 startPostgresFixture(),每个 Compose 项目占用一个 user-defined network。
  • 根因:两层。其一,质量门跑的是全量并行 npm test,而 test:db 才把数据库套件串行化;20 核 runner 上一轮实测创建了 26 个 jyotisha-postgres-* 网络,其中 4 个在分配子网时失败。其二,BUG-266 的回收脚本只做 docker network prune --filter until=6h,同一天内崩溃或未拆掉的 fixture 网络继续占着 Docker default address pools;该 prune 本来就不会删仍有 endpoint 的网络,6 小时门槛对地址池没有额外保护,只会把当天残留留到下次 compose up 爆掉。失败的 4 项分别在 database-local-businessidentity-auth-integrationmodel-configuration-securityrectification-pr4-database,与 927bdd7a 的咨询副运改动无关。
  • 修复:fixture 在 docker compose up 前领取最多 2 个并发槽(可用 JYOTISHA_POSTGRES_MAX_FIXTURES 覆盖),持有期覆盖整个 Compose 项目寿命;槽位用目录锁,进程已死则回收,启动失败与 stop() 都会释放。回收脚本在既有 6 小时 prune 之后,对名字匹配 jyotisha-postgres- 的网络逐个 docker network rm:空闲的删掉,仍有 endpoint 的留下并发 job。
  • 验证:postgres-fixture-contract.test.ts 2/2、staging-backend-workflows.test.ts 38/38 通过(含回收脚本会 network rm 残留 jyotisha-postgres- 网络、仍拒绝 docker rm --force / system prune,以及部署脚本 shell 语法)。未做的验证:没有在 xiaoxin 上复跑完整质量门。
  • 防复发:Docker user-defined network 和磁盘是两类资源。磁盘回收不能代替地址池回收;until=6h 对「今天刚留下的空网络」无效。每个并行测试文件一个 Compose 项目,在多核 runner 上会按核数打满 default address pools。数据库 fixture 必须自己限制并发,不能依赖 tsx 默认并行度。
  • 相关记录:BUG-266(同一 runner 上的磁盘耗尽与 6 小时网络 prune)、BUG-264(本地全量 npm test 并发跑 database-* fixture 的既有抖动)
  • 复发自:BUG-266(回收只覆盖磁盘与 6 小时以上空网络,未覆盖地址池)
  • 修复版本:待提交

BUG-282 | 生时纠正 opening 强制 named tool_choicethinking 模型立刻 run.failed

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:staging 首页进入生时纠正、POST /api/rectification/agentaction=opening、V10 runV9AgentTurn 第一步 prepareStep
  • 用户现象:从首页进入生时纠正后流立即结束。NDJSON 只有 run.started 接着 run.failed,没有 skill.boundcase.loaded 或任何回答增量。
  • 触发条件:登录后从首页打开生时纠正;当前会话模型处于 thinking/reasoning 模式。
  • 根因:runner 为了保证第一步读取 Case,把 prepareStep 设成 toolChoice: { type: "tool", toolName: "rectification-read-case" }。上游 thinking 模式拒绝任何非 auto 的 tool_choice,返回 Thinking mode does not support this tool_choiceisRetryable: false。该错误被压成泛化 run_failed,客户端因此只看到两个公开事件。鉴权、Case 绑定和 turn 创建都已成功,失败发生在第一次 provider 调用。
  • 修复:第一步仍只暴露 rectification-read-case,但 toolChoice 改为 "auto"。运行器继续拒绝在 case.loaded 之前调用其他公开工具。provider 若仍返回该原文,错误码改为可诊断的 thinking_tool_choice_unsupported,并写一条不含正文的 attempt 失败日志。
  • 验证:rectification-v9-agent.test.ts 断言第一步为 activeTools=["rectification-read-case"]toolChoice="auto"rectification-v9-stream.test.ts 新增 thinking-mode 拒绝用例,确认只尝试一次、公开事件仍是 run.started/run.failed、attempt 错误码为 thinking_tool_choice_unsupported
  • 防复发:thinking 模式不能发送 named 或 required tool_choice。需要限制第一步工具时,用 activeTools 收窄集合,把选择权留给 auto;真实读取门禁继续由 runner 在 case.loaded 之前拦截其他公开工具。
  • 相关记录:BUG-261
  • 复发自:无
  • 修复版本:待提交

BUG-283 | 今日星语每次进入首页都停在“没能生成”

  • 状态:regressed
  • 首次发现:2026-08-18
  • 最近更新:2026-08-19
  • 影响面:/ 首页“今日星语”卡片、POST /api/daily-starlanguage、进程内缓存与浏览器当日缓存
  • 用户现象:资料完整的账号每次打开首页,卡片都显示“今天的星语没能生成,稍后再回来看看;这里不会用通用文案顶替。”,没有当日内容。
  • 触发条件:出生时间可用于个人星盘的账号进入首页。冷路径或换实例后必现。
  • 根因:BUG-265 把卡片改成同步等待 Agent,失败就不展示内容;BUG-269 修好了请求守卫,但首页仍把 30–45 秒的模型生成当作首屏依赖。进程内缓存不跨实例、不跨重启,JSON 解析或超时失败后客户端只剩失败文案。再进入等于再走一条冷路径。
  • 修复:星盘、双轨 Dasha、分盘和过境算完后,立刻用这份证据拼一张个人卡片返回,不再等待模型。Agent 改为后台润色,成功才覆盖缓存。首页按账号+资料指纹把当天卡片写入 localStorage,刷新先显示已有内容;只有星盘算不出来才落到失败文案。日期改用出生时区日历日,不再用 UTC。
  • 验证:frontend/tests/daily-starlanguage.test.ts 覆盖证据卡片因盘而异、未确认出生时间不用上升宫、请求路径不阻塞 Agent、失败不覆盖已缓存卡片。
  • 防复发:首页入口卡片不得把 LLM 生成当作首屏成功条件。禁止用固定四句文案池顶替;也禁止在生成失败时让整张卡空白。这条防复发没有挡住请求路径继续等待完整 /api/chart 加四层附加计算,复发见 BUG-299。
  • 相关记录:BUG-265、BUG-269、BUG-299
  • 复发自:BUG-265(诚实降级正确,但把模型生成当成了首页可用性)
  • 修复版本:9d8b91acstaging,已被 BUG-299 覆盖)

BUG-284 | 技能激活的 77% 是 1592 条无说明的文件名,方法论从未进入任何一次 web 回答(referenceReads 恒为 0

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/lib/consultation-methodology.ts(新增)、frontend/src/mastra/consultation-tools.tstoModelDomainPlanContext 与运行时状态、frontend/src/mastra/index.ts 的 Agent 指令、frontend/src/lib/consultation-agent-events.tsfrontend/src/lib/agent-observability.ts 的回执与日志字段。所有走 run-jyotish-consultation 的个人盘咨询。
  • 用户现象:用户从项目一开始就在问「为什么 web 上调用模型调用 skill 达不到本地 Agent 框架调用 skill 的输出效果」。BUG-268 把 skill.referenceReads 暴露到回执后,两次 staging 手测(66c44875 综合主题、1797cc73 事业应期)都是 loaded: true, referenceReads: 0,10 步预算只用 2 步:加载 skill、调一次工具、直接开写。回答本身可读且盘面自洽,但用的是模型自带的印度占星常识加服务端精确数据,仓库里那套方法论至今没有参与过任何一次线上回答。
  • 触发条件:任何一次个人盘咨询。与 route、域数、模型无关。
  • 根因:技能激活的载荷结构使「按需读取」在这个包上不可能发生。实测 skill() 单次返回 129,651 字节,其中 SKILL.md 方法论正文 30,507 字节,## References 清单 59,584 字节(827 条路径),## Scripts 清单约 39,300 字节(760 条路径)——77% 是 1,592 条不带任何说明的文件名。Mastra 的 formatSkillActivation() 就是 instructions 加上包内全部文件的平铺列举(#discoverFilesInSubdir 递归收集,只跳过符号链接目录),本身没有筛选机制。SKILL.md 第 220 行那句「凡是用户询问事业、婚恋、财务、应期……必须先读取 references/strict-workflow-router.md」是这 30KB 里的一行,后面紧跟 1,592 个无从分辨的候选;而 Agent 指令另一侧明确要求「调 run-jyotish-consultation 再作答」。模型于是执行了那条可执行的,skill_read 一次未发。这不是模型偷懒:给定这份载荷,正确的文件无法被可靠地挑出来。Mastra 自己也在警告 Instructions have 717 lines (recommended: <500)
  • 修复:路由已在服务端定好,skill 又已经声明了每条路由必读哪份清单,所以这个选择根本不需要占用模型的一个回合。新增 consultationMethodologyForDomains():按本轮实际执行的域,从哈希锁定版技能包(versions/6.9.14registry 里 sha256 内容寻址)取出 strict-workflow-router.md 的共享基线段与该路由的 strict 段、event_judgment_skeleton.md 以及该域的 event_judgment_<domain>.md,按标题文本定段(路由文件重新编号不会失配),单段上限 6,000 字符、整块上限 24,000 字符,随工具结果的 methodology 字段与证据一起交付。实测单域 4 段约 9KB、最宽的四域计划 9 段 21KB,均未触上限。further_reading 不是硬编码,而是扫 SKILL.md 自己写出的 references/... 路径并剔除包内不存在的,得到 19 条可按需 skill_read 的索引——取代原来那 1,592 条。路由未声明 strict 清单的域(annual/general/health/education/migration/family——full-reading-strict 只出现在路由表里,文件中没有对应段落)如实列进 domains_without_strict_checklist,不拿别的路由顶替。Agent 指令新增一段:把 methodology 当作本次作答的方法而非背景,逐条对着证据走,遵守其输出纪律(事业路由要求把「机会接触 / 结构性机会 / 结果显现」三层分开写,不得合并成一个模糊的事业机会窗口——1797cc73 那轮恰好就是合并的),且这些段落已交付、不要再花回合重读。
  • 验证:新增 frontend/tests/consultation-methodology.test.ts 8 项:事业路由的 strict 段确实带着 D10Opportunity contact 到位;无 strict 清单的域如实上报且不被顶替;多域计划每条清单只出现一次;十个域逐个与最宽计划都在体积预算内;further_reading 只含包内存在的 references/ 路径且远少于包体清单;交付内容来自锁定版本号;按标题定段在下一个 ## 处停止。consultation-agentic-runtime.test.ts 45 项通过,新增一项钉住回执把「服务端交付了几段」与「模型主动读了几篇」分开报。tsc --noEmiteslint 清洁(5 条既有 warning 不在改动文件内)。未做的验证:没有在 staging 上比对同一问题在交付方法论前后的回答差异。
  • 待跟进:那 99KB 的文件名清单仍然每次请求都发一遍,只是不再是模型唯一的导航入口——已由 BUG-285 收掉(不是收窄清单,而是撤掉激活本身)。referenceReads 预期仍会经常为 0,这本身不再是缺陷,但它和 methodologySections 要一起读。
  • 防复发:「渐进式披露」只在候选集可判别时成立。把 1,592 个文件平铺给模型,等于既没有给方法也没有给索引,而 loaded: true 会让这件事看起来是好的——技能加载成功与方法论被采用之间没有任何因果关系,必须分别度量。另一条:当某个选择在服务端已经确定(这里是 route),就不要把它变成模型的一次工具调用;确定的事交付,不确定的事才询问。
  • 相关记录:BUG-268(把 referenceReads 暴露出来,这个问题才可见)、BUG-278(同为「skill 说的和 schema 收的不是一套词汇」)、BUG-279(同为服务端已算出的东西没送到模型手上)、BUG-285(撤掉激活,并补上激活内容从未被哈希覆盖这个洞)
  • 复发自:无
  • 修复版本:待提交

BUG-285 | 技能激活逐轮重发 129KB,且模型看到的包内容从未被任何哈希覆盖

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/mastra/skill-binding.ts(新增)、frontend/src/mastra/index.ts 的四个 Agent 与指令、frontend/src/mastra/consultation-tools.ts 的运行时状态与钩子、frontend/src/lib/stream-agent-response.ts 的契约门与事件映射、frontend/src/app/api/consult/route.ts 的回执与重试。个人盘咨询、无出生分钟的通用问答、首页日签的 general 路径、引导问题生成——四条路径全部。
  • 用户现象:没有直接的报错现象,这是一笔一直在付、但从没有人看见账单的开销。用户在 BUG-284 之后追问了两件事:模型在同一个会话里每问一次是不是就要重新调一次 skill;以及那 129,651 字节里 77% 是文件名这件事该怎么处理。
  • 触发条件:任何一次对话的任何一轮。
  • 根因:两件事叠在一起。(一)激活的开销是逐轮的:咨询路径没有配 Mastra memory,也没有 threadId/resourceIdAgent 每次请求新建(id 里含 requestId,不进缓存);回放的 history 只有 {role, text} 纯文本,不含任何工具调用或工具结果。所以模型每一轮都必须重新激活一次技能,那份载荷逐轮重发;更糟的是一次请求内的 retry(契约不完整)与 retryForAnswer(空回答)都是 agent.stream([...baseMessages, 追加一句]),各起一个全新的模型循环、各自再激活一次,最坏情况单次请求付三遍。(二)模型看到的包内容不受哈希约束:skills: [jyotishSkillPath] 指向的是工作树视图 skills/jyotish-vedic-astrology,而那里的 references 是软链到 ../../references 的 827 条全量树;完整性检查只逐字节比对 SKILL.mdregistry 的 sha256 管不到激活时列举出来的那 1,592 条路径。生时纠正的 Agent 没有这个洞,它用的是 resolveSkillPackageRuntimePath(skillPackage)(先校验 sha256,再建一个以规范技能名命名的符号链接别名指向锁定版目录)——正确的机制仓库里早就有,只是咨询路径没用。
  • 修复:把技能从「模型的一次工具调用」改成「服务端在跑之前就绑定好」,与生时纠正对齐。新增 frontend/src/mastra/skill-binding.ts:从锁定版包读 SKILL.md、剥掉 frontmatter,包进 <jyotish-skill name version> 交给 instructionsskills 指向 resolveSkillPackageRuntimePath() 的哈希校验路径;用一个 providesSkillDiscovery: "on-demand" 的输入处理器撤掉 skillskill_search,只留 skill_read(这是 Mastra 为「调用方自行负责技能发现与指令加载」留的声明位,不是绕路)。四个 Agent 统一走这个绑定,Load the ... skill before answering 一类句子改成声明方法已在提示里、并明确「以下是本产品的运行时契约,与 skill 方法冲突时以契约为准」。运行时状态的 jyotishSkillLoaded 改名 jyotishSkillBound 并在创建时即为真,绑定作为回执的第一步记下(不带耗时,因为确实没有取任何东西);skill.started/skill.completed 两个公开事件改由服务端在流开头宣告,客户端契约与文案不变,但首个 activity 从「等激活往返之后」提前到「立刻」。无出生分钟那条路径的契约重试整段删掉——它的 requireTool 为假、方法又已绑定,已经没有任何可修的契约了。那个处理器不只是个标记:它逐步检查系统提示里确实带着绑定标记,读不到提示就放过,能读到而没有方法则中止本轮——撤掉激活之后,一个装配时漏了方法块的 Agent 不会再报错,只会安静地在没有方法的情况下作答,这正是本轮要消掉的失败形态。
  • 验证:新增 frontend/tests/skill-binding.test.ts 4 项:绑定的正文与锁定版 SKILL.md 逐字一致且运行时路径落在 sha256 目录下;实测激活载荷与绑定载荷的比值(前者是后者两倍以上,后者不到前者 45%);绑定后的工具面恰好只有 skill_read 而技能仍被声明;系统提示缺方法时中止、带方法与读不到提示时都放过。实测数字:从锁定版包激活是 118,352 字节(## References 56,696、## Scripts 14,824),绑定后 47,289 字节,每次省 71,063 字节;相对生产当前实际发出的 129,651 字节省 82,362。顺带一项一致性:锁定版的 references 是 189 条的真目录,而工作树视图是软链进 827 条,所以 skill_read 能读到的范围与 methodology.further_reading 校验的范围现在第一次是同一棵树。非 DB 套件 1,666 项中 1,665 通过,唯一失败是 onboarding-route.test.ts 里 50ms 墙钟对 5ms 超时的竞速用例,单独跑通过(19ms)且该用例注入 generateText、不构建 Agent,与本轮无关。tsc --noEmit 清洁,改动文件 eslint 清洁(6 条既有 warning 都不在改动文件内)。未做的验证:没有在 staging 上比对绑定前后同一问题的回答差异,也没有实测冷启动多算一次包哈希的代价(resolveActiveSkillPackageresolveSkillPackageRuntimePath 各算一次)。评审中被自己的测试抓到一个真 bug:守卫最初用 JSON.stringify(system).includes(标记),而标记里带双引号、序列化后会被转义,那样每一轮都会误判「方法没绑上」并中止全部请求;改成按形状递归收集字符串。
  • 待跟进:Mastra 加载技能包时仍会对 SKILL.md 正文报行数警告(skill_read 仍要声明哈希目录),那是文件体积,不是 prompt 摘录。全谱系调用、强制工作流与 strict-workflow-router.md 的 Full-Spectrum / health-timing-strict 已写入商业入口并交付给网页模型,见 BUG-287。不要把研究仓入口绑进聊天 Agent。
  • 防复发:一次「模型可以忘记」的必要步骤,等于一条必然会出现的失败路径加一条修它的重试;当那一步的内容在服务端已经完全确定时,绑定比请求便宜,也比请求诚实。另一条:完整性检查必须覆盖模型实际看到的东西,而不只是入口文件——只比对 SKILL.md 的字节,会让人以为整个包都被钉住了。
  • 相关记录:BUG-284(同一条链路的上半段:先把方法按路由交付,本轮再撤掉激活)、BUG-282(生时纠正侧的服务端绑定与 skill.bound,本轮对齐的先例)、BUG-214runtime_contract_incomplete 的门,本轮去掉了它对模型加载动作的依赖)、BUG-286(摘录绑定并解开非解盘 Agent)
  • 复发自:无
  • 修复版本:待提交

BUG-286 | 咨询 prompt 绑了整份 SKILL.mdgeneral 与 onboarding 也各付 47KB 解盘方法

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:frontend/src/mastra/skill-binding.ts 的绑定正文、frontend/src/mastra/index.ts 的四个 Agent 工厂、frontend/src/app/api/consult/route.ts 的无出生分钟路径注释。个人盘咨询、无出生分钟一般问答、引导问题生成。
  • 用户现象:没有直接报错。Mastra 对技能入口报 Instructions have 717 lines (recommended: <500);无出生分钟问答与首页引导问题每次仍把约 47KB 解盘手册送进模型,而这两条路径一个不能做任何本命声明、一个只产主题建议问题。
  • 触发条件:任何一次个人盘咨询、无出生分钟咨询、或 onboarding 生成。
  • 根因:BUG-285 为了不改行为,把锁定版 SKILL.md 全文塞进 instructions,并让四条 Agent 共用同一绑定。商业入口相对研究稿只换了文件头,底下仍留着施工判断原则、37 个子命令、名人案例索引;这些对回答一个用户的盘没有作用。上游研究入口仍标 6.9.14,比产品快照只多一行 Formal Varga 审计,把它绑进 Mastra 会更胖,并与可见语音冲突。
  • 修复:咨询路径改为按标题从商业 SKILL.md 摘录运行时段落(商业路由、关联技法完整调取、五层硬约束、强制规则去掉开源复用边界、未闭环点、核心方法论、强制规范速查、注意事项),施工/CLI/案例索引留在包内供 skill_read。general 与 onboarding 不再插入方法块、不再声明 skill,与日签 Agent 对齐;onboarding 用一段产品范围条替代 47KB 方法。不绑研究入口。
  • 验证:frontend/tests/skill-binding.test.ts 断言摘录来自商业入口且不含施工/CLI/案例标题,并含关联技法完整调取;新增「只有解盘 Agent 绑定方法」。相关套件 skill-binding / consultation-birth-time-mode / consultation-voice-contract / consultation-workflow-contract / onboarding-route / daily-starlanguage / consultation-agentic-runtime / consultation-methodology 通过。tsc --noEmit 清洁,改动文件 eslint 清洁。Mastra 加载技能包时仍会对 SKILL.md 报行数警告,因为咨询 Agent 还要 skill_read 指向该目录;那是文件体积,不是 prompt 摘录。
  • 待跟进:SKILL.md 施工/CLI/案例段落仍在包内给本地 Agent 和 skill_read,聊天 prompt 继续只摘运行时段。未在 staging 上比对摘录前后同一问题的回答差异。
  • 防复发:哈希锁定的入口可以全文保留给本地 Agent,但聊天 prompt 只能摘运行时段落;一条不能解盘的路径不得绑解盘方法。Mastra 对技能文件的行数警告与 Agent.instructions 体积是两件事,不要用发版去修只属于 prompt 的问题。
  • 相关记录:BUG-285(全文绑定的直接前因)、BUG-284(路由方法论已随工具结果交付,本轮不再把同一份手册全文再塞一遍)、BUG-287(全谱系写入商业入口与咨询运行时)
  • 复发自:无
  • 修复版本:待提交

BUG-287 | 咨询只算主题相关分盘,西洋层与技法审计表到不了回答

  • 状态:resolved(本地修复,待提交与发布)
  • 首次发现:2026-08-18
  • 最近更新:2026-08-18
  • 影响面:scripts/jyotish_api_server.py 咨询本地层、scripts/unified_consultation_orchestrator.py 证据包与分盘调度、scripts/consultation_plan_contract.py 证据类别、frontend/src/mastra/consultation-workflow.ts 模型投影、frontend/src/mastra/product-voice.ts 可见回答格式、商业 SKILL.mdreferences/strict-workflow-router.md(与 versions/6.9.14 包内副本)。个人盘咨询。
  • 用户现象:没有直接报错。排盘只带 D2/D4/D9/D10/D11;其余 D1–D60 被写成「追问再展开」。西洋本命默认不算行运/回归/推运。模型上下文没有技法审计表,可见语音还禁止把它写进回答。对照本地 Agent 调用 yinduzhanxing-skill,网页还缺 Full-Spectrum 路由合同、健康清单、功能性吉凶星/Shadbala/Kakshya 进入作答,且口语被要求不要先写原始结构。
  • 触发条件:任何一次有出生分钟的个人盘咨询。
  • 根因:咨询路径按主题子集计算分盘以省成本;西洋 timing 要调用方逐项点名;toModelOutput 只投影四张卡片里的允许键,runtime_evidence_log 里的审计表从未进入模型上下文;VISIBLE VOICE 明确禁止复制 Technique Audit Table。
  • 修复:咨询默认计算 20 张正式分盘加其余 D-N 研究分盘,并自动挂上已实现的西洋本命与 timing 层(失败标 blocked,不静默省略)。consumer_context 带 varga_spectrumwestern_spectrumtechnique_audit_table。模型投影放行这些字段。可见回答末尾要求一张中文技法审计表(已执行 / 阻塞 / 不适用),口语须先用已交付的原始结构再综合。同一合同写入商业 SKILL.md(关联技法完整调取 / 全谱系真实调用 / 强制工作流 / P0-P1 观察层),并把 yinduzhanxing-skillFull-Spectrum Invocation Contracthealth-timing-strict 写入产品 strict-workflow-router.md 随方法论交付。咨询本地层补上 Functional Benefic/Malefic、Shadbala、Kakshya。重算 6.9.14 包哈希;不另维持一份产品覆盖层。
  • 验证:tests/test_consultation_consumer_context.py 断言正式 20 / 研究 40 张分盘及 Functional/Shadbala/Kakshya 本地层;tests/test_western_chart_engine.py 断言默认挂上已实现西洋 timing;前端 consultation-context / consultation-voice-contract / consultation-plan / skill-binding / consultation-methodology 断言审计表、全谱系合同、健康清单与商业入口强制工作流进入模型包。skill-registry 断言登记哈希与包树一致。
  • 防复发:主题相关分盘只决定优先解读,不得再把未算的正式分盘写成 deferred。西洋 natal 咨询不得再要求调用方点名每个 timing 层。模型包缺少 technique_audit_table 视为回归。商业 SKILL.md、strict-workflow-router.md 与咨询运行时必须是同一套调用面;改入口后必须同步根文件、versions/6.9.14 并重算 registry sha256。网页口语不得再禁止使用已交付的原始结构。健康域必须交付 health-timing-strict,不得再报「无清单」。
  • 相关记录:BUG-286(prompt 摘录,不靠发版修调用)、BUG-284(方法论随工具结果交付)
  • 复发自:无
  • 修复版本:待提交

BUG-288 | 生时校正一句多事件首轮未能纳入校正依据

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:V10 rectification-propose-evidence / rectification-confirm-evidence / record_agentic_rectification_evidence_batch、Activity「整理事件证据」、新 Case 绑定 jyotish-birth-time-rectification@10.0.1
  • 用户现象:用户在一句里说出两件带日期的清楚事件后,Activity 显示整理事件证据未完成,事件没有进入校正账本。
  • 触发条件:当前轮用户消息包含两件可拆分事件;模型按旧提示逐条 propose+confirm,或使用 education_milestone 等 TS 有、SQL CHECK 没有的 kind。
  • 根因:系统提示要求同轮逐条 propose+confirmconfirm_agentic_rectification_evidence_v10 会在第一条确认时 resolve 无目标证据的 opening focus,第二条变成 focus_not_activepropose 在 quote 不匹配时 raise,整轮失败。表 CHECK / batch allowlist 比 TS EVIDENCE_KINDS / EVIDENCE_DOMAINS 更窄,工具层过了、插入失败。批量路径本可按项原子写入并直接 confirmed。
  • 修复:SQL kind/domain/precision 与 TS 对齐;propose 对 quote_not_grounded / invalid_item 返回结构化失败;confirm 允许省略 focusId,且不 resolve opening focus;新事件只走 batchquote 规范化额外去掉 ASCII ,!.;:?。新增 Skill 10.0.1。不改 10.0.0 包哈希。
  • 验证:frontend/tests/rectification-ingest-p0.test.tsfrontend/tests/rectification-ingest-p0-database.test.ts(有 Docker 时)、kind allowlist 两边一致、propose 结构化拒绝且 receipt 为 completed、confirm 可无 focusId。
  • 防复发:一句两件及以上事件不得再要求逐条 propose+confirmSQL CHECK 必须覆盖 TS 枚举;quote 失败不得整轮 throw。
  • 相关记录:BUG-174、BUG-175
  • 复发自:无
  • 修复版本:待提交

BUG-289 | 用户确认日后正文把日级说成年级

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:生时校正证据投影、display_date_label、Skill/系统提示复述规则、revise_agentic_rectification_evidence
  • 用户现象:用户确认一件日级事件后,公开正文写成「年份已确定为 YYYY」,精度被说粗。
  • 触发条件:账本已有 date_precision=dayoccurred_from 为具体日期;用户回复「是 / 对」。
  • 根因:confirm RPC 本来不改日期;Dossier 虽有精度字段,投影和 Skill 没有强制复述必须与服务器精度同级。模型把日级复述降成年。
  • 修复:证据投影增加只读 display_date_label(日级 YYYY-MM-DD,禁止格式化成年);Skill 与系统提示要求复述用该标签;用户确认不得改 date_precisionrevise 在更粗且 quote 没有更粗日期表达时拒绝 precision_downgrade
  • 验证:日级 label 契约测试;confirm 工具不传日期字段;Docker 下「是」修订年级被拒绝且行仍为 day。
  • 防复发:公开复述不得使用模型自拟年份句;日级 label 不得含「年份」。
  • 相关记录:BUG-288
  • 复发自:无
  • 修复版本:待提交

BUG-290 | 生时校正不可分平台未作为服务器字段交给 Agent

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:latest_result 工具投影、candidate-comparison.md、系统提示第 9 条
  • 用户现象:对照里网页已能说出代表性分钟和约 25 分钟不可分区间,但该宽度不是 Agent 必读的服务器字段,后续容易学成本地 1 分钟尖峰。
  • 触发条件:v5 打分器返回 top 簇 tied_minute_count 很大、公开候选约 04:45/46/47。
  • 根因:投影只有 confirmation_allowed / 代表时间,没有 indistinguishable_width_minutesSkill 未把「宽度 > 5 或 top tied > 1 必须说不可分区间」写成硬合同。
  • 修复:投影增加 indistinguishable_width_minutes;宽度大于 maxConfirmationWidthMinutes5)时投影层关闭 confirmation_allowed;Skill 与提示词要求说不可分区间 / 代表性候选,不得说已定位到唯一分钟。v5 打分器不改。
  • 验证:04:45/46/47 且 tied=25 时宽度 ≥ 25 且 confirmation_allowed=false;禁止该夹具下 confirmation_allowed=true
  • 防复发:平台结果不得把代表分钟说成唯一出生分钟;宽度字段必须随 latest_result 交给 Agent。
  • 相关记录:BUG-288、BUG-289
  • 复发自:无
  • 修复版本:待提交

BUG-291 | 生时校正按缺失领域轮询迁居/健康/财务,而不是八大方法层

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:rectification-read-casemethod_followup_plan、Skill jyotish-birth-time-rectification@10.0.2
  • 用户现象:网页在已有学业事件后继续按迁居 / 健康 / 财务轮询;本地 skill 按方法层问关系、事业、家人。
  • 触发条件:已确认带日期事件,SQL missing_evidence_categories 仍列出未覆盖领域。
  • 根因:追问由 conversation summary 的缺失领域清单驱动,没有服务器方法层路由。
  • 修复:新增 method_followup_plan。下一问按有日期事件 → 关系 → 事业 → 家人;外貌 / 胎记 / 占问跳过。不再用缺失领域轮询。仍只产出候选,不宣布确认。
  • 验证:frontend/tests/rectification-eight-method.test.ts:学业事件后下一问是关系,不是迁居。
  • 防复发:Agent 必须跟 method_followup_planstop_domain_rotation=true 时不得按领域清单继续问。
  • 相关记录:BUG-288、BUG-290
  • 复发自:无
  • 修复版本:待提交

BUG-292 | 收集阶段要等用户说「没有更多了」才打分,Activity 显示 0 项计算依据

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:rectification-record-evidence-batchrectification-confirm-evidence、工具 receipt executed_methods
  • 用户现象:本轮已写入带日期事件,Activity 仍写「本轮完成 · 0 项计算依据」;要用户说没有更多事件才比较。
  • 触发条件:批量或确认写入了可评分证据,Agent 未再调用 compare-candidates。
  • 根因:打分只挂在显式 compare 工具上。收集阶段写入成功不触发服务器重算。
  • 修复:证据有效变化(账本指纹变了)时由服务器重算并持久化候选;失败不回滚证据写入。receipt 带上实际执行的方法。不因此进入 candidate_ready,也不 offer 采用。
  • 验证:批量写入后 persist 被调用且 completed receipt 含方法;引擎失败时 batch 仍 accepted。
  • 防复发:不要等「没有更多了」才比较;同一指纹不要再 compare。Agent 不得自己扫分钟。
  • 相关记录:BUG-291
  • 复发自:无
  • 修复版本:待提交

BUG-293 | 分钟窗口扫描若做成 Agent 工具,容易被说成 ±5 分钟确定结论

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:v5 window_scan 诊断、rectification-compare-candidates、候选卡投影
  • 用户现象:对照本地分钟扫描能给出候选簇;网页要么不扫,要么容易把扫描说成已确认到几分钟。
  • 触发条件:窗口内 D9/D10 升一不同;公开工具若再加第 14 个 scan 工具。
  • 根因:扫描能力没有作为现有 compare 路径的服务端字段;确认门也没有把扫描与「不得宣布唯一分钟」绑死。
  • 修复:Python 诊断增加 window_scan(只计 D9/D10 升一数量与是否不同)。折入现有 compare 与自动重算,结果进候选卡 / 平台语言。公开工具仍为 13 个。confirmation_allowed 不因扫描变为 true。
  • 验证:诊断测 D9 两个指数则 d9_candidates_differ=true 且无星座名;25 分钟平台仍禁止确认。
  • 防复发:不得新增第 14 个 rectification 工具;不得把若干事件写成 ±5 分钟确定性结论。
  • 相关记录:BUG-290、BUG-291
  • 复发自:无
  • 修复版本:待提交

BUG-294 | D9/D10 类型表若直接告诉用户会变成性格/配偶/事业标签

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:internal_observations、read-case 投影、Skill 10.0.2 技法路由
  • 用户现象:分盘升一不同时,正文可能把用户说成某星座或某类配偶/事业气质。
  • 触发条件:窗口扫描发现 D9 或 D10 升一在候选间不一致。
  • 根因:类型表若作为用户可见结论,没有强制投影成追问主题。
  • 修复:只投影 ask_theme(关系经历 / 事业经历)。解析时丢掉星座名等额外键。Skill 禁止贴标签。
  • 验证:含「白羊/天蝎/热情」的扫描输入投影后 JSON 不得出现这些词;D9 不同时下一问是关系主题。
  • 防复发:read-case / 候选投影不得输出星座名、配偶类型或事业特质标签。
  • 相关记录:BUG-291、BUG-293
  • 复发自:无
  • 修复版本:待提交

BUG-295 | 咨询把解盘 skill 钉在哈希包上,手动更新 SKILL.md 不会生效

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:skills/skill-package-registry.jsonfrontend/src/lib/skill-package-registry.tsfrontend/src/mastra/skill-binding.tsfrontend/src/lib/consultation-methodology.tsfrontend/src/mastra/index.tsfrontend/src/instrumentation.tsdeploy/railway-web.Dockerfile。个人盘咨询。
  • 用户现象:维护者手动更新仓库根 SKILL.md / references/ 后,网页咨询仍按钉死的 versions/6.9.14 哈希包作答;要让更新生效还得双份拷贝并重算 sha256。
  • 触发条件:咨询 Agent 绑定方法,或 consultationMethodologyForDomains() 取路由清单。
  • 根因:BUG-284/285 把咨询绑到 registry 里的 jyotish-vedic-astrology@6.9.14mastra/index.ts 还要求根 SKILL.md 与该快照逐字节相等,否则进程拒绝启动。这把「手动更新 skill」变成了发版手续。
  • 修复:咨询改为读 live 目录 skills/jyotish-vedic-astrology(及可选 JYOTISH_SKILL_PATH),不校验 sha256、不钉版本。Mastra 只挂 live 的 SKILL.md / references / scripts / assets,不把目录里遗留的 versions/ 快照交给 loader。生时纠正和个人报告仍用哈希包。启动时确认 live 树存在。镜像最终阶段同时拷入 /app/SKILL.mdassetsreferencesscripts,让 live 相对符号链接能解析。
  • 验证:frontend/tests/skill-binding.test.tsskill-registry.test.tsconsultation-methodology.test.tsregistry 不再把解盘 skill 列为 active hashed packageresolveActiveSkillPackage("jyotish-vedic-astrology") 抛错。
  • 防复发:咨询路径不得再 resolveActiveSkillPackage("jyotish-vedic-astrology"),不得恢复根入口与 versions/ 的逐字节相等门。哈希身份只留给 rectification 与 personal-report。
  • 相关记录:BUG-284、BUG-285、BUG-286、BUG-287
  • 复发自:无
  • 修复版本:待提交

BUG-296 | 生时校正确认门三层失败原因未投影给 Agent

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:latest_result.confirmation_gaterectification-confirm-birth-time、Skill jyotish-birth-time-rectification@10.0.3
  • 用户现象:相邻分钟分不开、VedAstro 官方分钟敏感校验未跑通、公开 AA holdout 未达门槛时,Agent 仍可能把代表性候选说成唯一出生分钟,或去追更细确认。
  • 触发条件:引擎 confirmation_allowed 恒为 falseexternal_validation_status=not_evaluated;v5 平台宽度大于 5;密封 holdout 有效例数低于 20。
  • 根因:确认门只把 confirmation_allowed=false 交给 Agent,没有结构化写出 VedAstro / 不可分区间 / holdout 三层 blocker。not_evaluated 容易被说成 fail;holdout 未就绪时也没有禁止放宽确认门的服务器字段。
  • 修复:latest_result 增加只读 confirmation_gate。VedAstro 保持 not_evaluated(不是 fail);宽度大于 5 时不可分层为 blockedholdout 为 not_ready(密封 v2 聚合,不含个案身份)。任一 blocker 未通过则投影 confirmation_allowed=falseconfirm 工具返回 confirmation_blocked。v5 打分器与确认门阈值不改。用户仍可 accepted 代表性候选。
  • 验证:frontend/tests/rectification-confirmation-gate.test.ts:窄宽度且引擎允许时仍因 VedAstro/holdout 禁止确认;25 分钟平台仍禁止;confirm 不调用写入 RPC;投影不含精确分钟宣传或个案身份。
  • 防复发:不得把 not_evaluated 写成 fail;不得为凑门槛编造 holdout;不得把 confirmation_allowed 改成 true;公开工具仍为 13 个。
  • 相关记录:BUG-064、BUG-290、BUG-294
  • 复发自:无
  • 修复版本:待提交

BUG-297 | 并列分钟把采用代表性时间当成失败而不是本次校正结果

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:decision_policy.py 采用门、session_outcomemethod_followup_plan、Skill jyotish-birth-time-rectification@10.0.3、候选卡文案
  • 用户现象:相邻分钟分不开时,用户这次校正没有可用结果;Agent 继续追问感情/事业/家人,而不是给出可采用的代表性时间。确认唯一分钟本来就不该通过。
  • 触发条件:至少 3 件可评分事件、至少 2 个领域,但 top 候选 tied_minute_count > 1(例如约 25 分钟平台)。确认门三层 blocker 仍未通过。
  • 根因:acceptance_allowedunique_top 与诊断稳定性并进采用门,并列分钟直接关掉 selection_allowed,候选卡不出现。推荐徽标还要求 confirmationAllowedmethod_followup_plan.next_followup 在已有可采用结果时仍要求继续补证据。
  • 修复:并列分钟与诊断稳定性只写入 confirmation_reasons,不再关闭采用。latest_result.session_outcome=adopt_representative 时本轮结果是采用代表性时间;next_followup 置空,方法追问进入 deferred_followup。确认门保持 fail-closed。正文与采用后文案写明还不能确认唯一分钟。不自动进入 candidate_ready
  • 验证:tests/test_rectification_v5_services.py 单领域仍禁止采用,双领域并列分钟允许采用但禁止确认;frontend/tests/rectification-eight-method.test.ts 平台结果 next_followup=nullsession_outcome=adopt_representativefrontend/tests/rectification-confirmation-gate.test.ts 锁定该 outcome;推荐徽标不再要求确认门。
  • 防复发:不得把 confirmation_allowed 改成 true;不得用采用冒充唯一分钟确认;不得在 adopt_representative 当轮再问 next_followup;重算后仍不得自动 candidate_ready。公开工具仍为 13 个。
  • 相关记录:BUG-120、BUG-292、BUG-294、BUG-296
  • 复发自:无
  • 修复版本:待提交

BUG-298 | 咨询回答把技法审计表铺进正文,列表和表格也缺少对话样式

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询消息渲染、VISIBLE VOICE、live jyotish-vedic-astrology SKILL.md、Agent 回执
  • 用户现象:个人盘回答后,比较表和技法审计表像未排版的管道符墙;末尾约四十行技法表压过口语正文。
  • 触发条件:有出生分钟的个人盘咨询,模型按旧合同把 | 技法 | 状态 | 说明 | 写进 answer.delta
  • 根因:可见语音要求口语末尾贴完整 Markdown 技法审计表;.markdown-table 没有样式;列表项里的段落仍吃正文边距。BUG-011 只禁止内部 EvidenceAuditPanel,没有另做用户可读的折叠表。
  • 修复:口语不再粘贴审计表。服务端把已交付行放进回执,界面用默认收起的 本轮技法 折叠块展示。历史回答若仍带该 Markdown 表,则从正文拆出再折叠。比较表与列表改用对话字号、边距和横向滚动。不恢复内部证据面板、英文状态码或 raw receipt。
  • 验证:frontend/tests/consultation-technique-audit.test.tsconsultation-voice-contractskill-bindingevidence-audit-panelchat-bundle-splitting-contractchat-stream-layout
  • 防复发:聊天消息不得再挂载 EvidenceAuditPanel;口语合同不得要求把技法审计表写进正文;折叠块默认不得带 open
  • 相关记录:BUG-011、BUG-287
  • 复发自:BUG-011
  • 修复版本:待提交

BUG-299 | 今日星语首屏仍失败:证据卡还在等完整星盘

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:/ 首页“今日星语”卡片、POST /api/daily-starlanguage/api/daily_guidance
  • 用户现象:资料完整的账号一进首页就看到“今天的星语没能生成。点这里从今日问起;不会用通用文案顶替。”,没有当日内容。
  • 触发条件:出生时间可用于个人星盘的账号打开首页。冷路径必现。
  • 根因:BUG-283 不再等模型,但首屏仍串行调用完整 /api/chart(含 VedAstro 总览、Shadbala、瑜伽等)再并行等 Vimshottari、Narayana、D9/D10、过境,最坏约 40 秒,超过路由 maxDuration = 30。超时后客户端只剩失败文案。失败文案还写了“点这里”,而整张卡本来就是点击热区。仓库里已有轻量 /api/daily_guidance(本命盘 + 今日月亮过境宫 + 当前大运),首页没用。
  • 修复:首屏只调 /api/daily_guidance。卡片按今日月亮过境宫与当前大运拼句,不用四句通用文案池,也不用本命月亮宫冒充今日。Agent 润色仍在后台。失败态改成“今天的星语还没写出来。”,行动改为“从今日问起”。引擎超时 8 秒,路由上限 20 秒。
  • 验证:frontend/tests/daily-starlanguage.test.ts 锁定 /api/daily_guidance、禁止首屏 /api/chart、今日过境宫与本命月宫区分、失败文案不含“点这里”;tests/test_daily_guidance_service.py 锁定结构化宫位/大运且不同日期会变。
  • 防复发:首页日签不得把完整 /api/chart 或四层附加计算当作首屏成功条件。失败文案不得写“点这里”,也不得解释内部“不用通用文案”政策。
  • 相关记录:BUG-265、BUG-269、BUG-283
  • 复发自:BUG-283(不再等模型,但仍把完整排盘当首页可用性)
  • 修复版本:待提交

BUG-300 | 网页咨询把已算技法从模型包裁掉,模型用参数知识填洞

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询 toModelOutput_attach_local_consultation_layers、口头回答密度
  • 用户现象:线上咨询缺 Yoga、Arudha/UL/A10、Chara、行运等本地 skill 会用的结构,模型用常识补度数和宫位。
  • 触发条件:有出生分钟的个人盘咨询。计算工作流已挂上分盘谱、Narayana、功能性吉凶,但发给模型的证据包没有这些层。
  • 根因:toModelOutput 的本命投影只保留升一/行星/宫位/Shadbala/AV/功能性吉凶/分盘谱。Yoga、Arudha、Gulika、Kakshya、KP、Chara、行运留在 toAgentConsultationContext 里却不进模型包。咨询路径也未把 Yoga/Chara/Sade Sati 写入 modules。
  • 修复:咨询 attach 补 Yoga、路线相关 Chara、Sade Sati 行运。模型包投影这些层,并给出 must_use_layers 已执行短名单。未交付层禁止用常识补算。MEVG / VedAstro 仍诚实 blocked。不绑 AGENTS.md 或整份 SKILL。
  • 验证:frontend/tests/consultation-context.test.tsconsultation-spectrum-parity.test.tsconsultation-voice-contract.test.tstests/test_consultation_consumer_context.py
  • 防复发:咨询 toModelOutput 必须包含 skill 共同基础里本地已执行层;local_layers 不得再读引擎未写入的 module 键。
  • 相关记录:BUG-279、BUG-287、BUG-298
  • 复发自:BUG-279(算了却不交给模型)
  • 修复版本:待提交

BUG-301 | 网页咨询前台把 VedAstro 整段跳过,审计只能诚实 blocked

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询 POST /api/consultexecute_consultation_workflow foreground、vedastro_gateway、模型包 vedastro_cross_check
  • 用户现象:技法表里 VedAstro 云状态一直是阻塞,即使官方网关和网络可用。
  • 触发条件:聊天前台 foreground: truedefer_optional_external_evidence: true
  • 根因:BUG-161 为避免 overview/full snapshot/range scan/gateway 串行超时,前台直接跳过 gateway 并写死 foreground_optional_evidence_deferred。本地层恢复后(BUG-300)仍把可选官方交叉验证当成“本轮不算”。
  • 修复:前台仍跳过 VedAstro 主入口 overview 与 range scan。gateway 与本地计算并行,子进程预算约 8 秒,本地完成后最多再等 1.5 秒。赶上且有 official raw 则合并为 official_verified 并投影日月升一星座;超时或失败保持 official_blocked,本地回答不失败。MEVG / PyJHora / jyotishganit 仍 blocked。不提高 AGENT_TIMEOUT_MS
  • 验证:tests/test_api_server_security.py 前台赶上合并与超时降级;tests/test_consultation_consumer_context.py 压缩包去掉经纬度;frontend/tests/consultation-context.test.tsconsultation-spectrum-parity.test.ts
  • 防复发:前台不得再同步串联 overview+snapshot+range scan;赶不上不得把未交付官方层标成 executed;压缩交叉验证不得带出生钟点或坐标。
  • 相关记录:BUG-161、BUG-159、BUG-300
  • 复发自:BUG-161(超时防护把可选官方层整段关掉)
  • 修复版本:待提交

BUG-302 | 咨询行运层只挂了 Sade Sati,触发窗从未搜索,模型只能说行运没算

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询 _attach_local_consultation_layersmodules.transitstoModelOutput timing / western_spectrum、口头应期
  • 用户现象:skill 共同基础含行运,引擎也有 transit_trigger.search_all_transit_triggers,但网页咨询只能看到土星过月阶段,回答继续写「行运触发未在本轮完整计算」。西洋应期层已算完,发给模型的压缩包却只剩 status。
  • 触发条件:有出生分钟的个人盘咨询,尤其问未来数月或行运。
  • 根因:attach 只复用 chart.transit_triggers(咨询排盘从不写这个键),从不调用触发搜索。西洋压缩只保留 status/boundary,把 target_date、aspects、duration windows 裁掉。
  • 修复:咨询对参考日起 90 天、土星/木星/罗睺/计都对升一与月亮做有界触发搜索,最多 6 条日期窗进模型包;空列表表示该窗无精确接触,不是没算。西洋压缩补日期、aspects、扫描窗,去掉回盘钟点和经度。口头合同:search_period 在场就不得再说行运没搜。不提高 AGENT_TIMEOUT_MS,不发明水星逆行。
  • 验证:tests/test_consultation_consumer_context.py 锁定搜索窗与触发压缩、西洋窗保留且去掉钟点;frontend/tests/consultation-context.test.tsconsultation-spectrum-parity.test.tsconsultation-voice-contract.test.ts
  • 防复发:modules.transits 必须带 search_period;模型包 timing 必须投影 triggers;西洋 techniques 不得只留 status。
  • 相关记录:BUG-279、BUG-300
  • 复发自:BUG-279(副运修好后,待跟进里的行运触发仍未接上)
  • 修复版本:80969c9b

BUG-303 | 校正确认门的 VedAstro 分钟快照从未在 V9 评分路径执行,状态永远 not_evaluated

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:/api/rectification/v5/score/api/rectification/v5/diagnosticsconfirmation_gate.vedastro_minute_sensitive
  • 用户现象:确认门 VedAstro 一行长期 not_evaluated。独立的 /api/rectification/v5/vedastro-validate 存在,但 V9 评分从不调用分钟身份层;SearchEvents 也不是确认判据。
  • 触发条件:V9 rectification-compare-candidates / diagnostics 产生两名候选且本地 acceptance_allowed
  • 根因:build_decision_receiptexternal_validation_status 写死为 not_evaluated。评分 HTTP 层不附加官方分钟快照。超时若被标成 fail,会把「没跑完」说成「校验失败」。
  • 修复:本地接受门通过且有两个不同 HH:MM 时,并行跑官方分钟快照(升一/宫界、D9、D10、Dasha 指纹),不跑 SearchEvents。区分则 passed,完整比较后无法区分则 failed;超时、不完整或未就绪保持 not_evaluated。引擎 confirmation_allowed 仍为 false;公开 AA holdout 仍 not_ready,不得写唯一分钟。回执不带坐标或 fingerprint。
  • 验证:tests/test_rectification_v5_vedastro_validation.py 锁定通过/无法区分/超时/本地未就绪跳过,且不调用 range scan。
  • 防复发:V9 评分路径必须写 gates.exact_confirmation.external_validation_status;赶不上不得改成 failSearchEvents 不得单独放行确认。
  • 相关记录:BUG-301
  • 复发自:无
  • 修复版本:80969c9b

BUG-304 | 用户说暂时想不到了之后,生时纠正只复述事件,不给结果也不引导下一步

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:V9 生时纠正 rectification-read-case、系统提示第 9 条、时间卡片
  • 用户现象:已口述多件带年份经历并说「暂时想不到了」后,回复只有事件复述和「以后再继续」。Activity 为「1 个步骤 · 0 项计算依据」,没有候选时间卡,也没有排盘。
  • 触发条件:用户停止补事件。模型本轮只调用 read-case。
  • 根因:提示把「没有更多事件」写成可以自然结束。工具投影没有服务器下一步。排盘结果本应是候选时间卡,但事件若未写入账本或未比较,卡片不会出现;正文又被禁止把时间写进聊天,用户因此什么都看不到。
  • 修复:read-case 增加 next_user_action / on_user_stop。用户停止时:账本空则 batch 已说的带日期经历再比较;有事件无结果则本轮 compare;已有代表性结果则解释并请采用卡片。禁止只说记下了以后再继续。公开工具仍为 13 个。
  • 验证:frontend/tests/rectification-eight-method.test.tsrectification-v9-agent.test.ts
  • 防复发:停止收集不得只做口头确认;下一步必须来自 next_user_action.on_user_stop
  • 相关记录:BUG-179、BUG-292、BUG-307
  • 复发自:BUG-179(禁止正文写时间后,若卡片未出现就没有任何排盘结果)
  • 修复版本:40a59504

BUG-305 | 咨询生成半截结束仍被当成成功回答并扣点

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询 POST /api/consultstreamAgentResponse、咨询页 NDJSON 收口、DeepSeek V4 Flash 生成预算
  • 用户现象:staging 一次咨询回答停在半截标题,输入框随即恢复可输入,界面看起来像已经答完。服务端把该条助手消息按成功咨询写入会话。
  • 触发条件:网页个人咨询,模型为 DeepSeek V4 Flash。计算工具已成功,随后组织回答时输出在句中停止。观测到会话更新约 2.5 分钟后收口,公开回执 stepBudget.truncated=false,客户端收到 run.completed
  • 根因:两层。其一,finish_reason=length 与超时中断后的半截正文仍走 run.completedcomplete_consultation_response,公开回执故意不含 modelFinishReason,界面无法区分正常结束与夹断。其二,咨询 agent.stream 未设可见输出预算,也未关闭 Flash 默认 thinking;隐藏推理与可见正文共用 max_tokens,更容易在标题中途 length 停住。AGENT_TIMEOUT_MS = 110_000 与路由 maxDuration = 120 仍可能在组答阶段掐流,旧逻辑同样把已发出的半截当成功。
  • 修复:可见正文在 length 结束,或超时/中止时已有输出,改为 run.failed / answer_truncated,保留已流出文本,账务走 cancel。咨询流设置 maxOutputTokens = 8192,并对当前供应商与 openai 兼容键关闭 thinking。客户端保存半截助手消息并提示未完成、不会扣点,不再要求 run.completed 才落盘。
  • 验证:frontend/tests/consultation-agentic-runtime.test.ts 覆盖 length 与超时半截不得 run.completed、不得调用 onCompleteconsultation-workflow-contract.test.ts 锁定输出预算与 thinking disabledconsultation-recovery.test.tschat-stream-layout.test.ts 锁定半截落盘与提示;agent-observability.test.tsanswer_truncated 纳入已知错误码。
  • 防复发:有可见正文不等于咨询完成。finish_reason=length、超时半截不得再映射为 run.completed。咨询生成必须显式保留可见 token 预算;不得依赖 Flash 默认 thinking 与提供方默认 max_tokens。公开回执仍不得带 modelFinishReason,失败码必须能单独说明夹断。
  • 相关记录:BUG-280、BUG-277、BUG-340
  • 复发自:无
  • 修复版本:待提交

BUG-306 | 普通咨询 Agent 回答下方缺少赞踩复制重跑操作栏

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:普通咨询会话消息列表、ChatMessageActions、生时校正消息操作栏
  • 用户现象:生时纠正每条 Agent 回答下方有赞、踩、复制和重跑图标,普通咨询同一位置没有。
  • 触发条件:打开普通咨询会话,查看已完成的助手回答。
  • 根因:BUG-049 / BUG-185 只把操作栏接到生时校正消息列表。普通咨询 page.tsx 只渲染 ChatMessageRow,没有复用同一组操作。
  • 修复:抽出共用 ChatMessageActions。普通咨询与生时校正共用赞踩互斥、复制和仅最新回答可重跑。普通咨询重跑去掉最后一条助手消息后走现有 /api/consult(会扣点),失败则恢复原文;生时校正仍走免费原位 regenerate。
  • 验证:frontend/tests/chat-message-actions.test.tschat-stream-layout.test.tsrectification-agentic-entry.test.ts
  • 防复发:普通咨询与生时校正的可见操作必须共用同一组件;不得再复制一套图标按钮。源码合同若切片 send() 失败回填,必须带着 restoreOnFailure 守卫,见 BUG-308。
  • 相关记录:BUG-049、BUG-094、BUG-185、BUG-308
  • 复发自:无
  • 修复版本:待提交

BUG-307 | 生时纠正把执行凭证当成结果,正文没有分盘,也看不到宫位表

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:V9 生时纠正对话、rectification-compare-candidates 决策回执、候选快照
  • 用户现象:用户看到的是「本轮完成 · N 个步骤 · M 项计算依据」。比较完成后,正文没有一句分盘,也没有十二宫表。
  • 触发条件:校正对话完成一轮工具执行,尤其是已经比较候选之后。
  • 根因:Activity 折叠占用了分盘披露。提示又把宫位表写成禁止展示。评分已经算出本命盘,但公开投影没有十二宫。
  • 修复:对用户隐藏 Activity 折叠和工具过程文案。比较完成后,正文末尾由服务器写一句对照过分盘;界面展示按代表性时间排出的 D1 宫位表。表中只有宫位、星座、入驻,不含经度或坐标。
  • 验证:tests/test_rectification_house_table.pyfrontend/tests/rectification-varga-sentence.test.tsrectification-candidate-result.test.tsrectification-agentic-entry.test.ts
  • 防复发:生时纠正用户面不得再渲染 CompletedActivityReceipt;分盘句来自公开 executed methods;宫位表来自 decision receipt,缺表不得由模型编造。
  • 相关记录:BUG-179、BUG-181、BUG-304
  • 复发自:BUG-181(完成凭证成为用户主结果后,分盘和宫位没有独立正文位置)
  • 修复版本:c8af18e9

BUG-308 | 咨询重跑改了草稿回填条件后,源码合同仍切旧 if,staging 质量门 1807/1808

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:Gitea backend-quality-gate.ymlnpm test --prefix frontendfrontend/tests/composer-isolation-contract.test.ts
  • 用户现象:c8af18e90a 的 validate 在 1808 项前端测试里红 1 项。日志末尾是已通过的 timing-output-guard / truth-source 合同,真正失败是 not ok 578 - every external draft writer keeps working through the page-owned setters
  • 触发条件:向 staging 推送含 BUG-306 重跑路径的 page.tsx
  • 根因:草稿隔离合同用 sourceBetween 按字面切 if (activeSessionIdRef.current === sessionId) {。重跑把持久化失败回填改成 if (!options.restoreOnFailure && activeSessionIdRef.current === sessionId):普通发送失败仍把问题放回输入框,重跑失败则还原被删的助手消息、不改草稿。旧标记在文件里已不存在,indexOf 得到 -1。产品回填还在,测试锚点过期。
  • 修复:切片起点改为当前守卫;继续断言该段含 setDraft(originalQuestion)
  • 验证:frontend/tests/composer-isolation-contract.test.ts 修复前 4/5、修复后 5/5。
  • 防复发:切 send() 失败回填不得再用无 restoreOnFailure 的旧 if。源码合同的起止标记必须随被切代码一起改,否则质量门会把无关提交打红。
  • 相关记录:BUG-249、BUG-306
  • 复发自:BUG-306(加了 restoreOnFailure 而未更新草稿隔离切片)
  • 修复版本:d434aa48

BUG-309 | Docker npm run build 因会话 schema 与动态 select 类型检查失败

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:deploy/railway-web.DockerfileRUN npm run buildPOST /api/sessionsPOST /api/daily-starlanguagePOST /api/synastry
  • 用户现象:staging publish 在 web 镜像构建失败。BuildKit 日志末尾是 skill-package-registry.ts 的 Import traces(动态文件访问追踪警告),真正失败是 Failed to type check
  • 触发条件:向 staging 推送后走 publish 镜像构建。push 上的 validate 跳过 npm run build,因此质量门绿、镜像红。
  • 根因:两处互不相关的 TypeScript 收口。limitTranscriptSize() 的泛型下界是 { messages: { text: string }[] }.superRefine() 之后推断输出被收成这个下界,parsed.data 不再有 id,也丢失消息上的 receipt 字段。每日星语与合盘把出生列 .join(",") 成普通 string 再交给 supabase-jsselect 结果变成 GenericStringError,无法传入 AccountBirthRow
  • 修复:limitTranscriptSize 按输入 schema 的 Output 原样返回 z.ZodType<Output>。出生列改为共享字面量 ACCOUNT_BIRTH_SELECTglobalBirthProfileFromAccountRow 接受 unknown 再收成账户行。
  • 验证:npx tsc --noEmit 无错误(修复前 3 个 route + 2 个测试文件)。tsx --test tests/chat-session-write.test.ts tests/server-owned-birth-profile.test.ts tests/daily-starlanguage.test.ts tests/chart-library-other-profile.test.ts 36/36。
  • 防复发:创建会话 schema 必须保留 id;出生资料 select 不得用 .join(",") 得到非字面量字符串。staging push 的 validate 不跑 next build,类型回归只在 publish Docker 暴露。
  • 相关记录:BUG-261、BUG-308
  • 复发自:无
  • 修复版本:d7887b03

BUG-310 | 生时纠正宫位表贴着头像左缘,没有和 Agent 正文对齐

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:生时纠正对话里的 RectificationHouseTableView、候选时间卡、已采用时间说明
  • 用户现象:Agent 正文和赞踩操作栏缩在头像右侧,本命宫位标题和表格贴着对话左缘,和头像齐平。
  • 触发条件:校正对话出现本命宫位表。
  • 根因:宫位表是 .message-list 里消息行的兄弟节点,不走 .message-assistant 的头像 + gap 列,也没有补上同一条起始线。
  • 修复:当时用 --assistant-content-inset 把头像宽加 gap 补到表上,让它先和正文齐。后续在 BUG-311 改成快照卡片,不再用消息缩进冒充气泡。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts 曾锁定 inset;现由 BUG-311 的快照卡片合同接替。
  • 防复发:宫位表不是聊天气泡。不要再把它缩进成 Agent 正文;结果块要用独立卡片表面。
  • 相关记录:BUG-307、BUG-311
  • 复发自:BUG-307(表作为消息兄弟落地,未接入 assistant 列)
  • 修复版本:053e63ef

BUG-311 | 生时纠正宫位表被当成聊天正文,而不是随线索更新的当前盘面

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:生时纠正对话的宫位表
  • 用户现象:本命宫位出现在最后一条 Agent 回答下面,看起来像那条回复的一部分;补充经历后表格其实会整表替换,界面没有把这一点说清楚。
  • 触发条件:校正已经有 Candidate Snapshot,对话里同时出现 Agent 正文和宫位表。
  • 根因:表来自 Case 快照、挂在消息列表末尾,却按聊天气泡的起始线排版,标题也只说“本命宫位”。
  • 修复:宫位表单独放在消息列表后的 rectification-snapshot,表面与结果卡同类。标题改为“当前本命宫位”,并写明补充经历后会按新线索重算。时间选择卡不再进这个快照,见 BUG-312。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts 锁定快照容器在 ChatMessageRow 之外、不含候选卡、aria-live、重算文案,以及成功回合后 loadCandidate
  • 防复发:宫位表必须在消息气泡外,并作为聊天右侧工作台原地刷新,不得再挂在消息列表底部。文案必须说明随新线索重算。候选卡仍用 --assistant-content-inset 对齐 Agent 正文;宫位表不再用 inset 冒充气泡。
  • 相关记录:BUG-307、BUG-310、BUG-312、BUG-322
  • 复发自:BUG-310(对齐 Agent 正文后更像一条聊天)
  • 修复版本:4d872c68

BUG-312 | 生时纠正时间选择卡在线索未齐时直接出现,且不在 Agent 气泡下方

  • 状态:resolved
  • 首次发现:2026-08-19
  • 最近更新:2026-08-19
  • 影响面:生时纠正对话的候选时间卡、selectionAllowed、Case 快照
  • 用户现象:当前可能的出生时间作为独立盘面模块直接出现;没有等线索齐到可以给出代表性时间,也不挂在刚说完的 Agent 气泡下面。
  • 触发条件:Case 已有快照或宫位表;引擎尚未 selection_allowed,或 Agent 回合仍在生成。
  • 根因:候选卡和宫位表被捆在同一块始终可见的 rectification-snapshot 里。宫位表应当随线索更新;时间选择是“可以给出代表性时间”之后的采用动作,应对齐当轮 Agent 回复。
  • 修复:候选卡只在 selectionAllowed、存在最近一条已落地且未失败的 Agent 正文、且当前不 busy/不重跑时,画在该条气泡和操作栏下方。生成中隐藏,避免挂在上一条气泡下。--assistant-content-inset 只用于这块卡片,不用于宫位表。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts 锁定候选卡在消息循环内、操作栏之后、快照容器之外,以及 selectionAllowed / latestOfferMessageKey / !busy 门。
  • 防复发:时间选择卡不得进 rectification-snapshot;不得在 selectionAllowed 之前、回合生成中、或尚未 rectification-offer-candidates 时显示。宫位表仍是消息外的 live 快照。
  • 相关记录:BUG-120、BUG-179、BUG-304、BUG-311、BUG-313
  • 复发自:BUG-120(过早出示采用卡);BUG-311(与宫位表捆成始终可见模块)
  • 修复版本:4d872c68

BUG-313 | 相邻分钟还分不开时,生时纠正就把时间选择卡提前拿出来

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正候选卡、next_user_actionrectification-offer-candidates
  • 用户现象:相对支持度约 34 / 33 / 33 的相邻分钟已经出现“当前可能的出生时间”采用卡。文案自己也说还不能确认唯一分钟,但采用动作已经摆在对话里。
  • 触发条件:比较已经跑过,引擎 selection_allowed=true,方法层仍有 next_followup(例如关系/事业还能区分),Agent 还在收集。
  • 根因:selection_allowed 只表示可以采用代表性时间。UI 用它直接画卡;buildNextUserAction 也在仍有下一问时把本轮动作写成 adopt,并把 follow-up 推迟。相邻分钟平台因此被当成“现在就选”。
  • 修复:仍有 next_followup 时会话留在收集,本轮继续问;用户停止才走 adopt。界面只在最近一条已落地 Agent 回复完成 rectification-offer-candidatesselectionAllowed 时,把卡片挂在该气泡下方。
  • 验证:frontend/tests/rectification-eight-method.test.ts 锁定“有 follow-up 时 ask、停止才 adopt”;frontend/tests/rectification-agentic-entry.test.ts 锁定 offer-candidates 门。
  • 防复发:selection_allowed 不得单独出示采用卡。卡片必须等本轮 rectification-offer-candidates。有 next_followup 时不得 offer。
  • 相关记录:BUG-120、BUG-304、BUG-312
  • 复发自:BUG-120(过早出示采用卡);BUG-312(用 selectionAllowed 当出示门)
  • 修复版本:a732ff4b

BUG-314 | 生时纠正宫位表比 Agent 正文更宽、更靠左

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正 rectification-snapshotRectificationHouseTableView
  • 用户现象:当前本命宫位灰卡从对话左缘铺开,比上方 Agent 正文更宽、更靠左,无法和回答左缘对齐成一条竖线。
  • 触发条件:校正对话出现本命宫位快照。
  • 根因:宫位表改成消息外的 snapshot 卡后,容器不再吃 --assistant-content-inset,仍按整列 message-list 铺宽;Agent 正文在头像加 gap 之后。
  • 修复:snapshot 与候选卡同一列:width: calc(100% - var(--assistant-content-inset))margin-inline-start 用同一 inset。表本身保持卡片描边和内边距,不用 padding-inline-start 冒充气泡。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts 锁定 snapshot 与候选卡共用 inset 宽度和起始线,且 .rectification-house-table 不写 inset。
  • 防复发:宫位表在右侧工作台内,不再与 Agent 正文同列对齐。候选时间卡仍用 --assistant-content-inset 对齐气泡。不得把工作台再缩进成消息列。
  • 相关记录:BUG-310、BUG-311、BUG-322
  • 复发自:BUG-311(去掉消息缩进后未收回正文列)
  • 修复版本:a732ff4b

BUG-315 | Docker npm run build 因候选卡消息可能为 undefined 类型检查失败

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:deploy/railway-web.DockerfileRUN npm run build、生时纠正时间选择卡
  • 用户现象:staging publish 在 web 镜像构建失败。BuildKit 日志末尾是 skill-package-registry.ts 的 Import traces,真正失败是 Failed to type checklatestOfferMessageKey 可能为 undefined
  • 触发条件:向 staging 推送后走 publish 镜像构建。push 上的 validate 跳过 npm run build,因此质量门绿、镜像红。
  • 根因:showSelectionCardsBoolean(...) 包了一层,TypeScript 不会因此收窄 find()T | undefined。随后 showSelectionCards ? latestOfferMessageKey.renderKey 在 true 分支仍可能是 undefinednext build 类型检查退出 1。
  • 修复:变量改名为实际类型 latestOfferMessage;取 renderKey 时用 showSelectionCards && latestOfferMessage 收窄。
  • 验证:./node_modules/.bin/tsc --noEmit 无错误。tsx --test tests/rectification-agentic-entry.test.ts 锁定收窄写法。
  • 防复发:对 find() 结果取属性不得只靠外层 Boolean(...)staging push 的 validate 不跑 next build,类型回归只在 publish Docker 暴露。
  • 相关记录:BUG-309、BUG-312、BUG-313
  • 复发自:BUG-309validate 跳过 next build;日志末尾 Import traces 掩盖类型检查失败)
  • 修复版本:待提交

BUG-316 | 家人问了也不改候选;外貌/胎记被硬禁止追问

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正评分引擎、method_followup_plan、Skill jyotish-birth-time-rectification@10.0.4
  • 用户现象:已经在问家人变化,候选几乎不动。外貌/胎记根本不问。同一件事业经历看不出同时用了本命第 10 宫和 D10。
  • 触发条件:写入带日期的 family_event;或追问走到家人之后;或事业事件进入评分。
  • 根因:family_event 被标成背景、不进引擎;D12 未进候选分盘。Skill 10.0.3 与 Agent 提示禁止问外貌/胎记。事业虽已按 D1 第 10 宫 + D10 计分,公开方法层没有同时标出。
  • 修复:家人事件可评分(D12 + 三/四/五/九宫)。工具默认 subject=self 时按领域纠成 family,否则旧家人证据仍进不了引擎。外貌/胎记可问;无日期只覆盖访谈,有日期进上升/一宫辅助降权,不当主评分,也不计入采用/确认的领域数。同一事业事件公开方法层同时给出 d1-rashid10-dashamsa。新 Case 绑定 Skill 10.0.4。已有 10.0.3 Case 保持原绑定。
  • 验证:tests/test_rectification_family_appearance_scoring.py(含 D12 真实引擎行);frontend/tests/rectification-eight-method.test.ts 家人之后问外貌/胎记,占问仍跳过。
  • 防复发:family_event 不得再列入背景-only;家人领域必须把 subject 纠成 family。do_not_poll 只保留 horary。外貌不得重新变成主评分或 D9/D10 类型标签。
  • 相关记录:BUG-291
  • 复发自:无
  • 修复版本:待提交

BUG-317 | D4 精度阶段混问家人;D5/D7 不算分;采用后不重算也不给技法审计

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正精度阶段、家人/教育计分、采用后本命表、Skill jyotish-birth-time-rectification@10.0.5
  • 用户现象:居所盘换升时却被问家人。子女/伴侣细节和学业成就问了也难改候选。采用某个分钟后,宫位表仍停在代表性时间,也看不到本轮技法执行/阻塞。
  • 触发条件:窗口扫描出现 D4 换升;写入家人或教育事件;或采用非代表性候选。
  • 根因:theme_refine 把 D4 与家人混成一问。引擎分盘不含 D5/D7,家人只计 D12。采用后界面不按采用分钟切宫位表,也不投影执行账本。
  • 修复:精度阶段改为本命上升 → D9 → D10 → D4 居所 → D5 成就。家人走 D12+D7 方法覆盖,不混进 D4。教育事件同时公开 D24 与 D5。采用后按该分钟重算本命宫位,并折叠展示技法审计;KP / VedAstro / 唯一分钟确认保持 blocked。新 Case 绑定 Skill 10.0.5。已有 10.0.4 Case 保持原绑定。confirmation_allowed 仍为 false。
  • 验证:tests/test_rectification_refinement_packet.pyD4 不再是 theme_refine;按候选分钟重算宫位;技法审计含 blocked 唯一分钟);tests/test_rectification_family_appearance_scoring.pyD7/D5 进入可用层);frontend/tests/rectification-eight-method.test.tsd4 问搬家、d5 问学业);frontend/tests/rectification-candidate-result.test.ts(采用分钟切换宫位表)。
  • 防复发:D4 精度阶段不得再问家人。家人计分必须含 D7。采用后宫位表必须跟采用分钟走。技法审计不得把未执行层写成已执行,也不得声称唯一分钟。
  • 相关记录:BUG-316
  • 复发自:无
  • 修复版本:a93a3cc1

BUG-318 | 校时 VedAstro 已跑仍显示尚未评估;确认门写死 false,与密封 holdout 合同脱节

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正确认门、Technique Audit、V9 /v5/vedastro-validate 接线、Skill jyotish-birth-time-rectification@10.0.6
  • 用户现象:采用后技法审计里 VedAstro 一行仍写「尚未评估」,即使评分路径已经跑过官方分钟快照。界面看不到确认门三行 blocker。confirmation_allowed 在引擎里写死 false,与公开 AA holdout 合同各写各的。
  • 触发条件:本地采用门通过且存在两个不同 HH:MM 候选;或打开确认门/技法审计。
  • 根因:build_technique_audit 在 attach 之前写死 VedAstro=blocked。attach 只改 external_validation_status,不回写审计行。Python 确认门不读密封 holdout。V9 比较候选后不调用 /vedastro-validate。SearchEvents 若被当成确认判据会复发 BUG-303。
  • 修复:attach 后按 passed / failed / not_evaluated 回写 VedAstro 审计行;超时保持 not_evaluated,不等于 fail。确认门改为真实 AND(引擎 granted + 相邻可分 + VedAstro passed + 密封 holdout ready)。前后端同读 references/rectification_sealed_holdout.v1.json,当前仍为 1/20 not_ready。V9 在 selection_allowed 且有 primary/runner-up 时调用 /vedastro-validateSearchEvents 只作旁证,不得改 confirmation_allowed。结果卡折叠展示确认门三行。新 Case 绑定 Skill 10.0.6;已有 10.0.5 Case 保持原绑定。
  • 验证:tests/test_rectification_v5_vedastro_validation.pytests/test_rectification_confirmation_and.pyfrontend/tests/rectification-confirmation-gate.test.tsfrontend/tests/rectification-v9-engine-contract.test.tsfrontend/tests/rectification-candidate-result.test.ts
  • 防复发:VedAstro 审计行必须与 external_validation_status 一致。超时不得写成 fail。SearchEvents 赢了本地第一名不得放行确认。不得把 LOEO/LODO 或空 intake 写成 holdout ready。不得在密封集未达标时把 confirmation_allowed 设为 true。
  • 相关记录:BUG-303、BUG-317
  • 复发自:BUG-303(分钟快照已接到 score,但产品审计与确认门未消费该状态)
  • 修复版本:55d3119c

BUG-319 | 教育精度忽略 D24 换升;家人事件不算 D3;窗口扫描不展示细分钟层

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正窗口扫描、d5_refine、家人评分、Skill jyotish-birth-time-rectification@10.0.7
  • 用户现象:学业分盘 D24 已计分,但窗口里 D24 换升不会进入学业追问。带日期的家人事件只对照 D12/D7,不对照 D3。候选卡看不到 Nakshatra pada / Hora Lagna / Ghati Lagna 换升。
  • 触发条件:教育事件已计分且 D5 升不变、D24 升变化;或提交带日期的家人事件;或窗口内升点跨 Nakshatra pada / 真实日出后的 Hora/Ghati。
  • 根因:window_scan / precision_stage 只看 D1/D9/D10/D4/D5/D7/D12。DOMAIN_CONFIG["family"] 只有 D12/D7。细分钟层未写入候选 feature。
  • 修复:D24 换升并入既有 d5_refine,不新增阶段。家人评分增加 D3。Pada 始终写入窗口扫描;Hora/Ghati 仅在真实日出可算时写入,不用 06:00 假日出,且不阻断 ready_to_adopt、不打开确认门。新 Case 绑定 Skill 10.0.7;已有 10.0.6 Case 保持原绑定。评分缓存身份升为 rectification-v5-matrix-scoring-5
  • 验证:tests/test_rectification_refinement_packet.pytests/test_rectification_family_appearance_scoring.pytests/test_rectification_technique_contract.pyfrontend/tests/rectification-eight-method.test.tsfrontend/tests/skill-registry.test.ts
  • 防复发:D24 单独换升必须得到 d5_refine / education_style。家人 public_technique_layers 必须含 d3-drekkana。Pada/Hora/Ghati 换升只展示。不得把 D16/D20/D27/D40/D45/D60、KP、Gulika、Kunda 或 Nadiamsa 当作确认层。不得用假日出填 Hora/Ghati。
  • 相关记录:BUG-317、BUG-318
  • 复发自:无
  • 修复版本:70efb569

BUG-320 | 财务/健康要用户主动说才进访谈;Bhava/Pranapada 不进换升表

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正方法覆盖、窗口扫描、Skill jyotish-birth-time-rectification@10.0.8
  • 用户现象:D2/D11 财务和 D30 健康已能计分,但访谈不主动追问,几乎只在用户自己开口后才有事件。候选卡看不到 Bhava Lagna / Pranapada Lagna 换升。
  • 触发条件:关系/事业/家人已覆盖后继续访谈;或窗口内 Bhava/Pranapada 换升。
  • 根因:method_followup_plan.not_in_rotation 把 finance/health 和迁居一起排除。窗口扫描不读 D2/D11/D30,也不写 Bhava/Pranapada。
  • 修复:财务(D2+D11)和健康(D30)进入方法覆盖,家人之后、外貌之前追问。仍不得按 SQL missing_evidence_categories 轮询。D2/D11/D30 换升只驱动观察追问,不新增精度阶段、不阻断 ready_to_adopt。Bhava 用本命日月;Pranapada 仅在真实 Hora+Ghati 可算时写入。新 Case 绑定 Skill 10.0.8;已有 10.0.7 Case 保持原绑定。
  • 验证:tests/test_rectification_refinement_packet.pytests/test_rectification_family_appearance_scoring.pyfrontend/tests/rectification-eight-method.test.tsfrontend/tests/skill-registry.test.ts
  • 防复发:家人之后下一问必须是财务,再健康,再外貌。D11 或 D30 单独换升不得改 precision_stage。不得把财务/健康改回 SQL 类别轮询。不得用假日出填 Hora/Ghati/Pranapada。
  • 相关记录:BUG-291、BUG-319
  • 复发自:BUG-291(迁居继续不轮询;财务/健康被一并排除后不再被方法层追问)
  • 修复版本:1b93afc5

BUG-321 | Docker npm run build 因宫位表 && 与八方法测试字面量类型检查失败

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:deploy/railway-web.DockerfileRUN npm run buildrectification-candidate-result.tsrectification-eight-method.test.ts
  • 用户现象:staging publish 在 web 镜像构建失败。BuildKit 日志末尾是 skill-package-registry.ts 的 Import traces。
  • 触发条件:向 staging 推送 Skill 10.0.8 后走 publish 镜像构建。push 上的 validate 跳过 npm run build
  • 根因:日志末尾 Import traces 只是动态文件访问追踪警告,真正失败是 Failed to type checkselectedTime && houseTablesByTime[selectedTime]string | null 下会被推断成 "" | HouseTable | null?? 不能消掉空字符串。八方法测试对 do_not_poll 传入 "appearance",并对观察层比较永远不存在的 "d24" / "d2"tsconfig 包含 tests/**/*.ts,因此这些测试错误也会挡住 next build
  • 修复:宫位表改用三元取值;测试改为断言 do_not_poll 恰好是 horary,并用完整观察层列表证明 D24/D2 不单独成层。
  • 验证:./node_modules/.bin/tsc --noEmitnpx tsx --test tests/rectification-eight-method.test.ts
  • 防复发:对 string | null 做查找不得用 && 当存在性守卫。readonly ["horary"].includes() 只能传入 "horary"。staging push 的 validate 仍不跑 next build,类型回归只在 publish Docker 暴露。
  • 相关记录:BUG-309、BUG-315、BUG-320
  • 复发自:BUG-309validate 跳过 next build;日志末尾 Import traces 掩盖类型检查失败)
  • 修复版本:0eebf971

BUG-322 | 生时纠正宫位表和换升观察仍埋在消息列表底部

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正对话布局、rectification-snapshot、换升时刻
  • 用户现象:本命宫位和换升时刻跟在消息列表下面。每次补充经历后表会重算,但要滚过整段对话才能看见变化。
  • 触发条件:校正已有 Candidate Snapshot,继续在对话里补充经历。
  • 根因:BUG-311 把宫位表移出气泡,但仍放在 .message-list 末尾。换升时刻按图层一行一条平铺,不适合侧栏。
  • 修复:生时纠正单独做成聊天 | 工作台分栏。宫位表、按分钟合并的换升、确认门和大运对照放在右侧并原地刷新。候选时间卡和“用这个时间看盘”仍留在对话里。窄屏用“当前盘面”打开对话框,不挤第二列。普通咨询 session 不改。
  • 验证:frontend/tests/rectification-agentic-entry.test.tsfrontend/tests/rectification-board-model.test.ts
  • 防复发:rectification-snapshot 不得回到消息循环。换升不得再按 user_meaning 一层一行平铺。.conversation:not(.is-rectification) 的 session 列宽不得套到校正对话。
  • 相关记录:BUG-311、BUG-312、BUG-314
  • 复发自:BUG-311(移出气泡后仍挂在消息列表底部)
  • 修复版本:e07d1bad

BUG-323 | 访谈还在收集时,生时纠正就把时间选择卡提前拿出来

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正候选卡、session_outcomerectification-compare-candidatesrectification-offer-candidates、Skill jyotish-birth-time-rectification@10.0.9
  • 用户现象:只有学业和感情两块经历、比较已经跑过时,对话里已经出现“当前可能的出生时间”采用卡。文案仍说还不能确认唯一分钟,但卡片已经摆出来。事业、家人、财务、健康都还没问。
  • 触发条件:引擎 selection_allowed=true(事件≥3、领域≥2),方法覆盖仍有挡住出牌的下一问(例如事业),用户也没有说“暂时想不到了 / 没有更多 / 先这样”。Agent 在 compare 之后调用 offer-candidates。
  • 根因:采用门和提出门被合成一个。selection_allowed 只表示可以采用代表性时间。compare 的 session_outcome 不看 next_followupoffer-candidates 只要有快照就可出卡。追问顺序先重复已覆盖领域的精度阶段,事业等未覆盖领域被饿死。KP_cusps 被写死进 missing_layers,又被当成确认门必需层,不能用 engine_granted 当出示卡片的门槛。
  • 修复:引擎增加 propose_allowed(事件≥4、领域≥3、唯一领先、宽度≤5、诊断稳定;KP 政策跳过不挡提出门)。大运冲突和 20% 边距仍只挡唯一分钟确认。compare / read-case / offer 共用同一套会话结果:仍有挡住出牌的下一问时保持收集;用户喊停且已过采用门时可走逃生舱。offer 在会话不是 adopt/awaiting_confirmation 时拒绝,且不把 Case 改成 candidate_ready。方法覆盖(感情→事业→家人→财务→健康)先于已覆盖领域的精度追问。外貌/疤痕不挡出牌。新 Case 绑定 Skill 10.0.9;已有 10.0.8 Case 保持原绑定。
  • 验证:tests/test_rectification_confirmation_and.pyfrontend/tests/rectification-eight-method.test.tsfrontend/tests/rectification-confirmation-gate.test.tsfrontend/tests/skill-registry.test.ts
  • 防复发:selection_allowed 不得单独出示采用卡。有挡住出牌的 next_followupsession_outcome 必须保持 collect_evidence,offer 必须拒绝。不得改已哈希的 10.0.8 包。不得把 KP 政策跳过写成 KP 已执行。
  • 相关记录:BUG-120、BUG-297、BUG-313
  • 复发自:BUG-313(有 follow-up 时仍可因 compare 投影为 adopt 而 offer);BUG-297compare/offer 不共享提出门)
  • 修复版本:7dc97a49

BUG-324 | 侧栏合同仍断言固定 chat-panel classstaging frontend 测试失败

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:Gitea Jyotish Skill CI / backend-quality-gatenpm test --prefix frontendfrontend/tests/sidebar-contract.test.ts
  • 用户现象:staging 推送后质量门禁失败。日志末尾 truth source identity 两条都是 ok,摘要为 # tests 1833 / pass 1832 / fail 1
  • 触发条件:向 staging 推送含 BUG-322 生时纠正分栏布局的提交后跑 frontend 全量测试。
  • 根因:BUG-322 把 SidebarInset 的 class 改成 chat-panel 加上 rectificationSurfaceOpen 时的 is-rectification。侧栏源码合同仍匹配固定的 className="chat-panel",所以 not ok 1676 - composes the chat page with the app sidebar shell
  • 修复:侧栏合同改为锁定当前模板 class,并继续要求 inert={modalOpen}
  • 验证:npx tsx --test tests/sidebar-contract.test.ts
  • 防复发:改 SidebarInset class 时必须同步 sidebar-contract;生时纠正分栏 class 另由 rectification-agentic-entry 锁定。
  • 相关记录:BUG-322
  • 复发自:无
  • 修复版本:3970410e

BUG-325 | 经典八方法访谈被财务/健康轮询替换,KP 宫头被政策跳过冒充已观察

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正方法覆盖、method_followup_plan、KP 宫头观察、窗口扫描、Skill jyotish-birth-time-rectification@10.0.10
  • 用户现象:家人问完后下一问变成财务/健康,职业和占问不在访谈里。事业带日期事件被当成已经覆盖职业。KP 技法审计写成政策跳过,候选卡也看不到 KP 子主换升。
  • 触发条件:新 Case 走方法覆盖;或比较候选并查看技法审计 / 换升时刻。
  • 根因:BUG-320 把财务/健康放进方法轮询,职业并进 D10,Horary 固定 skipped_by_policy。本命盘用 Equal houses,事件引擎把 KP_cusps 写死进 blocked_layers 再并入评分 missing_layers;BUG-323 用政策跳过让提出门不挡,但没有单独算 Placidus + Krishnamurti 宫头。
  • 修复:方法覆盖恢复感情→事业→家人→外貌→疤痕→职业→占问。职业挡出牌,且不由事业事件自动覆盖。占问只问一次、不挡出牌;有问起时间则观察重算,失败写成 blocked 观察。财务/健康离开轮询,用户主动说仍计 D2/D11D30 并保留换升。KP 用 Swiss Ephemeris Placidus + Krishnamurti 单独快照,写入窗口扫描 kp1/4/7/10 和诚实技法审计;不计分、不挡提出门或确认门。新 Case 绑定 Skill 10.0.10;已有 10.0.9 Case 保持原绑定。评分缓存身份升为 rectification-v5-matrix-scoring-6
  • 验证:tests/test_rectification_kp_cusp_observation.pytests/test_rectification_confirmation_and.pytests/test_rectification_horary_observation.pytests/test_rectification_family_appearance_scoring.pytests/test_rectification_v5_services.pyfrontend/tests/rectification-eight-method.test.tsfrontend/tests/rectification-ingest-p0.test.tsfrontend/tests/skill-registry.test.ts
  • 防复发:家人之后下一问必须是外貌,不得再是财务。事业证据不得把 occupation 标成 covered。Horary 不得挡 offer。KP 不得再写死进评分 missing_layers,也不得把政策跳过写成已观察。不得改已哈希的 10.0.8 / 10.0.9 包。不得把 KP 观察计分或打开确认门。
  • 相关记录:BUG-291、BUG-320、BUG-323
  • 复发自:BUG-320(财务/健康进入方法轮询是当时的产品决定,本记录故意改回经典八方法);BUG-323(KP 政策跳过只解决提出门,没有做出真实观察)
  • 修复版本:84e6191f

BUG-326 | 窄屏盘面对话框在 render 里写 refstaging ESLint 失败

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:Gitea backend-quality-gatenpm run lint --prefix frontendfrontend/src/components/rectification-board.tsx
  • 用户现象:staging 推送后质量门禁失败。摘要为 ✖ 11 problems (1 error, 10 warnings)
  • 触发条件:向 staging 推送含窄屏 <dialog> 盘面的提交后跑 frontend lint。
  • 根因:react-hooks/refs 禁止在 render 期间更新 ref.current。窄屏盘面用 onCloseRef.current = onClose 保存最新关闭回调,触发 Cannot update ref during render。另外 10 条是既有 unused-vars / exhaustive-deps warning,不构成这次失败。
  • 修复:关闭监听改为在 effect 里直接调用 onClose,并把 onClose 放进依赖。
  • 验证:npx eslint src/components/rectification-board.tsxnpx tsx --test tests/rectification-agentic-entry.test.ts
  • 防复发:盘面对话框不得在 render 里写 ref.current;源码合同锁定 close 监听只出现在 effect。
  • 相关记录:BUG-322
  • 复发自:无
  • 修复版本:dbbcb71b

BUG-327 | Web 对话与生时纠正仍露出原生滚动条

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:普通 session .conversation、生时纠正消息列表、右侧盘面 .rectification-board__body
  • 用户现象:聊天和生时纠正内容超出高度时,直接出现浏览器原生滚动条,和侧栏已有的安静 overlay 滚动条不一致。
  • 触发条件:普通对话或生时纠正历史超过可视高度,在桌面 Chromium / Firefox 中滚动。
  • 根因:只有侧栏 SidebarContent 使用产品内滚动条;会话区和盘面仍是 overflow-y: auto 并保留 scrollbar-gutter,因此继续露出系统滚动条。
  • 修复:会话列表、普通对话和生时纠正盘面共用同一套安静 overlay 滚动条:透明轨道、无 gutter、默认隐藏拇指,悬停或键盘焦点后显示低对比圆角拇指。
  • 验证:frontend/tests/sidebar-contract.test.tsfrontend/tests/rectification-agentic-entry.test.ts
  • 防复发:独立滚动容器必须同时覆盖标准 scrollbar 属性和 WebKit 伪元素,且不得再用 scrollbar-gutter 给系统滚动条留槽。
  • 相关记录:BUG-042
  • 复发自:BUG-042(只修了当时的生时纠正消息区,后来的 session 列宽和盘面分栏又回到原生滚动条)
  • 修复版本:994f0583

BUG-328 | 移动端盘面以模态挡住聊天且关闭入口弱

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正窄屏盘面、rectification-board
  • 用户现象:打开“当前盘面”后,白色面板盖住大部分聊天,几乎读不到刚才的对话;关闭只能靠一段会折行的文字按钮。
  • 触发条件:窄于分栏阈值的视口打开宫位表。
  • 根因:窄屏用 showModal() 对话框加不透明 backdrop,并把聊天设为 inert;高度约 86dvh / 100dvh,聊天被遮死。
  • 修复:窄屏改为工作区分栏下半屏 sheet(约 46% 高),聊天留在上半屏可继续阅读;关闭改为 44px 图标按钮,Escape 仍可关闭。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts
  • 防复发:关闭控件必须是独立 44px 目标并带 aria-label="关闭盘面"。窄屏分层策略以 BUG-330 为准:必须用高于输入框的覆盖层,不得再与输入框做同层网格分栏。
  • 相关记录:BUG-322、BUG-326、BUG-330
  • 复发自:BUG-322(分栏后窄屏改成对话框,聊天被挡住)
  • 修复版本:994f0583

BUG-330 | 移动端盘面与输入框同层叠字,无法作为弹出层使用

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正窄屏盘面、rectification-board、输入框
  • 用户现象:点开“当前盘面”后,盘面标题与输入区“当前盘面”文案叠在一起;宫位表像嵌在输入框同一层,而不是从下往上盖住聊天列表和输入框。
  • 触发条件:窄于分栏阈值的视口打开宫位表。
  • 根因:BUG-328 把窄屏盘面改成工作区第二行(约 46% 高)且未设叠层;输入框 .composer-wrap 仍是 position: relative; z-index: 2。盘面与输入框在同一工作区网格里重叠时,输入框画在盘面标题之上。
  • 修复:仅在视口宽度小于 768px 的移动端,把盘面改成工作区内 z-index: 20 的底部 sheet 覆盖层;桌面网页仍是右侧分栏,不出现底部弹出。遮罩点击、Escape、44px 关闭按钮可关;聊天列在打开时 inert
  • 验证:frontend/tests/rectification-agentic-entry.test.ts
  • 防复发:底部 sheet 只允许 matchMedia(max-width: 767px) 触发;工作区必须 isolation: isolatez-index: 0,打开时隐藏 composer(含 backdrop-filter),覆盖层 z-index 必须高于输入框。不得用聊天区 clientWidth 把桌面侧栏挤窄误判成移动端。
  • 相关记录:BUG-322、BUG-326、BUG-328
  • 复发自:BUG-328(当时为了露出聊天改成下半屏分栏,叠层低于输入框)
  • 修复版本:9670b661

BUG-331 | Raman 默认岁差后,Lahiri 校准的 CLI smoke 仍按旧盘面断言

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:Gitea backend-quality-gate Python quick quality gate、tests/test_cli_smoke.pytests/golden/golden_cases.json 只被 JSON 校验通过后进入同一套 pytest
  • 用户现象:质量门禁打印 json ok tests/golden/golden_cases.json 后继续跑 audit_* / validate_bphs_invariants / pytest tests/test_cli_smoke.py ...。JSON 本身有效;失败发生在 CLI smokeDasha 起始时刻、D9 Jupiter 尊严、Darakaraka 行星与 Navamsa 敌友标签对不上。
  • 触发条件:产品默认岁差改为 Raman 后,未给 Lahiri 校准样本显式传 --ayanamsa lahiri,再跑 quick quality gate。
  • 根因:这些断言锁的是旧 Lahiri 盘面(例如 1952-03-19T20:24:47NEECHA_BHANGA、DK=Sun)。默认 Raman 后月亮分宿与分盘换升改变,断言失败。golden_cases.json 的 SAV=337 等不变量与岁差无关,JSON 校验和 golden runner 仍通过。
  • 修复:Lahiri 校准的 dignity / Dasha / DK 样本显式传 --ayanamsa lahiri。产品默认仍是 Raman;Raman 元数据测试继续显式传 raman
  • 验证:tests/test_cli_smoke.py 中上述失败项;tests/test_cli_smoke.py::test_full_reading_golden_cases_cover_user_ready_output
  • 防复发:对照样本必须显式写岁差名,不得依赖引擎默认;改 DEFAULT_AYANAMSA_NAME 后必须跑 tests/test_cli_smoke.py 与 quick quality gate 的 CORE pytest 列表。
  • 相关记录:无
  • 复发自:无
  • 修复版本:4b02f63a

BUG-332 | 校时 skill 呈现改完后,前端质量门仍锁旧分盘句禁令和透传分数账本

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:Gitea backend-quality-gatenpm test --prefix frontendfrontend/tests/rectification-agentic-entry.test.tsfrontend/tests/rectification-candidate-result.test.ts、公开 event_dasha_ledger
  • 用户现象:质量门 tests 1840pass 1838 / fail 2。日志末尾是已通过的 health truth-source 合同;真正失败是校时 Agent 提示合同和候选账本解析。
  • 触发条件:把出牌/采用轮改成粘贴 skill_verification_report,并把账本 user_meaning 透传引擎原文后,再跑完整前端套件。
  • 根因:rectification-agentic-entry 仍要求提示词写「分盘句和宫位表由界面展示」,与现行「验证报告进正文、宫位表仍由盘面展示」冲突。parseEventDashaLedger 改为原样采用 user_meaning 后,公开账本会带上 12.5 points 一类评分泄漏。
  • 修复:入口合同改为锁定 skill_verification_report / D9-D10 类型对照,并明确不得再写旧分盘句禁令。公开账本只从 summary、匹配档和结构化 gochara 重建文案,拒绝 points 泄漏。
  • 验证:frontend/tests/rectification-agentic-entry.test.tsfrontend/tests/rectification-v9-agent.test.tsfrontend/tests/rectification-candidate-result.test.tsfrontend/tests/rectification-eight-method.test.ts。完整 npm test --prefix frontend 由 Gitea quality gate 验收。
  • 防复发:校时提示词合同必须与 rectification-v9-agent 同向;公开 ledger/window copy 不得透传可能含分数或星座类型标签的引擎 user_meaning
  • 相关记录:BUG-307、BUG-308、BUG-331
  • 复发自:BUG-307(当时把分盘句和宫位表从正文挪到界面后,入口合同锁死了那句提示词)
  • 修复版本:379f0e6f

BUG-333 | staging 发布 API 镜像时 DaoCloud TLS 握手超时

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:Gitea backend-quality-gatepublish job、deploy/railway-api.Dockerfilexiaoxin runner 拉官方基础镜像
  • 用户现象:validate 通过后 Build and publish exact-SHA ACR images 在 API Dockerfile 第一行失败:Head "https://m.daocloud.io/v2/docker.io/library/python/manifests/3.12-slim" TLS handshake timeout。
  • 触发条件:向 staging 推送后 publish 构建 deploy/railway-api.Dockerfile
  • 根因:API 基础镜像仍走 DaoCloud 代理。同一 runner 已验证可达华为 SWR / ECR / ACRWeb 镜像早已改用 swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/node:22-alpineAPI 未跟上。
  • 修复:API FROM 改为与 Web 相同的华为 SWR docker.io/library 路径,指向 python:3.12-slim。不改运行时端口、pip/apt 镜像或 ACR 目标仓库。
  • 验证:tests/test_railway_deployment.pyfrontend/tests/staging-backend-workflows.test.ts。远端 publish 由本提交后的 Gitea quality gate 验收。
  • 防复发:API/Web 官方基础镜像必须走华为 SWR ddn-k8s/docker.io/library,合同禁止 daocloud。不要为了重试再加一套 for attempt in 1 2 3,workflow 里该循环次数已被合同锁死为 5。
  • 相关记录:BUG-149、BUG-150、BUG-332
  • 复发自:无
  • 修复版本:9c4b64c9

BUG-334 | 移动端盘面覆盖层仍被输入框 backdrop-filter 盖住

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正窄屏盘面、.composer-wrap.rectification-board-overlay
  • 用户现象:打开“当前盘面”后,输入区预览条仍叠在弹层标题上;覆盖层和输入框像同一层,聊天列表也被挡住看不清。
  • 触发条件:移动端视口打开宫位表。iOS Safari 上更明显。
  • 根因:.composer-wrapz-index: 2backdrop-filter。iOS 会把该滤镜层合成到覆盖层之上。BUG-330 只把 overlay 设为 z-index: 20,工作区没有独立层叠上下文,也没有在打开时关掉输入框滤镜。
  • 修复:工作区 isolation: isolate; z-index: 0 锁住输入框层叠;覆盖层 z-index: 50。打开盘面时隐藏 composervisibility: hidden、关掉 backdrop-filter),并收起预览条。sheet 高度改为 72%,上方聊天列表可从遮罩后看见。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts
  • 防复发:窄屏覆盖层必须高于 composer;打开时 composer 不得再带 backdrop-filter。源码合同锁定 isolation: isolate、overlay z-index: 50visibility: hidden。展开入口不得放在输入框上方,必须挂在页头右上角。
  • 相关记录:BUG-328、BUG-330、BUG-335
  • 复发自:BUG-330(覆盖层 z-index 未锁住 iOS backdrop-filter 合成层)
  • 修复版本:8b8e5214

BUG-335 | 移动端当前盘面入口仍在输入框上方

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:生时纠正窄屏盘面入口、.chat-header-actions.rectification-board-peek
  • 用户现象:点开盘面的按钮在输入框上方,占掉打字区;希望放到右上角。
  • 触发条件:窄屏生时纠正会话。
  • 根因:RectificationBoardPeek 渲染在 .composer-wrap 里,输入框上方整条通栏。
  • 修复:页头 chat-header-actions 增加挂载点;窄屏未打开盘面时把预览按钮 portal 到余额按钮左侧。输入框上方不再放展开入口。
  • 验证:frontend/tests/rectification-agentic-entry.test.ts
  • 防复发:源码合同锁定 createPortaldata-rectification-header-slot,并禁止 composer 内出现 RectificationBoardPeek
  • 相关记录:BUG-330、BUG-334
  • 复发自:BUG-330(窄屏入口一开始就放在输入区)
  • 修复版本:8b8e5214

BUG-336 | 初始化对话列表与资料卡不在同一栏

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:首页 onboarding .welcome、出生资料卡、输入框
  • 用户现象:新用户初始化时,对话气泡和出生日期卡片左右对不齐;称呼阶段输入框也比上面的对话更窄。
  • 触发条件:未完成资料的新会话,尤其是填写出生日期与时间的步骤。
  • 根因:.welcome 被后续规则拉到 1040px 且无水平居中;资料卡 max-width: 680/760px 靠左。会话正文和输入框已经收成 760px 栏,初始化没有走同一套栏宽。
  • 修复:未完成资料时给会话加上 is-onboarding。欢迎区和资料卡使用 --session-column-width,与正式会话、输入框同一栏。
  • 验证:frontend/tests/session-conversation-layout.test.ts
  • 防复发:源码合同锁定 is-onboarding、欢迎区栏宽和资料卡 width: 100%; max-width: none
  • 相关记录:无
  • 复发自:无
  • 修复版本:8b8e5214

BUG-337 | 事件吻合已达提出门槛后,生时纠正仍继续 A/B/C/D 追问且不出时间卡

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:生时纠正提出门、精度阶段、method_followup_planrectification-offer-candidates
  • 用户现象:已确认事件与主限/行运高度吻合(事件吻合率≥80%),代表性时间已挤进约 14 分钟不可分区间,但对话仍继续 A/B/C/D 主题问卷,不出时间选择卡,也不自动采用。继续补事件会把已收敛的候选窗问偏。
  • 触发条件:申报窗口跨多个本命上升,但得分簇已落在同一上升的十余分钟平台;可评分事件和领域已过提出门槛。用户没有说“暂时想不到了 / 没有更多 / 先这样”。
  • 根因:precision_stage 用整段申报窗口扫描,跨上升时一直停在 lagna_frame。该方法追问插在方法覆盖之前,且 isOfferBlockingFollowup 把精度阶段的 dasha_events 当成挡牌问。BUG-323 又把唯一领先和宽度≤5写进 propose_allowed,14 分钟平台永远不能提出代表性时间。并列分钟本应只挡唯一分钟确认。
  • 修复:精度阶段改为扫描候选簇(按不可分宽度扩到代表分钟附近),不再用整段申报窗决定是否还要拆上升。propose_allowed 看事件≥4、领域≥3、诊断稳定,或事件吻合率≥80%;唯一领先和宽度≤5只进确认门。方法覆盖(感情→事业→家人→外貌→疤痕→职业→占问)先于 lagna_frame。精度阶段追问不挡出牌;职业仍挡。不自动 accepted / candidate_ready,仍要用户点时间卡。不改哈希冻结的 Skill 10.0.10 包。
  • 验证:tests/test_rectification_refinement_packet.py(整段窗三个上升、候选簇一个上升时 precision_stage 不是 lagna_frame);tests/test_rectification_confirmation_and.py14 分钟并列簇 propose_allowed 为真、confirmation_allowed 为假);frontend/tests/rectification-eight-method.test.tslagna_frame 先问未覆盖事业;经典八法覆盖后 lagna_frame 不挡出牌)。
  • 防复发:不得把确认门的唯一领先或宽度≤5重新写进 propose_allowedprecision_stage 不得再用整段申报窗决定 lagna_frame。精度阶段追问不得挡住 rectification-offer-candidates。不得把校时 skill 从 10.0.10 改哈希包。
  • 相关记录:BUG-297、BUG-313、BUG-323
  • 复发自:BUG-323(把确认宽度写进提出门);BUG-297(并列分钟应给出代表性时间,而不是继续当收集失败)
  • 修复版本:a88467ff

BUG-338 | 发布新版本后,旧网页停在「正在载入账户」转圈进不去

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:首页 bootstrap、/ / /login HTML 缓存、Next deploymentId、发布后已打开的旧标签页
  • 用户现象:旧聊天页或已打开的网页,在 push 新版本之后一直停在「正在载入账户 / 同步个人资料与对话记录」,进不到对话。Safari 可能同时提示降低高级隐私保护。
  • 触发条件:Web 镜像切换后,用户仍使用上一发布的 HTML/JS(后台冻住的标签、浏览器缓存的首页,或 401 跳转登录页时新 chunk 404)。
  • 根因:首页 SSR 在 hydrated 完成前就画引导动画。新镜像的 /_next/static 文件名已变,旧客户端加载失败后 hydration 和 8 秒超时都不跑。next.config.ts 未设置 deploymentId,HTML 也可被浏览器缓存。账户 401 会 replace("/login") 并清掉超时、不写 accountError,登录页脚本同样 404 时界面条件仍是「没账户也没错误」,继续转圈。
  • 修复:构建时把 Git SHA 写入 Next deploymentId,让跨发布客户端导航整页重载。//login 响应 Cache-Control: private, no-store。根 layout 对 chunk 加载失败和冻在引导层的 pageshow 做一次硬刷新。401 跳转不再清掉 bootstrap 超时,登录没走成时落到可重试错误页。成功载入后清掉刷新标记,避免死循环。
  • 验证:frontend/tests/stale-client-recovery.test.tsfrontend/tests/membership-page.test.tsfrontend/tests/staging-backend-workflows.test.ts
  • 防复发:Web 镜像必须在 next build 时注入 NEXT_DEPLOYMENT_IDnext.config.ts 写入 deploymentId。首页 HTML 不得长期缓存。引导动画不得在「无账户且无错误」时成为发布失败的终态。
  • 相关记录:BUG-160、BUG-204
  • 复发自:BUG-204(报告页 chunk 偏斜已修,首页引导层仍会把同一失败画成永久 loading)
  • 修复版本:50e1a4ac

BUG-339 | staging quality gate 因盘面入口 effect 同步 setState 失败

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:Gitea backend-quality-gate validaterectification-agentic-chat 窄屏盘面入口 portal
  • 用户现象:向 staging 推送后 quality gate 失败,镜像未发布,站点仍停在上一版。日志是 react-hooks/set-state-in-effect
  • 触发条件:npm run lint --prefix frontend。自 8b8e5214 起,gate run 2019/2020/2021 均因此失败。
  • 根因:窄屏盘面入口用 useLayoutEffectquerySelector 后立刻 setHeaderSloteslint-config-next 禁止在 effect 同步 setState。BUG-335 把入口 portal 到页头时引入该写法。
  • 修复:页头挂载点改用 callback ref,把 DOM 节点作为 headerSlot 传给校时会话。仍 createPortaldata-rectification-header-slot,不再在 effect 里查 DOM 或 setState。
  • 验证:frontend/tests/rectification-agentic-entry.test.tsnpm run lint --prefix frontend -- src/components/rectification-agentic-chat.tsx
  • 防复发:盘面入口不得在 effect 里 querySelector 后同步 setState。源码合同锁定 callback ref 与 headerSlot prop,并禁止 chat 内 setHeaderSlot
  • 相关记录:BUG-335、BUG-334
  • 复发自:BUG-335(把入口 portal 到页头时用了 effect 同步 setState
  • 修复版本:f4619df2

BUG-340 | 生时纠正未关闭 thinking,半截回答仍可能被当成完成

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:POST /api/rectification/agentrunV9AgentTurn、生时纠正与普通咨询等待态
  • 用户现象:Agent 写到一半停止,界面却像已经答完;等待期间只有「正在处理…」,看不出在做什么。
  • 触发条件:当前会话模型默认开启 thinking / reasoning;可见正文与隐藏推理共用输出预算,或墙钟超时后仍发出 finish
  • 根因:BUG-305 只修了咨询路径。纠正 agent.stream 没有 maxOutputTokens、没有关闭 thinking,并把任意 finish 当成成功。进度事件 tool.activity started 被客户端丢掉,所以长计算期间用户只能干等。
  • 修复:咨询与纠正共用 agentGenerationSettings8192 可见 tokenthinking disabled)。纠正在 finish_reason=length 时走 answer_truncated、不扣点、不把半截写入成功 Turn;客户端保留已流出正文并提示未完成。等待态改为公开工具进度(正在比较候选时间等)和 8 秒后的已用时,不展示模型思维链。
  • 验证:frontend/tests/rectification-v9-stream.test.ts 的 length 夹断不得 run.completedfrontend/tests/rectification-v9-agent.test.ts 锁定纠正流的输出预算与 thinking disabledfrontend/tests/agent-activity-progress.test.tsfrontend/tests/rectification-agentic-entry.test.tsfrontend/tests/consultation-workflow-contract.test.ts
  • 防复发:纠正与咨询必须走同一套 generation settings。不得为了等待体验打开 provider thinking。tool.activity started 必须驱动 Orb 文案。finish_reason=length 不得映射为 run.completed
  • 相关记录:BUG-305、BUG-282、BUG-329
  • 复发自:BUG-305(咨询已修,纠正仍用默认 thinking 与任意 finish
  • 修复版本:6a44c778

BUG-329 | 生时纠正 Agent 回答在结算后一次性出现,推理中无法停止

  • 状态:resolved
  • 首次发现:2026-08-20
  • 最近更新:2026-08-20
  • 影响面:POST /api/rectification/agentrunV9AgentTurn、生时纠正输入框
  • 用户现象:发送经历后接口长时间无增量文字,工具活动与回答在计费完成后才整段出现;推理过程中发送按钮不可用,也无法停止。
  • 触发条件:任意生时纠正 message / opening 流,尤其是 rectification-set-focus 同轮重试的长等待。
  • 根因:成功 attempt 的 tool.activity 与 answer.delta 被攒到 billing.complete 和持久化之后才发给浏览器。前端 fetch 没有 AbortSignal,输入区也没有停止按钮。
  • 修复:工具活动和回答增量在生成过程中即时发布;失败重试先发 attempt.reset 清掉弃用 attempt 的可见文字。前端在推理中显示停止按钮,中止请求;已流出的文字保留。
  • 验证:frontend/tests/rectification-v9-stream.test.tsfrontend/tests/rectification-agentic-entry.test.ts
  • 防复发:answer.delta 必须在 billing.settled 之前出现在公共流里;生时纠正 composer 在 busy 时必须提供停止按钮并绑定 fetch abort。
  • 相关记录:BUG-039、BUG-067
  • 复发自:BUG-039(当时为了 attempt 隔离把增量攒到结算后,V9 流式合同被一起推迟)
  • 修复版本:994f0583

BUG-341 | 无准确出生分钟时普通咨询被整题拒绝并叉到生时校正

  • 状态:resolved(已提交,待 staging 验收)
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:无具体出生分钟用户的普通 session、POST /api/consult 一般咨询与声明窗口咨询、首页「每日运势」与主题卡、General Agent / Window Agent、个人报告入口、无分钟输出 guard
  • 用户现象:初始化只填了日期/地点或大致时段、没有准确到分钟的出生时间后,在普通咨询里问「今天的运势」会被回复「当前一般咨询模式不能生成个人星盘结论」,并要求在一般知识和生时校正里二选一。用户再点「出生时间校正」仍留在普通 session,模型把服务端当前时间误认为用户提供了出生时间,然后改口给公共日盘。时段用户也无法进入事业/婚恋等主题咨询。未校正但已填报到分钟的用户无法生成个人报告。
  • 触发条件:birth_time_sourceperiod_only / unknown 等无具体分钟状态;或已有 reported_birth_time 但状态仍是 reported/candidate;从首页「每日运势」或普通 session 发出今日运势或主题问题;或在同一普通 session 里发送「出生时间校正 / 生时校正」类标签。
  • 根因:产品把生时校正当成功能门,而不是可选增强。无分钟用户被压进 general_no_birth_timeGeneral Agent 对任何本命预测整题拒绝并给出「百科 / 生时校正」二选一;首页无分钟日运还把 daily_starlanguage 入口丢掉。报告入口只认 accepted/confirmed,把未校正填报分钟也拦掉。这是 BUG-202 的回归叠加产品能力分层缺失。
  • 修复:校正改为可选。未校正填报分钟走 unverified_birth_time,可用咨询、今日星语和完整报告(标明未校正)。只有日期+时段或未知时刻走 declared_birth_window:约 4 个探针比较稳定层/变动层,禁止把中点、00:00、中午或探针写成出生分钟;主题咨询用窗口工具,每日运势仍走公共 Panchanga。无分钟百科咨询保留给真正没有日期/地点的情况。报告使用 reported_birth_time 且不把 reported 映射成 accepted。普通 session 的生时校正标签打开校正会话。
  • 验证:frontend/tests/consultation-entrypoint.test.tsfrontend/tests/consultation-birth-time-mode.test.tsfrontend/tests/consultation-route-service.test.tsfrontend/tests/declared-birth-window.test.tsfrontend/tests/starter-questions.test.tsfrontend/tests/personal-report-api.test.tsfrontend/tests/timing-output-guard.test.tstests/test_declared_window_chart.py
  • 防复发:period_only / unknown 不得再进无盘死胡同。窗口探针不得用时段中点、00:00 或任意单一分钟冒充出生时间。窗口请求不得携带 hour/minute。未校正填报分钟可出报告,窗口用户仍不得出完整本命报告。无分钟「每日运势」不得把 daily_starlanguage 丢掉。普通 session 不得把生时校正标签当一般咨询原文。General Agent 不得再写「exactly two safe next steps」。
  • 相关记录:BUG-009、BUG-073、BUG-202、BUG-273
  • 复发自:BUG-202(服务端公共日盘已修好,首页无分钟入口后来又把 entrypoint 丢掉,普通 session 仍走整题拒绝;校正还被当成功能门)
  • 修复版本:9958e00a

BUG-342 | BUG-341 未更新咨询源码合同,staging quality gate 连续失败

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:Gitea backend-quality-gate validatefrontend/tests/application-billing-contract.test.tsfrontend/tests/consultation-stream-recovery.test.tsfrontend/tests/chat-navigation-a11y-contract.test.tsfrontend/tests/composer-isolation-contract.test.ts;点选卡提交因此无法发布
  • 用户现象:向 staging 推送后质量门失败,镜像未发布,站点仍停在 e7f4030e。run 2026BUG-341)与 run 2027(点选卡)均是 validate 失败、publish 跳过。前端测试摘要 # tests 1871 / pass 1865 / fail 6
  • 触发条件:npm test --prefix frontend。BUG-341 增加声明窗口咨询路径并改了首页草稿变量后,精确计数合同仍按两条 agentic 路径与 setDraftEntrypoint(entrypoint) 断言。
  • 根因:咨询路由现有三条 agentic 首流(公共/百科、声明窗口、本命)及对应重试,continueAfterDisconnectabortSignalsettleRun onError/onCancel 都变成 3/5。首页 send() 把草稿 entrypoint 收成 consultEntrypoint,且校正交接会在积分检查前清一次草稿。合同仍用 send() 里第一次 setDraft("") 当发送清草稿标记,切片失效。这是 BUG-308 同类:产品改了字面,源码合同没一起改。
  • 修复:把计量次数改成三条路径的实际次数;失败回填断言 consultEntrypoint;积分不足合同改为对照真正发送清草稿(conversationAnchor.anchorToLatest() 之后),不再误伤校正交接。
  • 验证:上述四份合同测试本地 35/35;此前失败的 6 项均通过。
  • 防复发:再增加咨询 agent 路径时必须同步 usages.pushretryForAnswercontinueAfterDisconnectabortSignal: agentAbortSignalsettleRun onError/onCancel 的精确计数。切 send() 清草稿不得用文件里第一次 setDraft("")。失败回填的 entrypoint 变量名必须随 send() 一起改。
  • 相关记录:BUG-308、BUG-341
  • 复发自:BUG-308(源码合同锚点过期把无关提交打红);BUG-341(第三条咨询路径与草稿变量未改合同)
  • 修复版本:6a0394aa

BUG-343 | 生时纠正点选卡 effect 同步 setStatestaging lint 失败

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:Gitea backend-quality-gate validaterectification-agentic-chat 案例快照加载、rectification-choice-card 换题重置
  • 用户现象:BUG-342 合同修过后 npm test 1871/1871 通过,但 npm run lintreact-hooks/set-state-in-effect 失败。run 2028 validate 9m39s 失败,publish 1 秒跳过,站点仍停在 e7f4030e
  • 触发条件:npm run lint --prefix frontend。挂载校时会话或换一道 A/B/C/D 题。
  • 根因:挂载时 useEffect 直接调用含 setStateloadCaseSnapshot();点选卡用 effect 在 question_id 变化时 setSelectedKey("")eslint-config-next 禁止 effect 同步 setState。同类于 BUG-339。
  • 修复:案例快照改为 fetch().then 回调里应用 payload(事件处理仍走 loadCaseSnapshot)。点选卡用 key={question_id} 换题重挂,不再在 effect 里清选中态。
  • 验证:npm run lint --prefix frontend 0 errorfrontend/tests/rectification-agentic-entry.test.ts
  • 防复发:校时 UI 不得在 effect 里直接 setState 或调用会 setState 的 helper。换题重置必须用 key 重挂。合同禁止 void loadCaseSnapshot()setSelectedKey("")
  • 相关记录:BUG-339、BUG-342
  • 复发自:BUG-339(盘面入口 effect 同步 setState;点选卡与快照加载又写了同一模式)
  • 修复版本:a4ab229e

BUG-344 | 声明窗口模式未收窄本命类型,staging publish 镜像构建失败

  • 状态:resolved
  • 首次发现:2026-08-21
  • 最近更新:2026-08-21
  • 影响面:Gitea backend-quality-gate publishconsultation-route-service.serverChartFromProfileisNatalMinuteConsultationMode
  • 户现象:run 2029 validate 12 分钟通过,publishnpm run build 失败,镜像未发。站点仍停在 e7f4030e
  • 触发条件:staging push 的 publish 镜像构建跑 next build。validate 对 staging push 跳过 production build。
  • 根因:BUG-341 给 ConsultationBirthTimeMode 增加了 declared_birth_window,但 isNatalMinuteConsultationMode 仍返回 booleanTypeScript 无法收窄成 verified_chart | unverified_birth_timeserverChartFromProfilenext build 报 TS2345。
  • 修复:把该函数改成 mode is NatalMinuteConsultationMode 类型谓词。
  • 验证:frontend/tests/consultation-birth-time-mode.test.ts;本地 tsc --noEmit 通过。
  • 防复发:本命分钟模式判定必须是类型谓词。新增咨询模式时不得把窗口/百科模式传入 serverChartFromProfile。staging push 的类型错误只会在 publish 的 next build 暴露。
  • 相关记录:BUG-341、BUG-343
  • 复发自:BUG-341(第三条咨询路径扩大了 mode 联合类型,未改类型谓词)
  • 修复版本:eeee0e49