Gate run 2857 on 36a73761 ran 3,762 tests with one failure, the report
snapshot database test, so it is the last thing between staging and a deploy.
The brief told the executor to move the shared psql and psqlAs helpers to
stdin. That instruction is withdrawn. The two helpers serve 522 calls across
29 test files, and psql -c runs a multi-statement string as one implicit
transaction. selectAsAuthenticated relies on that: it sets the JWT subject
with set_config(..., true), which lasts only for the current transaction.
Statement-at-a-time execution would run every later RLS query with no user
set, so those tests would stop testing what they claim to test. Five other
files also write their own begin, commit or rollback.
The brief now asks for a separate single-transaction stdin helper with an
explicit maxBuffer, used only by the snapshot test, with a test proving a
transaction-local set_config stays visible. It also says that if the snapshot
test then fails a real assertion, that is a product defect to report, not an
assertion to relax.
Privacy scan on this tree before push: zero findings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
1.7 MiB
Bug History
本文件是本仓库产品与代码 Bug 的长期知识库。处理任何 Bug 前先读并搜索本文件;完成修复或确认阻塞后,在同一变更中更新本文件。
基础设施、外部引擎、碎片目录和开工预检类风险仍记录在 docs/research/pre_work_error_ledger.md。同一问题若同时影响两类台账,应互相引用,不复制大段内容。
使用流程
- 用报错原文、接口路径、状态码、模块名和用户操作搜索本文件。
- 找到相似记录时,先验证既有防复发措施是否仍存在,再定位新的回归入口。
- 修复必须包含与风险相称的自动化测试;生产问题还要记录部署版本和脱敏后的生产验证。
- 完成后更新已有记录,或按下方模板添加新记录。复发问题必须填写
复发自,不能伪装成无关的新问题。 - BUG 编号必须唯一。追加新记录前先搜索本文件当前最大编号,并从其后继续分配;出现重复编号时保留较早记录的编号,为后加入的记录分配新编号,并同步修正指向它的
相关记录与复发自。 - 不记录姓名、出生资料、邮箱、用户/案例 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]、Supabasechat_sessions - 用户现象:用户点击删除后对话无法真正删除,或删除交互与账户弹窗不一致。
- 触发条件:登录后删除自己的一条历史会话。
- 根因:删除曾缺少所有者约束的服务端入口和对应数据库授权,前端也没有统一的站内确认面。
- 修复:增加所有者约束的服务端删除接口与数据库 grant,并统一站内确认弹窗。
- 验证:
tests/test_session_management_entrypoints.py、frontend/tests/chat-session-delete-contract.test.ts。 - 防复发:删除必须经服务端所有权校验;测试同时锁定 API、迁移和前端入口。
- 相关记录:无
- 复发自:无
- 修复版本:
e3d619c、65b8c42、5650ed1、fcf5794
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。 - 相关记录:无
- 复发自:无
- 修复版本:
28d58d4、5ee925c、308e5d5、e13d595、346ec02、a9ffbd9
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
- 复发自:无
- 修复版本:
dc0077e、20260721140000_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
- 复发自:无
- 修复版本:
b981c4e、20260721150000_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.ts、frontend/tests/consultation-entrypoint.test.ts、frontend/tests/consultation-birth-time-mode.test.ts、frontend/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.ts、frontend/tests/session-model-persistence.test.ts、frontend/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.ts、frontend/tests/evidence-audit-panel.test.ts、frontend/tests/consultation-report-export.test.ts锁定聊天消息不再挂载这些内部组件。 - 防复发:聊天消息渲染契约禁止直接展示
techniqueTruth和workflowReceipt;需要运营或调试时使用独立的受控界面。 - 相关记录: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_validator从thin队列移入excellent断言,保持测试与同一审计规则一致。 - 验证:
tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources;Python 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的日志分别记录了RectificationTechnicalPacketRangeError和candidate_differences TimeoutError。 - 修复:稀疏扫描差异改用明确的范围级文案,禁止伪称相邻分钟切换;超过六小时的宽范围首轮跳过分钟级候选分区,较窄范围在候选分区发生已知超时、取消、配置或服务故障时保留服务端扫描证据并继续收集,不再返回 503;未知编程或契约错误仍失败关闭。
- 验证:
frontend/tests/conversational-technical-packet.test.ts、frontend/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_chart与unverified_birth_time两种星盘模式,继续拒绝general_no_birth_time、旧校正入口和不一致 request ID;分钟确认门禁保持不变。 - 验证:生产合成账号复现
409 mode_changed;frontend/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 处于
claimed或executing,两分钟租约已经过期,客户端重新加载交接。 - 根因:数据库 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,直到恢复、建案、计算和会话持久化全部完成才突然跳转,用户容易误以为点击无效并重复操作。
- 触发条件:
start、resume、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.ts21/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: answer、resultCategory: 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:30–06: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_apply、confirmation_allowed和can_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 ESMregisterPlugin加载错误阻断,尚未进入业务断言。 - 防复发:主聊天区不得从 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_ready、confirmation_allowed: false、can_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_date、event_detail、new_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,自动使用最新caseId和turnVersion执行resume,不重复扣点;如果刷新后仍没有声明匹配的 case,则保留原错误,避免未经用户确认自动放弃旧校正记录。 - 验证:
frontend/tests/consultation-entrypoint.test.ts23/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 的逐段生成体验不一致。
- 触发条件:任意生时校正
start、resume或answer命令成功返回自然语言 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.ts32/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 消息在
thinking、streaming、settled三种视图状态之间切换。 - 根因:共享
ChatMessageRow使用条件分支把thinking渲染为仅有AgentActivityStatus,又只在streaming时显示 composing 状态;settled分支只保留正文,因此活动状态和正文被实现成了互斥内容,而不是同一消息的连续生命周期。 - 修复:assistant 消息始终在同一气泡内先渲染活动状态,再渲染当前已有正文;thinking/streaming 继续使用 working/composing orb,settled 只把状态文案切换为“回答已完成”,并把 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_date与event_detail仍等待对应数据库迁移后持久化。 - 验证:直接 RPC 探针确认旧格式为
true、带followUp为false;新增 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
- 复发自:无
- 修复版本:本次自动滚动修复提交
- 2026-09-17 语义被 BUG-930 取代:生成中改为定位到本轮开头,不再贴底跟随。
BUG-240 | 非评分回答绕过 Agent 并显示固定澄清模板
- 状态:resolved(原始的“状态/首次发现/最近更新”三行在 2026-07-25 的合并提交
3ca30ed7中丢失,此处依据同批次记录与该提交内容补记,非原文) - 首次发现:2026-07-23
- 最近更新:2026-07-23
- 影响面:生时校正缺少年月、未来事件、换方向和非评分更正后的自然问答
- 用户现象:用户用自然语言补充“化学专业”“后来换了工作”等内容后,页面直接显示“你提到……具体内容我已经记下了。它大致是什么年月?”,与前后 Agent 语气断裂,也可能忽略刚才已经建立的事件上下文。
- 根因:编排器发现本轮暂时没有可评分事件后,直接进入同步
nonScoringTurn();该函数完全没有调用 narrative Agent,而是用固定字符串生成可见回复。Mastra 和模型没有获得生成这轮回答的机会。 - 修复:非评分分支先用当前候选技术包、完整事件账本、最新用户原话和未决证据调用现有 narrative Agent;状态机只在后台固定本轮追问属于
event_date、event_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 继续正常读取。迁移应用后自动使用完整查询。
- 验证:服务角色直接查询确认修复前错误为
42703;frontend/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 / UPDATE,PostgREST 返回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/account、POST /api/consult、POST /api/birth-time-journey、今日星语、合盘与遗留生时评估接口 - 用户现象:全球出生地点已保存且可进入首页,但已有生时校正记录无法恢复并反复触发
action_conflict 409;正式咨询可能在扣点前返回资料不可用;部分旧功能仍要求中国行政区代码,合法全球资料会静默退回模板或 503。 - 根因:全球地点迁移只修复了 onboarding 与 conversational rectification 主入口,多个兄弟读取路径仍各自维护旧合同:强制
timezone_offset为数字、直接比较 Geoapify 七位小数坐标,或把中国省市区中心当作唯一地点真值。账户接口因此看不到数据库中实际存在的未完成 case,前端误判为需要重新创建。 - 修复:抽取共享历史时区解析器,兼容数据库 snake_case 与浏览器 camelCase 资料,并按实际申报/确认时间调用本地 IANA 历史时区服务;账户恢复使用已存 case 的不可变 offset 作为仅用于匹配的 fallback,并把坐标统一到六位耐久精度;咨询在扣点前使用最终 active time 补算 offset;journey 入口和 case loader 在严格解析前补算;今日星语、合盘和遗留评估优先使用真实全球经纬度与时区,中国行政区仅作兼容 fallback。
- 验证:账户 nullable offset + 七位坐标可恢复旧金山已有 case,时区 ID 或地点 ID 不一致仍拒绝恢复;verified consultation 使用最终 active time 解析
-8且解析失败不调用扣点;journey、今日星语、合盘和遗留评估的全球地点回归通过。两组聚焦套件共 84/84 通过,目标文件 ESLint、TypeScript--noEmit与git 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 持久化声明已经支持
placeId、placeType、provider、timezoneId和timezoneSource,但数据库函数conversational_rectification_valid_declared_birth_input仍只允许旧中国地点字段。合法全球地点声明在create_conversational_rectification_case内被判无效,并被 RPC 统一映射为conversational_action_conflict。 - 修复:新增向前迁移同步数据库地点声明白名单,接受全球地点身份、IANA 时区来源及坐标字段;地点身份允许
city、cityCode或placeId任一存在,同时保留旧中国地点兼容和数值边界校验。 - 验证:数据库迁移单独执行成功;使用新的 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_conflict;case 的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 递增到 1,narrative 和目标event_detailfollow-up 正常返回;用同一 actionId 重放返回相同 turn,账户积分保持 99。服务端日志两次均为actionKind=answer、resultCategory=success、billingState=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_id与raw_text恢复,但旧恢复链路没有读取这些数据。 - 修复:新会话从首条 Agent narrative 开始持久化完整消息序列,每次回答后保存真实的
Agent → 用户 → Agenttranscript;resume 接口按 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_date和event_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 在消息列表末尾设置了滚动锚点和
scrollIntoVieweffect;生时校正使用独立的rectification-message-list滚动容器,却没有对应的末尾锚点和消息更新监听。 - 修复:在生时校正消息列表末尾增加滚动锚点,并在消息数量、最新回答文本、思考状态、提交状态、turn version 和错误状态变化时滚动到末尾;生成中即时跟随,完成后平滑滚动,同时尊重
prefers-reduced-motion。 - 验证:组件目标回归测试通过;TypeScript
--noEmit、目标 ESLint 与git diff --check通过。按用户要求未运行 Chrome/Playwright。 - 防复发:新增独立聊天滚动容器时必须同时提供末尾锚点,并覆盖乐观用户消息、生成状态和回答文本更新三类触发源。
- 相关记录:BUG-046
- 修复版本:待提交(本地可测)
- 2026-09-17 语义被 BUG-930 取代:生成中改为定位到本轮开头,不再贴底跟随。
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
- 影响面:生时校正首轮引导、中间事件追问、方向切换、未来事件、暂停、放弃与确认消息
- 用户现象:模型已经结合上下文生成回答后,网页仍显示“已记录”“当前累计”“下一步”“还差时间定位”等重复话术;部分操作还会新增程序模板气泡,使对话像问卷而不是连续的一问一答。
- 根因:编排层把事件提取结果、
followUpmetadata 和收敛状态当成可见文案的唯一真源,在模型生成之后又根据分支拼接或替换 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=true、schemaValidated=false、issues=["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 秒成功结果还曾因旧引用正则误判被丢弃。
- 修复:生产模型只生成
narrative与evidenceRequest,候选状态、时间、范围、分盘层、引用和领域依据由服务端从 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-conversation的regenerate与其他非最终叙事轮、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。生成结果携带实际模型 ID,validation 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_evaluated、blocked技法和确认权限,却没有继续提出下一个问题;回答像调试报告而不是连续的一问一答。 - 根因:叙事上下文直接暴露事件的私有
domain、建议领域和完整 expert workflow 状态,模型因此围绕工程元数据组织回答;同时问题修复器只处理首轮或明显截断的句子,语法完整但没有问号的中间回答会原样通过。技法保持not_evaluated的直接原因则是当前有效、受支持且可评分的事件不足三条,路由尚未进入scoreEvents(),并非 Mastra 无法调用技法。 - 修复:新增叙事专用上下文,移除事件
domain、建议领域和内部评分;证据采集阶段只允许暴露已实际used或partial的技法,不再展示未调用清单、阻塞项或确认权限。私有语义领域仍保留在服务端用于事件去重、跨领域路由和候选评分,但不得进入用户可见文案。所有非最终回答必须包含且只引导一个下一问题;模型正文漏问时追加同一次模型生成的evidenceRequest.prompt,不使用固定业务模板。 - 验证:新增回归覆盖事件正文可见但内部领域不可见、建议领域不进入叙事 packet、未调用技法不在采集轮展示,以及完整陈述漏问时补入模型自写问题;叙事、路由和编排聚焦测试通过,目标 ESLint、TypeScript
--noEmit与git 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
--noEmit与git 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 文本的
start、answer、regenerate命令加入可选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:07–05: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 原生
career、wealth、marriage领域各选择一条最强事件;直接映射优先于代理映射,日级优先于月级和年级,同精度选择较新事件。月级和年级事件只取范围中点作为代表日,两个候选最多发起六次外部事件扫描;响应明确记录符合条件事件数、实际选中数和选择策略,不再暗示全部事件均已外部验证。通用 range scan 保持不变。 - 验证:长对话回归断言十二条符合映射的事件只产生六次扫描,两个候选各覆盖事业、财富、关系三领域,所有扫描均为单日;相关 Python 测试 42/42 通过。使用真实 VedAstro key 重跑同一十二事件 fixture,首次完整命令约 9.7 秒、缓存后约 3.6 秒,官方响应状态为
official_verified。真实SearchEvents对05: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=false和vedastro_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,不能只跑单元测试或tsc;App Routerroute.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 校验中持久化
prompt、answerMode与proposedDate。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 <= 5且margin >= 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 持久化、日期候选确认与数据库约束
- 用户现象:代码已写入
prompt、answerMode和proposedDate,但生产迁移工作流不会上传或执行对应 migration;生产数据库仍可能按旧 JSON 合同拒绝新 turn。 - 触发条件:从 GitHub Actions 手动执行生产生时校正 migration 的
check或apply操作。 - 根因:新增
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_no和followUp.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-v3case,首轮候选已经包含rangeStart/rangeEnd,但可见 narrative 没有显示它们;旧生产版本还会调用模型生成同类泛化开场。 - 根因:BUG-072 的快速首轮正确保留了候选范围,却只把范围写入结构化
candidate;首页不单独渲染该字段,固定 narrative 仍沿用了不含范围的旧提问。 - 修复:复用首轮已经计算出的声明范围,在确定性 narrative 中明确显示当前待核对边界,并说明它不是已确认分钟;继续只问一件带年月的重要经历,不恢复零证据模型调用或分钟扫描。
- 验证:Orchestrator 回归断言首轮正文包含真实
rangeStart–rangeEnd、未确认边界和单事件提问;聚焦测试、目标 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:26–05:30,无空问题死状态,原active_birth_time不变且未主动保存前acceptedRange为空。静态浏览器验收确认日期修订输入框、范围保存按钮、非确认分钟文案和 390px 无横向溢出;真实登录态 UI 因隔离服务未连接认证配置,本轮未声称已验收。 - 防复发:生时校正业务真源必须是 V4 Case 和 append-only 事件台账,不得从聊天正文反向恢复;任何公开结果只能显示通过稳定性门的范围,单分钟峰值不得成为可保存结果;证据不足必须生成下一题或明确暂停,不能进入
awaiting_answer + currentQuestion=null;V4 路径不得写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 所在的 Composedefault网络。 - 根因:
rectification-v4-worker只加入app网络;api只在default网络。Worker 虽正确配置JYOTISH_API_BASE=http://api:5200,但 Docker DNS 无法跨网络解析和连接api。 - 修复:让 staging Worker 同时加入
default与app网络,不改变 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.ts和question-author.ts直接决定领域顺序与问题文案;模型只负责写下一问,后台没有形成完整 Agent 决策闭环。 - 根因:产品状态被压缩成“下一问字符串”,事件语义、候选特征、诊断结果、问题机会、模型决策和公开消息之间没有受约束的 durable contract;因此即使替换提示词,系统仍会沿用问卷式控制流,且无法审计模型为何选题或安全重放已完成 Job。
- 修复:删除旧
question-planner.ts与question-author.ts,将回答处理重构为完整 V5 主链:保存回答并创建后台 Job → Evidence Reconciliation → Candidate Engine / Feature Snapshot → Diagnostics → Opportunity Builder → Bounded Reasoner → Decision Validator → Renderer → Atomic Job Completion。可见层继续复用普通 session 聊天 Surface;Reasoner 只能选择服务端生成的 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-engine、identity-auth-integration、onboarding-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 policy;Renderer 根据 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 和重复追问预算不完整,固定领域与日期控制流绕过了信息增益、隐私成本和稳定性诊断。
- 修复:升级为兼容旧
prompt的semantic-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 测试未归一化 PostgreSQLDate联合类型。 - 根因:测试夹具落后于现有生产合同;这些报错不来自 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.txt与pyproject.toml仅声明mcp>=1.0;CI 在 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.txt和pyproject.toml,防止任一入口再次放宽到 MCP 2.x;Python 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 显式按createdAt、eventId、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_ENABLED、RECTIFICATION_AGENT_V5_SHADOW、RECTIFICATION_AGENT_V5_CANARY_PERCENT;因此selectRectificationDeploymentMode()把新 Case 持久化为v4_legacy,Orchestrator 必然调用 Legacy Projector。 - 修复:受控 staging rollout 现在原子写入 V5 Agent 开关,public 与 smoke rollout 使用
v5_agent、100% canary,并重建 web/worker;staging 中唯一满足 V6 版本、未完成、无 open Job 条件的错误 Case 已原位升级为rectification-evidence-v5/v5_agent,历史 Turn、Event、Job 与 Agent Run 保持不变。 - 验证:rollout 脚本测试断言三项 V5 选择器只写一次,并在成功前核对 web/worker 容器实际读取的
enabled、shadow、canary;staging 活跃 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_eventOpportunity 排序、问题验证、Renderer telemetry 与 staging 用户可见下一问 - 用户现象:用户提交“2020 年 4 月去石油化工研究院实习做研究员”后,系统仍显示“承接……请再说一件……哪次搬家、离乡或长期迁居……”,像固定问卷。
- 根因:Builder 将最近六轮答案拼接为当前主题,使较早教育事件中的“离家/外地”再次提升 relocation,同时未覆盖领域奖励在已经满足最小领域数后仍占主导;Renderer 对自然
new_dated_event问法使用过窄词面校验,校验失败后静默替换为 Builder 固定 fallback,telemetry 仍记为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 投影到对应 Turn;UI 的 thinking 状态只是客户端进度动画,不是模型 reasoning,也不是服务端执行收据。 - 修复:将“分析过程”定义为与 Turn 关联、可持久化和刷新后可恢复的服务端执行收据;只投影实际发生的阶段、工具调用和 allowlist 技法。供应商显式返回的 reasoning 内容只有通过服务端来源校验与安全过滤后才可作为可选摘要,缺失或不安全时直接省略,不伪造且不读取 hidden chain-of-thought。
- 安全边界:不公开分数、权重、贡献矩阵、内部 ID/字段、候选分钟、工具参数或原始结果、Prompt、模型内部信息和用户敏感原文;D60 不展示且不驱动结论;未执行、不可用或仅供参考的技法不得显示为已执行。
- 兼容边界:历史无收据记录继续读取;
v4_legacy与v5_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返回为 JavaScriptDate,且历史资料的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 / rendering;Case 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 layer,
KP_cusps为 optional、D60 为 reference-only、未知层失败关闭;事件 provenance 仅用于审计且不参与加权。 - 验证:本工作树的聚焦合同覆盖跨午夜分钟枚举、LODO 低于
0.8拒绝公开范围、required layer 缺失阻塞、KP_cusps/D60 缺失不阻塞,以及旧 Snapshot 兼容;本记录不把这些回归误写成独立 holdout 验证。 - Partial / deferred:per-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,没有保留 canonicalevent_kind。 - 根因:Agent 只在服务器预先枚举的机会中选择,无法基于完整 Case Dossier 自主理解当前访谈焦点并生成下一句;公开回复失败时继续由领域正则模板接管。与此同时 Python legacy request 把领域值当作事件种类,抹平关系事件的 start/end/change 语义。
- 修复:新增 Director 两阶段合同:服务器提供完整 Case Dossier,Director 可在一轮提出多个 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 继续传递 canonicalevent_kind,并为 relationship start/end/change 保留可验证的最小区分。 - 验证:TypeScript 相关套件 114/114 通过,其中 Director 专项 7/7;TypeScript
tsc --noEmit与修改文件 ESLint 通过;Pythontests/test_active_rectification_events.py14/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写入账本。 - 触发条件:
reviseproposal 引用真实 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_review;V5 completion 增加 ownership/target/replay 安全的 Pending resolution,并且date_unresolved只在日期确实变化后关闭;relationship_end强制pending_review,迁移清除由旧 scoreable 关系结束事件支持的最新 Snapshot,Dossier 与 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、或没有currentQuestion的awaiting_answerCase;pending_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_start与relationship_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_diagnostic,Dossier 没有 revision、Observation 和候选假设;Runtime 只累计旁路诊断数组,前端没有把既有 Job phase 投影成稳定的分析步骤。 - 修复:新增服务器拥有的
case_read、candidate_scan、evidence_gap、diagnostic_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 | 开放问题收集年份事件后先切换新事件、下一轮再回访旧事件
- 状态:resolved(staging pending deployment)
- 首次发现:2026-07-31
- 最近更新:2026-07-31
- 影响面:V8 Director 最终规划、确定性 fallback、年份/季度精度事件的访谈连续性
- 用户现象:开放问题收到一件只有年份的本人事件后,系统先用固定句式要求另一件经历;收到第二件月份明确的经历后,又突然回头追问第一件事件的月份,表现为事件焦点来回跳转。
- 触发条件:上一问没有
questionTargetEventId,本轮新建的可评分事件只有year或quarter精度,同时 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 会锚定该事件并请求
month;Orchestrator 回放断言开放问题收到年份事件后,下一问立即补月份且targetEventId指向新事件。相关 Director/分析轨迹测试、TypeScript 和 ESLint 均通过;完整前端测试 1157 项通过。 - 防复发:事件连续性由服务器的
currentTargetEventId + targetDisposition约束,不能只靠 Agent prompt;新事件的必要日期精度应在切换话题前闭合,用户明确“不知道/跳过/换方向”时才解除目标。 - 相关记录:BUG-092、BUG-097、BUG-106
- 修复版本:local / pending release
BUG-108 | V8 丢失事件承接说明且服务器领域模板覆盖 Agent 自主选题
- 状态:resolved(staging 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/52;TypeScript、ESLint 通过;前端全量测试 1164/1164。
- 防复发:回归测试覆盖模型伪造候选结论、手动 regenerate 以及历史
selectedOpportunity持久化路径。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-110 | 月份简答未继承目标事件年份导致重复追问并暂停
- 状态:resolved(local)
- 首次发现: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-09revision、无 pending、下一问不再绑定原事件且状态保持awaiting_answer;Director 测试确认记录fallback_rejected:question_repeated。 - 防复发:单元测试分别锁定确定性日期继承、Evidence 阶段边界、fallback 原因和完整两轮回放。
- 相关记录:BUG-104、BUG-107、BUG-109
- 复发自:无
- 修复版本:local / pending release
BUG-111 | Director 校验器覆盖 Agent 公开回复且复合事件缺少公开语义
- 状态:resolved(local)
- 首次发现: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 生时校正
- 状态:resolved(staging 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.ts、frontend/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_rectificationSession,页面保持空白;网络中没有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:00–23: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 重试循环
- 状态:resolved(local)
- 首次发现: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 内实际返回 JavaScriptDate,因此服务端仍误判missing_birth_date。失败回调清空rectificationSessionId却未设置现有的自动恢复暂停状态,resume effect 又会立即重新挂载聊天并再次发送 opening。 - 修复:共享持久化日期规范化函数统一接受合法
Date、ISO 字符串和纯日期字符串,由 Agent loader 与首页账户重新加载共同复用;服务端资料失败时先设置rectificationError暂停自动恢复,资料成功保存后再清除暂停状态。 - 验证:staging 只读诊断确认目标账户的
birth_date在服务端为Date,时间、地点和误差字段类型均有效;回归覆盖 PostgreSQLDate、数据库 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_rectificationSession,成功回复先原子更新完整消息再发送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 | 用户采纳最强候选后无法保存为平台排盘时间
- 状态:resolved(local)
- 首次发现:2026-08-04
- 最近更新:2026-08-04
- 影响面:Agentic 生时校正候选结果、个人资料出生时间、后续咨询排盘时间
- 用户现象:
04:55已是最强候选,用户多次明确表示“就用 04:55”,但 Agent 因唯一分钟确认门未通过而拒绝保存,个人资料和后续排盘仍未使用该时间。 - 根因:系统把引擎候选、用户采纳和引擎唯一确认压缩成单一
confirmed状态;没有可持久化的候选身份和用户采纳边界。 - 修复:引入
candidate / accepted / confirmed三态;服务端持久化候选身份、相对支持度、Session 所有权和 Profile 基线;新增 service-role 原子采纳 RPC;前端展示候选卡并允许用户采用;accepted接入个人资料和全平台排盘。 - 数据边界:相对支持度仅表示本次候选间的归一化比较,不是统计概率;保留
reported_birth_time;accepted不冒充引擎唯一确认。 - 安全边界:RPC 校验用户、Session、结果身份、有效期、候选成员、最新结果和 Profile 基线;出生申报资料变化使旧候选失效;采纳不计费。
- 验证:TypeScript 通过;聚焦测试 90/90;完整测试 1221/1221;lint 0 error、3 个既有 warning;production build 通过;本地 PostgreSQL 验证
04:55写为accepted、保留05:00reported time、重复采纳幂等,并验证出生申报时间变化会使结果失效且拒绝再次采纳。 - 相关记录:BUG-113、BUG-114、BUG-115、BUG-116
- 修复版本:待提交与发布
BUG-118 | 候选已落库但确认卡不显示,VedAstro 未执行被误述为未通过
- 状态:resolved(local,pending 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_evaluated与fail,不得把诊断项描述为硬阻塞。 - 验证:真实 local PostgreSQL business client 回归覆盖未过期候选读取;Agentic 工具回归覆盖
not_evaluated映射为external_validation_invoked=false;Session、entry、candidate persistence 聚焦测试通过。 - 防复发:self-hosted query builder 新增 Supabase/PostgREST 链式操作时必须由真实 PostgreSQL fixture 覆盖;公开文案必须按
external_engines.status区分未执行、失败和通过。 - 相关记录:BUG-116、BUG-117
- 修复版本:待本次 staging 修复提交与部署验收
BUG-119 | 候选采用覆盖兼容出生时间且个人资料不显示双时间记录
- 状态:resolved(local,pending 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()只刷新账户对象,没有同步个人资料展示使用的独立profilestate。 - 修复:新增向前迁移解除
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:55、birth_time_status=accepted、reported_birth_time=05:00、birth_time=null。 - 相关记录:BUG-117、BUG-118
- 修复版本:待提交与发布
BUG-120 | Agent 仍在追问事件时过早显示候选采用卡且采用后无法改选
- 状态:resolved(local,pending 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:00、birth_time=null;TypeScript、聚焦 ESLint 与 production build 通过;桌面端三列和移动端横向滚动截图已完成视觉检查。 - 防复发:任何非唯一候选卡必须由显式选择阶段开启;同一 Agent 回复不得既索取新证据又提供采用操作;候选采用测试必须覆盖幂等、改选、过期结果和 Profile 基线漂移。
- 相关记录:BUG-117、BUG-118、BUG-119
- 修复版本:待提交与发布
BUG-121 | 月份与区间事件在确认工具中被序列化成错误日期格式并耗尽 Agent 步骤
- 状态:resolved
- 首次发现:2026-08-04
- 最近更新:2026-08-04
- 影响面:Agentic 生时校正结束收集、V5 评分/诊断、旧确认门适配、无回复退款兜底
- 用户现象:用户明确表示不再补充事件后,接口返回“生时校正没有生成有效回复,本次不会扣除点数,请重新发送”,没有展示最终候选或后续选择。
- 触发条件:历史证据同时包含
year、month或range精度;Agent 在结束收集时调用rectification-confirm。月份或年份事件被发送成完整日期,区间事件又可能使用/、to等自然分隔形式。 - 根因:共享事件 schema 只检查字符串长度;V5 转换只识别
..区间;toV3Event()又把标准化后的YYYY-MM-DD与month/year精度一起发送给只接受YYYY-MM/YYYY的旧确认端点。引擎持续返回event date does not match its precision,Agent 在 8 个工具步骤内反复修正和重试,最终没有剩余步骤生成公开文本。该问题是 BUG-116 的输入契约残余变体,提高步骤数只能延后失败。 - 修复:事件日期统一复用严格日历范围转换;V5 保留年月日和区间的
date_start/date_end,并兼容..、/、to、中文范围符和紧凑年月范围;旧确认端点按精度发送严格的YYYY、YYYY-MM、YYYY-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 - 用户现象:身份库已持久化
admin或viewer角色的用户登录主站后,账户菜单不显示后台入口;即使显示旧入口,主站/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,仅admin或viewer可见入口,且不使用ADMIN_EMAILS替代角色授权;GET /api/account在服务端解析身份配置并返回AUTH_ADMIN_ORIGIN + /admin/codes,Supabase 模式继续返回/admin/codes;账户与侧栏类型透传该 URL,并将文案改为“后台管理”。后台独立登录和requireAdminSession保持不变。 - 验证:
frontend/tests/admin-contracts.test.ts、frontend/tests/admin-users-contract.test.ts、frontend/tests/account-api.test.ts、frontend/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 session;
identity.users.role的持久化admin是唯一后台授权事实。Better Auth 插件的/api/auth/adminendpoint 继续在主站 fail-closed404,未知 Host 继续421。 - 修复:删除活动运行时后台 origin/secret 与
services.admin,服务端数据 client 和requireAdminSession统一读取 user session;后台 layout 增加服务端 gate,匿名转/login、非 admin 不渲染;所有后台 API 保留独立 guard,payments/packages 改用requireAdminSession;isAdminUser、账户入口和 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_ORIGIN、BETTER_AUTH_ADMIN_SECRET或浏览器 admin auth service;viewer对后台入口、页面、读 API 和写 API 均必须为403;入口可见性不能替代 route guard。 - 相关记录:BUG-010、BUG-083、BUG-084、BUG-122
- 复发自:BUG-122
- 修复版本:
435e628806390e7ae138363491e7bae63ee801d4,staging 已验收
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_packages、payment_orders权限,也未添加对应 RLS 策略;因此数据库健康且新 SHA 已部署,后台 SQL 仍被 PostgreSQL权限门禁拒绝。同日还确认 staging 迁移与部署工作流错误地要求目标 SHA 属于main历史,使完全独立的测试分支被生产分支阻塞;该控制面耦合导致为恢复 staging 而误合并生产 main。 - 修复:支付记录改为通过
queryAdminRows执行参数化 SQL,联表public.payment_orders、public.payment_packages和identity.users,以窗口计数保留分页合同并用独立聚合 SQL输出统计;不再使用 Supabase builder 或 Admin Auth。后台在支付管理之后新增独立“套餐管理”资源和页面,套餐新增、编辑、停用、错误重试及原字段保持完整,支付页只保留概览、Z-Pay(易支付)渠道配置和支付记录。配置读取补回chat_enabled与chatEnabled。创建订单完成登录、开关、配置、SSRF、套餐和订单校验后,直接返回带sign/sign_type的标准submit.php收银台 URL,不服务端请求网关、不返回商户密钥;对话页用浏览器打开该 URL,套餐加载异常显示安全错误,正常enabled=false仍静默隐藏。复发修复将 Z-Pay 配置改为默认收起的 Ant DesignCollapse,展开后才显示表单和操作;为 AdminApp 增加admin-app-shell的100dvh独立纵向滚动边界而不改聊天全局规则;套餐 CRUD 全部改用queryAdminRows参数化 SQL、UUID 校验、returning与 404;易支付读取仅在 PostgreSQL42P01时回退环境变量,保存直接参数化调用public.admin_save_epay_settings并使用函数返回行,保留原子审计和脱敏响应。2026-07-30 新增前向迁移20260730010000_admin_payment_permissions.sql,向admin_runtime最小授予套餐读写、订单只读、易支付配置读取及保存函数执行权限,并为启用 RLS 的支付表补齐角色策略;不授予订单写入或删除权限。Gitea 与 GitHub 的 staging 迁移、部署和测试环境运维工作流统一 checkoutstaging,删除 staging SHA 属于main历史的要求;生产工作流保持不变。误合入 main 的 PR #1 已由 PR #2 的 revert 恢复,恢复后 main 内容树与合并前提交43581ac0f75e7f157032503475e878bd53ad161d完全一致。针对 run 1309,Gitea 两条远端工作流的 previous-SHA 探测与 registry login/logout 保持sudo -n docker;脚本只接受受控的docker或sudo -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=docker,DOCKER_CONFIG仍指向 incoming 的.docker;cleanup 使用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_runtimegrant/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_settings的admin_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 | 个人报告入口对不可用出生时间状态错误开放
- 状态:resolved(local,pending staging deployment)
- 首次发现:2026-08-06
- 最近更新:2026-08-06
- 影响面:首页个人报告 CTA、
POST /api/reports出生时间门槛 - 用户现象:资料流程已经完成、但出生时间仍为
reported或candidate的用户会看到“生成个人报告”,点击后服务端必然返回422 birth_time_not_usable。 - 触发条件:用户有咨询会话和消息,
profileComplete=true,但当前排盘时间尚未被用户采用或引擎确认。 - 根因:首页只用资料完整度判断入口可见性,没有镜像报告 API 的
accepted/confirmed + 有效 active time门槛;UI 与服务端各自正确但组合后形成误导入口。 - 修复:首页复用既有
isBirthTimeReadyForConsultation(profile),只有accepted或confirmed且当前排盘时间有效时才显示个人报告入口;服务端门槛保持不变,不把候选范围或填报时间伪装成已采用时间。 - 验证:
frontend/tests/personal-report-entry.test.ts15/15 通过,新增回归直接覆盖reported=false、candidate=false、accepted=true、confirmed=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、GiteaStaging 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 run1459在完整 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 全迁移验收、GiteaStaging Backend Quality Gate - 用户现象:quality gate run
1456的 1409 项 frontend 测试中 1408 项通过;真实 PostgreSQL fixture 成功创建public.personal_reports后,精确表集合断言因预期值缺少该表而失败。 - 触发条件:在完整 Docker/PostgreSQL runner 中应用全部迁移并枚举
publicschema 表。 - 根因:个人报告迁移契约覆盖了双迁移语义、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 env;root 控制脚本验证一个由
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 校验两个 env;backup 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 run1465在完整 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
- 复发自:无
- 修复版本:
f7a615a5bf11ed95b3a6c7e6d28dfe8150a825ef;staging migration/deploy 与安全验收完成
BUG-129 | staging trusted-main checkout 无界 fetch 导致自动部署长期占用 mutation queue
- 状态:resolved
- 首次发现:2026-08-06
- 最近更新:2026-08-06
- 影响面:Gitea staging deploy/migration 控制器的 trusted-main checkout;production 与 staging 应用数据面未受影响。
- 用户现象:exact-SHA quality gate run
1473成功后,自动 deploy run1474在git fetch --no-tags origin main "$DEPLOY_SHA"长时间没有日志进展;fetch 后续自行恢复,run 最终于 18 分钟成功部署02cc483b7c303e6cc0f26fb31462c50adb007f12。第一轮 bounded-retry 修复合入后,run1480的 3 次 120 秒 fetch 全部在服务端压缩 16,093 个对象时耗尽并 fail closed;SSH/远端 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 == stagingcontroller,手工旧版 rollback 也不得执行旧 controller。refs 与 forward/rollback 关系通过有界 Gitea API 和完整 commit-DAG 路径证明,字段缺失、分页不完整、头不一致或证据冲突均 fail closed。 - 验证:第一轮 bounded retry 的本地 workflow contracts 31/31、PR gates
1475/1477与 staging gate1479成功;run1480证明 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 gate1481、staging gate1483成功。自动 deploy1484在 1 分钟内成功部署e59f15d352787f3d05425ba8c459d092e9801a20,日志 mutationgit fetch=0、controller hash check 存在、SSH secret 遮蔽且无私钥材料;main/staging/public/state精确一致,5 个容器 restart count 均为 0,health、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 第一轮修复未覆盖对象范围
- 修复版本:
e59f15d352787f3d05425ba8c459d092e9801a20;gate-attested controller bundle 已完成 staging exact-SHA 验收
BUG-130 | self-hosted 计费查询与订单领域调整缺少并发和审计边界
- 状态:resolved(local)
- 首次发现: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 不支持的关联 select,adapter 又缺少
lte/like参数化能力;一次性限制没有由不可变订单快照和数据库唯一约束共同承担;订单状态和余额缺少统一的管理员领域函数、幂等请求、乐观版本及审计合同;旧兑换码 RPC 不接收 reason。 - 修复:商品及权益改为两次简单查询后在服务端组装,adapter 增加参数化
lte/like;订单创建写入不可变oneTimePerUser商品快照,结算与人工补偿统一通过(user_id, product_code)唯一兑换记录原子阻止重复发放;新增admin_adjust_order,只允许retry_grant、compensate、record_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.ts与tests/database-local-business.test.ts通过;计费 route/reauth/contract 测试 38/38 通过;目标 ESLint 与补丁检查通过。全仓 TypeScript 当前被共享工作树中非本任务的tests/identity-auth-integration.test.ts:412类型错误阻断。 - 防复发:self-hosted 支付查询不得重新引入 nested PostgREST select;LIKE/范围条件必须参数化;一次性权益必须同时依赖不可变快照和数据库唯一约束;订单与兑换码后台不得直接更新余额或订单状态,所有写入必须经过带 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 Gatevalidate/publish 的 exact-SHA checkout;staging mutation controller、应用数据面与 production 未受影响。 - 用户现象:docs-attestation staging gate run
1485在 validate 的首步失败;Gitea 已枚举/压缩 3,246/2,895 个 shallow objects,但客户端传输降速后触发curl 28 Operation too slow、early EOF。publish 被依赖关系跳过,自动 deploy 未触发;公网继续健康运行e59f15d352787f3d05425ba8c459d092e9801a20。 - 触发条件:quality gate 的 exact-SHA
--depth=1fetch 只有单次调用,并把低速失败设为连续 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 gates1519/1523、staging push gate1525均成功;最终 gate 对 exact SHA6c1dcbe857006ec6ae7463b57b2b7d5947da4851完成 validate/publish,artifact ID12成功上传,deploy1526成功。 - 防复发:质量门禁和 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 gate1525与 deploy1526均成功。 - 防复发:任何
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 gate;customer handler 权限和业务逻辑未受改动。 - 用户现象:PR gate run
1493的 Python 路由回归和 frontend 1472/1472 均通过,但next build报Next.js can't recognize the exported runtime field in route. It mustn't be reexported,publish 被跳过,自动 deploy 未触发。 - 触发条件:legacy
/api/admin/usersRoute 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、默认 Turbopacknext build、next build --webpack、全 route segment config re-export 扫描与git diff --check已通过;Gitea PR gate1499及最终 PR/push gates1523/1525均完成 production build,deploy1526成功。 - 防复发:Route Handler 的
runtime、dynamic、revalidate等 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 run1502;应用容器、业务数据库和 production 未被修改。 - 用户现象:staging gate
1500已成功验证并发布 SHA9a3d0d440f43deab66c1f8a4a08cdbfc6f9d73eb的 immutable artifact,但自动 deploy1502在远端 env 校验时报invalid staging selector: ADMIN_USER_ORIGIN并 fail closed;公网继续健康运行旧 SHAe59f15d352787f3d05425ba8c459d092e9801a20。 - 触发条件:包含双 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 未在发布前同步新增的非密钥 selector;quality gate 验证仓库合同,不读取主机 secret/env,因此直到 mutation 前远端校验才暴露漂移。
- 修复:已在共享 staging mutation lock 下,仅向原文件原子补入公开
ADMIN_USER_ORIGINselector,保留全部既有内容、deploy:deployowner 和0600mode;未输出、复制或重写其他 secret 值。随后完整 validator 暴露独立的 service runtime 漂移,转由BUG-135处理;仍待 exact-SHA artifact 重新部署。 - 验证:脱敏只读检查先确认
.env.staging为deploy:deploy 0600、AUTH_USER_ORIGIN精确且唯一、ADMIN_USER_ORIGIN计数为 0;原子修复后ADMIN_USER_ORIGIN精确且唯一,正式 validators 与 deploy1526通过。最终 user host admin paths 为 404;admin 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
- 复发自:无
- 修复版本:
6c1dcbe857006ec6ae7463b57b2b7d5947da4851(staging 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_runtimelogin 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_rolemembership 和数据库 CONNECT;将 raw password 仅原子写入.env.staging.database,percent-encoded URL 仅原子写入.env.staging,两文件保持deploy:deploy 0600。随后撤销admin_runtime的service_rolemembership;未重启容器、未输出凭据、未改 production。已应用的20260806000000_personal_reports.sqlchecksum 与仓库一致,保持历史迁移不可变。 - 验证:两个正式 env validator 均通过;
service_runtime真实密码登录、service_rolemembership 和 CONNECT 均通过;最终审计再次确认service_membership=true、service_connect=true、admin_service_membership=false、admin_bypassrls=false、service_can_login=true。两份 env 均为deploy:deploy 0600,migration1516、push gate1525和 deploy1526成功。 - 防复发:非空数据卷不能依赖
/docker-entrypoint-initdb.d自动重放;每次新增 runtime role 或 host-managed URL 都必须有兼容性 role repair、脱敏 pre-deploy presence 检查和真实登录/role-membership smoke。admin_runtime永不得继承service_role;service writes 只能使用独立SERVICE_DATABASE_URL。已应用迁移不得为修正文案而改 checksum。 - 相关记录:BUG-128、BUG-134、ERR-093、ERR-098
- 复发自:无
- 修复版本:
6c1dcbe857006ec6ae7463b57b2b7d5947da4851(staging role/env 与 exact-SHA 验收)
BUG-136 | staging quality gate frontend build 无界卡住并耗尽 45 分钟 job
- 状态:resolved
- 首次发现:2026-08-06
- 最近更新:2026-08-06
- 影响面:Gitea
Staging Backend Quality Gatevalidate job、staging artifact publication;应用代码、staging host 和 production 未被本次失败修改。 - 用户现象:push gate
1507对 reviewed SHA6fd22921197715e065d0d137fbd7ea5a82a188e4完成 frontend1472/1472、ESLint0 error,Next.js 输出Compiled successfully in 38.2s后约 44 分钟无 further output,45 分钟 job 超时,publish 被跳过;公网继续运行旧健康 SHAe59f15d352787f3d05425ba8c459d092e9801a20。 - 触发条件:质量门禁执行
npm run build --prefix frontend没有命令级 bounded timeout;Turbopack 在编译后静态生成/收尾阶段无输出卡住时只能等待 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/35;PR gates
1511/1519/1523和 staging push gates1514/1521/1525均在 600 秒 command deadline 内完成 production build;最终 gate1525publish 与 deploy1526成功。 - 防复发:所有可能长时间静默的编译、镜像构建和外部网络步骤都必须有命令级 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与 migration1516成功后,deploy1517、1518均启动目标 web/API image 并达到容器 healthy,却在约 3 秒后的公网 verification 返回非零,随后成功恢复旧 web/worker;公网和.state/deployed-revision均保持旧 SHAe59f15d352787f3d05425ba8c459d092e9801a20。 - 触发条件: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/35;deploy
1522的脱敏摘要证明 bounded verifier 等待到目标 public SHA 后才报告独立 admin-host 问题;后续 PR gate1523、push gate1525、artifact ID12和 deploy1526均成功,最终公网/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.chatTLS 握手失败。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 gate1525、artifact ID12和 deploy1526成功。Caddy 被 force-recreate,容器内配置包含 admin host;公网 user host/admin与/api/admin/session均 404,admin host/为 308/admin、/admin为 307/login、session API 为 401;Caddy 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 形成无限循环
- 状态:resolved(local candidate,pending 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=1、auth_users=1、bootstrap_eligible_admins=1,但active_admin_users=0、owner_assignments=0、identity_admins_missing_rbac=1,因此当前唯一 active identity admin 没有 RBAC Owner,真实授权结果为 403,而不是 cookie/host 隔离或数据库不可用。 - 修复:后台 layout 对 401 仍转
/login,403 使用 Next.js forbidden interrupt 返回明确 403 页面;503 以 307 转到 admin layout 外的独立/admin-unavailableroute,由该 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,其余未知基础设施异常统一转换为后台服务暂时不可用503,APIadminErrorResponse保留该最终状态。Owner 恢复 migration 保留在frontend/db/migrations,但在任何表查询前用to_regclass/to_regprocedure检查 identity/auth 表、RBAC 表与关键函数;identity-onlyMIGRATIONS_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-card、daily-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
- 状态:resolved(local candidate)
- 首次发现:2026-08-07
- 影响面:self-hosted staging 的 profile/consultation 读取;production 未受影响。
- 根因:本地 PostgreSQL adapter 将
DATE查询结果保留为 JavaScriptDate,下游业务合同要求无时区的YYYY-MM-DD。 - 修复:按列类型将查询结果中的
DATE归一化为本地日历字符串,timestamp 仍保持Date;同步删除 legacy rectification 后的 account stale test。 - 验证:focused 回归 37/37、完整前端 1017/1017、local quick quality gate 291 passed/1 skipped;TypeScript、lint(0 error)、production build 与
git diff --check通过。 - 修复版本:本次 staging-only 集成候选
BUG-142 | Gitea staging publish job Run 1540 post-job timeout
- 状态:resolved(local candidate)
- 首次发现:2026-08-07
- 最近更新:2026-08-07
- 影响面:Gitea
Staging Backend Quality Gatepublish job;validate、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 页面未同步能力审计精确路由集合
- 状态:resolved(local 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-126(reports 页面)、BUG-132(后台 14 页面)同类精确路由集合防复发模式第三次复发。旧防线失效的直接原因是会员页本地交付矩阵只运行了 frontend tests(
membership-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,保留精确完整集合,不放宽为子集/包含断言。 - 验证:本地目标节点
<home>/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 迁移失败
- 状态:resolved(local candidate,远端 gate 待 follow-up 更新)
- 首次发现:2026-08-07
- 最近更新:2026-08-07
- 影响面:
frontend/db/migrations/20260807020000_redeem_security.sql(已删除)、frontend/tests/redeem-orders-contract.test.ts、frontend/tests/database-redeem-security.test.ts;staging quality gate run1552frontend 1054 passed / 2 failed,Python quick 291 passed / 1 skipped,publish / deploy 均未发生。 - 用户现象:gate run
1552数据库真实失败,publish 被跳过,自动 deploy 未发生。 - 触发条件:新增依赖业务 schema(
public.redemption_codes、public.credit_transactions)的 redemption 安全迁移时,同时在frontend/db/migrations与frontend/supabase/migrations各放一份;database-local-business聚合迁移(两目录全量)通过,而只应用frontend/db/migrations的 identity-only fixture 中业务 schema 尚不存在。 - 根因:
database-self-hosted-identity与identity-auth-integration只应用frontend/db/migrations(identity 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.sql;contract 测试只审该单一 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_security;SQL 内容未作任何变更。 - 验证:本机无 Docker,无法执行 identity-only 与 aggregate DB 实测;运行
npx tsx --test tests/redeem-orders-contract.test.ts(7 passed,原 6 项 + 新增 existsSync 防复发守卫 1 项)、tsc --noEmit(clean)与git diff --check通过;目标 identity DB 实测(database-self-hosted-identity、identity-auth-integration)与 aggregate DB 测试(database-local-business、database-redeem-security)留待远端 gate 确认,本记录不提前声称远端收口。 - 防复发:
frontend/db/migrations只放 identity foundation 独立可执行迁移;依赖业务 schema(public.redemption_codes、payment_orders等)的迁移只进frontend/supabase/migrations;任何新增/删除迁移必须在本变更中同时跑 identity-only 两测试(database-self-hosted-identity、identity-auth-integration)与 aggregate DB 测试(database-local-business等)。 - 相关记录:BUG-127、BUG-143
- 复发自:无(独立根因,非 BUG-127 / BUG-143 复发)
- 修复版本:待 follow-up commit / gate
BUG-145 | staging Web health 被空模型目录错误阻断
- 状态:resolved(local 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_unavailable或database_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 时报告
degraded;database_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.ts(12 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,未新增同义 env,staging 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.ts(34 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 模型供应商保存成功但列表为空
- 状态:resolved(local candidate,staging migration/deploy 待验证)
- 首次发现:2026-08-08
- 最近更新:2026-08-08
- 现象:POST 保存成功但 GET providers 为空。
- 触发:self-hosted
admin_runtime直查五张 model 表:model_providers、model_configs、model_config_versions、model_publish_events、model_connection_test_evidence。 - 根因:表启用 RLS 且有 SELECT grant,但缺少
admin_runtimeSELECT policy;SECURITY DEFINER写成功、读被静默过滤。 - 修复:
20260808020000migration 为model_providers、model_configs、model_config_versions、model_publish_events、model_connection_test_evidence增加仅 SELECT policy。 - 验证:focused 7/7;full frontend 1071/1071;lint 0 errors、3 warnings;build 通过;
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.connect以lookup选项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 读取、密钥解密、/modelsURL、响应解析和错误翻译均不是本次失败根因。 - 修复:抽出
pinnedAddressLookup供共享 HTTPS 请求使用;options.all=true时返回单元素已验证地址数组,其他模式继续返回address, family。仍只使用已通过完整公网校验的固定地址,不重新解析、不跟随重定向,也不删除任何 SSRF/信任边界校验。 - 验证:
npx tsx --test tests/model-provider-encrypted-credentials.test.ts的真实本地 TCP 回归在修复前稳定失败为ERR_INVALID_IP_ADDRESS(4 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 分钟黑洞
- 状态:resolved(local candidate,远端 gate/deploy 待本提交)
- 首次发现:2026-08-09
- 最近更新:2026-08-09
- 影响面:Gitea staging quality gate 的依赖安装阶段、
manman-linuxrunner 与 Gitea/runner 控制面可用性;测试、lint、build、exact-SHA checkout/publish/deploy 合同未改变。 - 用户现象:Run 1618(SHA
ffbe505c)在 Python pip 完成后进入 digest-pinned Node 容器执行npm ci;步骤无后续 npm 输出,直到 45 分钟 job timeout,publish/deploy skipped。 - 触发条件:self-hosted runner 在受限 Node 容器中执行 frontend
npm ci,但安装命令本身没有 fail-closed deadline,npm 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.ts32/32 passed;git 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 资源
- 状态:resolved(local 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 路径切换到高开销 Webpack;Gitea 与 runner 共享资源时因此拖垮控制面。直接重跑旧 revision 会重复相同故障。
- 修复:恢复默认
npm run build为next 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.ymlRun1638;旧生产、Supabase、DNS 和新生产应用均未被切换。 - 用户现象:候选 SHA
9235ee66f6d889e6c27e4b85411448889dd7d37d已通过 staging gate 并在公网 staging 运行,但手工 Release Gate 在 1113 个前端子测试中出现 17 个失败。失败均从docker compose --project-name或docker compose --env-file返回unknown flag开始。 - 触发条件:完整 release profile 在
xiaoxinrunner 执行 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 Run1636已报告 Docker Compose2.40.3并成功完成质量门;本地聚焦 workflow 测试 4/4 通过。仍需新 SHA 的手工 Release Gate 成功后再视为完整关闭。 - 防复发:任何执行 Compose 集成测试的 runner 必须在昂贵依赖安装和测试前显式验证 Compose v2;Docker Engine 可用不能替代 Compose 能力证明。
- 相关记录:BUG-128、BUG-129、BUG-136、ERR-095、ERR-099、ERR-103
- 复发自:无
- 修复版本:待新 SHA 的 Release Gate 验证
BUG-152 | 订单返回套餐后再次返回会重新进入订单页
- 状态:resolved(local 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 | 个人报告页面无法下滑且命盘与打印版式失真
- 状态:resolved(local 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=936、scrollHeight=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 | 个人报告错误绑定会话且创建请求同步阻塞
- 状态:resolved(local candidate,待 staging gate/deployment)
- 首次发现:2026-08-09
- 最近更新:2026-08-09
- 影响面:个人报告入口、
/reports信息架构、POST/GET /api/reports、报告生成等待体验;报告证据合同、用户归属与每日限额不放宽。 - 用户现象:只有进入一条已有消息的咨询 session 后才看得到“生成个人报告”,但实际报告使用的是账户出生资料而非该次对话;点击后 HTTP 请求会等待完整排盘与模型生成,用户必须停留并感知长时间阻塞,也没有集中查看历史报告的位置。
- 触发条件:聊天页用 active session、消息数和临时 workflow receipt 控制报告 CTA;
POST /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 登录后密码状态接口误报未登录
- 状态:resolved(local 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,但允许
user与admin两个已配置 surface;未知 Host 仍返回 401,Cookie 继续保持 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 报告创建被未实现的数据库过滤器直接打断
- 状态:resolved(local 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-hostedLocalPostgresQueryBuilder只实现了现有的.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-154(staging 发布前遗漏 self-hosted 适配能力核对)
- 修复版本:本次报告接口修复提交
BUG-157 | 侧栏“我的报告”按钮缺少 flex 布局导致图标与文字分离
- 状态:resolved(local candidate,待 staging gate/deployment)
- 首次发现:2026-08-10
- 最近更新:2026-08-10
- 影响面:展开状态的全局侧栏“我的报告”入口;导航行为、折叠态 tooltip 与 accessibility 语义不变。
- 用户现象:报告图标贴在按钮左上方,文字单独位于中间,整体未与上方“新对话”按钮对齐。
- 触发条件:侧栏展开并渲染
.report-nav-button的图标与文字。 - 根因:样式设置了
justify-content和gap,但没有启用 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/python33.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/python3.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 晚于证据包生成
- 状态:resolved(local candidate,待 staging gate/deployment)
- 首次发现:2026-08-10
- 最近更新:2026-08-10
- 影响面:个人报告 consultation workflow、Technique Audit、VedAstro official evidence、
modules.ashtakavarga与modules.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;相关聚焦测试与开工预检通过。 - 防复发:本地计算器存在不等于已进入报告 modules;machine 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 | 管理端商品权益枚举未本地化且登录、套餐与充值返回状态出现前端闪烁或陈旧状态
- 状态:resolved(local candidate,待真实登录态浏览器验收)
- 首次发现:2026-08-11
- 最近更新:2026-08-11
- 影响面:管理端商品与权益列表/编辑表单、主站登录跳转、首页 composer、套餐页兑换码弹窗、支付后首页点数余额
- 用户现象:商品类型、计费周期、权益类型和重置周期直接显示英文枚举;未登录进入
jyotisha.chat时登录页接管前短暂出现“暂时无法进入 Jyotisha”;首页 composer 展示过多状态提示;套餐页默认打开兑换码弹窗;充值成功返回首页后仍显示旧点数。 - 触发条件:管理端读取数据库枚举;首页并行账户/会话请求返回 401;composer 业务流程写入 notice;账户菜单携带
redeem=1进入套餐页;支付页返回已存在的首页历史记录且该记录未按普通pageshow刷新账户。 - 根因:商品与权益 UI 直接渲染数据库英文值;401 跳转后继续抛出普通错误并被首页 bootstrap 写入
accountError;composer footer 集中渲染所有 notice;套餐页用 URL 参数初始化redeemOpen,首页账户菜单默认写入该参数;余额同步事件只在套餐页当前窗口发出,而首页已卸载,首页又只在 BFCachepageshow.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 与前台排盘在模型调用前超时
- 状态:resolved(local 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_role对consultation_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 与服务器排盘工具
- 状态:resolved(staging 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|enabledruntime 开关;新路径由带 Jyotish Skill 的 Mastra Agent 调用请求级run-jyotish-consultation,工具参数只允许 question/theme,出生资料始终由服务器上下文绑定,并以请求内 Promise 保证重复调用只计算一次。服务端把fullStream清洗为 NDJSON,仅公开安全的 run/Skill/tool/activity/answer 事件;Skill 和主工具合同未完成时最多重试一次,仍失败则释放预留点数且不保存成功消息。Web 按 content-type 保留 legacytext/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 的完整 metadata;partial metadata 不得通过删除plan_version降级为 legacy,且 plan 只能收紧、不能授予精确应期权限。ConsultationPlan在创建时对对象和嵌套数组执行深冻结,确保扣点后的 workflow 不能改写边界。模型 evidence packet 不再接收任意对象后依赖 denylist 清洗,而是分别对natal_foundation、domain、timing、validation与evidence_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_time、lng、utc_offset、account_balance、entitlements、access_scopes六类恶意嵌套别名不进入模型 packet,同时保留本命、Shadbala、Ashtakavarga、Dasha、Narayana 与 evidence status。目标 ESLint、Pythonpy_compile与git 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 且并发会重复创建
- 状态:resolved(local 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):
- 新增独立 Skill
skills/jyotish-birth-time-rectification(SKILL.md ≤200 行 + 5 个 references),方法源只在 Skill,系统提示词不再复制完整方法。 - 新增 V9 领域 contracts:Case 状态机(10 状态 + resumable/terminal 谓词 + 单向终态)、Evidence kind/date precision/append-only lineage、public receipt/activity allowlist、open request/response schemas;
supersedeActive=true被 schema 与 RPC 双重禁止。 - 新增向前业务迁移
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_turns(pending/completed/failed/retryable,禁止 reasoning)、agentic_rectification_tool_receipts(只存 fingerprint/phase/tool/status)、agentic_rectification_open_ledger(requestId 幂等);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 或权限决定。 - 一次性幂等 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()。 - 新增 Case Service + 五个 API:
POST /api/rectification/cases/open(homepage resume-or-create / session 精确恢复 / new 安全冲突)、GET entry-summary(首页 CTA 真值)、GET cases/[caseId](脱敏投影,绝不返回 baseline_birth_snapshot)、POST close、POST upgrade-skill;profile 不完整不建案;错误不泄露身份、出生资料或 DB 原文。
- 新增独立 Skill
- 验证:新增
rectification-v9-contracts.test.ts(12)、rectification-v9-case-service.test.ts(14)、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 warning;git diff --check通过;database-local-business.test.ts精确 public 表清单同步新增 5 张 v9 表并断言新迁移 applied(BUG-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)
- 状态:resolved(local 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 turn(pending→completed)、没有 fullStream→allowlist NDJSON 映射、没有 Case-ref 工具、没有 Skill 真实加载证据、没有 DB 驱动的 runtime selector。
- 修复:
- 重构
agentic-rectification.ts:系统提示词压缩为约 25 行高优先边界(不再复制 gate→scan→score→diagnostics);固定加载skills/jyotish-birth-time-rectification;maxSteps按 action 有界(opening/read-only 6、evidence 8、rescore 12、accept/confirm 6)+ 硬上限 16 + 重复工具调用检测(>3 次相同调用中止)。 - 新增十个 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-case;input 只含 caseId/sourceTurnId/evidenceId/resultId/candidateId/quote/proposedKind 等最小引用,绝不接 userId/出生资料/range/events[]/分数/权限开关;每个工具走 RLS 仅 service_role 的 RPC(evidence ledger、指纹缓存、receipt、幂等);accepted与confirmed由 RPC 分列,confirm 需要 confirmation gate + 用户原话 consent quote 原文匹配;candidate 相同 evidence/range/engine 指纹复用缓存。 /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 输出 NDJSON(run.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 传播释放。- 新增向前业务迁移
20260813010000_agentic_rectification_v9_agent_api.sql:case 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_versionfeature flag(published、100%,config version=v9 legacy_mode=readonly),DB 驱动 selector,两个 runtime 不可能同时写 profile/扣费/confirm。 - 前端:
page.tsx删除hasRectificationSession与sessions.find(sessionType==='birth_time_rectification');新增openRectificationFromHomepage/openRectificationSession(sessionId)/startNewRectification,全部请求服务端 Case open API 并使用返回的 exact sessionId/caseId;首页 CTA 由 entry-summary 驱动(开始/继续上次/再次校正);侧边栏校正 Session 点击走 intent=session + 精确 sessionId(terminal 只读 + “再次校正”);shouldStartOpening只来自服务端;聊天组件改为 caseId/sessionId/readonly/shouldStartOpening 初始化并从持久化 Turns 恢复,候选卡来自 Candidate Snapshot API,活动展示来自真实 NDJSON + 持久化 receipt(可折叠“本轮做了什么”,不显示 reasoning),删除本地 timer 模拟与隐藏 sentinel。 - 新增
GET cases/[caseId]?sessionId=返回持久化 turns/evidence/latest_result/逐轮 receipt 支持刷新恢复;新增POST cases/[caseId]/candidates/accept(UI 候选卡直连,不扣费、幂等、case-scoped)。
- 重构
- 验证:新增
rectification-v9-entry-routing.test.ts(13)、rectification-v9-evidence.test.ts(11)、rectification-v9-agent.test.ts(12)、rectification-v9-stream.test.ts(9)、rectification-v9-status-security.test.ts(11);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 error;git diff --check通过。 - 防复发(补充):任何新 rectification 前端逻辑不得再根据消息数/候选存在/session 排序推断 Case 状态;
/api/rectification/agent只能消费 fullStream 并输出 allowlist NDJSON;工具输入 schema 必须 strict 且只含最小引用;首轮必须保留真实 skill 加载证据;billing request identity 只能绑定 caseId;DB 驱动 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)、候选采用重放幂等、确认重放返回字段
- 现象:
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/domain(education_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_candidates或engine_http_error,V9 无法产出任何候选。accept_agentic_rectification_candidate_for_case的幂等重放分支位于 profile 基线校验之后;第一次 accept 写入active_birth_time后,基线快照与 profile 必然分歧,重放/双击/断线重试返回candidate_profile_changed而不是idempotent=true(旧accept_agentic_rectification_candidate是先重放后校验,语义回归)。confirm_agentic_rectification_birth_time幂等分支在v_result载入前引用v_result.id,confirmed 重放响应的result_id恒为 null。
- 触发条件:任何进入 compare-candidates 的真实运行;accept 成功后同一候选再次 accept;confirmed 后同一 confirm 重放。
- 根因:
- 新增
engine-client.ts从未与真实引擎做契约测试(既有测试全部 mockrunV9CandidateScore,未覆盖真实响应形状);V9 evidence 领域模型与引擎粗粒度评分词汇之间缺少 kind/domain 翻译层。 - accept/confirm RPC 的重放语义被基线保护逻辑错误地前置/后置,未对齐旧实现的先重放后校验顺序。
- 新增
- 修复:
engine-client.ts对齐真实引擎契约:新增toEngineScoreableEventkind/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_pressure;family/other 留在账本但不再进引擎);readCandidates由time+score按分数降序推导 rank、同分 tied_minute_count、相对支持度归一化;representative_time=top1;selection_allowed=有候选;confirmation_allowed只来自引擎can_confirm_exact_minute(真实引擎当前为 false,confirm 门诚实关闭);margin_percent取自 diagnostics;confidence 由 margin+retention 推导;无 scorable 事件/无候选时 fail-closed(no_scorable_evidence/engine_no_candidates)。20260813010000_agentic_rectification_v9_agent_api.sql:accept 重放分支移到 profile 基线校验之前,重放分支校验 profile 与已采用时间一致(对齐旧语义);confirm 幂等分支补select id into v_result.id,result_id不再为 null。- 移除
rectification-v9-tools.tscompare-candidates 中双分支同 throw 的死代码。
- 验证(真实执行,非 mock):
- 本机安装 PostgreSQL 17(brew),按
deploy/postgres/001-bootstrap-roles.sh建角色,从空库全量应用 95 个迁移 ×2(second run 95 already applied,--checkexit 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 backfill(confirmed/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 test1256(1232 pass / 18 Docker ENOENT 环境失败,与基线6d7a9a97同因,worktree 实证);tsc --noEmit仅剩 6 个既有未触碰测试文件错误;ESLint 0 error;next build通过;git diff --check通过。
- 本机安装 PostgreSQL 17(brew),按
- 防复发:引擎适配器必须有真实响应形状的契约测试;新增任何映射层必须对照
scripts/rectification/contracts.py:SCOREABLE_EVENT_KINDS;RPC 重放/幂等语义以“先重放后校验、重放校验已落库状态”为唯一实现顺序;迁移修改必须在真实 PostgreSQL 上从空库全量应用并重跑。 - 相关记录:BUG-163、BUG-112
- 修复版本:本地 staging 候选(未 push / deploy)
BUG-165 | 合并咨询 Agentic runtime 后生时校正执行步骤复用共享 activity 字段导致构建失败
- 状态:resolved(local candidate,待 staging gate)
- 首次发现:2026-08-11
- 最近更新:2026-08-11
- 影响面:
RectificationAgenticChat、共享ChatMessageView、Next.js production type-check - 用户现象:生时校正与最新 staging 的咨询 Agentic runtime 单独测试均通过,但合并后
next build在rectification-agentic-chat.tsx报string[]不能赋给结构化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-stream、rectification-agentic-entry、chat-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-11(Gitea staging gate run 1732)
- 最近更新:2026-08-11
- 影响面:
frontend/tests/rectification-v9-database.test.ts、frontend/src/lib/db/local-postgres-client-core.ts的按 URL 连接池生命周期 - 用户现象:staging gate 的 Docker DB 套件恰好三个 V9 测试失败。
- 触发条件:在 CI(Docker fixture)中运行
rectification-v9-database.test.ts。 - 根因(脱敏):
- 两个测试创建了
createLocalPostgresDataClient的 service 客户端后没有关闭其全局按 URL 缓存的连接池;fixture.stop()先销毁 PostgreSQL,池的异步终止随后触发 57P01(terminating connection)类报错。不能全局关闭所有池(node:test 的 DB 用例可能并发),必须按各自 service URL 关闭,且任何情况下fixture.stop()都要执行。 - 最后一个 “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 进入候选查询。
- 两个测试创建了
- 修复:
- 审查并保留新增的
closeLocalPostgresDataPool(connectionString):先按 key 从全局缓存删除再pool.end(),未知 key 与重复关闭均为安全 no-op,删除后再注册同名 key 可创建新池,与全局closeLocalPostgresDataPools并发/先后调用无冲突(end 幂等)。 rectification-v9-database.test.ts中所有创建 service 客户端的测试(open / profile gating / evidence / backfill / agent api,共 5 个)在finally中先await closeLocalPostgresDataPool(service_url)再fixture.stop(),嵌套 try/finally 保证池关闭失败时 fixture 仍会停止;纯迁移测试无需关闭。- 最后一个测试先经合法的
identity_runtime播种identity.users,再由 postgres admin 更新触发器自动创建的 profile 并播种其它public.*行;同时改用模板中的既有 fingerprint,并按 RPC 真实合同断言 consent grounding 失败。保留生产最小权限,不改任何 grant、不改迁移。 - 新增非 Docker 单测
tests/local-postgres-pool-close.test.ts(4 项):未知 key no-op、按 key 关闭互不影响、关闭后同 key 可重建、全局关闭后再按 key 关闭 no-op。
- 审查并保留新增的
- 验证:
local-postgres-pool-close.test.ts4/4 通过;本地 Docker PostgreSQL fixture 的rectification-v9-database.test.ts6/6 通过;目标 ESLint 0 error;git diff --check通过。Gitea gate 仍需对最终提交复跑。 - 防复发:任何测试创建本地数据客户端必须在
fixture.stop()之前按自身 service URL 关闭连接池;测试不得用identity_runtime向public.*播种 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 - 用户现象:已登录且具备账号管理权限的管理员执行账号重置时,仍需额外发送并输入邮箱验证码,增加不必要的操作步骤。
- 触发条件:在用户列表打开重置弹窗并提交账号重置。
- 根因:账号重置复用了面向角色变更、账务调整等操作的
requireHighRiskAdminMutation与reauthPermission,把一次性邮箱复核错误扩展到了已有登录、权限、原因、可信来源及数据库审计保护的重置路径。 - 修复:该路由改用既有
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已是published、enabled=true、rollout_percentage=100。 - 触发条件:self-hosted Web 通过
ADMIN_DATABASE_URL以admin_runtime查询启用了 RLS 的public.feature_flags。 - 根因:
20260806050000_operations_feature_flags.sql给admin_runtime授予了表级 SELECT,但启用 RLS 后没有创建对应 SELECT policy。PostgreSQL 因此不报权限错误而是返回零行;loadRuntimeFeatureFlags将缺失记录安全降级为 disabled,路由遂返回“生时校正服务暂未开放”。 - 修复:新增向前迁移
20260811030000_feature_flags_admin_runtime_read_policy.sql,保留最小 SELECT grant,并为admin_runtime创建feature_flags_admin_readRLS SELECT policy;不放宽匿名、普通用户或其它运行时角色权限。 - 验证:Docker PostgreSQL 回归先在修复前稳定得到空结果,新增迁移后要求
admin_runtime能读取true:100:published;staging 还需验证迁移账本、角色可见性及真实 Agent 接口不再返回 runtime disabled。 - 防复发:任何对启用 RLS 的表新增 runtime grant 时,必须同时测试对应运行时角色的真实可见行,而不能只断言
has_table_privilege=true;feature 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_rectificationSession 后立即发送 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_rectificationSession 的路径都必须在同一事务内得到可解析的持久化模型与版本;前端显示的默认模型不能替代数据库绑定。 - 相关记录: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-ndjson,Skill 与 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/agent的opening与read_only免费 Turn,以及回答完成后的 Turn 最终状态与 assistant message 持久化。 - 用户现象:Agent 已加载 Skill 与正确 Case,并输出完整回答,但 NDJSON 最终事件仍为
run.failed;数据库 Turn 为retryable且没有持久化 assistant message。 - 触发条件:免费
opening或read_onlyTurn 正常生成回答并进入成功收尾。 - 根因:路由的
billing.reserve()对免费 Turn 不创建usage_reservations,但billing.complete()仍调用complete_usage;数据库因找不到 reservation 返回request_missing,Agent runner 将已成功回答降级为usage_settlement_failed。 - 修复:免费 Turn 在 settlement adapter 中直接成功返回;只有
messageTurn 才执行 reservation 与 settlement,保留原有付费消息的计费、幂等与失败保护。 - 验证:staging 数据库确认目标 Case 没有 usage reservation,直接调用同一结算函数稳定返回
request_missing;新增合同回归要求免费 Turn 同时绕过 reservation 与 settlement。部署后需以真实 opening 确认最终run.completed、Turncompleted且 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-evidence与rectification-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_message与assistant_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 tree;staging 需继续验证 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;用户无法保留旧记录并另开一段校正。
- 触发条件:存在
draft、collecting_evidence、candidate_ready、candidate_accepted、needs_rebaseline或pausedCase 后点击首页生时校正卡片。 - 根因:V9 初始设计把首页
homepageintent 定义为 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 + Session,session仍按精确 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 Database;production 发布门禁未修改。
- 用户现象:staging 作为测试分支需要领先或偏离 main 时,旧部署工作流仍要求 main 与 staging 同一 SHA,且
workflow_run从默认 main 加载控制器,导致 staging-only 变更无法按自身已测试工作流发布。 - 触发条件:
staging推送了尚未进入main的测试提交并完成 quality gate。 - 根因:staging 发布把 production 的 reviewed-main 收敛约束复用到了测试环境,同时依赖默认分支的
workflow_runcontroller;即使删除 SHA 相等检查,旧 main controller 仍可能继续执行旧门禁。 - 修复:将 staging gate 重命名为
Independent Staging Quality Gate,使默认 main 上遗留的workflow_run监听器不再匹配并抢占staging-mutation并发组;staging push gate 在发布同一 exact-SHA 的不可变镜像与 allowlisted controller bundle 后,显式从refs/heads/stagingdispatchDeploy 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 SHA;staging 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-suggestions;Prompt/Skill 只限制“最多一个问题”,未明确完整回复可以零问题、内部执行不得进入正文、用户没有更多事件时不得继续轮换领域,也未划清候选卡与 Agent 正文的内容所有权。 - 修复:新增客户端候选快照解析模块,按 Case API camelCase 外层字段读取结果并规范化候选内部字段;候选卡独占时间、排名、相对支持度、采用动作和选中状态,近乎并列且未开放确认门时不标记“当前推荐”;生时校正改用只提取正文与标题的解析入口,移除建议状态、持久化和
composer-suggestions;Prompt、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/confirm;
rectification-read-case从服务端 Dossier 派生有界的evidence_context与conversation_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_methods的tool.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-hero与product-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-display、font-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_stream、stream_aborted或stream_unfinished失败并自动重试;用户对 active question 作简短承接回复;会话超过近期窗口;一条消息包含多件明确经历;或注册表 active Skill 版本在 Case 创建后升级。 - 根因:旧运行器把
skill.bound、case.loaded主要写在 Prompt 约定中,没有 durable attempt ownership 和成功 attempt 投影;ConversationFocus 由 Assistant 文本正则倒推,没有服务器持久化的 target evidence/domain/kind;Case dossier 缺少 durable conversation summary 和批量 evidence 幂等合同;运行时按全局 active Skill 解析,而不是严格使用 Case 已绑定的 immutable package identity。 - 修复:新增 additive V10 migration,建立
agentic_rectification_run_attempts、agentic_rectification_conversation_focuses与agentic_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.settled、run.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 skipped(Docker 场景由独立真实数据库测试覆盖);真实 Docker PostgreSQL 从空库应用全部 migration 并完成业务测试 1/1,覆盖 request replay/mismatch、legacy RPC 撤权、terminal attempt 取回与新 attempt 拒绝、completed Turn 单调性及 superseded attempt;目标 ESLint 0 error/0 warning;
git 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;失败/重试不得重复扣费或重复 evidence;focus 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_exit、relationship_commitment/relationship_separation等生命周期事件进行候选评分;展示候选卡或 Activity;采用代表候选;或调用旧的 time-based candidate RPC。 - 根因:Python 事件合同只接受少量粗粒度 kind,TypeScript adapter 承担了有损映射、排序/tie/置信度/permission 和 technique 推断;结果表与 RPC 只保存调用方提供的布尔值和候选 JSON,没有版本化 decision receipt、真实 execution ledger 与服务器 candidate UUID 边界;历史 accept RPC 仍可把 accepted 与 confirmed 混合。
- 修复:引入 Event Contract v2 与 Decision Policy v2,由 Python 返回版本化
candidate_decisions、decision_receipt和真实execution_ledger,日期精度进入服务器评分;Web 只做严格安全投影,不再自行推断 rank、tie、margin confidence、selection gate 或已执行技法。forward-only migration 保存政策与执行凭证,以服务器生成的 candidate UUID 绑定 result,并将 representative candidate 重写为持久化 UUID;accepted 与 confirmed 使用独立 RPC 和状态门禁,精确确认必须引用已接受候选、有效 consent/source turn,旧 time-based RPC 对service_role撤权。Case/Dossier 恢复路径同时投影完整 v2 receipt、ledger、selected candidate 与 display gate。 - 验证:Python 服务回归 36 passed;TypeScript 核心合同 49 passed、0 failed;migration 与 PR-4 真实 PostgreSQL 场景 75 passed、0 failed;V9/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 warning,Python 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 出生时间生成报告。 - 根因:报告路由把所有完整报告折叠到单次
generalworkflow,旧 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.jsafter(),改为由数据库原子创建的持久化 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_role;writer 与单次 repair retry 均接收 worker AbortSignal。accepted 状态改为accepted_directional_only,Bundle 不生成candidateRange;缺失 D2/D11 等证据只能生成blocked + executed=falsereceipt。Web renderer 继续只消费结构化文档和真实 chart data,并使用浏览器原生打印,不接受模型生成 HTML/CSS/SVG。 - 验证:个人报告合同、planner/writer、API、entry、job state/service/worker、renderer/export、migration 与 Skill registry 完整聚焦回归 247 passed、0 failed;Python 报告合同与 Skill package 41 passed、0 failed;目标 ESLint 0 error/0 warning,
git 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/consensus;Agent 不得接收 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 closed,annual普通咨询不等于annual_report。UI 保留 answer delta 期间的真实 Activity 与失败 receipt,只从服务端 event/receipt 渲染执行状态,并严格区分“已采用(未确认)”和“已确认”。另建独立 product registry 与 feature flags;个人报告、合盘、生时校正写入/计算 API 在鉴权后执行 fail-closed 产品 gate,报告中心同时保留既有环境变量开关。 - 验证:TypeScript focused 矩阵 178 passed、0 failed;Python consultation/thematic/API focused 70 passed、0 failed,
py_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或 verified;accepted 不得升级为 confirmed,Activity 不得从 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绕过,accepted与confirmedminute 显式分离且后者只在明确 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 availability,
requiresEvidence不能关闭事实/时间证据规则,accepted 不得升级为 confirmed;Agent 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
/sflag 且假设 PATH 中存在可导入 PyYAML 的python。因此不能安全提交、推送或发布 staging。 - 触发条件:在 PR-7 十域 registry、PR-4 V2 candidate decision contract、生产迁移列模型与独立 staging workflow 合同合并后运行全量
tsx --test tests/*.test.ts、tsc --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 gate;billing 测试分别锁定 reserve/complete/release 的免费 turn 短路与付费调用;数据库测试保留 legacy RPC 撤权断言,并恢复persist_agentic_rectification_candidate_v2到accept_agentic_rectification_candidate_for_case_v2的真实纵向链路,覆盖服务端 candidate UUID、首次接受、幂等重放、切换候选、profile 落库、reported time 保留及基线变化后的 expired 拒绝;生产迁移 fixture 补齐 identity/data type 元数据;YAML 检查改用PYTHON、VIRTUAL_ENV、仓库.venv与 PATH fallback,quality gate 通过os.environ.setdefault("PYTHON", sys.executable)向前端测试传递解释器,并移除 worktree 层级假设。 - 验证:聚焦非数据库测试 70 passed、0 failed;本地 PostgreSQL 全迁移与业务链路 1 passed、0 failed;全量前端 1532 passed、0 failed;
tsc --noEmit通过;ESLint 0 errors、4 个既有 warnings;Next 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 cases(3/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 漏提交上游 .agents,clean 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.sh和run-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,使创建和幂等分支统一返回完整
focusrow 与idempotent;set-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_incompletefail 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且不自动 confirmed;period/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_source为period_only或unknown时,以homepage/newintent 创建 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:00–03:59;unknown使用00:00–23:59。缺少出生日期、地点、时区,或选择period_only却没有合法时段等真正不完整组合,仍在调用创建 RPC 前 fail closed。 - 验证:回归测试先证明旧实现对 period/unknown 稳定抛出
profile_incomplete,修复后锁定 morning08:00–11:59、late-night23:00–03:59、unknown00:00–23: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_offset为null。 - 根因:资料表单和账户保存合同允许用 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:00–22: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_source为period_only或其他没有具体分钟的状态,首页以general_no_birth_time发起daily_starlanguage请求。 - 根因:BUG-200 只修正了首页任务式文案,没有闭环服务端能力合同:前端无分钟请求主动丢弃
daily_starlanguageentrypoint;General Agent 又只允许百科知识,并把所有 forecast 一律拒绝。初始化保存的出生时段不能安全替代具体分钟,因此也不能直接走个人命盘日运链路。 - 修复:无分钟请求保留受限的
daily_starlanguageentrypoint,并在服务端确认最终咨询模式后将其展开为“公共日历趋势”问题。新增服务器公共 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。 - 用户现象:
main与staging已同步且 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 只保留CHOWNcapability,却按“父目录在前、lock 子文件在后”的顺序处理;Run 1873 已先把.state改成deploy:deploy 0700,再因没有DAC_OVERRIDE无法遍历到仍未修复的 lock,留下半修复状态。Run 1877 虽改为 child-first,但 helper 启动时父目录已经是不可遍历的0700 deploy,所以仍无法通过原宿主路径到达 lock。四次失败都发生在pg_dump前,没有生成可用恢复证明,也没有执行 schema migration。 - 修复:恢复脚本先对
.state、backups和既有mutation.lock做类型与非符号链接校验,取得当前运行 PostgreSQL 容器的不可变本地 image ID,再复用既有sudo -n docker边界启动一次性所有权修复容器:--pull never、无网络、只读根文件系统、no-new-privileges、删除全部 capability 后只保留CHOWN。除绑定.state与backups外,把已经验证为普通非 symlink 文件的 lock inode 直接绑定到容器/mutation.lock,先通过该直接 mount 恢复 lock,再恢复两个目录 mount point 到当前deployUID/GID。这样无需遍历半修复的 mode-0700父目录,也不需要增加DAC_OVERRIDE;不递归改动历史备份、不删除或替换 lock inode,随后仍用同一个flock -nfail-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 couldn’t load,只能点击 Reload 或手工刷新;同一登录会话刷新后报告列表恢复正常。 - 根因:生产 Next.js Web 构建未设置
deploymentId。旧标签页仍运行上一发布的客户端 runtime,在新镜像切换后进行客户端导航时请求了当前发布无法匹配的静态 chunk,触发ChunkLoadError并落入 Next.js 通用错误页。报告 API、登录态和报告数据本身没有失败。 - 修复:Web Docker build stage 接收并设置
NEXT_DEPLOYMENT_ID;Gitea 主发布链与 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-id,JS/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.md、deploy/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)参数校验前就创建并缓存calculationPromise。首次非法调用产生的 rejected Promise 被永久保留;后续合法调用命中缓存后直接复用旧拒绝,导致 workflow 调用数保持为零,最终由既有合同门禁判定runtime_contract_incomplete。 - 修复:在读取或写入计算缓存前同步完成域计划校验,非法参数不启动、不计数也不污染缓存;实际计算 Promise 拒绝时仅清除仍指向该 Promise 的缓存,使后续调用可以重新执行。成功 Promise 继续保留,合法并发调用仍共享同一次服务器计算。未移除运行合同门禁,未伪造 workflow receipt 或成功状态。
- 验证:新增 invalid
domains + theme→ validtheme: 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、ReasonActionModal、ConfirmActionModal、相关Popconfirm/Modal.confirm与 pending-confirm 状态。真实的数据录入 Modal 继续保留,但点击表单的“生成 / 保存 / 发布”等主按钮即直接执行;无额外参数的撤销、重试与开关动作由原按钮直接执行。客户端不再提交手工reason,服务端按动作注入固定审计标识并继续传给数据库 RPC 的非空审计字段。 - 安全边界:保留 request ID、数据库 actor 身份校验、领域 RPC、细粒度权限、可信 Origin、append-only 审计和最后一位 Owner 保护;保留账户级 Better Auth TOTP MFA 与一次性恢复码;普通用户登录、注册和找回密码的邮箱 OTP 不受影响。
- 验证:全局源码扫描确认管理业务中不存在
ConfirmActionModal、Popconfirm、Modal.confirm、reason-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 身份校验。 - 验证:本地使用项目实际
pgserializer 对比确认显式序列化后保持合法 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
pg的jsonb参数必须显式 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_boundfail closed。attempt 内的活动与文本在成功前统一缓冲,因此该合同错误在公开流中折叠成只有run.started → run.failed。 - 根因:Skill 的真实性与版本已经由服务器加载和校验,但运行合同仍把“是否完成绑定”交给模型是否主动选择
skill工具,形成服务器事实与模型行为之间的不一致;首步 Case 读取同样没有由服务器强制。该缺陷可确定性复现用户现象,但在缺少 staging 运行日志时不据此断言某个具体 provider 一定返回了直接文本或特定工具序列。 - 修复:要求
agent.getSkill()返回非空指令,并将其作为本 attempt 的服务器 system bootstrap 注入;在 provider 执行前持久化唯一 Skill receipt 和skill.boundphase,并将 Skill 标记为已绑定。通过 MastraprepareStep把 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--noEmit与git diff --check通过。部署同构 Dockerbuildtarget 成功,镜像内@mastra/core为1.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_time与birth_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-onlyfrontend/db/migrations。 - 验证:账户回归覆盖新建、既有 reported 原样重存、已 accepted 分钟修改、confirmed/legacy confirmed 保护及所有非严格准确来源,13/13 通过;账户、出生时间 intake、报告 API 与报告入口聚焦测试 82/82 通过。PostgreSQL 全业务迁移测试实际执行新增 migration,验证严格 0/0 记录得到
05:00:05:00:accepted,10 分钟误差记录保持active null + reported,1/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_blocked;workflow 返回后applyBirthTimeModeToWorkflowContext()又无条件把can_answer_precise_timing改为false。最终输出 guard 根据被强制阻断的 receipt 删除年月日,而不是根据计算证据是否完整决定。 - 修复:有具体分钟的
verified_chart与unverified_birth_time统一使用server_evidence_required,精确应期权限由服务器计算证据决定;未校正模式继续保留birth_time_confidence=unverified_reported_time和candidate_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.domains和agentExecutionReceipt.workflow.domains,前端在下一轮生成前 PATCH 完整消息数组。 - 根因:公开 Agent 事件使用的 canonical
workflowReceiptSchema已允许可选domains,聊天写入合同却重复维护了一套.strict()旧 schema,导致messages[].workflowReceipt.domains被 Zod 判定为unrecognized_keys;嵌套 execution receipt 使用新版 schema,因此同一业务对象在两个位置具有不同合法字段。 - 修复:聊天写入合同直接复用
consultation-agent-events.ts导出的workflowReceiptSchema和WorkflowReceipt类型,删除重复字段定义;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,其直接子节点却是嵌套的 ReactTypography.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_products与product_entitlementsSELECT,并分别创建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.completed(55ms)。 - 根因:合同门禁
contractReady()要求consultationToolCallCount === 1,而该计数在createConsultationTools()中对每次未命中缓存的执行递增,失败尝试同样计入。BUG-205 的修复让被拒绝的计算缓存可以释放、从而允许重试,但门禁仍按总尝试次数判定,导致只要发生一次瞬时失败,计数就永久大于 1,之后无论计算是否成功都不可能满足合同。服务端补跑无法降低计数,因此纯属浪费,最终以runtime_contract_incomplete结束并丢弃已经算出的结果。 - 修复:新增
consultationToolSuccessCount,仅在工作流真正成功时递增;门禁改为判定成功次数为 1。失败尝试继续记入consultationToolCallCount供观测使用,但不再影响合同。未放宽单次计算边界:请求级缓存保留成功 Promise,后续调用一律复用,因此每个请求仍最多执行一次计费计算;两次真实成功计算依然判定为违约。 - 验证:新增修复前失败的回归,复现“两次失败 + 一次成功 + 补跑命中缓存”序列并断言
run.completed且回答正常输出;新增“两次成功仍然违约”边界回归以锁定单次计算约束;工具层补充断言失败重试后consultationToolCallCount=2而consultationToolSuccessCount=1。回退门禁到旧实现可确认新回归失败。frontend/tests/consultation-agentic-runtime.test.ts17/17 通过。 - 防复发:运行合同门禁只能依据成功语义的计数,不得用包含失败尝试的总调用次数;任何允许重试的缓存改动,必须同步检查下游门禁是否仍按尝试次数判定。合同类回归必须覆盖“失败后恢复”与“重复成功”两个方向。
- 相关记录:BUG-205、BUG-186、BUG-189
- 修复版本:本地未提交候选
待跟进
前两次 calculation_failed 的服务端原因尚未定位(工作流超时为 90s,两次失败均在 20s 内,可排除超时)。本条修复只保证瞬时失败可恢复,不替代对失败本身的排查。
排查所需的可观测性已随本批补齐:calculation_failed 是 safeToolError() 的兜底码,除中止与超时外的一切失败都会被压成它,而上游真实错误文本属于 provider payload,按 agent-observability.ts 的封闭契约不得进入日志。因此改为按封闭机器码分类:runConsultationWorkflow 抛出带 code 的 ConsultationWorkflowError,按 HTTP 状态区分 workflow_rate_limited(429)、workflow_bad_request(400)、workflow_queue_full(503)、workflow_server_error(5xx) 等,并单独标识 workflow_contract_invalid(HTTP 通过但响应未过 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 因此停留在7050f7ee,bced1b9d与其后两批后端修复均无法部署。 - 根因:
cssBlock()以globalStyles.indexOf(selector + " {")取首个匹配规则。bced1b9d在媒体查询中新增了一条同名规则[data-sidebar="content"] { -webkit-overflow-scrolling: touch; },位置早于第 333 行的基础规则,helper 因而返回媒体查询块。该 helper 在sidebar-contract与membership-page两个文件各有一份副本,且自身没有任何测试。修复过程中又暴露两个同源缺陷:CSS 注释写在规则上方时会被计入捕获的选择器文本(.membership-page因此找不到);调用方会把整个选择器组当作 key 传入(".membership-plan-card, .membership-credit-card"),旧实现仅靠字面量子串匹配碰巧生效。 - 修复:抽出共享
tests/css-contract-test-support.ts的cssDeclarations(),先剥离注释再逐条规则解析,按逗号拆分并归一化空白后做精确或后代组合匹配,支持以整组作为查询,并返回所有命中规则声明的并集而非首个。并集使assert.match语义为“任一规则声明即可”、assert.doesNotMatch为“任何规则都不得声明”,后者比原实现更严格,也更贴近“唯一滚动容器”这类断言的本意。未放宽任何既有断言,未改动globals.css。 - 验证:新增 9 个 helper 回归,覆盖媒体查询先于基础规则、注释引入的规则、跨行选择器、选择器组查询、后代组合、以及选择器缺失时必须响亮报错;
sidebar-contract与membership-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,把提示按语义分派到既有 sonnertoast.success/toast.error/toast;根布局早已挂载<Toaster />,无需改动。以固定id复用同一条 toast,避免 1750ms 恢复轮询把同一句话堆成几十条;空字符串走 dismiss 而不弹空 toast。44 处调用点与全部中文文案逐字未改。 - 验证:新增
frontend/tests/chat-notice-and-scroll-contract.test.ts锁定状态不再被丢弃、提示进入 toast、空串不弹窗、轮询防刷屏与分级语义;tsc --noEmit、eslint清洁;与改动文件相关的 43 个测试文件共 386 条断言全绿。 - 防复发:禁止以
const [, setX]形式声明用户可见文案状态;任何面向用户的提示必须有可断言的渲染出口,合同测试需覆盖“文案确实可达 UI”而不仅是“文案存在”。 - 相关记录:BUG-211
- 修复版本:本地未提交候选
BUG-217 | 个人报告页轮询 120 秒后静默停止,界面仍显示“生成完成后页面会自动显示”
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-17
- 最近更新:2026-08-17
- 影响面:
/reports/[reportId]报告详情页的生成等待态。 - 用户现象:报告生成超过两分钟后,页面永远停在“报告正在生成中,请稍候…”的转圈上,即使报告已经在后台完成也不会刷新;页面同时声称“生成完成后页面会自动显示”,与实际行为矛盾。
- 根因:
personal-report-page.tsx以MAX_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 的合同测试覆盖锚定守卫、有意跳转与无障碍跳转控件;
tsc、eslint清洁。浏览器内的视觉位置未经人工目视确认。 - 防复发:聊天类自动滚动必须做底部锚定判断,禁止把流式文本直接作为无条件滚动的依赖项。
- 相关记录: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.tsx、global-error.tsx、not-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.ts6 条断言,覆盖文件存在、客户端指令、global-error自带文档骨架、role="alert"、标题层级与中文文案;tsc、eslint清洁。未做浏览器目视验证。 - 防复发:新增顶层路由段时必须同步确认错误与未找到边界覆盖;
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/server配ServerInsertedHTMLContext实测 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-ink、text-ink-secondary、text-ink-tertiary、text-danger、text-warning、bg-canvas、hover:bg-canvas-muted共 7 个类名,30 处调用点,分布在personal-report-page.tsx(17 处)、reports/[reportId]/的 error、loading、not-found 页、generate-personal-report-button.tsx与app/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-primary、text-muted-foreground、bg-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,且全文件useCallback与useMemo各为 0,React Compiler 也未启用。因此每次击键都会重渲染整个组件、重建全部 47 个内部函数、给所有子组件换一批新的函数引用。草稿仅存在于 React state,没有任何持久化。 - 修复:新增
frontend/src/lib/composer-draft.ts作为模块级草稿 store,新增frontend/src/components/chat-composer.tsx通过useSyncExternalStore订阅它。Home()中draftstate 移除,setDraft/setDraftTheme/setDraftEntrypoint保留为同名函数(前者转发到 store,后两者写 ref),十余处既有调用点逐字未改。选用外部 store 而非useImperativeHandle:输入框在生时校正面板与引导表单打开时会真的卸载,而selectSession、openRectificationCase恰好在这些窗口里调用setDraft(""),ref 句柄此时为 null 会让清空静默失效、旧文本在重新挂载后复活。草稿以jyotisha.composer-draft写入 sessionStorage(沿用jyotisha.pending-consultation命名约定),发送成功即删除键,读取时校验类型、拒绝未来时间戳、丢弃超过 24 小时或超出 500 字上限的值,sessionStorage抛错(隐私模式、配额)时降级为纯内存,绝不向打字路径抛出异常。输入框保持受控,isComposing中文输入法守卫原样保留。 - 验证:
tsc --noEmit、eslint(未新增任何 disable 注释)、npx next build均通过;读取page.tsx的 24 个既有测试文件 268 条断言无需修改任何一条即全部通过;新增frontend/tests/composer-isolation-contract.test.ts5 条。逐项确认发送后清空、停止后恢复原问题、推荐问题填入、咨询恢复回填、发送按钮空态禁用五个行为均未变。未做的验证:未在真实浏览器中实测中文输入法,也未用 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/react与thinking-orbs,chat-message-content.tsx静态导入react-markdown与remark-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 effect,gsap.matchMedia()/motion.revert()结构不变。thinking-orbs 用next/dynamic+ssr: false,占位符预留精确的 20×20 盒子避免抖动。三个 chunk 在首屏绘制后经requestIdleCallback(Safari 回退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 gzip(−73.2 KB,−13.3%),raw 1819.4 KB → 1601.4 KB(−12.0%)。两次构建的page.tsxsha256 相同,19 个首屏 chunk 中 17 个逐字节相同,三个新 lazy chunk 合计 74,006 字节 gzip,占降幅的 98.7%,可归因。以最小化产物特征串(gsap 的GreenSock、markdown 的micromark、orbs 的ribbon)确认三者已不在任何首屏脚本中。tsc、eslint清洁;新增frontend/tests/chat-bundle-splitting-contract.test.ts5 条(含对"绝不渲染裸 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内的客户端fetch,router.refresh()只重取服务端组件、不会重新挂载客户端树,改了按钮反而会静默失效)。此判断与本分支membership/page.tsx保留认证硬跳转的处理一致。 - 验证:
tsc、eslint清洁;next build通过;新增frontend/tests/chat-navigation-a11y-contract.test.ts13 条,同时锁定"哪些是软导航"与"认证重定向是有意保持硬跳转",未来有人机械改写会被测试拦下。修改既有测试 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,把回答生命周期归为六个阶段并为每个阶段指定唯一播报出口,避免重复播报:generating与completed走新的状态区域,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-list的aria-busy保留不动,它同时在抑制agent-activity-status那个按阶段变化的role="status"标签,去掉会造成串读。启动失败页从<main>上的aria-live="assertive"改为内层role="alert",同等紧急度但不再覆盖 main 地标,全文件不再出现aria-live="assertive"。 - 验证:
tsc、eslint清洁;next build通过;新增合同测试 13 条(与 BUG-251 同一文件);全部非数据库套件 199 个文件 1592 条断言全绿。同时确认 BUG-217 新增的"跳到最新"控件键盘可达:真实<button>、无tabIndex、标签与aria-label一致、min-h-11 min-w-11、pointer-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-018至BUG-043连续 26 个编号加上BUG-209,共 27 个编号各被两条不同记录占用。成因是并行分支各自追加记录、合并时两侧都保留。该失效模式仍在持续:仅在准备本批提交的一小时内,staging就两次抢占了本地待提交记录的编号(先BUG-214后BUG-215),迫使本地记录两轮顺延。 - 修复:保留每对中较早的记录编号,为后加入的一条分配
BUG-221~BUG-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-020~BUG-041区间内记录正文与标题整体错位一位,部分标题下只剩 3 行结尾字段而没有状态/根因;且BUG-221~BUG-234本质是BUG-020~BUG-035的重复记录,编号已唯一但内容仍冗余。两者都需要移动正文内容,属内容编辑,未在本轮处理,已由 BUG-254 完成。另有 7 行属"混合编号"(此前有人部分修正过),证据不足以判定,已就地加(编号存疑:…)标注而未改数字。 - 防复发:BUG 编号必须唯一;追加记录前先检索当前最大编号;出现重复时保留较早记录的编号并同步修正指向它的
相关记录与复发自。强制性文档约束的措辞必须与文档实际体量相称,否则会被普遍忽略而失去约束力。 - 相关记录:BUG-253 无前序同类记录
- 修复版本:本地未提交候选
BUG-254 | 22 条记录的正文挂在别人的标题下,其中一条正文被压在另一条记录末尾
- 状态:resolved
- 首次发现:2026-08-17
- 最近更新:2026-08-17
- 影响面:
docs/BUG_HISTORY.md中BUG-020~BUG-043与BUG-221~BUG-240区间的 22 条记录。 - 用户现象:无终端用户可见现象。对维护者与 AI agent 而言,按标题检索到的记录读出来是另一个 bug 的现象、根因与修复——比查不到更危险,因为它看起来完全正常。BUG-253 已修复编号唯一性,但当时判断"需要移动正文内容"而留作待跟进。
- 触发条件:按
BUG-036~BUG-041、BUG-235~BUG-240任一编号检索并阅读其正文。 - 根因:三个缺陷叠在一起,都源自 2026-07-25 的合并提交
3ca30ed7。其一,该提交自身的标题序列与正文序列就已错开一位,从BUG-036起每份正文实际属于前一个标题,末尾BUG-240的正文则被整段追加到BUG-043之后,使BUG-043带了两份完整正文。其二,两条分支各自保留了同一批记录的结尾三行(相关记录/复发自/修复版本),多出来的副本落在下一个标题之下,形成 11 处"有标题无正文、只有三行结尾"的空壳。其三,BUG-221~BUG-234与BUG-020~BUG-035标题逐字相同、共用同一份正文,本就是同一个 bug 的两份记录,BUG-253 为消除编号冲突给后者另分了编号,反而把"重复"固化成了 14 条独立记录。 - 修复:按内容而非位置重新绑定。11 处只剩结尾三行的空壳先行删除——逐条比对确认其中 7 处与所属记录的结尾逐字同义(仅编号体系不同),另 4 处是所属记录结尾的真子集,差集恰为 BUG-253 标注过的
BUG-018/BUG-019存疑残留,删除不丢信息。随后把错位区间内 11 份正文各回退一个标题,BUG-240的正文从BUG-043末尾取回;每一次归属都以正文中的用户现象/根因与标题语义逐条核对,不依赖行号。14 对重复记录合并为一条,保留较早编号,BUG-221~BUG-234退役成空号,23 处指向退役编号的引用回指合并后的编号。BUG-235~BUG-240在3ca30ed7中就缺失的状态/首次发现/最近更新三行按同批次记录补记,并在状态行内标明"非原文",不冒充原始内容。 - 验证:记录数 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在写回答之前耗尽。 - 根因:三层叠加。其一,
consultationToolInputSchema把domains与theme声明为两个彼此独立的可选字段,互斥关系只在canonicalDomainPlan()里以invalid_consultation_domain_plan运行期抛出,工具description也从未提到该约束;更直接的是jyotishInstructions明确写着“Use the legacy theme field only for a single-domain compatibility retry”,等于主动引导模型去用一个会被拒绝的组合。其二,这类无效调用发生在步骤记录 try/catch 之前,既不产生chart-calculation活动也不追加运行步骤,因此每次都白耗一个模型步骤且在回执里不留痕迹。其三,maxSteps: 6与AbortSignal.timeout(110_000)约束同一次运行却分别硬编码:1 次 Skill 加载 + 4 次工具调用已占 5 步,skill_read/skill_search这类渐进式披露工具未映射进公开事件流,第 6 步一旦被一次不可见的参考读取拿走,运行就在没有任何回答的情况下结束。finishReason在整个仓库中没有任何记录点,因此“步数耗尽”只能靠事后数事件推断,无法证实。 - 修复:把互斥关系改为不可表达而非运行期拒绝——模型可见的
inputSchema只保留question与domains,theme从模型契约中移除,.strict()保持不变,使theme在进入工具体之前即被 Mastra 的入参校验拒绝;description补齐“只用一个有序domains数组,省略即接受服务端已选领域,出生资料服务端绑定”的显式契约;jyotishInstructions同步删除引导模型使用theme的那句。canonicalDomainPlan()继续处理单值theme形态并保留invalid_consultation_domain_plan,导出后由直接单元测试覆盖,供不经模型 schema 构造计划的调用方使用。步数预算与时钟预算改为相邻声明的AGENT_MAX_STEPS = 8与AGENT_TIMEOUT_MS = 110_000,并注明二者约束同一次运行、必须一起考虑;运行步骤记录预算随之对齐,避免耗尽步数的运行同时截断自身证据。新增受控观测字段modelFinishReason(封闭枚举,未知取值一律归一为unknown)与modelStepCount,在流中按step-finish计数、以终止finish携带的步骤列表为准,跨重试累计。未放宽单次计算边界,未放宽运行合同门禁,未把任何模型原文或 provider payload 写入日志。 - 验证:新增修复前失败的回归 6 项——模型可见 schema 必须拒绝
theme(单独出现与与domains同时出现)、被拒调用不得推进任何运行状态(consultationToolStarted/consultationToolCallCount/steps全部不变)、完成运行必须记录finishReason与权威步数、以tool-calls结束且无回答的运行必须记下耗尽的步数、重试必须累计步数并归一化未识别的 provider 取值、步数与时钟预算必须成对声明。回退任一源改动可确认对应回归失败。另新增公开回执守卫:agentExecutionReceiptSchema是 strict,modelFinishReason/modelStepCount一旦透出会让成功运行在序列化自身回答时报错,故断言按白名单构建的回执不含这两个字段、直接透出则必须抛错。canonicalDomainPlan()补 8 项直接断言,锁定“单值 theme 不得覆盖路由选定领域”。BUG-205 的缓存不被污染性质改由 schema 合法但注册表非法的输入(domains: ["career", "unknown"])复验,因其原始触发条件已不可达。全量非数据库套件 1586/1586 通过,npx tsc --noEmit0 错误,改动文件npx eslint0 错误。 - 待跟进: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_contract、claim_cards、rectification、route、status、question、packet_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_blockers与missing_route_layers取并集;answer_policy中can_answer_*一类许可布尔必须每个执行领域都为 true 才为 true,should_lead_with_limitations一类限制布尔任一领域为 true 即为 true,数组字段取并集,其余字段仅在所有领域取值一致时保留;出现无法合并的分歧时不选边,记入unresolved_policy_fields并强制should_lead_with_limitations = true。available_layers是唯一取并集的许可类字段——某层只要为任一领域真实算出就确实存在,否认它等于否认真实证据,真正约束回答的是缺失与阻断的并集。rectification.boundary只要有一个领域报not_auto_rectified就整体沿用该边界。本命投影在各领域逐字相同时上提为顶层单份并从各领域移除,不同时保持每领域各自携带,不挑一份充当共享。单领域形状继续走摊平分支,逐字不变,另补success与omitted_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.started与chart-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 对整轮运行是累计的,runConsultationWorkflow用AbortSignal.any叠加的 90s 才是每次调用各自的。因此六领域约 126s 永不可能完成,四领域约 84s 也几乎不给模型留下写回答的时间。schema 允许表达一个注定失败的计划,且失败时序(循环内抛出、无evidence-validation)与该推断一致。并发不是出路:Python API 是单进程ThreadingHTTPServer,核心计算受 GIL 约束,/api/consultation_workflow为同步处理,异步作业另有JYOTISH_ASYNC_JOB_WORKERS=2与JYOTISH_ASYNC_JOB_QUEUE_SIZE=8的有界队列,满载即以 HTTP 503ERR_JOB_QUEUE_FULL回绝(前端workflow_queue_full即由此映射)。并行只会把串行等待换成排队与 GIL 争抢,不会缩短总时长,故不并行。 - 修复:上限改为由时钟推导而非选定,与它约束的同一轮预算相邻声明:
CONSULTATION_DOMAIN_DURATION_MS = 21_000(staging 实测)、CONSULTATION_ANSWER_RESERVE_MS = 45_000(三领域运行在 110s 内实际留给写回答的余量口径)、CONSULTATION_DOMAIN_WALL_CLOCK_MS = 110_000 - 45_000 = 65_000,MAX_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为 120,110s 已贴近上限。 - 验证:新增修复前失败的回归 4 项——上限必须等于时钟能支付的领域数(同时断言
AGENT_TIMEOUT_MS、循环份额与executableDomainPlan()/domainFitsRunBudget()的边界取值,并显式记录旧上限 6 在 110s 内不可能完成);超上限计划必须不可表达且一次计算都不启动(consultationToolStarted与steps均不变);每领域 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、步数预算、工作流路由在运行失败时一概拿不到——恰好是最需要它们的时刻。 - 触发条件:其一,任何走到
streamAgentResponsecatch 分支的运行;其二,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 只挂在streamAgentResponse的onError上,更早的失败没有任何路径抵达它,观测事件因此对失败最重的那类运行完全缺席。 - 修复:
run.failed增加可选receipt,内容用既有白名单助手publicConsultationRuntimeSteps()构建,与run.completed走同一条边界,因此每步durationMs、步数预算与工作流路由到达调用方,而内部failureCode、modelFinishReason、modelStepCount仍留在服务端。构建回执本身被包在 try 内:回执构建失败不得把失败事件替换成一次静默关闭,此时照旧发出不带receipt的run.failed。服务端侧由 agentic 装配把 settle-and-log 入口发布为agenticFailure.report,请求级 catch 优先经它上报(内部按toAgentObservabilityErrorCode()归一化错误码),仅在该入口尚未发布时退回裸cancel(),从而保证每条 agentic 失败路径都留下一条封闭观测事件。未放宽任何 strict schema,未新增自由格式字段。 - 验证:新增修复前失败的回归 3 项——失败运行必须携带与成功运行同构的白名单回执(断言两步的
durationMs与stepBudget.used,并断言序列化结果中不出现内部分类与模型循环诊断);回执构建抛错时仍须恰好发出一次不带receipt的run.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.py的strict_workflow、scripts/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 两次调用都被文本判成 career,wealth 那次声明 wealth,必然 400。补齐词表只会把矛盾推到下一个问法上——只要一次运行发出多个领域调用,文本选路与声明路由就在结构上不可能同时满足。frontend/src/lib/consultation-workflow-request.ts里theme === "timing"时给问题加前缀“应期与阶段问题:”,正是为了把“应期”这个词塞进文本让文本路由同意声明路由,是本 bug 只对一个领域打过的绕行补丁;同批 timing 运行成功恰恰因为它带着这个前缀。 - 修复:让服务端自己签发的声明路由成为权威,两套路由不再可能互相矛盾。
consultation_plan_contract新增declared_workflow_route():只在计划元数据完整、版本受支持、路由在服务端白名单内时返回该路由,缺一即抛;无任何计划元数据时返回None。execute_consultation_workflow()先取声明路由,再以resolve_route(question, themes, declared_route=...)解析,声明存在即直接返回该路由定义,声明为None时文本启发式逐字不变——这保证遗留调用方行为不变。路由包新增route_source(declared_plan/question_text),使响应能自证由哪套路由决定;routing在前端是z.record+ passthrough,新增键不破坏契约。执行面同步正确:question_type/primary_theme/focus_techniques都取声明领域的RouteDefinition,因此runtime_planner的同步步骤、consumer_context.route的必需层、machine_evidence_packet、real_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.py、tests/test_consultation_workflow_domains.py、tests/test_unified_consultation_orchestrator.py、tests/test_consultation_consumer_context.py、tests/test_api_server_security.py、tests/test_mcp_strict_workflow_career.py、tests/test_runtime_import_boundaries.py、tests/test_historical_event_backtest.py共 275 项通过;scripts/run_quality_gate.py --profile quick --skip-yoga-logic --skip-frontend-runtime通过,ruff门禁文件全通过(改动文件相对修复前无新增告警),py_compile、commercial_privacy_artifact_scan(findings 0)、python -m build均通过;前端npx tsc --noEmit0 错误、改动文件npx eslint0 错误、非数据库套件 1664 项中 1660 通过,4 项失败全部是本机并发 Postgres 容器争抢(另有工作树同时在跑数据库测试,本机同时存在 11 个测试用 Postgres 容器),逐个单独重跑后onboarding-route、rectification-v9-database、admin-database、identity-auth-integration均通过,model-configuration-security的database ...一项单独重跑仍以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.tsx 的 Home:编译器静默拒编 2730 行组件且不报任何错
- 状态:won't fix(本轮不修;Next 已升到 16.3.1 并保留,编译器配置已回滚,原因见下)
- 首次发现:2026-08-17
- 最近更新:2026-08-17
- 影响面:
/主对话页frontend/src/app/page.tsx的重渲染性能,以及后续任何「靠 React Compiler 免除手写记忆化」的计划。 - 用户现象:无终端用户可见现象。对维护者而言的现象是:
next.config.ts里reactCompiler: true开启后构建打印✓ turbopackRustReactCompiler、退出码 0、测试全绿,看起来完全成功,但Home一个函数都没被优化。这个失败不产生任何警告、错误或日志,只看构建输出无法察觉。 - 根因:本条所有行号与槽数均测于交付基点
e8d201dd;page.tsx此后仍在演进(交付时远端已改到 3719 行),复现时应按函数名而非行号定位。page.tsx当时有 3738 行,其中export default function Home()单个函数占 2730 行(1002–3738),带 24 个useState、18 个useEffect、0 个手写useCallback/useMemo。Next 16.3.1 的 Rust 版 React Compiler 会正常编译同一文件里的其他函数,却拒编Home:默认infer模式下全项目 44 个函数拿到缓存槽(app-sidebar112 槽、use-birth-time-guided-journey84 槽、sidebar-session-row73 槽等),page.tsx内部两个小组件(原始行 760、815)也拿到了 11 和 24 槽,唯独Home的生成代码仍以裸useState序列开头、没有_c(N)前导。把compilationMode设为all(绕过组件识别启发式、强制编译每个函数)后page.tsx被编译函数从 2 涨到 47,Home依然不在其中——既然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.0对Home报 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/finally和try/catch内的throw,而Home有 13 个finally和 9 个这样的throw。这 13 个finally做的全是finally该做的事——window.clearTimeout(bootstrapTimeout)、polling = false、rectificationOpenInFlight.current = false、cancellationRequests.current.delete(requestId),以及 8 处setProfileSaving(false)/setAvatarSaving(false)/setCreatingSession(false)/setBirthTimeAssessmentPhase(null)之类的加载态复位;删掉任何一个,try 块抛错时 UI 就永久卡在加载态,是拿真 bug 换假优化。另在更新的0.0.0-experimental-a1856f3-20260507上复测:那 22 条try/finally与throw错误已被上游修好,但Home随即撞上编译器内部断言失败Invariant: Expected all references to a variable to be consistently local or context references(page.tsx:2666,catch (error)的绑定同时被直接使用和被setRequestError((current) => ...)的闭包捕获);在仓库外的副本上把这一处改掉后,又冒出下一个Invariant: [PruneHoistedContexts] Unexpected hoisted function(page.tsx:1837的refreshAccount,被 1626 行的useEffect提前引用)。Invariant:在 React Compiler 的分类里是编译器 bug 而非用户代码违规。Home共 50 个函数声明,其中 6 个被声明前引用(refreshAccount1837/1626、editDeclaredBirthTimeDetails2280/1098、completeGuidedBirthTime2308/1097、openRectificationFromHomepage2515/2373、openRectificationSession2519/1982、handleRectificationProfileIncomplete2531/2457),要满足编译器就得在 2700 行的组件里跨千行重排这 6 个定义,且照上述规律修完还会有下一个内部 panic。诊断顺带交叉验证了产物取证的正确性:Babel 版报告成功编译的两个函数是BirthLocationFields @ 760、ProfileFields @ 815,与先前从 Rust 版构建产物 source map 反查出的两个缓存槽(行 760/815,槽 11/24)逐一对上,两条独立证据互相印证。 - 修复:未修复。
next.config.ts的reactCompiler: true与experimental.turbopackRustReactCompiler: true已回滚到与origin/staging逐字节一致;Next 16.2.10 → 16.3.1 的升级保留(该升级本身独立验证通过,是 Rust 版编译器的前置条件)。本轮共试六种配置全部失败:reactCompiler: true、加panicThreshold: "all_errors"、再加文件顶部"use memo"、compilationMode: "all"、"all"+all_errors、compilationMode: "annotation"+Home体内"use memo"。其中compilationMode: "all"还会直接把构建搞坏:它会编译模块作用域的普通回调,birth-time-intake.tsx:22的Array.from({length:24}, (_, index) => ...)被插入useMemoCache,预渲染/时抛TypeError: Cannot read properties of null (reading 'useMemoCache')。注解模式(领导指定的退路)反而最差:全项目 0 个缓存槽,连原本能编的 44 个也停了。 - 验证:
npx tsc --noEmit无输出;npx eslint0 error(4 个既有 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/core的transformSync配parserOpts.plugins = ["jsx", ["typescript", {isTSX:true}]]单独跑该文件,给插件传logger: { logEvent(file, event) {} },event.kind为CompileError的即为 bailout,event.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/staging(c8d9ec64)时远端已占 254–255,改 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.3(containerd-overlayfs 快照器)必定失败,因此这是一颗按 runner 环境触发的定时炸弹,而不是一个稳定可见的错误。 - 触发条件:用一个不允许「以文件覆盖已存在目录」的 BuildKit 快照器构建该 Dockerfile。与 Next 版本、CPU 架构均无关:在升级前的基线提交(
next16.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 ... -> ../../assets且ls能列出其中文件、/app/frontend/server.js存在。运行时冒烟:容器启动打印▲ Next.js 16.3.1,GET /返回 200(日志中的SupabaseConfigurationError是未注入环境变量所致,属预期)。对照实验确认与本批 Next 升级无关——在升级前的3371baca(next16.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/*/assets、skills/*/references、skills/*/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.Dockerfile。node:22-alpine当前解析到 Node v22.15.0,而posthog-node@5.41.0声明需要^20.20.0 || >=22.22.0,v22.15.0 落在两个区间之外。 - 根因:尚未确认实际影响面。已确认的事实只有两点:一是版本区间确实不满足,二是与本批 Next 16.3.1 升级无关——
git diff 3371baca 37938187 -- frontend/package-lock.json中没有任何posthog相关改动,且该警告在升级前的基线构建日志里就已出现。尚未确认的是posthog-node是否真的用到了 Node 22.22 才有的 API:EBADENGINE只是警告、不阻断安装,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子元素,而.conversation带padding-bottom: var(--composer-reserve)(桌面 148px / 移动 116px)。sticky 元素被钉在视口底部之前,会先被自己的包含块——也就是滚动容器的 内容盒——夹住,而内容盒底边正好比容器可视底边高出这一段 padding。于是bottom: 12px从未生效,按钮实际停在 padding + 12px 处。这段 padding 是早期输入框覆盖式布局的遗留:现在.chat-panel用grid-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,与线上截图相符;量完即删,未留在仓库。tsc、eslint清洁。未做的验证:没有在真实登录会话里目视确认(需要长对话与账户),复刻页面只覆盖了本条涉及的布局规则。 - 防复发:滚动容器留了
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/account、POST /api/consult的出生真值校验。 - 用户现象:新用户在初始化里选择“我知道准确出生时间”并填到分钟,走完地点一步进入首页,发出第一条咨询即被拒绝,提示“出生时间状态已经变化 / 请刷新后重新选择使用填报时间、一般咨询或先完成校正”。刷新页面后同一条问题可以正常发出。
- 触发条件:声明为
family_exact且误差为 0,经账户接口保存后不刷新页面直接发第一条咨询。 - 根因:账户写入会在服务端派生出生真值——零误差的准确时间被直接采用为
active_birth_time且状态升级为accepted——但PATCH /api/account只回{ok:true},页面又用提交的草稿覆盖profile,草稿里状态仍是reported、time为空。于是客户端按未确认分钟选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 test1710 项中 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填进一句固定模板,action与caution是常量;引擎 2.5 秒超时或资料不全就走pickCard,用「日期+生日+省市」的字符码之和对 4 取模选一张写死卡片。page.tsx还把同样的 4 张卡抄了一份作为客户端兜底,因此即便接口整个挂掉,界面也照样显示得像有内容。 - 修复:改成 Agent 生成。新增
getDailyStarlanguageAgent(frontend/src/mastra/index.ts)与纯函数模块frontend/src/lib/daily-starlanguage.ts(证据摘要、模型输出解析、缓存键)。路由先取/api/chart,再并行取/api/dasha(Vimshottari 与 Narayana 双轨)、/api/varga_full(D9/D10)、/api/transit,把结果压成一份紧凑证据交给 Agent,只接受完整的{trend, action, caution}JSON。生成前要求已登录会话(避免匿名烧 token),按「账号+出生载荷+日期」在进程内缓存当天结果,并用 in-flight Map 合并并发请求。前端与路由里那 4 张写死卡片全部删除。 - 验证:新增
frontend/tests/daily-starlanguage.test.ts7 条:证据抽取覆盖 Vimshottari 当前 MD/AD、按今天选中的 Narayana 期、D9/D10、过境触发与功能吉凶星;引擎层缺失时必须显式列进missingLayers;status: blocked的功能吉凶层不得进入证据;未确认的出生时间必须写成硬约束进提示词;模型输出缺字段或过短一律拒收;缓存键不得跨账号、跨资料、跨日期复用。另有源码合同断言鉴权、缓存键、双轨 Dasha 调用与「不留写死卡片」。tsc、eslint清洁,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 质量门的数据库集成测试、publish与Deploy 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-linuxrunner 以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-v2(2.40.3),确认docker compose version --short与--project-name两项门禁能力检查通过;把.gitea/workflows/里 8 个仍指向manman-linux的 job 全部改为xiaoxin(该机 20 核、61G 内存、877G 空闲);新增deploy/reclaim-runner-disk.sh,在validate与publish的 checkout 之后、拉取 Node 工具之前回收磁盘,并在空间仍不足 10 GiB 时带着原因 fail-closed,而不是让 23 个数据库测试去暴露磁盘问题。回收只针对没有任何容器持有的资源(container prune --filter until=6h、name=^jyotisha-postgres-且dangling=true的卷、network prune --filter until=6h、悬空镜像、buildx 缓存,以及除当次 SHA 外的历史api-/web-标签),因此不会掀掉同一台 runner 上并发 job 的 fixture。 - 验证:
staging-backend-workflows.test.ts38 项通过,其中新增两项——遍历.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-103(
docs/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)层的任何结论,也没有性别解读边界。回执的「齐备」与回答的内容互相矛盾,而回执本身不含任何可据以追问的线索。 - 触发条件:任何走
marriage、wealth、health、education、migration、family、annual路由的咨询(即除career、timing、general外的全部路由);以及任何问题文本没落在七条关键词正则里的领域提问。 - 根因:同一个函数里三处判据各自接错了来源。其一,
route_requirements的键写成relationship与finance,而_ROUTE_DEFINITIONS里的路由名是marriage与wealth,.get(route, general)于是静默把这两条路由降级成general的门(D1/D9/dasha_boundaries),婚姻的 UL 与财富的 D2 从未进入检查;另有 5 条路由(health/education/migration/family/annual)压根没有条目,同样落到general。missingLayers: []因此不表示证据齐备,只表示没检查过。其二,7 个领域上下文层与解读边界由 7 块re.search匹配问题文本决定,而领域此时已经由模型声明并写进了route_packet——用文本再猜一遍既是重复,又只能覆盖关键词表里的写法:本次实测中一句明显是情感取向的问题因为写作「情感」而非表里的「感情」,在marriage路由上完全没拿到性别解读边界。其三,timing_layers_ready判断dasha_boundaries与narayana_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改为直接读sections的status,与路由要不要求该层无关。唯一保留文本探测的是precise_timing_requested:「用户有没有要一个具体时间」是服务端没有权威来源的信号,因此改成 timing/annual 路由结构性携带、文本探测仅作叠加,一次措辞漏判不再能把信号清零。 - 验证:
tests/test_consultation_consumer_context.py新增 8 条并全绿(该文件共 16 条通过)。8 条覆盖:遍历_ROUTE_DEFINITIONS断言每条路由都有自己的证据门条目(这是本该拦住本 bug 的守卫),且表里每个层名都能在真实证据包的 sections 里找到;marriage缺 UL 必须报degraded且missingLayers == ['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_ready在narayana_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(修完本条核对同一张表时发现门仍比产品声明松三处)
- 复发自:无
- 修复版本:
10ae149c(staging)
BUG-268 | 模型有没有真的翻开方法文档,运行结束后无处可查:计数器数完就丢
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:
/api/consult的公开回执AgentExecutionReceipt与服务端可观测事件;影响「web 输出为何不如本地 Agent」这一类问题的可诊断性。 - 用户现象:用户对比本地 Agent 与 web 的输出质量,web 明显更弱。但两份成功回执里能看到的只有
skill.loaded: true、steps: [skill, tool]与stepBudget.used: 2,无法回答「模型到底有没有读过方法文档」——而这正是两条链路最可能的差异所在。 - 触发条件:任何一次咨询运行结束后试图回溯模型的方法使用情况。
- 根因:Mastra 的
skill工具返回 SKILL.md 的全文指令加上 references/scripts/assets 的文件名清单,真正打开某份参考文档要另调skill_read。createConsultationRuntimeHooks确实在afterToolCall里数了skillReferenceReadCount,但这个计数器既没进公开回执,也没进agentObservabilityEventSchema,数完即丢。同时skill_read按设计不记成 runtime step(只有skill/tool/validation三类会记),所以steps与stepBudget.used天然看不见它;服务端日志里唯一能间接反映的是modelStepCount。结果是:唯一能直接回答该问题的数字被算出来后丢弃,只留下一个需要推断的替代量。 - 修复:把
referenceReads加进回执的skill对象,并设为必填而非可选——正是「可选且没人填」让这个数字消失的,必填能让将来任何一处新的回执构造点无法再省掉它。同时把skillReferenceReads加进consultationModelStepTelemetry(),它已被展开进可观测日志,因此服务端日志自动与modelStepCount并列拿到该值。两个构造点(工具可选路径与强制工具路径)都改为从state.skillReferenceReadCount读取。 - 验证:
frontend/tests/consultation-agentic-runtime.test.ts37 项通过,其中新增 1 项:走真实 hooks,断言加载 skill 后计数仍为 0(说明「已加载」不等于「读过方法」)、两次成功的参考读取记为 2、失败的读取不计数、参考读取不出现在 steps 里(因此 steps 无法替代该字段),最后断言回执解析后skill.referenceReads === 2。既有断言同步收紧:consultationModelStepTelemetry()的期望值现在包含skillReferenceReads。tsc --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(同为「失败/结束时回执信息不足」的形态) - 复发自:无
- 修复版本:
10ae149c(staging)
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_status为candidate、accepted或confirmed且已有可用出生时间的任何账号打开首页。也就是说,越是资料完整的账号越必然命中。 - 根因:三处独立问题,都在同一屏上。
- 每日星语 effect 的守卫是
if (!hydrated || !profileComplete || birthTimeDisplayState(profile)) return;。birthTimeDisplayState恰好在出生时间可用(candidate/accepted/confirmed)时返回非 null,因此条件的实际含义是「星盘可用时不要请求」,与卡片的渲染条件personalChartAvailable完全相反,请求从未发出,state 永远停在pending。这个守卫在 BUG-265 之前就存在,但那时客户端还有buildDailyStarlanguageCard写死兜底把空状态遮住了;BUG-265 删掉兜底、保留守卫,于是暴露成永久等待态。 /api/daily-starlanguage里/api/chart是唯一没有.catch()的引擎调用(其余四层都有),且engineTimeoutMs只有 8 秒、agentTimeoutMs只有 30 秒。线上实测冷路径端到端 30.6 秒,紧贴 30 秒上限;首次调用在 9.5 秒就返回agent_generation_failed,正是 8 秒引擎超时被当成模型失败上报。失败原因被压成同一个字符串,无法区分是引擎、模型目录还是模型输出。- 首页同时存在三套问候实现:
page.tsx的greetingForHour(hero 第一行)、starter-prompt.ts的 8 条静态随机池(hero 的h1)、onboarding-client.ts的createStartGreeting(按时段+称呼,只用在 onboarding 气泡)。用户要求的按时段问候在第三套里,而 hero 标题读的是第二套,所以「改了却没生效」。同时/api/onboarding返回的 Agent 欢迎语在客户端被greeting: createStartGreeting(presentationName)覆盖,而onboarding.greeting在整个页面里没有任何渲染点,等于 Agent 每次都白写一句欢迎语。
- 每日星语 effect 的守卫是
- 修复:守卫改成
!hydrated || !profileComplete || !personalChartAvailable,与卡片渲染个人内容的条件对齐,并在服务端报 unavailable 时延迟 5 秒重试一次后才落到失败态,卸载时清理定时器。路由给/api/chart补.catch(),把生成结果改成判别联合,失败原因区分chart_unavailable/model_unavailable/agent_generation_failed;engineTimeoutMs提到 20 秒、agentTimeoutMs提到 45 秒,仍在maxDuration = 60之内。问候收敛成一套:createStartGreeting拆出createStartGreetingParts,返回{salutation, question},hero 第一行用 salutation、h1用 question,选中变体在离开首页时重抽;删除starter-prompt.ts与greetingForHour。客户端不再覆盖 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 引擎与模型密钥,无法起完整栈,卡片从pending到ready的实际观感、重试是否够用、以及 hero 换行后的排版都要等 staging 发布后确认。 - 防复发:一个界面元素的「取数条件」必须和它的「渲染条件」写成同一个表达式,不能一边用
personalChartAvailable渲染、一边用另一个语义相反的谓词决定是否请求。删除兜底文案时必须回头检查被兜底遮住的空状态路径是否本来就是坏的——BUG-265 删兜底是对的,但没有验证删掉之后真实账号能不能拿到内容,代价是上线即空转。同一个概念(这里是「登录后的问候」)不允许存在多套并行实现,否则改动必然落在没被渲染的那一套上。Agent 生成的字段如果没有渲染点,就不要生成,更不能在客户端覆盖后还继续消耗 token。 - 相关记录:BUG-265(本次修复的直接前序:Agent 化改造正确但守卫未同步,且其「待跟进」已经预告了首屏等待态问题)、BUG-201(每日星语 effect 依赖完整 Profile 对象的既有决定,本次沿用其引用保持策略,未改依赖形状)、BUG-200(首页文案第一人称与真实性边界)、BUG-272(本次 hero 说明行处置的后续推翻)
- 复发自:无
- 修复版本:
a12f5797(staging)
BUG-270 | 证据门比产品自己向用户预告的层更松:两份声明分处两端且没有任何机制保证一致
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:
scripts/jyotish_api_server.py的_ROUTE_REQUIRED_LAYERS与frontend/src/lib/consultation-domain-registry.ts的requiredLayers。涉及marriage、wealth、general三条路由的证据门,其中general是最常走的兜底路由。 - 用户现象:暂无可见现象。与 BUG-267 同一形态——门比声明松,失败方向是静默放宽,健康运行里看不出差别。
- 触发条件:任何走
marriage、wealth、general路由的咨询,且该路由声明要用的某一层实际缺失。 - 根因:修完 BUG-267 后核对时发现,前端领域注册表早就为每个领域声明了
requiredLayers,键名正确(marriage/wealth),并且这份列表会驱动界面上的evidencePreview——也就是产品明确告诉用户「这次会用这些证据」。但服务端的门是另写的一份,两份用不同词汇描述同一个合同,彼此没有任何链接。对照下来门少查三处:marriage声明了 A7(Darapada)而门只要求 UL,wealth声明了 Ashtakavarga 而门只要求 D2,general声明了 D10 与 D2 而门只要求 D1/D9。三者实测在真实排盘中都是used,所以补进去不会把状态推成degraded。这正是 BUG-267 里键名漂移能发生两个月的同一片土壤:合同有两份,没有一份是权威。 - 修复:
_ROUTE_REQUIRED_LAYERS补上marriage的 A7、wealth的 ashtakavarga、general的 D10 与 D2。新增一条测试解析前端注册表,断言其中每个指向真实 section 的条目都出现在服务端的门里。之所以只比对真实 section:注册表里同时有人看的标签(7th house/lord、negative holdout gate)和引擎压根不产出的 D11,机械全量对齐会把每条路由钉死在degraded。该测试对解析失败采取 fail-closed——先断言 10 条路由全部解析到且列表非空,否则一次正则失配就会让它无声通过。另外把tests/test_consultation_consumer_context.py加进run_quality_gate.py的CORE_PYTEST_TARGETS:核对时发现该文件不在任何 CI 档位里,BUG-267 的 8 条与本条的 1 条此前都只在本地手动执行过,staging 门(--profile quick)从不运行它们。防漂移的钉子本身没人跑,等于没钉。 - 验证:
tests/test_consultation_consumer_context.py17 条通过(新增 1 条)。已验证这条钉子是真守卫而非恰好通过:分别从marriage去掉 A7、wealth去掉 ashtakavarga、general去掉 D10,三次都报出对应路由与缺失层,恢复后通过。另用真实排盘跑了 general/marriage/wealth/career/timing/health/annual 七条路由,收紧后core_status仍为ready、missing_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(同为路由与合同之间的口径不一致)
- 复发自:无
- 修复版本:
5caf47a4(staging)
BUG-271 | 工具调用在进入工具体之前被拒时,回执里查不到它失败过:步没记、调用没计、原因塌成兜底码
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:
/api/consult的公开回执与服务端可观测日志。凡是模型给出的工具参数不被工具inputSchema接受的运行都受影响。 - 用户现象:staging 手测 run
951a841e(10ae149c)第一次工具调用发出tool.failed/calculation_failed,重试成功并给出了完整回答。但run.completed的回执里steps只有 2 条(skill + 那次成功的 tool)、stepBudget.used: 2,失败的那次完全不存在;tool.failed的 code 是兜底值,服务端日志同样只有兜底值。也就是说:客户端看见「失败过一次」,回执却说没有,而且两边都查不到为什么。 - 触发条件:模型对
run-jyotish-consultation传出不被consultationToolInputSchema(.strict(),只接受question与domains)接受的参数。 - 根因:三处叠加。其一,schema 拒绝发生在 Mastra 调用
execute之前,所以工具体内的一切都没跑——consultationToolCallCount不增、appendConsultationRuntimeStep不执行、连chart-calculation活动事件都没发出(这也正是本次能定位的证据:失败那次调用没有任何 activity,重试那次有)。工具自己无法记录一次它从未收到的调用。其二,safeToolError()把一切非AbortError/TimeoutError的错误塌成calculation_failed,公开事件因此不携带任何可区分信息。其三,即使失败落进了工具体的catch,consultationWorkflowFailureCode()对非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.ts40 项通过(新增 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(补正本条对触发路径的归因,并接手枚举化之后新的拒绝路径)
- 复发自:无
- 修复版本:
b5bcbaed(staging) - 生产验证(2026-08-18 补记):staging run
a5f4409e(2605f34a)出现连续两次tool.failed,回执steps里如实带上两条status: "failed"(39ms / 56ms)、stepBudget.used: 4。这正是本条修复前会丢掉的那两条记录,「无法主动构造」的验证点由手测撞上。同时补正本条根因里的一处归因:实际观测到的触发路径不是inputSchema拒绝,而是execute已进入、但canonicalDomainPlan()在任何状态写入之前就抛(领域不在注册表里),因此工具体同样没能记录任何东西。「失败发生在工具记录任何东西之前」这个机制是对的,落到inputSchema这一层的具体归因是错的——真正的inputSchema拒绝路径见 BUG-278:Mastra 对它 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 连续三行都在做同一件事(称呼、提问、再一次欢迎并再问一次「想从哪里开始」)。
- 根因:两处独立问题。
- hero 的信息层级在 BUG-269 之后变成三行同义内容。
starter-greeting已经完成称呼、h1已经完成提问,Agent 欢迎语在句式上又重复了这两件事,因此第三行没有新增信息。但这一行同时是「无可用出生分钟」账号的真实性边界声明所在(BUG-200 约束,starter-questions.test.ts有断言钉住),直接整段删除会连边界声明一起删掉。 - 首页主题卡渲染的是
consultationDomainRegistry的全部十个域,而/api/onboarding的suggestions是career/marriage/timing的固定三元组。页面按主题 id 逐个find,找不到就回落到 registry 的静态prompt,因此其余七个域(wealth、health、education、migration、family、annual、general)恒定是写死文案,个性化只覆盖三分之一的入口,而界面上十张卡看起来完全同级,用户无法分辨哪些是为自己生成的。加载态文案「根据你的资料整理三个起点。」也与实际渲染的十张卡不符。
- hero 的信息层级在 BUG-269 之后变成三行同义内容。
- 修复:分两步。
- 界面收敛:删除
starter-hero-note元素及其两处 CSS 规则,hero 收敛为称呼加提问两行。出生时间边界声明迁到主题区小标题,按personalChartAvailable分支——读者正要挑主题时才看到这句限制,位置比 hero 更贴近实际动作。加载态文案改为「根据你的资料整理今天的起点。」,不再声明具体条数。 - 契约收敛(用户评审后决定,同一分支内完成):
onboarding.greeting从 Agent 契约里彻底移除——schema、fallback、prompt、客户端响应校验与OnboardingContent类型全部不再有这个字段,不再为无渲染点的内容付费。suggestions从career/marriage/timing的固定三元组改成按consultationDomainIds顺序覆盖全部十个域,校验用 refine 钉住「长度与顺序都必须与 registry 一致」,于是首页十张卡全部是 Agent 写的,静态prompt退回纯兜底角色。fallback 直接由generalGuidedJyotishTopics派生,避免手写十条又与 registry 漂移。生成十条比三条显著更慢,预算随之上调:路由maxDuration30→60 秒、服务端生成超时 18→45 秒、客户端单次请求超时 25→50 秒。缓存版本ayanam-onboarding-v4→v5,让所有存量 v4 payload 重新生成一次。
- 界面收敛:删除
- 验证:
onboarding-presentation.test.ts的 hero 断言改为同时检查page.tsx与globals.css中不再出现starter-hero-note,避免只删元素留下死样式;starter-questions.test.ts的边界声明断言从「文件里存在这句话」收紧为「这句话出现在主题区小标题且受personalChartAvailable分支控制」,防止下一次挪动文案时悄悄丢掉。契约部分新增 5 条回归:只覆盖三个域的旧形态响应必须整体拒绝并落到覆盖全域的兜底、主题顺序被交换必须拒绝(否则问题会挂到错误的主题标签下)、fallback 自身必须能通过 payload schema(registry 里的 prompt 一旦不再第一人称就会被这条抓住)、prompt 与 payload/client 源码中不得再出现 greeting 字段、路由必须把 registry 主题列表发给 Agent。客户端请求超时断言从 25 秒下限改到 45 秒下限,与服务端生成预算对齐。全量套件 1720/1729 通过,9 个失败全部是本机 Docker/PostgreSQL fixture(与 BUG-265、BUG-269 同一类环境失败);tsc --noEmit清洁,ESLint 仅存量 4 条 warning,next 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-metadata与agent-reply的解析契约、会话写入 schema、composer-wrap相关样式与 DESIGN.md 的对应条目。 - 用户现象:用户实际测试后反馈「Agent 回答后用户输入框上方的三个推荐问题」使用频率很少,要求删除,并要求 Agent 也不再生成这三个问题。
- 触发条件:任何咨询会话收到 assistant 回复之后,输入框上方固定出现三条按钮。
- 根因:这里有一个与用户预期不同的事实——这三条问题从来不是 Agent 生成的。
createConsultationReplyMetadata按会话主题从写死的十主题三元组里取一组,服务端总是把它塞进parseAgentReply的 metadata 参数,而 metadata 分支优先级高于模型输出(metadata?.suggestions ?? …),因此模型即便真的输出了AYANAM_SUGGESTIONS也会被覆盖;Prompt 本身还明确禁止模型产出隐藏元数据块。同一份三元组表被完整复制在agent-reply.ts与consultation-reply-metadata.ts两处。也就是说,界面上看起来「个性化」的追问入口,实际是按主题查表的十组固定文案,与用户的具体问题、星盘证据都无关——这正是使用率低的合理解释。附带发现:chooseConversationSuggestion里针对「先完成生时校正」这一条的生时校正跳转分支在当前链路下不可达,因为服务端 metadata 恒定覆盖,三元组里从不含这条文案。 - 修复:删除渲染点(
composer-suggestions块)、派生状态activeSuggestions、点击处理chooseConversationSuggestion与常量rectifyBeforeConsultationSuggestion、客户端readSuggestions与三处suggestions写入(send、本地预览、预览会话种子)。服务端consultationReplyMetadataSchema收缩为只有title且.strict(),createConsultationReplyMetadata不再需要theme入参,两份fallbackSuggestions表全部删除。parseAgentReply与parseAgentReplyBody在失去 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-suggestions/activeSuggestions/chooseConversationSuggestion且 CSS 同步无残留、metadata schema 必须拒绝suggestions字段(防止 chips 从元数据侧回流)、两个库文件与 consult 路由中不得再出现主题三元组或 suggestions 写入、历史遗留的AYANAM_SUGGESTIONS块必须被剥离且不得复活成字段、输入框与正文之间不得再有会改变高度的兄弟节点(原 chip 行会在流式过程中撑高 composer)、生时校正入口在失去 chip 跳转后仍可从首页卡片进入。全量 1716/1721 通过,5 个失败全部是本机 Docker/PostgreSQL migration fixture;tsc --noEmit清洁,ESLint 仅存量 4 条 warning,next 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: 100dvh和overflow-x: hidden,没有被视口卡住的纵向滚动容器。报告正文虽然有独立滚动层,但高度写成100dvh:在移动浏览器里它可能比body的100%更高,多出来的部分被根滚动锁裁掉,容器自己却还没溢出,所以也滑不动。 - 根因: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/products、admin_save_product_draft、商品管理页;已有订单/订阅的硬删除限制不放宽。 - 用户现象:编辑已发布套餐保存时返回「提交内容不符合业务约束」;列表里没有删除或下架。
- 触发条件:对已发布种子套餐(如
standard_monthly)调用action=save并带上该商品 id;或在后台寻找删除按钮。 - 根因:保存函数只更新
status='draft'的行,已发布 id 会抛product_draft_not_found(PostgreSQL22023),被统一映射成「提交内容不符合业务约束」。即便命中草稿,审计after_value仍写入禁止字段code,触发admin_audit_logs_after_value_check(23514),同样被折叠成这句话。界面把所有行都标成「编辑草稿」,也没有 delete/retire API。已发布商品因订单/订阅外键不能硬删除,正确动作是下架。 - 修复:保存已发布/已下架商品时,复用该 code 的现有草稿或创建下一版本草稿,不再改正在售行。新增
admin_delete_product:草稿硬删除,已发布改为retired且enabled=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/consult、GET /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.ts的outputText。所有/api/consult运行都经过它,但只有「契约变绿之前模型输出过文本」的运行会显形。 - 用户现象:staging 手测 run
a5f4409e(2605f34a)最终回答里没有任何占星结论,整段是模型在向用户解释自己的工具调用出错以及打算怎么改参数(大意:域名单有误、我改用另一组域、两次都不被接受、我改为不指定域),然后就结束了。用户问的问题(「大概哪一年」)没有得到任何回答。三句自述装在同一个answer.delta里——这就是一次性 flush 的指纹。回执显示计算其实成功了:4 步、两次失败 tool 之后一次成功 tool。 - 触发条件:模型在契约未绿(skill 未加载完或工具尚无一次成功)之前输出过文本,且此后契约变绿。失败重试的运行几乎必然满足。
- 根因:守卫写成了「先累加再判断」。
held += text无条件执行,紧随其后的if (!held || !contractReady(options)) return只是「暂不发送」。设计意图(原测试名写着 holds answer text until the contract completes)是「计算未验证前不要把文本流给用户」,但实现选的是攒着晚点发而不是丢掉。契约一变绿,下一次outputText就把攒下的内容整段 flush;emitted随之置真,ensureFinalResponseText()的空回答兜底因此也不会触发,用户既拿不到回答也拿不到「本次没能生成回答」的提示。要命的地方在于这段文本的内容与运行是否顺利强相关:顺利的运行里它是无害的开场语,失败重试的运行里它正好是模型在复述后端错误——而失败重试恰恰是它唯一会变长的场合。换句话说,这个缓冲在最需要它闭嘴的时候话最多。 - 修复:契约未绿时直接丢弃,不进
held。attemptOutput仍然无条件累加,所以「模型说过话但始终没给出回答」(runtime_contract_incomplete)与「模型全程沉默」(empty_answer)的诊断区分不受影响。随之first.held恒为空串,已连同consumeAttempt()的该返回字段一起删除:留一个恒假的判断在最终结算处,会让后来人以为held仍然跨越契约边界携带意义。 - 验证:
frontend/tests/consultation-agentic-runtime.test.ts43 项通过。原有的 holds-answer-text 断言按新意图改写并改名(契约前文本不得以任何形式到达客户端、契约后文本原样送出,并断言序列化后的事件流里搜不到那段自述);新增一条按 runa5f4409e形状构造的回归:模型只有自述、契约变绿后再无文本,断言用户拿到的是空回答兜底文案而不是那段自述。已确认改写后的断言在修复前失败。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.ts的consultationToolInputSchema.domains、frontend/src/lib/consultation-domain-registry.ts、Agent 指令,以及 skill 侧的SKILL.md第 223 行与references/strict-workflow-router.md。 - 用户现象:staging 手测 run
a5f4409e(2605f34a)连续两次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 schema(items.enum37 项),否则整个改动等于没做。同时在 Agent 指令里点明检查单标签不是域 id:指令层不受 skill 包哈希约束,且按本项目设计其优先级高于 skill 内容。刻意未改 SKILL.md:根SKILL.md与skills/jyotish-vedic-astrology/versions/6.9.14/SKILL.md之间有逐字节相等校验(frontend/src/mastra/index.ts),包目录整树的 sha256 钉在skills/skill-package-registry.json,改内容的正确做法是升版本;但6.9.14同时是pyproject.toml、CHANGELOG.md、README.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.ts43 项通过(本条新增 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、改动文件eslint、tests/test_consultation_consumer_context.py17 项均清洁。未做的验证:没有在 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.ts的local_layers与projectTimingEvidence、scripts/jyotish_api_server.py的_attach_local_consultation_layers与_build_consumer_context、scripts/unified_consultation_orchestrator.py的证据分节表。所有/api/consult个人盘运行。 - 用户现象:staging 手测中两次成功运行(「未来一年」「财运」)的回答都出现「副运细节与行运触发未在本轮完整计算」,只能给到大运级别的方向;而同一次运行的回执写着
preciseTiming: "allowed"。用户直接问了这两句为什么对不上,以及副运为什么没算。 - 触发条件:任何依赖应期的回答。恒定发生。
- 根因:一个名字指着两个对象。模型侧
local_layers.dasha_boundaries读chart.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.mahadasha与chart.dasha.periods里的 Sun 段逐字段相同(2024-07-03→2030-07-03),当前副运为 Jupiter 2026-07-21→2027-05-09,9 段副运首尾正好贴合大运首尾,local_consultation_layers.diagnostics为空,分节used,can_answer_precise_timing为真。tests/test_consultation_consumer_context.py21 项通过(新增 4 项:副运边界必须切自包里展示的 periods 且覆盖参考日;跨语言键名守卫;只缺副运时精确应期必须被拒;副运在场时放行)。测试基准盘原先只写了{'current_md': 'Sun'},已改为用compute_vimshottari_timeline派生真实 periods,否则新层在 fixture 上根本不会被执行。前端除数据库套件(本机无 Postgres)外 1633 项通过,tsc --noEmit与eslint清洁。 - 待跟进: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」的原因。至于模型为何不写,本轮无法定案:最接近的证据是既有回归里同形状运行的modelFinishReason为tool-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.ts44 项通过。新增两项:计算成功但无文本时触发一次 answer-retry 并交付重试写出的回答(末步为answer-retry);重试仍沉默则run.failed/empty_answer、onComplete不触发、文案含「不会扣点」。改写两项:BUG-277 那条「只有自述」的回归改为断言既不交付也不结算;步数耗尽那条(modelFinishReason为tool-calls的生产形状)不再断言兜底被结算。计费契约测试改为钉住 4 次usages.push(retried.totalUsage)与 2 处retryForAnswer(两条路径各有契约重试与回答重试,四次模型调用的 token 都必须计量)。前端除数据库套件外 1633 项通过,tsc --noEmit与eslint清洁。未做的验证:没有在 staging 上复现一次空回答来观测重试是否救回。 - 待跟进:仍未取到那次运行的
[agent-observability]日志,modelFinishReason未定案;若日后确认多域运行普遍在第 8 步耗尽,应当调预算而不是靠重试兜。回答重试没有独立时间预算:若上一轮已把 110 秒用尽,它会立即被 abort 并以calculation_failed结束(同样不扣点),代价是失败码不如empty_answer精确。 - 防复发:兜底文案不能既当回答又当结算依据。「保证客户端总能收到点东西」和「这次咨询算完成」是两件事,用同一段文本表达两者,必然出现「没回答也扣点」。空回答的正确出口是失败码加不扣点,而不是一句道歉。另一条:一旦某个兜底把最终变量填满,它下游所有以「该变量为空」为条件的分支都成了死代码——
empty_answer就这样被埋了整轮。加兜底时要顺手确认它没有吃掉下游的诊断分支。 - 相关记录:BUG-277(同一段结算逻辑;它删掉的缓冲正是兜底此前不触发的原因之一)、BUG-214(同为契约门与可见输出的耦合)、BUG-271(同一批「失败在回执里查不到」)
- 复发自:无
- 修复版本:待提交
BUG-281 | staging 质量门 1731/1735:xiaoxin 上 Docker 地址池耗尽,4 个 PostgreSQL fixture 起不来
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:Gitea
backend-quality-gate.yml的npm test --prefix frontend、frontend/tests/helpers/postgres-fixture.ts、deploy/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推送后,xiaoxin(20 核)上tsx --test按os.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-business、identity-auth-integration、model-configuration-security、rectification-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.ts2/2、staging-backend-workflows.test.ts38/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_choice,thinking 模型立刻 run.failed
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:staging 首页进入生时纠正、
POST /api/rectification/agent的action=opening、V10runV9AgentTurn第一步prepareStep - 用户现象:从首页进入生时纠正后流立即结束。NDJSON 只有
run.started接着run.failed,没有skill.bound、case.loaded或任何回答增量。 - 触发条件:登录后从首页打开生时纠正;当前会话模型处于 thinking/reasoning 模式。
- 根因:runner 为了保证第一步读取 Case,把
prepareStep设成toolChoice: { type: "tool", toolName: "rectification-read-case" }。上游 thinking 模式拒绝任何非 auto 的tool_choice,返回Thinking mode does not support this tool_choice且isRetryable: 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(诚实降级正确,但把模型生成当成了首页可用性)
- 修复版本:
9d8b91ac(staging,已被 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.ts的toModelDomainPlanContext与运行时状态、frontend/src/mastra/index.ts的 Agent 指令、frontend/src/lib/consultation-agent-events.ts与frontend/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.14,registry 里 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.ts8 项:事业路由的 strict 段确实带着D10与Opportunity contact到位;无 strict 清单的域如实上报且不被顶替;多域计划每条清单只出现一次;十个域逐个与最宽计划都在体积预算内;further_reading只含包内存在的references/路径且远少于包体清单;交付内容来自锁定版本号;按标题定段在下一个##处停止。consultation-agentic-runtime.test.ts45 项通过,新增一项钉住回执把「服务端交付了几段」与「模型主动读了几篇」分开报。tsc --noEmit与eslint清洁(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/resourceId;Agent 每次请求新建(id里含requestId,不进缓存);回放的history只有{role, text}纯文本,不含任何工具调用或工具结果。所以模型每一轮都必须重新激活一次技能,那份载荷逐轮重发;更糟的是一次请求内的retry(契约不完整)与retryForAnswer(空回答)都是agent.stream([...baseMessages, 追加一句]),各起一个全新的模型循环、各自再激活一次,最坏情况单次请求付三遍。(二)模型看到的包内容不受哈希约束:skills: [jyotishSkillPath]指向的是工作树视图skills/jyotish-vedic-astrology,而那里的references是软链到../../references的 827 条全量树;完整性检查只逐字节比对 SKILL.md,registry 的 sha256 管不到激活时列举出来的那 1,592 条路径。生时纠正的 Agent 没有这个洞,它用的是resolveSkillPackageRuntimePath(skillPackage)(先校验 sha256,再建一个以规范技能名命名的符号链接别名指向锁定版目录)——正确的机制仓库里早就有,只是咨询路径没用。 - 修复:把技能从「模型的一次工具调用」改成「服务端在跑之前就绑定好」,与生时纠正对齐。新增
frontend/src/mastra/skill-binding.ts:从锁定版包读 SKILL.md、剥掉 frontmatter,包进<jyotish-skill name version>交给 instructions;skills指向resolveSkillPackageRuntimePath()的哈希校验路径;用一个providesSkillDiscovery: "on-demand"的输入处理器撤掉skill与skill_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.ts4 项:绑定的正文与锁定版 SKILL.md 逐字一致且运行时路径落在 sha256 目录下;实测激活载荷与绑定载荷的比值(前者是后者两倍以上,后者不到前者 45%);绑定后的工具面恰好只有skill_read而技能仍被声明;系统提示缺方法时中止、带方法与读不到提示时都放过。实测数字:从锁定版包激活是 118,352 字节(## References56,696、## Scripts14,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 上比对绑定前后同一问题的回答差异,也没有实测冷启动多算一次包哈希的代价(resolveActiveSkillPackage与resolveSkillPackageRuntimePath各算一次)。评审中被自己的测试抓到一个真 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-214(runtime_contract_incomplete的门,本轮去掉了它对模型加载动作的依赖)、BUG-286(摘录绑定并解开非解盘 Agent) - 复发自:无
- 修复版本:待提交
BUG-286 | 咨询 prompt 绑了整份 SKILL.md,general 与 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.md、references/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_spectrum、western_spectrum、technique_audit_table。模型投影放行这些字段。可见回答末尾要求一张中文技法审计表(已执行 / 阻塞 / 不适用),口语须先用已交付的原始结构再综合。同一合同写入商业SKILL.md(关联技法完整调取 / 全谱系真实调用 / 强制工作流 / P0-P1 观察层),并把yinduzhanxing-skill的Full-Spectrum Invocation Contract与health-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+confirm;
confirm_agentic_rectification_evidence_v10会在第一条确认时 resolve 无目标证据的 opening focus,第二条变成focus_not_active。propose在 quote 不匹配时 raise,整轮失败。表 CHECK / batch allowlist 比 TSEVIDENCE_KINDS/EVIDENCE_DOMAINS更窄,工具层过了、插入失败。批量路径本可按项原子写入并直接 confirmed。 - 修复:SQL kind/domain/precision 与 TS 对齐;propose 对
quote_not_grounded/invalid_item返回结构化失败;confirm 允许省略 focusId,且不 resolve opening focus;新事件只走 batch;quote 规范化额外去掉 ASCII,!.;:?。新增 Skill10.0.1。不改 10.0.0 包哈希。 - 验证:
frontend/tests/rectification-ingest-p0.test.ts、frontend/tests/rectification-ingest-p0-database.test.ts(有 Docker 时)、kind allowlist 两边一致、propose 结构化拒绝且 receipt 为 completed、confirm 可无 focusId。 - 防复发:一句两件及以上事件不得再要求逐条 propose+confirm;SQL 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=day且occurred_from为具体日期;用户回复「是 / 对」。 - 根因:confirm RPC 本来不改日期;Dossier 虽有精度字段,投影和 Skill 没有强制复述必须与服务器精度同级。模型把日级复述降成年。
- 修复:证据投影增加只读
display_date_label(日级YYYY-MM-DD,禁止格式化成年);Skill 与系统提示要求复述用该标签;用户确认不得改date_precision;revise 在更粗且 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_minutes;Skill 未把「宽度 > 5 或 top tied > 1 必须说不可分区间」写成硬合同。 - 修复:投影增加
indistinguishable_width_minutes;宽度大于maxConfirmationWidthMinutes(5)时投影层关闭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-case、method_followup_plan、Skilljyotish-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_plan;stop_domain_rotation=true时不得按领域清单继续问。 - 相关记录:BUG-288、BUG-290
- 复发自:无
- 修复版本:待提交
BUG-292 | 收集阶段要等用户说「没有更多了」才打分,Activity 显示 0 项计算依据
- 状态:resolved
- 首次发现:2026-08-19
- 最近更新:2026-08-19
- 影响面:
rectification-record-evidence-batch、rectification-confirm-evidence、工具 receiptexecuted_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.json、frontend/src/lib/skill-package-registry.ts、frontend/src/mastra/skill-binding.ts、frontend/src/lib/consultation-methodology.ts、frontend/src/mastra/index.ts、frontend/src/instrumentation.ts、deploy/railway-web.Dockerfile。个人盘咨询。 - 用户现象:维护者手动更新仓库根
SKILL.md/references/后,网页咨询仍按钉死的versions/6.9.14哈希包作答;要让更新生效还得双份拷贝并重算 sha256。 - 触发条件:咨询 Agent 绑定方法,或
consultationMethodologyForDomains()取路由清单。 - 根因:BUG-284/285 把咨询绑到 registry 里的
jyotish-vedic-astrology@6.9.14。mastra/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.md、assets、references、scripts,让 live 相对符号链接能解析。 - 验证:
frontend/tests/skill-binding.test.ts、skill-registry.test.ts、consultation-methodology.test.ts;registry 不再把解盘 skill 列为 active hashed package;resolveActiveSkillPackage("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_gate、rectification-confirm-birth-time、Skilljyotish-birth-time-rectification@10.0.3 - 用户现象:相邻分钟分不开、VedAstro 官方分钟敏感校验未跑通、公开 AA holdout 未达门槛时,Agent 仍可能把代表性候选说成唯一出生分钟,或去追更细确认。
- 触发条件:引擎
confirmation_allowed恒为 false,external_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 时不可分层为blocked;holdout 为not_ready(密封 v2 聚合,不含个案身份)。任一 blocker 未通过则投影confirmation_allowed=false,confirm 工具返回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_outcome、method_followup_plan、Skilljyotish-birth-time-rectification@10.0.3、候选卡文案 - 用户现象:相邻分钟分不开时,用户这次校正没有可用结果;Agent 继续追问感情/事业/家人,而不是给出可采用的代表性时间。确认唯一分钟本来就不该通过。
- 触发条件:至少 3 件可评分事件、至少 2 个领域,但 top 候选
tied_minute_count> 1(例如约 25 分钟平台)。确认门三层 blocker 仍未通过。 - 根因:
acceptance_allowed把unique_top与诊断稳定性并进采用门,并列分钟直接关掉selection_allowed,候选卡不出现。推荐徽标还要求confirmationAllowed。method_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=null且session_outcome=adopt_representative;frontend/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、livejyotish-vedic-astrologySKILL.md、Agent 回执 - 用户现象:个人盘回答后,比较表和技法审计表像未排版的管道符墙;末尾约四十行技法表压过口语正文。
- 触发条件:有出生分钟的个人盘咨询,模型按旧合同把
| 技法 | 状态 | 说明 |写进answer.delta。 - 根因:可见语音要求口语末尾贴完整 Markdown 技法审计表;
.markdown-table没有样式;列表项里的段落仍吃正文边距。BUG-011 只禁止内部EvidenceAuditPanel,没有另做用户可读的折叠表。 - 修复:口语不再粘贴审计表。服务端把已交付行放进回执,界面用默认收起的
本轮技法折叠块展示。历史回答若仍带该 Markdown 表,则从正文拆出再折叠。比较表与列表改用对话字号、边距和横向滚动。不恢复内部证据面板、英文状态码或 raw receipt。 - 验证:
frontend/tests/consultation-technique-audit.test.ts、consultation-voice-contract、skill-binding、evidence-audit-panel、chat-bundle-splitting-contract、chat-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.ts、consultation-spectrum-parity.test.ts、consultation-voice-contract.test.ts;tests/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/consult、execute_consultation_workflowforeground、vedastro_gateway、模型包vedastro_cross_check - 用户现象:技法表里 VedAstro 云状态一直是阻塞,即使官方网关和网络可用。
- 触发条件:聊天前台
foreground: true→defer_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.ts、consultation-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_layers的modules.transits、toModelOutputtiming / 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.ts、consultation-spectrum-parity.test.ts、consultation-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/diagnostics、confirmation_gate.vedastro_minute_sensitive - 用户现象:确认门 VedAstro 一行长期
not_evaluated。独立的/api/rectification/v5/vedastro-validate存在,但 V9 评分从不调用分钟身份层;SearchEvents 也不是确认判据。 - 触发条件:V9
rectification-compare-candidates/ diagnostics 产生两名候选且本地acceptance_allowed。 - 根因:
build_decision_receipt把external_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;赶不上不得改成 fail;SearchEvents 不得单独放行确认。 - 相关记录: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.ts、rectification-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/consult、streamAgentResponse、咨询页 NDJSON 收口、DeepSeek V4 Flash 生成预算 - 用户现象:staging 一次咨询回答停在半截标题,输入框随即恢复可输入,界面看起来像已经答完。服务端把该条助手消息按成功咨询写入会话。
- 触发条件:网页个人咨询,模型为 DeepSeek V4 Flash。计算工具已成功,随后组织回答时输出在句中停止。观测到会话更新约 2.5 分钟后收口,公开回执
stepBudget.truncated=false,客户端收到run.completed。 - 根因:两层。其一,
finish_reason=length与超时中断后的半截正文仍走run.completed→complete_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、不得调用onComplete;consultation-workflow-contract.test.ts锁定输出预算与 thinking disabled;consultation-recovery.test.ts与chat-stream-layout.test.ts锁定半截落盘与提示;agent-observability.test.ts将answer_truncated纳入已知错误码。 - 防复发:有可见正文不等于咨询完成。
finish_reason=length、超时半截不得再映射为run.completed。咨询生成必须显式保留 8192 可见 token 预算。若打开 provider thinking,必须走独立thinking.delta,不得把推理写进answer.delta。公开回执仍不得带modelFinishReason,失败码必须能单独说明夹断。 - 相关记录:BUG-280、BUG-277、BUG-340、BUG-346
- 复发自:无
- 修复版本:待提交
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.ts、chat-stream-layout.test.ts、rectification-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.py、frontend/tests/rectification-varga-sentence.test.ts、rectification-candidate-result.test.ts、rectification-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.yml的npm test --prefix frontend、frontend/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.Dockerfile的RUN npm run build、POST /api/sessions、POST /api/daily-starlanguage、POST /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-js,select结果变成GenericStringError,无法传入AccountBirthRow。 - 修复:
limitTranscriptSize按输入 schema 的Output原样返回z.ZodType<Output>。出生列改为共享字面量ACCOUNT_BIRTH_SELECT,globalBirthProfileFromAccountRow接受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.ts36/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_action、rectification-offer-candidates - 用户现象:相对支持度约 34 / 33 / 33 的相邻分钟已经出现“当前可能的出生时间”采用卡。文案自己也说还不能确认唯一分钟,但采用动作已经摆在对话里。
- 触发条件:比较已经跑过,引擎
selection_allowed=true,方法层仍有next_followup(例如关系/事业还能区分),Agent 还在收集。 - 根因:
selection_allowed只表示可以采用代表性时间。UI 用它直接画卡;buildNextUserAction也在仍有下一问时把本轮动作写成 adopt,并把 follow-up 推迟。相邻分钟平台因此被当成“现在就选”。 - 修复:仍有
next_followup时会话留在收集,本轮继续问;用户停止才走 adopt。界面只在最近一条已落地 Agent 回复完成rectification-offer-candidates且selectionAllowed时,把卡片挂在该气泡下方。 - 验证:
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-snapshot、RectificationHouseTableView - 用户现象:当前本命宫位灰卡从对话左缘铺开,比上方 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.Dockerfile的RUN npm run build、生时纠正时间选择卡 - 用户现象:staging publish 在 web 镜像构建失败。BuildKit 日志末尾是
skill-package-registry.ts的 Import traces,真正失败是Failed to type check:latestOfferMessageKey可能为undefined。 - 触发条件:向
staging推送后走 publish 镜像构建。push 上的 validate 跳过npm run build,因此质量门绿、镜像红。 - 根因:
showSelectionCards用Boolean(...)包了一层,TypeScript 不会因此收窄find()的T | undefined。随后showSelectionCards ? latestOfferMessageKey.renderKey在 true 分支仍可能是undefined,next 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-309(validate 跳过
next build;日志末尾 Import traces 掩盖类型检查失败) - 修复版本:待提交
BUG-316 | 家人问了也不改候选;外貌/胎记被硬禁止追问
- 状态:resolved
- 首次发现:2026-08-20
- 最近更新:2026-08-20
- 影响面:生时纠正评分引擎、
method_followup_plan、Skilljyotish-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-rashi与d10-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.py(D4 不再是 theme_refine;按候选分钟重算宫位;技法审计含 blocked 唯一分钟);tests/test_rectification_family_appearance_scoring.py(D7/D5 进入可用层);frontend/tests/rectification-eight-method.test.ts(d4 问搬家、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接线、Skilljyotish-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/20not_ready。V9 在selection_allowed且有 primary/runner-up 时调用/vedastro-validate;SearchEvents 只作旁证,不得改confirmation_allowed。结果卡折叠展示确认门三行。新 Case 绑定 Skill 10.0.6;已有 10.0.5 Case 保持原绑定。 - 验证:
tests/test_rectification_v5_vedastro_validation.py;tests/test_rectification_confirmation_and.py;frontend/tests/rectification-confirmation-gate.test.ts;frontend/tests/rectification-v9-engine-contract.test.ts;frontend/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、家人评分、Skilljyotish-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.py;tests/test_rectification_family_appearance_scoring.py;tests/test_rectification_technique_contract.py;frontend/tests/rectification-eight-method.test.ts;frontend/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.py;tests/test_rectification_family_appearance_scoring.py;frontend/tests/rectification-eight-method.test.ts;frontend/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.Dockerfile的RUN npm run build、rectification-candidate-result.ts、rectification-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 check。selectedTime && 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 --noEmit;npx 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-309(validate 跳过
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.ts;frontend/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_outcome、rectification-compare-candidates、rectification-offer-candidates、Skilljyotish-birth-time-rectification@10.0.9 - 用户现象:只有学业和感情两块经历、比较已经跑过时,对话里已经出现“当前可能的出生时间”采用卡。文案仍说还不能确认唯一分钟,但卡片已经摆出来。事业、家人、财务、健康都还没问。
- 触发条件:引擎
selection_allowed=true(事件≥3、领域≥2),方法覆盖仍有挡住出牌的下一问(例如事业),用户也没有说“暂时想不到了 / 没有更多 / 先这样”。Agent 在 compare 之后调用 offer-candidates。 - 根因:采用门和提出门被合成一个。
selection_allowed只表示可以采用代表性时间。compare 的session_outcome不看next_followup,offer-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.py;frontend/tests/rectification-eight-method.test.ts;frontend/tests/rectification-confirmation-gate.test.ts;frontend/tests/skill-registry.test.ts。 - 防复发:
selection_allowed不得单独出示采用卡。有挡住出牌的next_followup时session_outcome必须保持collect_evidence,offer 必须拒绝。不得改已哈希的 10.0.8 包。不得把 KP 政策跳过写成 KP 已执行。 - 相关记录:BUG-120、BUG-297、BUG-313
- 复发自:BUG-313(有 follow-up 时仍可因 compare 投影为 adopt 而 offer);BUG-297(compare/offer 不共享提出门)
- 修复版本:7dc97a49
BUG-324 | 侧栏合同仍断言固定 chat-panel class,staging frontend 测试失败
- 状态:resolved
- 首次发现:2026-08-20
- 最近更新:2026-08-20
- 影响面:Gitea
Jyotish Skill CI/backend-quality-gate的npm test --prefix frontend、frontend/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。 - 防复发:改
SidebarInsetclass 时必须同步sidebar-contract;生时纠正分栏 class 另由rectification-agentic-entry锁定。 - 相关记录:BUG-322
- 复发自:无
- 修复版本:3970410e
BUG-325 | 经典八方法访谈被财务/健康轮询替换,KP 宫头被政策跳过冒充已观察
- 状态:resolved
- 首次发现:2026-08-20
- 最近更新:2026-08-20
- 影响面:生时纠正方法覆盖、
method_followup_plan、KP 宫头观察、窗口扫描、Skilljyotish-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/D11、D30并保留换升。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.py;tests/test_rectification_confirmation_and.py;tests/test_rectification_horary_observation.py;tests/test_rectification_family_appearance_scoring.py;tests/test_rectification_v5_services.py;frontend/tests/rectification-eight-method.test.ts;frontend/tests/rectification-ingest-p0.test.ts;frontend/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 里写 ref,staging ESLint 失败
- 状态:resolved
- 首次发现:2026-08-20
- 最近更新:2026-08-20
- 影响面:Gitea
backend-quality-gate的npm run lint --prefix frontend、frontend/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.tsx;npx 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.ts、frontend/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: isolate且z-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-gatePython quick quality gate、tests/test_cli_smoke.py、tests/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 smoke:Dasha 起始时刻、D9 Jupiter 尊严、Darakaraka 行星与 Navamsa 敌友标签对不上。 - 触发条件:产品默认岁差改为 Raman 后,未给 Lahiri 校准样本显式传
--ayanamsa lahiri,再跑 quick quality gate。 - 根因:这些断言锁的是旧 Lahiri 盘面(例如
1952-03-19T20:24:47、NEECHA_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-gate的npm test --prefix frontend、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-candidate-result.test.ts、公开event_dasha_ledger - 用户现象:质量门
tests 1840中pass 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.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-candidate-result.test.ts、frontend/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-gate的publishjob、deploy/railway-api.Dockerfile、xiaoxinrunner 拉官方基础镜像 - 用户现象:
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 / ACR;Web 镜像早已改用
swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/library/node:22-alpine,API 未跟上。 - 修复:API
FROM改为与 Web 相同的华为 SWRdocker.io/library路径,指向python:3.12-slim。不改运行时端口、pip/apt 镜像或 ACR 目标仓库。 - 验证:
tests/test_railway_deployment.py、frontend/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-wrap带z-index: 2和backdrop-filter。iOS 会把该滤镜层合成到覆盖层之上。BUG-330 只把 overlay 设为z-index: 20,工作区没有独立层叠上下文,也没有在打开时关掉输入框滤镜。 - 修复:工作区
isolation: isolate; z-index: 0锁住输入框层叠;覆盖层z-index: 50。打开盘面时隐藏 composer(visibility: hidden、关掉backdrop-filter),并收起预览条。sheet 高度改为 72%,上方聊天列表可从遮罩后看见。 - 验证:
frontend/tests/rectification-agentic-entry.test.ts。 - 防复发:窄屏覆盖层必须高于 composer;打开时 composer 不得再带
backdrop-filter。源码合同锁定isolation: isolate、overlayz-index: 50、visibility: 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。 - 防复发:源码合同锁定
createPortal、data-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_plan、rectification-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.py(14 分钟并列簇propose_allowed为真、confirmation_allowed为假);frontend/tests/rectification-eight-method.test.ts(lagna_frame先问未覆盖事业;经典八法覆盖后lagna_frame不挡出牌)。 - 防复发:不得把确认门的唯一领先或宽度≤5重新写进
propose_allowed。precision_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、
///loginHTML 缓存、NextdeploymentId、发布后已打开的旧标签页 - 用户现象:旧聊天页或已打开的网页,在 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.ts;frontend/tests/membership-page.test.ts;frontend/tests/staging-backend-workflows.test.ts。 - 防复发:Web 镜像必须在
next build时注入NEXT_DEPLOYMENT_ID且next.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-gatevalidate、rectification-agentic-chat窄屏盘面入口 portal - 用户现象:向
staging推送后 quality gate 失败,镜像未发布,站点仍停在上一版。日志是react-hooks/set-state-in-effect。 - 触发条件:
npm run lint --prefix frontend。自8b8e5214起,gate run 2019/2020/2021 均因此失败。 - 根因:窄屏盘面入口用
useLayoutEffect里querySelector后立刻setHeaderSlot。eslint-config-next禁止在 effect 同步 setState。BUG-335 把入口 portal 到页头时引入该写法。 - 修复:页头挂载点改用 callback ref,把 DOM 节点作为
headerSlot传给校时会话。仍createPortal到data-rectification-header-slot,不再在 effect 里查 DOM 或 setState。 - 验证:
frontend/tests/rectification-agentic-entry.test.ts;npm run lint --prefix frontend -- src/components/rectification-agentic-chat.tsx。 - 防复发:盘面入口不得在 effect 里
querySelector后同步 setState。源码合同锁定 callback ref 与headerSlotprop,并禁止 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/agent、runV9AgentTurn、生时纠正与普通咨询等待态 - 用户现象:Agent 写到一半停止,界面却像已经答完;等待期间只有「正在处理…」,看不出在做什么。
- 触发条件:当前会话模型默认开启 thinking / reasoning;可见正文与隐藏推理共用输出预算,或墙钟超时后仍发出
finish。 - 根因:BUG-305 只修了咨询路径。纠正
agent.stream没有maxOutputTokens、没有关闭 thinking,并把任意finish当成成功。进度事件tool.activity started被客户端丢掉,所以长计算期间用户只能干等。 - 修复:咨询与纠正共用
agentGenerationSettings(8192 可见 token,thinking disabled)。纠正在finish_reason=length时走answer_truncated、不扣点、不把半截写入成功 Turn;客户端保留已流出正文并提示未完成。等待态改为公开工具进度(正在比较候选时间等)和 8 秒后的已用时,不展示模型思维链。 - 验证:
frontend/tests/rectification-v9-stream.test.ts的 length 夹断不得run.completed;frontend/tests/rectification-v9-agent.test.ts锁定纠正流的输出预算与 thinking disabled;frontend/tests/agent-activity-progress.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/consultation-workflow-contract.test.ts。 - 防复发:纠正与咨询必须走同一套 generation settings 与 8192 可见预算。不得为了等待体验打开 provider thinking。若打开 thinking,必须走独立
thinking.delta,不得把推理写进answer.delta。tool.activity started必须驱动 Orb 文案。finish_reason=length不得映射为run.completed。 - 相关记录:BUG-305、BUG-282、BUG-329、BUG-345
- 复发自:BUG-305(咨询已修,纠正仍用默认 thinking 与任意 finish)
- 修复版本:
6a44c778
BUG-329 | 生时纠正 Agent 回答在结算后一次性出现,推理中无法停止
- 状态:resolved
- 首次发现:2026-08-20
- 最近更新:2026-08-20
- 影响面:
POST /api/rectification/agent、runV9AgentTurn、生时纠正输入框 - 用户现象:发送经历后接口长时间无增量文字,工具活动与回答在计费完成后才整段出现;推理过程中发送按钮不可用,也无法停止。
- 触发条件:任意生时纠正
message/opening流,尤其是rectification-set-focus同轮重试的长等待。 - 根因:成功 attempt 的 tool.activity 与
answer.delta被攒到billing.complete和持久化之后才发给浏览器。前端 fetch 没有 AbortSignal,输入区也没有停止按钮。 - 修复:工具活动和回答增量在生成过程中即时发布;失败重试先发
attempt.reset清掉弃用 attempt 的可见文字。前端在推理中显示停止按钮,中止请求;已流出的文字保留。 - 验证:
frontend/tests/rectification-v9-stream.test.ts、frontend/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_source为period_only/unknown等无具体分钟状态;或已有reported_birth_time但状态仍是reported/candidate;从首页「每日运势」或普通 session 发出今日运势或主题问题;或在同一普通 session 里发送「出生时间校正 / 生时校正」类标签。 - 根因:产品把生时校正当成功能门,而不是可选增强。无分钟用户被压进
general_no_birth_time,General 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.ts、frontend/tests/consultation-birth-time-mode.test.ts、frontend/tests/consultation-route-service.test.ts、frontend/tests/declared-birth-window.test.ts、frontend/tests/starter-questions.test.ts、frontend/tests/personal-report-api.test.ts、frontend/tests/timing-output-guard.test.ts、tests/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-gatevalidate、frontend/tests/application-billing-contract.test.ts、frontend/tests/consultation-stream-recovery.test.ts、frontend/tests/chat-navigation-a11y-contract.test.ts、frontend/tests/composer-isolation-contract.test.ts;点选卡提交因此无法发布 - 用户现象:向
staging推送后质量门失败,镜像未发布,站点仍停在e7f4030e。run2026(BUG-341)与 run2027(点选卡)均是validate失败、publish跳过。前端测试摘要# tests 1871 / pass 1865 / fail 6。 - 触发条件:
npm test --prefix frontend。BUG-341 增加声明窗口咨询路径并改了首页草稿变量后,精确计数合同仍按两条 agentic 路径与setDraftEntrypoint(entrypoint)断言。 - 根因:咨询路由现有三条 agentic 首流(公共/百科、声明窗口、本命)及对应重试,
continueAfterDisconnect、abortSignal、settleRunonError/onCancel 都变成 3/5。首页send()把草稿 entrypoint 收成consultEntrypoint,且校正交接会在积分检查前清一次草稿。合同仍用send()里第一次setDraft("")当发送清草稿标记,切片失效。这是 BUG-308 同类:产品改了字面,源码合同没一起改。 - 修复:把计量次数改成三条路径的实际次数;失败回填断言
consultEntrypoint;积分不足合同改为对照真正发送清草稿(conversationAnchor.anchorToLatest()之后),不再误伤校正交接。 - 验证:上述四份合同测试本地 35/35;此前失败的 6 项均通过。
- 防复发:再增加咨询 agent 路径时必须同步
usages.push、retryForAnswer、continueAfterDisconnect、abortSignal: agentAbortSignal与settleRunonError/onCancel 的精确计数。切send()清草稿不得用文件里第一次setDraft("")。失败回填的 entrypoint 变量名必须随send()一起改。 - 相关记录:BUG-308、BUG-341
- 复发自:BUG-308(源码合同锚点过期把无关提交打红);BUG-341(第三条咨询路径与草稿变量未改合同)
- 修复版本:6a0394aa
BUG-343 | 生时纠正点选卡 effect 同步 setState,staging lint 失败
- 状态:resolved
- 首次发现:2026-08-21
- 最近更新:2026-08-21
- 影响面:Gitea
backend-quality-gatevalidate、rectification-agentic-chat案例快照加载、rectification-choice-card换题重置 - 用户现象:BUG-342 合同修过后
npm test1871/1871 通过,但npm run lint因react-hooks/set-state-in-effect失败。run2028validate9m39s 失败,publish1 秒跳过,站点仍停在e7f4030e。 - 触发条件:
npm run lint --prefix frontend。挂载校时会话或换一道 A/B/C/D 题。 - 根因:挂载时
useEffect直接调用含setState的loadCaseSnapshot();点选卡用 effect 在question_id变化时setSelectedKey("")。eslint-config-next禁止 effect 同步 setState。同类于 BUG-339。 - 修复:案例快照改为
fetch().then回调里应用 payload(事件处理仍走loadCaseSnapshot)。点选卡用key={question_id}换题重挂,不再在 effect 里清选中态。 - 验证:
npm run lint --prefix frontend0 error;frontend/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-gatepublish、consultation-route-service.serverChartFromProfile、isNatalMinuteConsultationMode - 用户现象:run
2029validate12 分钟通过,publish在npm run build失败,镜像未发。站点仍停在e7f4030e。 - 触发条件:staging push 的 publish 镜像构建跑
next build。validate 对 staging push 跳过 production build。 - 根因:BUG-341 给
ConsultationBirthTimeMode增加了declared_birth_window,但isNatalMinuteConsultationMode仍返回boolean,TypeScript 无法收窄成verified_chart | unverified_birth_time,serverChartFromProfile在next 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
BUG-345 | 生时纠正把工具重试自述当成回答,且 kind 在 schema 里是自由字符串
- 状态:resolved
- 首次发现:2026-08-21
- 最近更新:2026-08-21
- 影响面:
POST /api/rectification/agent、runV9AgentTurn、证据工具proposedKind/domainschema、生时纠正对话 - 用户现象:用户补了一条升学经历后,结算气泡里是英文工具重试自述(大意:proposedKind 被拒、education 不是合法 kind、改用 education_start),没有中文跟进问句。棋盘已推进,说明计分跑过,失败的是用户可见正文。
- 触发条件:用户给出升学类经历,模型把
missing_evidence_categories里的领域名education当成proposedKind调用证据工具。 - 根因:与 BUG-278 / BUG-277 同类。
proposedKind与domain在 Zod schema 里是自由字符串,白名单只在execute的isEvidenceKind()里;模型在 JSON schema 里看不到education_start。被拒后把重试过程写进text-delta。纠正 runner 立刻把每个text-delta发成answer.delta,没有咨询路径那种契约门。BUG-340 关闭 provider thinking 后,过程自述更容易落到正文通道。 - 修复:证据 kind/domain 改为枚举,模型在调用前能看到
education_start。工具失败未恢复成功前的正文、以及纯英文过程自述,不得进入answer.delta。中文思维链走独立的thinking.delta(来自reasoning-delta或契约未绿的中文片段);英文过程自述丢弃。纠正打开 provider thinking。界面灰色「思考过程」,有正文后默认折叠。 - 验证:
frontend/tests/rectification-v10-tool-contract.test.ts拒绝proposedKind: "education";frontend/tests/rectification-v9-stream.test.ts英文重试自述不得出现在公共流,用户只拿到中文回答;思维链映射与折叠 UI 的源码合同。 - 防复发:模型必须遵守的词汇写在 schema 枚举里,不要只写在 execute。
answer.delta不得承载工具重试自述。思维链必须是独立事件类型。thinking.delta不得写入 run_phase 表。 - 相关记录:BUG-277、BUG-278、BUG-340、BUG-346
- 复发自:BUG-277(纠正未做契约门)、BUG-278(kind 未枚举)、BUG-340(关 thinking 后自述改走正文)
- 修复版本:4e247c11
BUG-346 | 普通咨询未打开思维链通道,灰色折叠 UI 只在生时纠正生效
- 状态:resolved
- 首次发现:2026-08-21
- 最近更新:2026-08-21
- 影响面:
POST /api/consult、streamAgentResponse、咨询页ChatMessageRow - 用户现象:生时纠正已有灰色「思考过程」、正文出来后折叠;普通咨询仍无思维链。
- 触发条件:网页个人咨询 / 无分钟百科 / 声明窗口咨询。
- 根因:BUG-305 / BUG-340 为保住可见 token 预算,把咨询
consultationGenerationSettings固定为 thinking disabled,并丢弃reasoning-*。BUG-345 只给纠正打开独立thinking.delta与折叠 UI。 - 修复:咨询同样启用 provider thinking,8192 可见预算不变。
reasoning-delta经中文清洗后发thinking.delta;契约未绿的text-delta仍按 BUG-277 丢弃,不升格为思维链。咨询页把thinkingText接到共用ChatMessageRow,有正文后默认折叠。后续 BUG-347 把思维链写入会话落盘,并在失败时保留已展示的思考过程。 - 验证:
frontend/tests/consultation-agentic-runtime.test.ts中文 reasoning 进 thinking 通道、英文过程自述不进正文;consultation-workflow-contract.test.ts锁定thinking: "enabled";chat-stream-layout.test.ts锁定咨询页与折叠 UI。 - 防复发:咨询与纠正的思维链必须是
thinking.delta,不得混进answer.delta。BUG-277 的契约前正文仍须丢弃。打开 thinking 不得降低 8192 可见预算,finish_reason=length仍不得当完成。 - 相关记录:BUG-277、BUG-305、BUG-340、BUG-345
- 复发自:BUG-305(咨询关 thinking 保预算)、BUG-345(只修了纠正)
- 修复版本:4e247c11
BUG-347 | 咨询失败或刷新后思维链被清掉
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:咨询流式 UI、
complete_consultation_response落盘、chatSessionWriteSchema、会话读取 - 用户现象:开始出回答时思维链消失;若随后报「Agent 回答未完成」或刷新,已展示的思考过程一起没了。
- 触发条件:普通咨询流式生成中出现
run.failed、空正文,或成功后刷新页面。 - 根因:思维链只活在
streamingReply.thinkingText。失败路径回滚到提问前的会话并清掉 streaming;成功路径虽然把 thinking 写进内存消息,但结算 RPC 与会话 PATCH 都不保存该字段,读取时也会丢掉额外消息字段。 - 修复:失败时保留用户问题、已有思考过程和失败说明,不再把思维链卸掉。成功结算把清洗后的
thinkingText写入p_response_message;客户端会话读写契约同步接收该字段。界面改成时间线思考块,完成步骤一行一项。 - 验证:
frontend/tests/chat-session-write.test.ts接受 thinkingText;consultation-stream-recovery.test.ts锁定结算消息含思维链;chat-stream-layout.test.ts锁定时间线 UI;失败路径保留thinkingText的源码合同。 - 防复发:咨询结算消息与会话 PATCH 必须能保存
thinkingText。失败不得用回滚抹掉已展示的思考过程。读取会话时不得只保留role/text。 - 相关记录:BUG-277、BUG-346
- 复发自:BUG-346(打开思维链通道但明确不落盘)
- 修复版本:59559d4b
BUG-348 | 生时纠正一进场就出 A/B/C/D,比纯提问更慢
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:staging 生时纠正开场、
method_followup_plan、rectification-choice-card、Skilljyotish-birth-time-rectification@10.0.11、decision receiptdiscriminating_event_probes - 用户现象:账本还没有任何已确认证据时,开场就出现 A/B/C/D 点选卡(学业 vs 工作)。用户点 A 之后仍没有具体年份事件,下一问又变成“2016 年前后,更像哪一件?”——A/B 是认真关系 vs 职责加重,或“时间大致对得上 vs 略偏”。比之前直接问一件带年份的经历更慢,也无法筛时间。
- 触发条件:新建或恢复空账本生时纠正会话;不确定范围约半小时,例如 04:45–05:15。有账本后候选仍分不开时继续出冲突排序卡。
- 根因:conversation-strategy 把“降低回忆负担”写成开场就从候选簇差异生成点选卡。
buildMethodFollowupPlan对空账本的dasha_events以及后续方法覆盖一律挂choice_frame;系统提示强制把 A/B 写成两套盘前事。区分阶段用lifePeriodLabel()把别的事件年份贴到当前主题,并让用户给冲突排序,而不是收集可评分生平。评分链路已有_score_event,但没有逆运算把两套代表分钟的年差/激活差写成可问题干。 - 修复:收集阶段用自然语言问一件带大概年份的经历,
choice_frame为 null,界面不出点选卡。候选已经分不开时,服务器生成discriminating_event_probes(锁定年份和事件家族,不写死题干)。Agent 把题干和 A/B/C/D 写成自然语言;A/B 是同一件事的吻合程度。新 Case 绑定 Skill 10.0.11;已有 10.0.10 Case 保持原绑定,但服务器不再给空账本发点选框,也不再问两套盘哪个更像。 - 验证:
tests/test_rectification_event_probes.py锁定高考性质追问、年龄带退路、D4 激活差问搬家、大运起年只差几天不得写成新年、缺 Narayana 不得声称 dasha 年;frontend/tests/rectification-choice-card.test.ts锁定空账本无卡、探针题干不含“更像哪一件”、跨主题不复用年份;frontend/tests/rectification-eight-method.test.ts锁定方法覆盖用自然语言、分盘差异仍出 A/B/C/D。 - 防复发:空账本或
collect_method_evidence不得挂choice_frame。区分卡不得写“更像哪一件 / 两套盘 / 可能性”。年份必须来自探针或同主题账本/年龄带,禁止跨主题lifePeriodLabel。不得把已哈希的 10.0.10 改包;未提交的 10.0.11 可折进同包。已有 10.0.10 Case 继续可解析。 - 相关记录:BUG-020、BUG-337
- 复发自:BUG-020(语言问答被包装成高阻力卡片;后来又把开场改成强制 A/B/C/D)
- 修复版本:9873ba42
BUG-349 | 唯一分钟确认门被 holdout 吊着,会话没有收口
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:确认门投影、
session_outcome、采用卡文案、Skilljyotish-birth-time-rectification@10.0.11、decision receiptunique_minute_path - 用户现象:采用代表性时间之后,界面和 Agent 仍说“还不能确认唯一分钟”,像下一步还要等 VedAstro / 公开 holdout / 宽度 ≤ 5 分钟。holdout 当前是 1/20
not_ready,确认门永远打不开,访谈没有终点。 - 触发条件:生时纠正已经可以出示或采用代表性时间;密封 holdout 为
not_ready。 - 根因:确认门三层 blocker 是诚实的 fail-closed,但产品把
confirmation_allowed=false写成待完成的下一步,而不是本会话的终点。candidate_accepted还写着“可进入确认门”。 - 修复:不放开
confirmation_allowed。confirmation_gate.unique_minute_path=closed_at_representative时,本会话以代表性时间收口;不得调用 confirm,不得把唯一分钟确认当下一步。文案从“还不能确认”改为“本会话以代表性时间收口,不确认唯一分钟”。折进未提交的 Skill 10.0.11,不改已哈希的 10.0.10。 - 验证:
frontend/tests/rectification-confirmation-gate.test.ts锁定 holdoutnot_ready时unique_minute_path=closed_at_representative且confirmation_allowed=false;tests/test_rectification_confirmation_and.py锁定 14 分钟并列簇提出门开、确认门关、路径为收口。 - 防复发:不得把
confirmation_allowed改成 true 来假装收口。holdout 未达标时不得把确认写成下一步。不得为把不可分区间问到 5 分钟以内而继续 A/B/C/D。 - 相关记录:BUG-303、BUG-318、BUG-348
- 复发自:BUG-318(确认门与密封 holdout 对齐后,产品仍把确认当未完成步骤)
- 修复版本:9873ba42
BUG-350 | 采用代表性时间后没有按该分钟核对前事,也无法改选
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:
method_followup_plan采用后路由、next_user_action、生时纠正候选卡、Skilljyotish-birth-time-rectification@10.0.11 - 用户现象:用户采用一个代表性时间后,会话直接请看盘;盘外核对也不计分、不改候选。没有用该分钟去核几件还没提过的前事,对不上也不能改选其他冲突时间。
- 触发条件:生时纠正已经采用代表性时间;decision receipt 仍有
discriminating_event_probes(大运年界/同年激活差或年龄带退路)。 - 根因:
buildMethodFollowupPlan({ accepted: true })把下一问清空,只挂不计分的oos_blind。buildNextUserAction在 adopted 后一律start_consultation。候选卡又要求!selectedTime,采用后改选入口消失。 - 修复:采用后最多核两件剩余探针前事(跳过已确认/已拒绝领域和
known_event_quality),next_followup.method_id=reverse_verify且计分。A 写入账本并重算;C 关闭该问;对不上或核对结束后可改选。不打开唯一分钟确认门。折进未提交的 Skill 10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts锁定采用后verify_adopted_time与无剩余探针时start_consultation;frontend/tests/rectification-choice-card.test.ts锁定核对卡计分且不是盘外核对;frontend/tests/rectification-agentic-entry.test.ts锁定有点选卡时先核前事、否则在已 offer 过的会话里显示改选。 - 防复发:采用后不得把不计分盘外核对当默认下一步。
showSelectionCards不得因selectedTime永久藏改选;有前事点选卡时不得同时出示时间卡。不得把confirmation_allowed改成 true。 - 相关记录:BUG-120、BUG-348、BUG-349
- 复发自:BUG-120(采用后无法改选;后来又把采用后的核对做成不计分盘外问句)
- 修复版本:9873ba42
BUG-351 | 候选时间冲突时反推前事被方法轮询和出牌 defer 挡住
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:
method_followup_plan、isOfferBlockingFollowup、Skilljyotish-birth-time-rectification@10.0.11 - 用户现象:账本已有带年份经历、两套代表分钟已经分不开时,Agent 仍继续问下一条感情/事业,或直接请采用代表性时间。不会按冲突分钟反问「某年前后有没有搬家/入职」,答案也无法先筛窗。
- 触发条件:生时纠正已有至少一件可评分事件;decision receipt 含
dasha_boundary或dasha_activation探针;方法覆盖尚未齐,或propose_allowed已开。 - 根因:第一件带日期事件之后下一问固定走感情 → 事业 → 家人 → 职业。精度阶段/分盘差异问句不挡出牌,
session_outcome=adopt_representative时把它们放进deferred_followup。探针年份锁在 choice_frame 里,但问句轮不到。 - 修复:已有带日期事件且仍有大运冲突探针时,
source=event_probe先问该年前事并挡住出牌。A 写入并重算以筛窗。年龄带探针不插队。空账本仍只自然语言收集。不打开唯一分钟确认门。折进未提交的 Skill 10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts锁定教育事件 + 事业激活探针先问 2018 年入职且session_outcome保持收集;年龄带探针不插在感情收集前。 - 防复发:大运冲突探针在采用前必须挡住出牌。不得在空账本挂
choice_frame。不得把confirmation_allowed改成 true。不得在采用门事件/领域未齐时插队(见 BUG-386)。 - 相关记录:BUG-348、BUG-350、BUG-386
- 复发自:BUG-348(收集后再区分;区分问句被方法轮询和出牌 defer 排到采用之后)
- 修复版本:9873ba42
BUG-352 | 个人报告失败被压成 report_schema_invalid,列表把 Date 显示成时间未知
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:个人报告中心
/reports、GET /api/reports、generatePersonalReport、自托管timestamptz投影、报告 writer 绑定 - 用户现象:生成完整报告后记录为「未完成」,右侧只显示
report_schema_invalid;卡片时间显示「时间未知」。出生时钟在创建时是可用的,失败发生在后台生成之后。 - 触发条件:自托管 PostgreSQL 上创建个人完整报告;writer 输出与 section plan 不完全一致,或生成中被中止;列表读取
created_at。 - 根因:
generatePersonalReport把 writer JSON、plan 绑定、组装、终态 parse 和 abort 全部吞成同一个report_schema_invalid,且不记录内层 token。evidenceRefs 还要求顺序全等。自托管queryValue()只把date收成字符串,timestamptz仍是Date,列表投影只接受 string,UI 便显示「时间未知」。 - 修复:schema 失败返回 allowlist
innerReason并打无 PII 日志;abort 向上抛出,不再伪装成 schema 失败。plan 绑定失败走现有那一次 repair;evidenceRefs 改为集合相等。queryValue把timestamptz/timestamp收成 ISO,列表投影同时接受Date。 - 验证:
frontend/tests/personal-report-generation-v2.test.ts、frontend/tests/personal-report-generation.test.ts、frontend/tests/personal-report-api.test.ts、frontend/tests/local-postgres-query-value.test.ts、frontend/tests/personal-report-worker.test.ts。 - 防复发:用户可见码可以仍是
report_schema_invalid,但生成结果和日志必须带 allowlist 内层 token。lease abort 不得再断言成 schema 失败。列表时间必须能从Date或 ISO 字符串格式化。不得把模型原文、出生资料或路径写进失败日志。 - 相关记录:BUG-154、BUG-188、BUG-217
- 复发自:无
- 修复版本:97620634
BUG-353 | 不确定时间只扫五档时段,补充描述里的钟点纠正用不上
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:出生时间表单、
profiles.declared_window_start/end、V9 Case 候选窗、普通咨询 declared window、账户 PATCH - 用户现象:用户已把不确定时间改成 14:30–15:00,或写在补充描述里,生时纠正和 Agent 仍按下午 12:00–17:59 扫描。
- 触发条件:
birth_time_source=period_only且用户记得的是一段钟点而不是五档粗时段;旧资料把钟点写在birth_time_clue。 - 根因:
period_only只把birth_time_period映射成五档固定窗。birth_time_clue不进 Case fingerprint、不进candidate_range、也不被解析。Case 窗在创建时冻结,聊天工具不能改。正则解析备注会把“6—8 点”这类歧义话当成扫描分钟。 - 修复:不确定路径改为开始/结束钟点,不再收集备注。保存
declared_window_start/end;自定义钟点优先于五档时段;开始结束相同则拒绝;允许跨午夜。旧afternoon等行回填对应钟点,用户可以收紧到 14:30–15:00。收紧后 fingerprint 变化,首页开新 Case。不解析 clue,保存时把 clue 写成 null。 - 验证:
frontend/tests/birth-time-intake.test.ts锁定开始结束表单、14:30–15:00 持久化且不留下 afternoon 桶;frontend/tests/declared-birth-window.test.ts锁定自定义钟点覆盖 afternoon;frontend/tests/rectification-v9-case-service.test.ts锁定 Case 开窗 14:30–15:00 且 fingerprint 变化;frontend/tests/consultation-route-service.test.ts锁定咨询窗;frontend/tests/account-api.test.ts锁定 PATCH 接受开始结束、拒绝同一分钟。 - 防复发:不得把备注正则解析成扫描窗。不得只扫五档时段而丢掉用户选择的开始结束。不得把自定义窗的中点写成
reported_birth_time。改窗必须开新 Case,旧会话继续展示冻结窗。 - 相关记录:BUG-197、BUG-198
- 复发自:BUG-197(不确定入口恢复后仍用五档时段加备注,备注从未进入扫描窗)
- 修复版本:8ed6118b
BUG-354 | 咨询 thinking 抢占正文额度,思考过程被清洗成英文碎片且没有回答
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:
POST /api/consult、streamAgentResponse、咨询页思考/分析 UI、组答maxOutputTokens - 用户现象:咨询页灰色「思考过程」出现断裂英文碎片(如
Let me at the.、The for),正文为空;流在技法审计表标题处因finish_reason=length结束。 - 触发条件:网页个人咨询,尤其是多领域 Level 2 组答;供应商默认打开 thinking,且
max_tokens与可见正文共用。 - 根因:BUG-346 在 8192 可见预算不变的情况下重新打开 provider thinking。Flash 的
reasoning_content占满额度后content为空。公开thinking.delta又按 token 清洗英文,把连贯 CoT 剪成碎片。Skill 体积走的是输入上下文,不是这次截断的原因。 - 修复:咨询与纠正组答改回
thinking: disabled。组答正文预算改为独立的 16384。计算仍一次完成,不按领域再开模型。服务器用已执行领域、required_blocks和must_use_layers生成thinking.section步骤树。finish_reason=length且已有正文时关 thinking 续写一次;两次仍空或不增长才answer_truncated,不扣点。咨询页按领域渲染可折叠「思考」和「分析」,不再把模型 CoT 当主思考通道。 - 验证:
frontend/tests/consultation-workflow-contract.test.ts锁定 thinking disabled 与 16384 正文预算;frontend/tests/consultation-agentic-runtime.test.ts锁定reasoning-delta不进公开流、length续写可完成、不增长则截断;frontend/tests/consultation-thinking-plan.test.ts锁定中文步骤树不含工具 id;frontend/tests/chat-stream-layout.test.ts锁定思考/分析 UI。 - 防复发:咨询组答不得再打开 provider thinking 来充当思考 UI。打开 thinking 必须同时拆开或加大正文预算,且不得把
reasoning-delta经逐 token 清洗后当作用户可见思考过程。finish_reason=length且半截正文必须续写,不得直接run.completed。步骤树只使用中文产品文案,禁止把run-jyotish-*、JSON 键或 Skill 正文送进思考 UI。 - 相关记录:BUG-277、BUG-305、BUG-340、BUG-345、BUG-346、BUG-347
- 复发自:BUG-305(thinking 与正文抢
max_tokens)、BUG-346(8192 预算不变就重新打开 thinking) - 修复版本:
0ca7da99
BUG-355 | staging web 镜像 next build 被 declared window 类型检查挡住
- 状态:resolved
- 首次发现:2026-08-22
- 最近更新:2026-08-22
- 影响面:
deploy/railway-web.Dockerfile的RUN npm run build、GET/PATCH /api/account、declared window 类型收口 - 用户现象:xiaoxin 不可用时本机按 publish 合同构建 web 镜像,
next build在 TypeScript 阶段失败,staging 无法换上已推送的90db4098。 - 触发条件:当前
staging头包含 BUG-353 的列缺失回退,且走 Dockernext build。push 上的 validate 跳过npm run build,因此测试绿、镜像红。 - 根因:列缺失回退把
declared_window_start/end写成undefined或省略,对不上 supabase 行类型。isBirthClockText不是类型谓词,收窄后仍是string | null | undefined。Boolean(focus) && focus.intent不能收窄focus。 - 修复:缺失列回退写成
null。isBirthClockText改为value is string。焦点判断改为focus && (...)后再使用focus.intent。 - 验证:
npx tsc --noEmit通过。tsx --test tests/declared-birth-window.test.ts tests/profile-persistence.test.ts14/14。 - 防复发:账户列缺失回退必须补齐 declared window 字段为
null,不得用undefined。staging push 的类型回归仍只在 publish Docker 暴露,本机/xiaoxin 镜像构建失败不得改 SHA 蒙混上线。 - 相关记录:BUG-309、BUG-353、BUG-354
- 复发自:BUG-309(validate 跳过
next build,publish 才暴露类型错误) - 修复版本:
707327ef
BUG-356 | 咨询思考与正文交错,完成步骤仍显示「还有 N 项」
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:咨询页
ConsultationThinkingReport、思考步骤树、回答 Markdown 列表、技法审计折叠 - 用户现象:思考与「分析」看起来像同一段;领域步骤树插在正文中间和文末;已完成步骤仍显示空心圆「还有 N 项」;行运/适合推进等并列项是挤在一起的段落,技法表与「本轮技法」重复。
- 触发条件:带
thinking.section的本命咨询回答,尤其模型未按领域 H2 切开、或步骤超过 4 项时。 - 根因:报告按每个 thinking section 交错渲染思考+切片正文。
visibleThinkingSteps把第 5 项起收成「还有 N 项」且固定空心圆。模型用「标签:解释」段落而不是列表。正文已贴审计表时仍再叠一层折叠表。 - 修复:一轮只保留一个可折叠「思考」和一个完整「分析」。结算后未写出的步骤标为完成并展开全部步骤。并列「标签:解释」提升为 Markdown 列表。正文已有完整审计表时不再重复折叠副本。组答要求并列项用 bullet list。
- 验证:
frontend/tests/consultation-thinking-plan.test.ts、frontend/tests/chat-definition-lists.test.ts、frontend/tests/chat-stream-layout.test.ts、frontend/tests/consultation-technique-audit.test.ts - 防复发:咨询思考不得按领域把正文切片后反复插入步骤树。完成态不得用空心圆「还有 N 项」代替未展示步骤。正文已含完整技法表时不得再渲染折叠副本。
- 相关记录:BUG-354
- 复发自:BUG-354(按领域交错思考/分析,步骤树截断)
- 修复版本:
995b752e
BUG-357 | 生时纠正思考与结论同一样式,过程自述进了正文
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:生时纠正对话、
runV9AgentTurn、ChatMessageRow、灰色思考折叠 - 用户现象:生时纠正里思考和模型结论是同一样式;用户可见气泡里先出现长段内部权衡(日期精度、batch 写入、skill 规则),最后才是确认口误和下一个追问,分不清哪段是思考、哪段是给用户的话。
- 触发条件:生时纠正已读取 Case 且工具成功后,模型把中文过程自述写进
text-delta。 - 根因:BUG-354 为保住正文预算关闭了纠正 provider thinking。
reasoning-delta不再出现,中文自述改走text-delta。契约门只在 Case 未加载或工具失败时把正文改送到thinking.delta;工具成功后全部当作回答。界面上思考折叠和正文也没有咨询页那种「思考 / 回复」分层。 - 修复:保持
thinking: disabled与 16384 正文预算。对已加载 Case 的text-delta按段落拆成过程自述与口语结论:自述进thinking.delta,结论进answer.delta并作为落盘正文。界面与咨询对齐:折叠「思考」(13px 次要色,有正文后默认收起)和完整「回复」。 - 验证:
frontend/tests/rectification-spoken-answer.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/chat-stream-layout.test.ts;tsc --noEmit通过。口语/思考拆分的emit类型必须与RunV9AgentTurnInput.emit同为Promise<void> | void,否则 Dockernext build会在 publish 阶段失败。 - 防复发:纠正组答不得在不拆开正文预算的情况下重开 provider thinking 来充当思考 UI。
answer.delta不得承载 skill/schema/batch 过程自述。思考与回复必须分层渲染,不得共用同一段正文样式。 - 相关记录:BUG-345、BUG-354、BUG-356、BUG-373
- 复发自:BUG-345(关 thinking 后自述改走正文)、BUG-354(纠正与咨询一并关闭 provider thinking)
- 修复版本:
f03a2706
BUG-358 | 生时纠正离开页面再回来,当轮回答被另一段开场覆盖
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:生时纠正会话恢复、
shouldStartOpening、Turn 水合、聊天组件 remount key - 用户现象:当轮已经看到 Agent 追问,跳到别的页面再回来后,原来的回答不见了;会话里多出一段新的助手正文(例如从教育追问变成感情追问)。
- 触发条件:生时纠正 Case 已有用户+助手同一物理 Turn;离开校正表面后再进入。常见路径是侧栏切走再切回,客户端仍留着创建时的
shouldStartOpening=true。 - 根因:聊天组件用最后一条 Turn id 做 React key,新回合会整棵重挂,
openingStarted被清掉。创建 Case 时的shouldStartOpening在开场成功后仍保持 true;切走再回来若不再走 open RPC,就会对已有账本再发一轮opening。模型读到已有证据后写出下一条追问,盖过用户刚看过的当轮正文。已落盘的过程自述也会原样回到气泡。 - 修复:有持久化 Turn 时禁止自动 opening;开场一旦发出就把
shouldStartOpening收成 false。水合 key 只在空历史/ready之间切换,不跟最后一条 id。回合成功后刷新 Case turns。恢复时把已落盘过程自述拆进折叠「思考」,正文只留口语结论。 - 验证:
frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-spoken-answer.test.ts - 防复发:已有 Turn 的校正表面不得因 remount 再发 opening。
shouldStartOpening只能表示「这个 Case 还没有第一轮」;开场发出后必须立刻关掉。水合 key 不得使用会随新回合变化的 Turn id。 - 相关记录:BUG-176、BUG-345、BUG-357
- 复发自:BUG-176(同一物理 Turn 的助手正文在恢复时丢失;后来又用最后一条 id 做 key,把 opening 状态一起冲掉)
- 修复版本:
f03a2706
BUG-359 | 生时纠正把截断的过程自述落成完成回复
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:生时纠正对话、普通咨询会话、
runV9AgentTurn、streamAgentResponse、Turn / 会话水合 - 用户现象:工具已写入带日期经历并完成评分后,折叠「思考」和「回复」出现同一段第三人称过程自述;回复在句中被截断,Turn 仍显示完成并扣点。普通咨询在 thinking 关闭时也会把过程写进正文。
- 触发条件:供应商 thinking 关闭后,模型把中文过程自述写进
text-delta并在写完口语结论前结束。 - 根因:BUG-354 为保住正文额度关闭了纠正和咨询的 provider thinking。
reasoning-delta不再出现,过程自述改走正文。BUG-357 再用正则从正文里切「思考 / 回复」,分类漏了「用户在上一轮 / 批量工具」,finalize 还把单段自述回灌成完成回复。 - 修复:纠正与咨询重新打开 provider thinking。
reasoning-delta进折叠「思考」,text-delta进「回复」。输出预算改为正文 16384 + 思考 8192,避免 CoT 抢正文额度。length续写仍关闭 thinking。正则只作正文漏检网和旧 Turn 水合,不再当主分流。没有口语结论时走empty_stream重试。 - 验证:
frontend/tests/rectification-spoken-answer.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/consultation-agentic-runtime.test.ts、frontend/tests/consultation-workflow-contract.test.ts、frontend/tests/agent-activity-progress.test.ts、frontend/tests/chat-stream-layout.test.ts - 防复发:纠正与咨询组答若打开 thinking,必须把思考预算加进
maxOutputTokens,且不得把reasoning-delta混进answer.delta。不得用正则当思考/回复主分类器。length续写必须关 thinking。纯过程自述不得run.completed。 - 相关记录:BUG-345、BUG-346、BUG-354、BUG-357、BUG-358
- 复发自:BUG-354(关 thinking 后过程进正文)、BUG-357(用正则从正文里切两栏)
- 修复版本:
04463e9a
BUG-360 | 生时纠正后段思考被写进第一段「思考」
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:生时纠正对话、
AgentActivityStatus、流式thinking.delta与tool.activity - 用户现象:界面先列出「读取校正记录」「正在整理多条事件证据…」,再给一整块「思考」。工具之后的思维链被追加进第一段思考,而不是出现在「正在整理…」下面。
- 触发条件:纠正回合先出现
thinking.delta,再tool.activity,之后继续thinking.delta。 - 根因:
AgentActivityStatus先渲染completedTrail和当前活动,再渲染整段thinkingText。流式处理把所有thinking.delta拼进同一个thinkingRaw,无法按事件顺序把后段思考放到工具行之后。 - 修复:纠正流按事件维护
activityTrace。工具开始会结束当前思考行;工具之后的thinking.delta开新的思考行。有轨迹时不再把拼接后的thinkingText单独渲在工具列表下面。 - 验证:
frontend/tests/agent-activity-trace.test.ts、frontend/tests/chat-stream-layout.test.ts、frontend/tests/consultation-run-timeline.test.ts - 防复发:纠正流不得把全部
thinking.delta拼成一块再画在活动列表下方。工具后的思考必须是新的 trace 行。咨询时间线同样不得把后段thinking.deltaupsert 回第一条思考。 - 相关记录:BUG-095、BUG-357、BUG-359
- 复发自:BUG-095(步骤轨迹只证明工具发生过,没有与思维链按时间交错)
- 修复版本:
6b225a28
BUG-361 | 生时纠正职业无日期证据卡死,反推前事问不出来,时间卡也不出
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-23
- 影响面:
record_agentic_rectification_evidence_batch、method_followup_plan、isOfferBlockingFollowup、choice_card、discriminating_event_probes、生时纠正系统提示 - 用户现象:方法层已经问到职业,用户说了职业类型但没有日期。候选分钟在窗口里来回跳,Agent 反复问职业,从不按冲突年份做 A/B 反推,也不出示时间选择卡。
choice_card一直是 null。 - 触发条件:经典八法已覆盖带日期教育/感情/事业/家人;职业是无日期
occupation_note;引擎propose_allowed已开但分钟仍不可分;receipt 里有 dasha 年界/激活探针,同时教育质量探针占满 3 个槽。 - 根因:三层叠加。(1) 批量写入把
date_precision=unknown/ 缺occurred_from一律写成draft+needs_clarification=["date"],覆盖要求confirmed,职业层永远 uncovered,offer-candidates被拒。(2)remainingConflictProbes/remainingReverseVerifyProbes按已确认领域整段跳过,Python 质量探针又用已回答过的高考发挥题占满MAX_PROBES=3,反推年份进不了next_followup。(3) 引擎在 31 分钟窗、同分、宽度 7、holdout 1/20 时本来就不能唯一分钟;产品却继续访谈,而不是在方法覆盖齐且propose_allowed时出示代表性时间。 - 修复:无日期
occupation_note写入即确认,并回填已有 draft;路由把 draft/pending 职业笔记也算覆盖,忽略已经覆盖领域上的过期 collect focus。方法覆盖未齐时仍按 BUG-351 让 dasha 探针插队并挡出牌;挡住出牌的方法层齐了且propose_allowed时session_outcome=adopt_representative,event_probe不再挡出牌。采用后按「领域+年份」而不是整段领域跳过反推探针,并跳过已编码的考试质量题。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts锁定 draft 职业笔记覆盖并放行出牌、过期职业 focus 不再追问、覆盖齐后event_probe不挡出牌、采用后核未出现过的事业年份;frontend/tests/rectification-choice-card.test.ts锁定可出牌时 GET A/B 卡为 null、采用后核对卡仍显示;frontend/tests/rectification-ingest-p0.test.ts锁定 dateless SQL;tests/test_rectification_event_probes.py锁定已编码高考质量题不再占满探针槽。 - 防复发:职业笔记不得因缺日期停在 draft。事业带日期事件仍不能代替职业层。方法覆盖未齐时 dasha 探针必须挡住出牌。覆盖已齐且
propose_allowed时不得为筛到唯一分钟继续访谈。不得把已回答的考试质量题再问一遍。不得改已哈希 Skill10.0.11来绕过服务器路由。 - 相关记录:BUG-348、BUG-349、BUG-350、BUG-351
- 复发自:BUG-351(冲突探针插队挡出牌后,质量探针占槽、职业 draft 永不覆盖,覆盖已齐时仍不收口)
- 修复版本:
75dbab08
BUG-362 | 生时纠正持续提问但不更新候选后验,无法收敛
- 状态:resolved
- 首次发现:2026-08-23
- 最近更新:2026-08-24
- 影响面:
rectification-agentic/core、method_followup_plan、discriminating_event_probes、rectification-v9-tools、decision receiptinference_state - 用户现象:纠正会话一直在收集经历并提问,但候选时间不固定、不淘汰,轮次增加后仍给不出可信区间或代表性时间。覆盖齐后低信息探针不再挡出牌,高信息未答探针仍可能挡出牌。
- 触发条件:出生时间范围为若干分钟;已有带日期事件;引擎给出多枚候选;Agent 继续用自然语言追问。
- 根因:收集、提问、打分和收口都挤在对话 Agent 里。没有版本化候选集、事件×候选评分账本、由候选差异算出的探针,也没有统一收敛评估。Prompt 要求“提出冲突问题”并不更新后验;职业/领域覆盖被误当成收敛。
- 修复:新增服务端推断状态机:固定
candidate_set_id、事件分层 holdout、等价分钟聚类、纯函数applyProbeOutcome、按信息增益选探针、统一evaluateConvergence。分数只由 reducer 更新。Python 探针带information_gain/expected_outcomes;followup 只改写程序给出的探针。decision receipt 持久化inference_state。点选 C/D/B 且没有新证据时,rectification-resolve-focus当场把后验写入追加的推断 transition,读路径 overlay 到引擎回执,不再等下一次带日期事件重算。高信息未答探针在覆盖齐后仍可挡出牌;无information_gain的旧探针保持 BUG-361 不挡出牌。无法区分时返回可信区间,最大轮次不是converged。 - 验证:
frontend/tests/rectification-inference-machine.test.ts锁定有效回答降低熵、淘汰不可复活、重复切分禁用、最大轮次≠成功、等价分钟返回区间、holdout 不参与训练、赢家需两轮稳定、C 无新证据立刻更新后验、D 只记录已问切分、盘外核对与收集拒答不写探针;frontend/tests/rectification-v10-conversation-focus.test.ts锁定 resolve-focus C 走append_agentic_rectification_inference_transition而不是候选缓存或原地 patch;frontend/tests/rectification-eight-method.test.ts锁定同领域不同年份仍问冲突探针、高信息探针覆盖后仍挡出牌、无增益探针覆盖后不挡出牌。 - 防复发:禁止 Agent 直接改候选分数或宣布收敛。禁止把方法覆盖或职业笔记当成
result_status=converged。同一semantic_key/candidate_split_hash不得连问。淘汰候选不得在同一candidate_set_id复活。最大轮次只能是max_rounds_reached。点选 C/D/B 且没有新证据时必须走append_agentic_rectification_inference_transition追加不可变 transition,禁止jsonb_set已落库的引擎decision_receipt,禁止走候选 persist RPC 的证据指纹缓存来写后验。读路径必须 overlay 最新 transition。盘外核对与收集阶段拒答不得写探针。下一轮 followup 必须读已答切分,避免把同一探针再问一遍。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-361、BUG-351、BUG-363
- 复发自:BUG-361(覆盖门槛和出牌行为修了,但仍没有候选后验更新闭环)
- 修复版本:
29750d38
BUG-363 | 生时纠正推断后验原地改 receipt,persist-v2 只认 evidence fingerprint
- 状态:resolved
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:
agentic_rectification_inference_transitions、append_agentic_rectification_inference_transition、compose_agentic_rectification_decision_receipt、persist_agentic_rectification_candidate_v2、get_agentic_rectification_case_dossier、rectification-resolve-focus - 用户现象:C/D 回答可以改后验,但数据库里只剩更新后的分数,无法回放哪次回答改变了它;证据没变时 persist-v2 仍可能把旧 receipt 当成当前世界。
- 触发条件:点选 C/D 且没有新增带日期证据;随后读 Case、命中 persist-v2 缓存、刷新或重试同一回答。
- 根因:BUG-362 热修复用
patch_agentic_rectification_inference_state对最新decision_receipt.inference_state做jsonb_set。执行回执被原地修改。缓存键仍是 evidence/range/policy fingerprint,不含推断修订。 - 修复:追加不可变
agentic_rectification_inference_transitions。C/D 走append_agentic_rectification_inference_transition(乐观锁expected_revision、stale_probe、幂等键)。引擎结果行不再被 patch。persist-v2 HIT/MISS 与 dossier/get_case 用compose_agentic_rectification_decision_receiptoverlay 与当前result_id匹配的最新 transition。decision_state_fingerprint存在 transition 上,不进入 persist-v2 等值匹配。patch_agentic_rectification_inference_state改为inference_patch_retired。不改已哈希 Skill10.0.11。P0 inference ledger implemented, verification pending。 - 验证:
frontend/tests/rectification-inference-machine.test.ts锁定 C 立刻改后验且revision+1、evidence fingerprint 不变时 decision-state fingerprint 与 posterior 变、重复 D 幂等、C→D 新 revision 且旧 transition 仍在、旧 probe 为stale_probe、过期 revision 为revision_conflict、reducer replay 等于当前 posterior、候选集变化的 persist-v2 MISS 仍 replay 已答探针、append SQL 不 UPDATE 引擎行;frontend/tests/rectification-v10-conversation-focus.test.ts锁定 resolve-focus C 走 append RPC;frontend/tests/rectification-v9-agent.test.ts锁定 persist-v2 查找键不含p_decision_state_fingerprint,HIT 返回 overlay 后的inference_state;frontend/tests/rectification-v9-case-service.test.ts锁定 409stale_probe/revision_conflict/inference_patch_retired。P0 inference ledger implemented, verification pending。 - 防复发:禁止
jsonb_set已落库引擎decision_receipt来写后验。禁止把 inference revision 加进 persist-v2 等值匹配。有效 C/D 必须追加 transition。读inference_state必须 overlay 最新且result_id匹配的 transition。重复提交必须幂等。过期probe_id必须 409,不得改当前 posterior。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-362
- 复发自:BUG-362(C/D 写入后验修了,但用原地 patch 绕过缓存,receipt 不可审计)
- 修复版本:待发布
BUG-364 | staging quality gate 在 Mac Docker runner 上 npm ci 因 bind mount 被拒
- 状态:investigating
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:Gitea
backend-quality-gate.yml的validate「Install dependencies」步骤、publish(因needs: validate被 skip)、staging 发布 - 用户现象:向
staging推送后质量门失败;测试步骤未执行。publishskipped。 - 触发条件:job 被调度到
jesse-mac-xiaoxin(Docker Desktop 里的ubuntu:24.04act_runner,labelxiaoxin:host,使用宿主机docker.sock)。Linux 宿主 runnerxiaoxin未接走该 job。已在 run2051(29750d38)和2052(909de8b8)复现。 - 根因:pip 安装成功。随后
docker run --volume "$workdir:$workdir"跑npm ci时,workdir 是 runner 容器内的/root/.cache/act/<id>/hostexecutor。该路径不在 Docker Desktop 的 Mac 文件共享里,daemon 返回mounts denied,步骤以 exit 125 失败。不是 ledger 测试或 Python 依赖错误。最近一次完整成功是 run2038,跑在 Linuxxiaoxin上。 - 修复:未修复。候选路径是让 Linux
xiaoxin重新在线并接runs-on: xiaoxin的 job,或不要用套了宿主机 docker.sock 的 Mac 容器跑这个 workflow。不要靠在 Docker Desktop 里共享 Linux 容器内部路径。 - 验证:Gitea job
5135日志含Successfully installed全套 pip 包,随后docker: Error response from daemon: mounts denied与frontend npm ci failed with status 125。job5133(run2051)同一错误。尚未在 Linuxxiaoxin上重跑909de8b8。 - 防复发:
runs-on: xiaoxin不得由无法给 job workdir 做 bind mount 的嵌套 Docker runner 抢占。质量门失败不得在未读 Install dependencies 日志时当成业务测试失败。 - 相关记录:BUG-136、BUG-149、BUG-266、BUG-363、BUG-365
- 复发自:无
- 修复版本:未修复
BUG-380 | 覆盖完成后仍用整窗 D9/D24 追问已入账日期
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
session_outcome、candidate_contrast_packet、method_followup_plan、record_agentic_rectification_evidence_batch、生时纠正系统提示 - 用户现象:方法覆盖已齐、引擎探针已空、候选仍是并列区间时,访谈继续问整窗学业/感情盘,并再次追问已入账的入学或分手日期。同一日感情结束用不同原句会写入第二行。
- 触发条件:挡住出牌的方法层已齐;剩余候选分钟共享 D9;学业质量与职业备注已入账;引擎
discriminating_event_probes为空;用户用另一句话重复已记日期。 - 根因:(1) 收集计划把整窗
varga_observation当成区分探针,discriminatorProbe: null被??回退成该追问,会话无法进入provisional_range。(2) 剩余分钟拆分仍问已编码的高考发挥与职业类型,且 D4 不在剩余层顺序里。(3) 证据批处理只按 quote 做幂等。 - 修复:区分探针只来自引擎探针和当前候选分钟的剩余分盘;已编码的学业质量/职业备注跳过 D24/D10;加入 D4 剩余拆分。没有剩余拆分时
next_followup为空并落实offer_provisional_range。同 case、同 kind、同发生日的确认行按日期去重。不改 Skill10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-decide-next-action.test.ts、frontend/tests/rectification-inference-machine.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-v9-migration.test.ts。 - 防复发:覆盖完成后不得把整窗 D9/D10/D24 观察当成区分探针。已入账的入学、分手日期和考试质量不得再问。没有剩余分钟拆分时必须给并列区间,不得继续访谈。不得改已哈希 Skill
10.0.11。 - 相关记录:BUG-192、BUG-351、BUG-366、BUG-375、BUG-379
- 复发自:BUG-379(邻近年学业存在性探针已跳过,但仍用整窗观察和剩余 D24 题干继续问入学/高考)
- 修复版本:待发布
BUG-381 | 生时纠正进度条与点选卡重叠、分盘名消失
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:生时纠正聊天气泡、阶段进度、点选卡、「回到最新」
- 用户现象:有工具进度的回复看起来比上一句更往里缩;阶段和正文之间空一大截;进度条只剩「读取校正记录 / 整理证据」,看不到 D 盘对照;点选卡最下面的 D 和「先这样」被「回到最新」挡住。
- 触发条件:本轮有公开工具进度;证据写入触发了重算但比较工具没单独出场;用户略微离开底部看点选卡。
- 根因:(1) 有进度又有正文时套了咨询页
consultation-thinking-report,格子间距 24px,进度条自己还有下边距。(2) 已落盘回合不回放工具进度,最新一轮有勾选列、上一轮没有,看起来像缩进。(3) 证据批处理把executed_methods放在rescore里,公开活动只读顶层字段,技法句又写在口语后面。(4) 「回到最新」贴在输入框上方居中,离开底部 96px 就出现,正好盖住点选卡下沿。 - 修复:进度和正文改用 8px 紧凑叠放。已落盘回执回放工具步骤。证据重算的分盘名并进进度条,不写进口语。点选卡仍在视口下沿时不显示「回到最新」,按钮改到末行外侧。不改 Skill
10.0.11。 - 验证:
frontend/tests/chat-stream-layout.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/agent-activity-progress.test.ts。 - 防复发:有进度的纠正气泡不得用咨询页 24px 报告间距。技法句属于进度条,不得再进
answer.delta。点选卡仍可见时不得用居中浮层挡住 D / 「先这样」。 - 相关记录:BUG-373、BUG-376、BUG-377
- 复发自:BUG-376(关掉思考正文后,进度条只剩工具名,技法句不再出现;回到最新仍按咨询页浮层)
- 修复版本:待发布
BUG-382 | 点选题与剩余切开不是同一题型,选题不是按剩余最大切开
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
discriminating_event_probes、candidate_contrast_packet、choice_card、method_followup_plan、window_scan transitions - 用户现象:生时纠正点选卡一律问「某年前后有没有这类事」。口语在问相处或职责时,卡仍问存在性。剩余分钟其实还能被家人盘切开时,下一问仍按写死的学业/事业/感情/搬家顺序出。
- 触发条件:覆盖已齐、候选仍并列;剩余 D9/D10 换升,或 D12/D7 切开而 D9 已齐;或大运年界与剩余切开同时存在。
- 根因:(1) 选择题走
eventHypothesis存在性模板,D10 内部职责三分仍显示成有没有这件事。(2) Python 探针按整窗*_candidates_differ和精度阶段优先选题,每个领域取第一个能分开的年。(3) TS 剩余层写死D24→D5→D10→D9→D4,并用假 IG 让学业压过事业/感情。 - 修复:计分领域按当前剩余分钟仍切开的层进池,财务/健康仍要用户先提过。Python 对剩余分钟打分,按分组熵加置信度取最高年。TS 按分组熵选题。年界差问存在性;D9/D10 换升问类型表相处/职责;学业问考试质量。两路 style 的「都不像」计
unsure,不把另一组分钟当「没有这件事」。不改 Skill10.0.11。 - 验证:
tests/test_rectification_event_probes.py、tests/test_rectification_refinement_packet.py、frontend/tests/rectification-candidate-contrast-packet.test.ts、frontend/tests/rectification-choice-card.test.ts、frontend/tests/rectification-inference-machine.test.ts。 - 防复发:剩余分钟选题不得写死学业优先。点选卡题型必须跟
choice_kind与expected_outcomes一致。两路 style 的 C 不得淘汰另一组分钟。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-375、BUG-379、BUG-380
- 复发自:BUG-375(区分卡已落到剩余分钟,但题型仍是存在性,选题顺序仍写死)
- 修复版本:待发布
BUG-383 | 点选卡开关在 render 里写 ref,staging ESLint 失败
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:Gitea
backend-quality-gate的npm run lint --prefix frontend、frontend/src/components/rectification-agentic-chat.tsx - 用户现象:向
staging推送f5e73ef3后 quality gate run2073失败。validatejob5170前端测试 2042/2042 通过,随后 lint✖ 17 problems (1 error, 16 warnings)。publishjob5171因needs: validate跳过。 - 触发条件:含 BUG-381「回到最新」避让点选卡的提交进入完整
xiaoxinrunner 的 frontend lint。 - 根因:
react-hooks/refs禁止在 render 期间更新ref.current。BUG-381 用choiceCardsOpen.current = showChoiceCards给滚动回调最新点选卡开关,写在 render 里。父提交7a4360d8的 run2072已因同一行失败。 - 修复:把 ref 写入现有
useLayoutEffect,再调用updateFollowState。不改 Skill10.0.11。 - 验证:
npx eslint src/components/rectification-agentic-chat.tsx;frontend/tests/rectification-agentic-entry.test.ts。 - 防复发:点选卡可见状态不得在 render 里写
ref.current;源码合同锁定该赋值只出现在 layout effect。 - 相关记录:BUG-326、BUG-381
- 复发自:BUG-326(窄屏盘面
onCloseRef.current = onClose同样在 render 里写 ref) - 修复版本:待发布
BUG-384 | 记下事件后口语与点选卡不是同一题
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
rectification-record-evidence-batch、projectTurnDecision、persistServerOwnedFocus、bindSpokenToOpenQuestion、生时纠正运行器 - 用户现象:刚记下大学毕业后,气泡问「2016 年前后入学考试有没有发挥失常」,点选卡却是「2023 年前后有没有入职或职责加重」。题干和选项是服务器模板,没有跟口语对齐。
- 触发条件:账本已有带日期学业事件;引擎发出 dasha 冲突探针并盖戳点选卡;本轮只调了
record-evidence-batch,没有compare-candidates。 - 根因:(1) 证据写入后的自动重算会
persistServerOwnedFocus,但 batch 工具结果没有把open_question.prompt回给模型。(2)turn_decision.current_question只有 id,没有题干。模型按上一轮学业对话另起了考试质量问,界面 GET 却展示已盖戳的事业卡。(3) 选题必须用刚算完的 decision receipt;若只信 persist RPC 回包,探针可能被丢掉。(4) 用系统提示逐条禁止高考发挥/入学年份堵不住用户回答的多种写法。点选卡仍由服务器出,保证点选直接改后验。 - 修复:batch / confirm 重算后返回
open_question.prompt;选题用刚写入的 receipt。有已盖戳题干时,运行器丢掉口语里的其它问句并接到这句题干,不在提示词里枚举禁止主题。不改 Skill10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-v9-stream.test.ts。 - 防复发:有点选卡时口语由运行器接到
open_question.prompt/current_question.prompt,不得靠提示词枚举禁止追问。证据写入后的工具结果必须带回已持久化题干。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-375、BUG-379、BUG-382
- 复发自:BUG-375(点选卡已由服务器盖戳,但模型仍按探针列表另写一问)
- 修复版本:待发布
BUG-385 | 「回到最新」贴在右下角,没有水平居中
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:生时纠正「回到最新」
- 用户现象:向上翻看历史时,「回到最新」出现在输入框右上角,而不是聊天区水平居中。
- 触发条件:对话不在底部;BUG-381 之后的布局。
- 根因:BUG-381 为了不挡住点选卡的 D / 「先这样」,把按钮改成
justify-content: flex-end。点选卡仍在视口下沿时本来就会隐藏该按钮,不必靠右。 - 修复:按钮改回水平居中。点选卡仍占 overlay 带时继续隐藏。不改 Skill
10.0.11。 - 验证:
frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-answer-choice.test.ts。 - 防复发:「回到最新」必须水平居中;不得为了避让点选卡改到右下角,避让靠隐藏阈值。
- 相关记录:BUG-381
- 复发自:BUG-381(避让点选卡时把居中改成了靠右)
- 修复版本:待发布
BUG-386 | 采用门未齐就按冲突时间出反推前事点选卡
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
method_followup_plan、meetsAcceptanceEventQuality、生时纠正系统提示 - 用户现象:刚记下学业等一两件带日期经历后,点选卡就按 dasha 冲突年反问另一领域「某年前后有没有入职」。方法层感情/事业还没问完,采用门也还没到 3 件可评分事件、2 个领域。
- 触发条件:账本已有至少一件可评分事件,但不足 3 件或不足 2 个领域;decision receipt 含
dasha_activation/dasha_boundary探针。 - 根因:BUG-351 在第一件带日期事件之后就让冲突探针插队并盖戳点选卡。引擎采用门仍要 3 件/2 领域,访谈却提前进入反推前事。
- 修复:冲突探针插队改到与采用门相同的事件质量门槛:至少 3 件主评分事件、至少 2 个领域。
occupation_note与外貌/疤痕/占问不计。未齐时继续感情→事业→家人→职业自然语言收集,不盖冲突卡。齐了之后仍按 BUG-351 插队并挡出牌。不改 Skill10.0.11,不打开confirmation_allowed。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-agentic-entry.test.ts。 - 防复发:不得在采用门事件/领域未齐时把 dasha 冲突探针写成
source=event_probe或挂choice_frame。不得把occupation_note算进 3 件。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-351、BUG-361、BUG-379
- 复发自:BUG-351(一件带日期事件后冲突探针插队,早于采用门)
- 修复版本:待发布
BUG-387 | staging publish 的 next build 被口语绑定 chunk 类型挡住
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
openQuestionPromptFromToolResult、runV9AgentTurn、deploy/railway-web.Dockerfile的RUN npm run build - 用户现象:向
staging推送97b02aef后 quality gate run2080的 validate 通过,publish 在 web 镜像next build失败。没有 dispatchDeploy staging。公网仍为上一成功 SHA。 - 触发条件:staging push 的 publish 镜像构建跑
next build。validate 对 staging push 跳过 production build。 - 根因:运行器把流式 chunk 传给
openQuestionPromptFromToolResult,函数把payload收成{ result?: unknown; output?: unknown }。流式 chunk 的 payload 是toolName/text/args,TypeScript 判为 TS2345。日志末尾 Import traces 只是动态文件访问警告。 - 修复:函数改收
payload?: unknown,再从 result/output/object 读已盖戳题干。不改 Skill10.0.11。 - 验证:
npx tsc --noEmit不得再报agent-run.ts的 TS2345;frontend/tests/rectification-v9-stream.test.ts。 - 防复发:改
runV9AgentTurn流式 chunk 形状后必须跑npx tsc --noEmit或等价的next build。staging push 的类型回归仍只在 publish Docker 暴露。 - 相关记录:BUG-309、BUG-365、BUG-371、BUG-384
- 复发自:BUG-309(validate 跳过
next build,publish 才暴露类型错误);BUG-384 口语绑定收窄了 payload - 修复版本:待发布
BUG-388 | 证据已写入并重算后整轮仍被 105 秒 attempt 超时打成 run_timeout
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
runV9AgentTurnattempt 超时、record-evidence-batch自动重算、compare-candidates、公开run.failed、已盖戳open_question - 用户现象:自由文本回答当前追问后,界面已显示读取 Case、记下证据、比较候选(两次工具完成事件都带同一套已执行方法),随后
run.failedcode=run_timeout,文案「服务端运行超时,状态已记录。」没有口语气泡,也没有点选卡。 - 触发条件:
POST /api/rectification/agentaction=message;本轮record-evidence-batch已accepted并自动重算;模型接着调用compare-candidates,再以相同参数第二次read-case;attempt 墙钟超过 105 秒。本轮没有已盖戳点选卡可点。 - 根因:(1) 运行器 attempt 超时是 105 秒,路由
maxDuration120 秒;Python 评分单次上限 60 秒。scoreAndPersistCurrentEvidence每次都先打引擎,指纹缓存只发生在随后的 persist RPC,因此 batch 自动重算之后的 compare 会再跑一轮完整评分。(2) 第二次read-case只有completed没有started,符合相同参数重复 tool-call 被跳过 started 回执。(3) 超时走failedAttempt,发生在把已盖戳题干接到口语之前,所以工具已提交的状态不会变成可见回复。 - 修复:attempt 超时提到 210 秒,agent / regenerate 路由
maxDuration提到 240 秒。证据指纹和候选窗指纹都未变时 compare 直接复用已落盘快照,不再打 Python。超时且本轮已盖戳open_question时仍落该题干并完成本轮,而不是空失败。不改 Skill10.0.11,不打开confirmation_allowed。 - 验证:
frontend/tests/rectification-v9-status-security.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-answer-choice.test.ts。 - 防复发:compare 在相同证据/范围指纹上不得再调用
runV9CandidateScore。attempt 超时必须小于路由maxDuration。超时后若工具已盖戳题干,不得把整轮打成空run_timeout。不得把 Cookie、JWT、案例 ID 或用户原文写入本记录。 - 相关记录:BUG-078、BUG-368、BUG-384、BUG-389
- 复发自:BUG-078(评分已完成后叙事超时把整轮打成失败)
- 修复版本:待发布
BUG-389 | 已记下学业年后发挥质量追问没有点选卡
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-09-10
- 影响面:
method_followup_plan、persistServerOwnedFocus、known_event_quality、askedKeysForSameYearDedup - 用户现象:记下带年份的学业经历后,口语会问那次考试有没有发挥失常,界面却没有 A/B/C/D 点选卡,只能打字。dasha 冲突反推卡仍按采用门等待,不在此列。
- 触发条件:账本已有该年学业等可评分事件;decision receipt 含
known_event_quality;采用门 3 件/2 领域未齐,下一方法层仍是感情收集。 - 根因:
remainingReverseVerifyProbes跳过known_event_quality。采用前只有 dasha 冲突探针能变成event_probe点选卡,且还要等 3 件/2 领域。发挥质量探针留在 receipt 里,模型用自然语言问,服务器不盖choice_frame。即便盖了,information_gain为 0 也会被shouldSkipDiscriminatorFollowup丢掉。 - 修复:已记下对应年份后,
known_event_quality在方法轮换之前出event_quality点选卡,不要求采用门。摘要已编码发挥质量则不再出卡。零信息增益不再挡住发挥质量卡。dasha 冲突探针仍等 3 件/2 领域。不改 Skill10.0.11。2026-09-10:账本派生的domain.year键不得再把发挥质量题判成same_year_asked(见 BUG-637)。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-server-focus.test.ts、tests/test_rectification_event_probes.py;2026-09-10 补rectification-probe-year-dedupe-20260906.test.ts、rectification-engine-convergence.test.ts。 - 防复发:已记下年份的发挥质量探针必须挂
choice_frame并持久化。不得把 dasha 冲突探针的采用门门槛套到发挥质量卡上。不得把confirmation_allowed改成 true。账本domain.year键只拦存在性探针。 - 相关记录:BUG-348、BUG-384、BUG-386、BUG-388、BUG-592、BUG-637
- 复发自:BUG-384(口语问发挥质量,点选卡却被冲突探针占住;采用门修好后变成完全没有卡);2026-09-10 再次复发自 BUG-592(见 BUG-637)
- 修复版本:待发布
BUG-390 | 事业经历未过采用门就出高考发挥点选卡
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
method_followup_plan、event_probes._quality_probes、choice-cardevent_quality、GETchoice_card - 用户现象:记下同年实习入职和离职后,点选卡直接问「那年前后高考或重要考试有没有发挥明显失常」。采用门仍是 2 件/1 领域,方法层感情还没问。
- 触发条件:账本只有事业可评分事件,不足 3 件或不足 2 个领域;引擎把该年事业写成
known_event_quality。 - 根因:BUG-389 让
known_event_quality在采用门前插队并盖戳。Python_quality_probes对每个已记领域都发质量探针,事业的quality_family与存在性相同。choice-card又把所有event_quality写成高考发挥题,事业探针被盖成考试卡。 - 修复:质量探针只对学业(
QUALITY_HINTS)发出。Followup 只让学业质量卡在采用门前插队;事业质量探针继续走方法轮换。点选题干不再套高考句,也不再回退「有没有明显…」存在性模板;服务器只锁period · family。dasha 冲突探针仍等 3 件/2 领域。不改 Skill10.0.11,不打开confirmation_allowed。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-choice-card.test.ts、tests/test_rectification_event_probes.py。 - 防复发:不得把非学业
known_event_quality写成采用门前点选卡。不得把高考发挥题干套到事业/感情探针。不得把存在性「有没有明显…」句当成用户可见题干。不得把occupation_note算进 3 件。 - 相关记录:BUG-351、BUG-386、BUG-389
- 复发自:BUG-389(学业发挥质量卡免检插队后,事业年也被当成发挥质量并套用高考题干)
- 修复版本:待发布
BUG-391 | 生时纠正点选题干走模板,Agent 自己写的问句被丢掉
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
bindSpokenToOpenQuestion、agentic-rectification系统提示、choice-cardevent_quality、GETchoice_card - 用户现象:记下事业经历后,界面和口语都问「那年前后高考或重要考试有没有发挥失常」。Agent 即使按探针写了入职问句,运行器也会丢掉,改贴服务器模板。
- 触发条件:工具返回了
open_question.prompt;模型正文里另有一句问句。 - 根因:BUG-384 为了对齐点选卡,把已盖戳题干当成唯一追问,
bindSpokenToOpenQuestion删除所有带问号的模型句。event_quality又把所有发挥质量卡写成高考题。系统提示要求「不要另写追问」。 - 修复:年份和事件家族仍由探针锁定,A/B/C/D 仍由服务器出。Agent 自己写追问;问对年份且未改领域时保留原句,并盖到点选卡题干。服务器只持久化
period · family锁,不代写「有没有明显…」题干;问错时只留应答,不把锁贴进口语。发挥质量选项标签仅学业用失常档。不改 Skill10.0.11,不打开confirmation_allowed。 - 验证:
frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-choice-card.test.ts、frontend/tests/rectification-eight-method.test.ts。 - 防复发:不得再要求模型只复读已持久化题干。不得把高考发挥句套到非学业探针。不得把存在性模板句当成用户可见题干。问错领域时不得把锁贴进口语。
- 相关记录:BUG-348、BUG-384、BUG-390
- 复发自:BUG-384(点选卡与口语句不一致时,改成强制模板,连对得上的 Agent 问句也丢掉)
- 修复版本:待发布
BUG-392 | 生时纠正把存在性模板句当成用户题干
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
choice-cardeventHypothesis、bindSpokenToOpenQuestion、persistServerOwnedFocus、GETchoice_card - 用户现象:点选卡和口语仍是「那年前后,有没有明显入职…」或高考发挥句。用户要求 Agent 自己出题,服务器只锁年份和事件家族。
- 触发条件:工具盖戳
open_question.prompt后,GET 或运行器把已落盘 copy 当作用户可见题干。 - 根因:
eventHypothesis把锁写成${period},有没有明显${family}?。persistServerOwnedFocus在 Agent 开口前盖这句。bindSpokenToOpenQuestion在模型没问或问错时把这句贴进口语。 - 修复:事件卡只持久化
period · family。Agent 写追问;问对则口语和点选卡都用该句。问错或超时只留应答/记下了。,不把锁当问句。旧库里带问号的题干仍可作最后兜底。不改 Skill10.0.11,不打开confirmation_allowed。 - 验证:
frontend/tests/rectification-choice-card.test.ts、frontend/tests/rectification-v9-stream.test.ts。 - 防复发:不得把「有没有明显…」或高考发挥句写成
serverOwnedChoiceCopy.prompt。不得把无问号的锁贴进answerText。 - 相关记录:BUG-348、BUG-384、BUG-391
- 复发自:BUG-391(保留 Agent 问句后,仍用存在性模板句当服务器兜底题干)
- 修复版本:待发布
BUG-393 | 生时纠正把点选卡当成纠正完成,未按候选差异驱动
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
discriminating_event_probes、candidate_contrast、method_followup_plan、decision receipt、inference transitions、Skill 10.0.11 运行时合同 - 用户现象:纠正流程以“显示一张选择题卡”为完成标准。质量探针混进区分题,信息增益为 0、候选映射为空。一条事件后只保留三个相邻分钟,Agent 自行发明年份或候选映射。
- 触发条件:账本可评分事件不足 3 件或不足 2 个领域;或引擎对完整出生窗做特征签名聚类之前就截成 top-3 相邻分钟。
- 根因:(1)
known_event_quality与age_band混入discriminating_event_probes且role=distinguish。(2)build_candidate_decisions用ranked[:3]。(3) 前端discriminatorFromFollowup伪造left/right映射。(4) inference 轮次没有一等字段保存熵与淘汰名单,零信息回答仍可被当成有效区分。 - 修复:探针拆成 evidence_collection / event_clarification / candidate_discriminator / holdout_validation。质量探针只进 clarification。区分探针必须
candidateIds>=2、expectedOutcomes>=2、informationGain>0,hash 含真实分组和candidateSetVersion。门未开只收集缺失领域。完整出生窗按 D9/D10/D24/D4/D12 签名聚类。引擎输出CandidateContrastOpportunity,Agent 只改写。用户回答写入 inference round(posterior/delta/entropy/eliminated);不改变候选则low_information。CI 禁止空映射或非正增益的 distinguish 探针。不改 Skill10.0.11。 - 验证:
tests/test_candidate_discriminator_contract.py(已列入CORE_PYTEST_TARGETS)、tests/test_rectification_event_probes.py、frontend/tests/rectification-distinguish-contract.test.ts。 - 防复发:不得把
known_event_quality放进discriminating_event_probes。不得在一条事件后只留三个相邻分钟。不得让 Agent 发明年份、事件事实或候选映射。role=distinguish且informationGain<=0或候选/结果为空时 CI 必须失败。 - 相关记录:BUG-375、BUG-379、BUG-386、BUG-389、BUG-390、BUG-391、BUG-392
- 复发自:BUG-386(采用门前发出区分题);BUG-375(slice-3 / 相邻分钟)
- 修复版本:待发布
BUG-394 | 选择题身份、决策器、holdout 与区间收口未闭合
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
answer_choice、ConversationFocus、RectificationDecision、credible range、per-case holdout、公开流、聊天滚动、生时纠正完成路径 - 用户现象:刚返回的点选卡立刻
stale_question;A/B/C/D 被模型重新理解;覆盖完成被当成可确认唯一分钟;可信范围只覆盖第一名;收集阶段不留 holdout;普通用户卡在 exact-minute 门上无法结束;流式输出抢走滚动并泄漏思考过程。 - 触发条件:GET 卡片用派生
questionId当库身份;precision_stage/selection_allowed在 decision 之外独立判断;credibleRange取 rank=1 簇;holdout 要到 4 条才预留;确认门 fail-closed 后没有completed_with_range。 - 根因:选择题把派生
question_id当成 focus 主键。业务状态有多处 reducer。评分和 Probe 吃全部已收事件。无法区分唯一分钟时仍按 exact-minute 拦完成。 - 修复:GET/POST 统一
focusIdUUID +caseRevision;stale_question只表示 focus 非 active。单一decideRectification计算 phase / nextAction / canOfferRange / canAdopt / canConfirmExactMinute。credibleRange并集仍有效并列簇。收集满 2 条带日期事件即预留 holdout,不参与初筛、评分和 Probe。收敛后必须holdout_validation,失败回到区分。无法唯一分钟时允许completed_with_range。点选同一事务写 receipt / 后验 / focus / inference / revision / NextAction,actionId幂等。Narrator 失败不回滚状态。公开流禁止 reasoning / thinking / 工具步骤正文。近底部才跟随滚动,上滑后显示「回到最新」。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-hidden-e2e.test.ts、frontend/tests/rectification-decide-next-action.test.ts、frontend/tests/rectification-inference-machine.test.ts、tests/test_candidate_discriminator_contract.py。 - 防复发:不得用派生
questionId当数据库身份。刚返回且 Case 未更新的卡片不得stale_question。不得在 decision 之外独立判断precision_stage/selection_allowed/active_focus。不得只用 rank=1 生成credibleRange。holdout 不得进入评分或区分 Probe。不得因 exact-minute 未过就让普通用户无法完成。确定性点选错误不得attempt.reset。点选验收必须看到后验变化和 holdout/收口,只出选择题卡不算通过。 - 相关记录:BUG-367、BUG-368、BUG-366、BUG-393、BUG-379
- 复发自:BUG-367(点选身份与滚动);BUG-366(覆盖完成被当成收敛);BUG-393(出卡即完成)
- 修复版本:待发布
BUG-395 | staging publish 的 next build 被 holdout 类型和重复 re-export 挡住
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:Gitea
backend-quality-gate.ymlpublish、deploy/railway-web.Dockerfile的RUN npm run build、core/index.ts、method-followup.ts - 用户现象:向
staging推送a0ce55f0后 quality gate run2088validate 通过,publish 在 web 镜像next build失败。没有 dispatchDeploy staging。公网仍为上一成功 SHA7718317d。第一次失败是 publish exact-SHA checkout 三次超时;重跑后 checkout 成功,类型检查才暴露。 - 触发条件:staging push 的 validate 跳过
npm run build;publish 才在 Docker 里跑 TypeScript。 - 根因:(1)
core/index.ts对decide-next-action.ts和rectification-decision.ts都export *,两者都导出offerSessionKinds/sessionKindFromNextAction,触发 TS2308。(2) BUG-394 的 holdout 追问写成method_id: "holdout_validation"和ask_theme: "holdout",未加入MethodFollowup联合类型。 - 修复:barrel 只具名导出
decideNextAction及其输入类型,session helpers 只从rectification-decision.ts再导出。MethodFollowup联合类型补上holdout_validation与holdout。不改 Skill10.0.11。 - 验证:
npx tsc --noEmit不得再报core/index.tsTS2308 或method-followup.tsTS2322;frontend/tests/rectification-decide-next-action.test.ts。 - 防复发:
core/index.ts不得再export *两个都导出 session helpers 的模块。holdout 追问的method_id/ask_theme必须在MethodFollowup联合类型里。改决策 barrel 或 holdout followup 后必须跑npx tsc --noEmit或等价的next build。staging push 的类型回归仍只在 publish Docker 暴露。 - 相关记录:BUG-309、BUG-365、BUG-371、BUG-387、BUG-394
- 复发自:BUG-309(validate 跳过
next build,publish 才暴露类型错误);BUG-394 重复导出 session helpers 且 holdout 种类未进联合类型 - 修复版本:待发布
BUG-396 | Holdout 门槛、用户停止收口与决策入口未钉死
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:training 门槛、
decideRectification、session_outcome、/api/health数据库版本、Skill 10.0.11 示例 - 用户现象:3 条事件预留 1 条 holdout 后可能既不能出 Probe 又以为事件够了;用户主动停止被写成已验证完成;健康检查只证明镜像 SHA,不证明迁移已执行。
- 触发条件:满 2 条即预留 holdout,同时区分门按全部已收事件计 3 条;用户停止与 holdout 通过共用
completed_with_range;/api/health只有deployment.gitCommit。 - 根因:区分门槛把 holdout 算进可评分事件。用户停止与验证通过共用收口状态。公开决策字段仍可能读 snapshot 而不是 reducer。
- 修复:区分和冲突探针只计 training 事件:3 training / 2 training 领域才开门。2 条继续收集;3 条(2 training + 1 holdout)继续收集;4 条(3 training + 1 holdout)才进入区分。用户停止为
provisional_range_user_stopped,holdout 通过为validated_range,不得把停止写成已完成验证。GET/点选/刷新/工具投影的selection_allowed等从decideRectification派生。/api/health增加database.latestMigration与rectificationContractVersion=v3。Skill 10.0.11 仍不改写;后续新建 10.0.12 时用{timeWindow}/{semanticTarget}/{domain}抽象示例。 - 验证:
tests/test_candidate_discriminator_contract.py、frontend/tests/rectification-decide-next-action.test.ts、frontend/tests/rectification-decision-authority.test.ts、frontend/tests/rectification-hidden-e2e.test.ts、frontend/tests/health-deployment.test.ts。GET/api/rectification/cases/[caseId]的latest_result.selection_allowed与interview由decideFromDossier覆盖,不得回退 snapshot。 - 防复发:不得用含 holdout 的总事件数开区分门。不得把用户停止标成
validated_range或“最终校正结果”。不得在 interview / answer_choice / turn_decision / Case GET 里独立判断selection_allowed。不得只凭gitCommit声称迁移已执行。不得原地改 Skill10.0.11。后续 Skill10.0.12只用{timeWindow}/{semanticTarget}/{domain}。 - 相关记录:BUG-394、BUG-395、BUG-393、BUG-366
- 复发自:BUG-394(holdout 预留后未把门槛改成 training);BUG-395(staging publish 被 holdout 类型和重复 re-export 挡住)
- 修复版本:待发布
BUG-397 | staging web 因 health 直查 migration 账本而不健康
- 状态:resolved
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
GET /api/health、deploy-staging.ymlrun2094、app_runtime权限、生时纠正 schema 合同 - 用户现象:quality gate run
2093成功后自动 Deploy staging。web 容器一直不健康,部署回滚。公网仍为上一成功 SHA0511bc48。 - 触发条件:self-hosted web 用
APP_DATABASE_URL(app_runtime)跑健康检查;compose healthcheck 把非 2xx 当成失败。 - 根因:
app_runtime按 foundation 合同不得SELECT migration.schema_migrations。health 直查该表后权限失败被标blocked,接口 503,Docker 判定 web unhealthy。 - 修复:新增
20260826030000_rectification_migration_ledger_health.sql,SECURITY DEFINER函数rectification_schema_migration_filenames()只把文件名暴露给app_runtime。health 改查该函数。账本可读且缺合同文件才blocked/503;查询失败为degraded,不把容器打挂。合同升到v4。不把业务 SQL 拷进frontend/db/migrations。 - 验证:
frontend/tests/health-deployment.test.ts。staging 必须先Migrate Staging Database再Deploy staging;GET /api/health的deployment.gitCommit等于本次 SHA,且database.requiredMigrationsPresent=true、rectificationContractVersion=v4。 - 防复发:health 不得直查
migration.schema_migrations。app_runtime不得获得该表 SELECT。缺迁移仍由 checker 拦应用发布。不得只凭gitCommit声称迁移已执行。 - 相关记录:BUG-396、BUG-144、BUG-127
- 复发自:BUG-396(health 要证明迁移,但走了
app_runtime禁止的账本查询) - 修复版本:待发布
BUG-379 | 生时纠正已记入学后仍编造高考年并再问入学
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
discriminating_event_probes、method_followup_plan、serverOwnedChoiceCopy、生时纠正系统提示 - 用户现象:已经记下入学年月后,下一问仍假定某年高考、再问大学哪年入学或毕业。点选卡题干变成空泛的「有没有这件事」。
- 触发条件:账本已有带日期
education_start;引擎仍按出生年+17 发出邻近年学业存在性探针;模型把探针年说成已经发生的事实。 - 根因:(1) 学业存在性探针只跳过同一日历年,高考/入学同一学年的前一年仍会出题。(2) 冲突探针在方法覆盖未齐时插队,挡住事业等下一层。(3) 系统提示只写「不得发明年份」,没有禁止把探针年说成事实,也没有禁止再问已入账的入学/分手日期。(4) 服务器点选题干写成「有没有这件事」,丢掉事件家族。
- 修复:学业存在性探针跳过已记学业年的 ±1 年;质量探针仍按精确年。Followup 对邻近年存在性探针不再插队。追问提示带上已记入学/毕业/感情起止,并禁止把未证实年份说成已经发生。点选题干改用假设句里的事件家族。不改 Skill
10.0.11。 - 验证:
frontend/tests/rectification-eight-method.test.ts、frontend/tests/rectification-choice-card.test.ts、frontend/tests/rectification-v9-agent.test.ts、tests/test_rectification_event_probes.py。 - 防复发:已有入学证据时不得再问入学年,也不得把年龄带推算的高考年说成事实。邻近年学业存在性探针不得挡住下一方法层。点选题干必须带事件家族,不能只写「有没有这件事」。不得改已哈希 Skill
10.0.11。 - 相关记录:BUG-192、BUG-351、BUG-375
- 复发自:BUG-192(已说外地上大学后仍换词重问迁居;本次是已记入学后按年龄带编造高考年再问入学)
- 修复版本:待发布
BUG-378 | staging 质量门 2021/2022:activity 合同仍要求 settled.spoken
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:Gitea
backend-quality-gate.ymlvalidatenpm test --prefix frontend、rectification-activity-receipt.test.ts、staging 发布 - 用户现象:向
staging推送2e8c748b后 quality gate run2069失败。validatejob5163前端 2021 通过、1 失败。publishjob5164因needs: validate1 秒跳过。 - 触发条件:完整
xiaoxinrunner 跑前端全量测试。 - 根因:BUG-377 把直播
answer.delta改成原样显示raw,聚焦套件未覆盖rectification-activity-receipt.test.ts。该合同仍扫描state: settled.spoken ? "streaming" : "thinking"。 - 修复:合同改为锁定
state: raw.trim() ? "streaming" : "thinking"和text: raw,并禁止再出现settled.spoken。流式时仍保留正在组织回答…,不得把activeActivity清掉。 - 验证:
frontend/tests/rectification-activity-receipt.test.ts。 - 防复发:改生时纠正直播
answer.delta渲染时必须跑rectification-activity-receipt,不能只跑 stream/entry 聚焦套件。 - 相关记录:BUG-369、BUG-370、BUG-377
- 复发自:BUG-377(直播回复改成模型正文后,activity 源码合同未同步)
- 修复版本:4a400bff103ce0d1d966a8dad8596395933c6927
BUG-377 | 生时纠正直播回复必须是模型正文,不得用正则或 Case 模板顶替
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
runV9AgentTurn、纠正组thinking、公开answer.delta、生时纠正聊天气泡 - 用户现象:关 thinking 后模型把规划写进
text-delta。漏检网按句式切口语,换一种说法就再次把规划当回复。用服务器按 Case 拼「已经记下」会丢掉模型自己的访谈措辞。 - 触发条件:纠正组
thinking: disabled;公开流不发thinking.delta。工具完成后的最终text-delta混有规划和对用户说话,或直播路径用正则 / Case 模板决定回复。 - 根因:规划没有稳定表面形式,词表无法从同一段散文里拆出口语。关掉 provider thinking 后模型没有隐藏思维链通道,只能写进正文。用服务器叙述顶替正文等于不用大模型写对用户说的话。
- 修复:直播用户可见回复是模型终端
text-delta原样。打开 hidden thinking(reasoning-delta),正文预算与思维链预算分开,规划离开正文。公开流仍丢弃thinking.delta。纠正 UI 不渲染thinkingText,阶段仍来自工具进度。服务器叙述只在工具后没有正文时兜底。正则只用于旧 Turn 水合,不得再当直播分流。不改 Skill10.0.11。 - 验证:
frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-spoken-answer.test.ts。 - 防复发:禁止在
runV9AgentTurn里用splitRectificationSpokenAndThinking决定answer.delta。禁止用 Case 模板替换已有模型正文。禁止把thinking.delta发给纠正公开流或写成用户可见思考块。 - 相关记录:BUG-357、BUG-368、BUG-373、BUG-374、BUG-376
- 复发自:BUG-376(继续加正则切规划;随后一度误用服务器叙述顶替模型回复)
- 修复版本:5e34dd69cefc10520219326d499d1733b9b8b3a3
BUG-376 | 生时纠正再次把中文规划写进回复:账本/草稿/探针/我应该
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
POST /api/rectification/agent、splitRectificationSpokenAndThinking、生时纠正聊天气泡 - 用户现象:工具阶段(读取校正记录、整理证据、比较候选)之后,回复气泡里先出现大段自我规划:账本几件已确认、草稿待确认、当前探针、current_question、我应该继续访谈,最后才是对用户说的话。有一轮整段都是规划,没有口语。
- 触发条件:纠正组
thinking: disabled;公开流不发thinking.delta。模型把中文规划写进最终text-delta。句式不在 BUG-374 漏检网里。 - 根因:漏检网只认上一轮字段名(occupation_note / candidate_contrast_packet / 这意味着)。新规划改用「账本 / 草稿 / 探针 / 用户后面 / 我应该 / 根据规则 / current_question」。整段匹配失败后被当成口语并落盘。界面还把拆出的 thinkingText 渲成灰色思考块。
- 修复:纠正聊天不再渲染思考正文,只保留阶段进度和口语。漏检网补上账本/草稿/探针等句式,仅用于旧 Turn 水合,不得当直播分流。直播根因由 BUG-377 处理:打开 hidden thinking,用户可见回复用模型
text-delta。不改 Skill10.0.11。 - 验证:
frontend/tests/rectification-spoken-answer.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-agentic-entry.test.ts。 - 防复发:纠正 UI 不得把
thinkingText当作用户可见输出。直播路径不得再靠加词表从正文切思考。规划必须走reasoning-delta。 - 相关记录:BUG-357、BUG-368、BUG-373、BUG-374、BUG-377
- 复发自:BUG-374(关 thinking 后用正则从正文切思考;句式换了就漏)
- 修复版本:5e34dd69cefc10520219326d499d1733b9b8b3a3
BUG-375 | 生时纠正分不开时 A/B/C/D 出不来,职业回答不计分,「没有了」不停问
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
persistServerOwnedFocus、stampChoiceSchemaWithProbe、GET/api/rectification/cases/[caseId]choice_card、candidate-contrast-packet、event_probes.py、answersFromEvidence、latestUserStoppedCollecting - 用户现象:覆盖已齐、候选仍并列且
selection_allowed已开,点选卡一直是空。Agent 反复用自然语言问已答过的职责倾向。用户说「没有了」后仍继续问,没有出示并列区间。 - 触发条件:剩余候选已落在同一段 D10;窗口扫描真正切开的是 D24。引擎探针只剩已答的教育质量题。职业回答写成
occupation_note,不计分。 - 根因:(1)
stampChoiceSchemaWithProbe用引擎最高增益探针盖 schema,把对比探针换成已答教育题;event_probe缺probe_id时被当成已答,不建焦点。GET 投影不传contrastPacket/userStopped。(2) 对比探针按整窗 D10 星座建题,supportsCandidateIds是星座名不是分钟;askedKeys不含自然语言答过的职责倾向。(3) 质量探针先占领域,挡住 dasha 年界;代表对取整窗第一次换升。answersFromEvidence把同年入学当成考试失常。(4) 停问词匹配不到「没有了」。 - 修复:对比探针用自己的
semantic_key盖戳,缺引擎probe_id时仍建焦点;GET 与工具侧同一套 plan 输入。剩余候选按 D24/D10 分钟切开;职责倾向记入askedKeys。质量探针不得挡住 dasha;代表对取当前候选集。入学不再自动回答质量探针。停问词加上「没有了」等,停问且可出牌时出并列区间。不把occupation_note改成主评分事件,不打开confirmation_allowed,不改 Skill10.0.11。 - 验证:
rectification-spoken-answer、rectification-server-focus、rectification-choice-card、rectification-decide-next-action、rectification-eight-method、rectification-inference-machine、tests/test_rectification_event_probes.py。 - 防复发:覆盖已齐且候选并列时必须落 A/B/C/D;对比探针的
supports/conflicts必须是剩余候选分钟。点选必须改后验。停问且可出牌时走offer_provisional_range,不得再问已答职责题。2026-09-16 复发(BUG-912):无探针的分盘风格题借最高增益探针。2026-09-17(BUG-915):给风格题自建探针只解决了盖戳,自建探针没有像月宿边界探针那样在每个读 state 的地方重建,答题 / GET / idle 三处读持久化 receipt 都找不到它;产品同日拍板风格题不再作为计分判别题。 - 相关记录:BUG-348、BUG-350、BUG-351、BUG-366、BUG-373、BUG-374、BUG-912、BUG-915
- 复发自:BUG-348 / BUG-366(区分卡与覆盖≠收敛已写过,焦点持久化和剩余候选切开未接到这条会话)
- 修复版本:d7afe5b50de488d98a2ca666763784158e7c3be2
BUG-374 | 生时纠正规划句再次漏进正文:candidate_contrast_packet / 这意味着
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
POST /api/rectification/agent、splitRectificationSpokenAndThinking、已落盘 Turn - 用户现象:方法覆盖已齐后,回复气泡先出现「这意味着:方法资料已齐」「服务器给了 candidate_contrast_packet」「第 7 条边界 / 不得 offer」等规划句,然后才是对用户的追问。
- 触发条件:纠正组
thinking: disabled;模型把内部规划写进text-delta。句式不在 BUG-373 漏检网里;\bcandidate_contrast\b匹配不到candidate_contrast_packet。 - 根因:漏检网只覆盖了上一轮已见的方法覆盖 / occupation_note / 本轮对照了。新规划句复制了
candidate_contrast_packet、choice_frame和「这意味着 / 服务器给了 / 不得 offer / 第 N 条边界」。 - 修复:漏检网补上这些句式与
candidate_contrast(?:_packet)?。混有规划和对用户提问时只留口语。不重开 provider thinking,不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-spoken-answer.test.ts锁定规划段进思考、口语追问保留;candidate_contrast_packet不得出现在 spoken。 - 防复发:thinking 关闭时漏检网必须覆盖服务器字段名和新的中文规划句,不能只认上一轮的
occupation_note/本轮对照了。对用户说话的追问不得被一起丢掉。 - 相关记录:BUG-357、BUG-368、BUG-373
- 复发自:BUG-373(关 thinking 后用正则从正文切思考;句式换了就漏)
- 修复版本:d7afe5b50de488d98a2ca666763784158e7c3be2
BUG-373 | 生时纠正把中文过程自述当成回答,工具完成标签和 occupation_note 漏进正文
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
POST /api/rectification/agent、splitRectificationSpokenAndThinking、已落盘 Turn 水合 - 用户现象:工具进度(读取校正记录、整理证据、比较候选)之后,回复气泡里先出现大段自我规划:方法覆盖、occupation_note、open_question、还不能出牌、本轮对照了哪些分盘,最后才是对用户的一句追问。看起来像思考过程被当成推理结果。
- 触发条件:provider thinking 关闭;模型在工具完成后的最终
text-delta里先写中文过程自述,再写对用户的问题。 - 根因:BUG-368 维持
thinking: disabled。漏检网只认旧的datePrecision/用户提到/让我调用 batch句式。新的规划句复制了公开工具完成标签和服务器字段名,整段被当成口语并落盘。末尾「本轮对照了…」与界面已有的技法句重复。 - 修复:漏检网补上工具完成标签回声、occupation_note / method_followup_plan / open_question / not_separated 等内部字段,以及方法覆盖、不可分宽度、让我继续访谈等规划句。混有过程句和对用户提问时只保留口语。不改已哈希 Skill
10.0.11。 - 验证:
frontend/tests/rectification-spoken-answer.test.ts锁定规划段进思考、入职追问留在口语;frontend/tests/rectification-v9-stream.test.ts锁定最终text-delta只把口语发成answer.delta。 - 防复发:禁止把工具完成标签、内部字段名或「本轮对照了」写进
answer.delta。thinking 关闭时漏检网必须覆盖中文规划句,不能只认英文 / batch / datePrecision。对用户说话的追问不得被一起丢掉。 - 相关记录:BUG-357、BUG-359、BUG-368、BUG-360
- 复发自:BUG-357(关 thinking 后用正则从正文切思考;句式换了就漏)
- 修复版本:32f7d774b64466bd900089f8535864ab35386339
BUG-372 | 生时纠正同一轮重复工具调用把已成功写入打成失败
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:
POST /api/rectification/agent、runV9AgentTurn观察器、公开 NDJSON - 用户现象:同一轮已读盘、写入证据、跑完诊断和候选比较后,流以
run.failed/repeated_tool_call结束,文案「本轮没有完成,状态已记录。」,没有助手叙述。公开事件里每个工具只出现一次 started/completed。 - 触发条件:
action=message的自由文本经历回合;模型在rectification-compare-candidates(或同类只含 caseId 的公开工具)完成后,又发出一次相同工具名与相同参数的tool-call。 - 根因:BUG-368 P0-3 把「相同工具参数」做成观察器硬上限 1,第二次相同
toolName + args在发布tool.activitystarted 之前抛错。该码不在自动重试集合,recoverable=false。证据与比较已经落库,只是本轮被标失败且跳过 dossier 叙述器。rectification-set-focus已从 Agent 工具列表移除,这条防线不再对应真实循环。 - 修复:相同公开工具调用视为幂等,跳过重复的 started/phase 收据,不 abort、不
attempt.reset。真循环仍由maxSteps与超时约束。无模型正文时走既有服务器叙述。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-v9-agent.test.ts锁定重复 read-case 完成叙述且无run.failed;frontend/tests/rectification-v9-stream.test.ts锁定重复 set-focus 继续出回答、诊断后重复 compare 走叙述且无attempt.reset。 - 防复发:禁止对第二次相同公开
tool-call抛repeated_tool_call或失败整轮。禁止把幂等工具调用放进自动重试。禁止用观察器 abort 代替maxSteps/ timeout。 - 相关记录:BUG-368、BUG-367
- 复发自:BUG-368(去掉模型驱动 set-focus 后,仍用 abort-on-second-identical-call 防循环,误杀正常比较重发)
- 修复版本:01ac4b9b561229998d106d6f4fca3220c467fb52
BUG-371 | staging publish 的 next build 找不到 createServerSupabaseClient
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:Gitea
backend-quality-gate.ymlpublish、deploy/railway-web.Dockerfile的RUN npm run build、POST /api/rectification/agent、staging 镜像发布 - 用户现象:向
staging推送c41c0e17后 run2058validate 通过,publish 在 web 镜像next build失败。没有 dispatchDeploy staging。 - 触发条件:staging push 跳过 validate 里的
npm run build;publish 才在 Docker 里跑 TypeScript。 - 根因:BUG-368 改 agent 路由时丢掉
createServerSupabaseClient的 import,调用仍在。源码扫描测试不跑tsc,所以 2006 项测试和 lint 都绿。 - 修复:补回
@/lib/supabase/server导入。计费合同与入口合同断言该 import 与await createServerSupabaseClient()同时存在。 - 验证:本机
npx tsc --noEmit退出 0;application-billing-contract与rectification-agentic-entry共 45/45。 - 防复发:改
frontend/src/app/api/**/route.ts后必须跑npx tsc --noEmit或等价的next build,不能只跑字符串合同。staging push 的类型回归仍只在 publish Docker 暴露。 - 相关记录:BUG-309、BUG-355、BUG-365、BUG-368、BUG-370
- 复发自:BUG-365 / BUG-309(validate 跳过
next build,publish 才暴露类型错误);BUG-368 丢掉 import - 修复版本:3519cf253ea0240654479e37598e74bad8ceabd0
BUG-370 | staging 质量门测试全绿后被 prefer-const ESLint 挡住
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:Gitea
backend-quality-gate.ymlvalidate 的npm run lint --prefix frontend、frontend/tests/rectification-inference-machine.test.ts、staging 发布 - 用户现象:向
staging推送76743683后 quality gate run2057失败。前端测试 2006/2006 通过,随后 lint 以 exit 1 结束。publish未构建镜像。 - 触发条件:
npm run lint --prefix frontend。推断 ledger 测试夹具用let engineReceipt且从未再赋值。 - 根因:BUG-367 引入的 ledger helper 写成
let,prefer-const报 error。BUG-369 只修了测试合同,没有跑 lint。既有 16 条 warning 不阻断;这一条 error 会。 - 修复:改为
const engineReceipt。不改生产代码,不放宽 ESLint。 - 验证:本机对该文件
eslint0 error;rectification-inference-machine11/11;全量npx eslint0 error / 16 既有 warning、exit 0。 - 防复发:生时纠正合同改动在推 staging 前必须跑
npm run lint --prefix frontend,不能只跑聚焦测试。 - 相关记录:BUG-339、BUG-367、BUG-369
- 复发自:BUG-339(质量门在测试之后跑 lint;本次是
prefer-const而不是 effect setState) - 修复版本:d2d7542df1730efdb240b4902e7eb5aeb7a827c0
BUG-369 | staging 质量门 2003/2006:计费合同、思考轨迹与业务表清单未跟上 367/368
- 状态:resolved
- 首次发现:2026-08-25
- 最近更新:2026-08-25
- 影响面:Gitea
backend-quality-gate.ymlvalidatenpm test --prefix frontend、application-billing-contract.test.ts、chat-stream-layout.test.ts、database-local-business.test.ts、staging 发布 - 用户现象:向
staging推送1082338c后 quality gate run2056失败。publish因needs: validate未构建镜像。公网仍为上一成功 SHA。 - 触发条件:完整
xiaoxinrunner 跑前端全量测试;Docker PostgreSQL fixture 应用全部frontend/supabase/migrations。 - 根因:三处源码/schema 合同未与 BUG-367/368 同步。(1) 计费合同仍要求
action只有opening|message|read_only,忽略已加入且不计费的answer_choice/stop_and_review。(2) 布局合同仍要求生时纠正聊天把thinking.delta写入appendActivityTraceThinking,与禁止公开思考流冲突。(3) self-hosted 精确public表清单仍缺agentic_rectification_choice_actions与agentic_rectification_inference_transitions。本机先前只跑了聚焦套件,未跑这三项。 - 修复:计费合同改为完整 action enum,仍要求只有
message走 reserve/complete/release。布局合同改为禁止appendActivityTraceThinking和thinking.delta,保留共享 Agent action bar 与工具步骤轨迹。全迁移测试补 applied ledger(含 turn origin)和两张新表,并断言 origin 列与record_agentic_rectification_turn_origin仅 service_role 可执行。不把业务 SQL 拷进frontend/db/migrations。 - 验证:本机
application-billing-contract与chat-stream-layout共 12/12;database-local-business在 Docker PostgreSQL fixture 上 1/1。 - 防复发:新增业务表必须同步
database-local-business的 applied ledger 和精确表集合。生时纠正路由 enum 变更必须更新计费合同。禁止再把公开thinking.delta写回纠正聊天合同。 - 相关记录:BUG-127、BUG-144、BUG-367、BUG-368
- 复发自:BUG-127(新业务表未更新精确表清单);BUG-367/368 的合同测试未覆盖计费 enum 与思考轨迹扫描
- 修复版本:2325b30227136323035f377dac13957c49d8868b
BUG-368 | 工具步骤英文规划被当成回答,set-focus 失败又整轮重放证据
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:
POST /api/rectification/agent、MastrafullStream步骤边界、rectification-set-focus、Evidence Batch、用户消息 origin、推荐问题 - 用户现象:用户说「2020年4月开始实习、6月转正、10月离职」后,界面先刷出残缺英文(
Let me、_probe、_gain)。rectification-set-focus连续失败后整轮重跑,已成功的证据再提交一次,只落地实习、另外两条被引文拒。随后聊天里出现一条用户从未输入的「2002 年发生什么了」。 - 触发条件:自由文本经历回合;Agent 在调用工具前输出规划文本;
set-focus因重复探针或零信息增益被拒;失败运行的推荐问题被写进历史。 - 根因:三组独立回归。(1) 禁止
thinking.delta后,把每个 step 的text-delta都公开成answer.delta,工具前规划变成用户正文;再对碎片做英文过滤,句子被剪成残片。(2)compare-candidates已算出下一问,仍让模型自己调set-focus;确定性校验失败后attempt.reset重放已成功的 Evidence 写入。(3) 未完成运行的 suggestion / 角色映射把「2002 年发生什么了」写成 user 消息。2002 不是引擎从 2020 算出来的。 - 修复:按 step 缓冲,只发布无工具且
stop/length的终端文本;reasoning-delta不下发。Agent 工具列表去掉set-focus,由 compare/read-case 在服务端持久化open_question。duplicate_focus等域错误不整轮重试。同一流里相同公开工具参数视为幂等,不得 abort 整轮;循环由maxSteps/ timeout 约束。用户消息记录origin/clientActionId/content_hash。Evidence quote 用源消息 offset,按条返回 created/already_exists/quote_mismatch。推荐问题只在run.completed后解析,纠正 UI 仍无 suggestion chip。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-step-answer.test.ts、frontend/tests/rectification-v9-stream.test.ts、frontend/tests/rectification-server-focus.test.ts、frontend/tests/rectification-evidence-quote.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-v10-tool-contract.test.ts。 - 防复发:禁止把含工具调用的 step 的
text-delta发给浏览器。禁止把thinking.delta改名为answer.delta。禁止模型驱动set-focus。禁止对duplicate_focus/quote_mismatch/zero_information_gain做attempt.reset。禁止对第二次相同公开工具调用 abort 整轮(见 BUG-372)。禁止未完成运行解析或自动提交 suggestion。禁止模型改写 Evidence quote。 - 相关记录:BUG-345、BUG-354、BUG-357、BUG-367、BUG-372
- 复发自:BUG-367(关掉公开
thinking.delta后,中间 step 的text-delta被整段当成回答) - 修复版本:fe87a9ecdb71eae4eb79664664cc20c534177c19
BUG-367 | 点选 A 被当成聊天,部分写入后长推理失败并抢走滚动
- 状态:resolved
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:
POST /api/rectification/agent、选择题卡、applyRectificationChoice、推断 ledger、公开流、生时纠正对话滚动 - 用户现象:点选 A 后界面把「A. 是,大概就在那段时间」当普通聊天发给 Agent。模型重读整份 Case、思考上万字,随后「本轮处理未完成」。再点一次又看到证据已存在但问题仍开放。流式输出期间无法往上滑。
- 触发条件:当前有 A/B/C/D 问题卡;点击选项或「先这样」;流式过程中向上滚动。
- 根因:四点叠加。(1) 没有
answer_choice协议,点选被转成action: "message"。(2) 证据/后验写入与关闭当前问题不在同一事务,自然语言失败被当成整轮失败。(3) 公开流发送thinking.delta,持续触发强制scrollTo。(4) 用户原话引用可能写成问题里的年份。 - 修复:点选与停止走
answer_choice/stop_and_review,服务端按questionId + optionId + actionId原子写入 receipt、后验、关闭 focus。同一actionId幂等回放。公开流只发白名单,含粗粒度activity.changed。滚动改为近底部才跟随,并提供「回到最新」。read-case默认turn_decision。失败返回具体finishReason文案,不把「本轮处理未完成」写入聊天。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-inference-machine.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/rectification-v9-stream.test.ts。 - 防复发:禁止把点选全文当
message交给语言模型。禁止thinking.delta进入生时纠正公开流。禁止在用户离开底部后强制scrollTo/scrollIntoView。禁止用助手题干年份充当用户 quote。选择题写入必须带unique(case_id, action_id)并在同一事务关闭当前问题。 - 相关记录:BUG-357、BUG-360、BUG-362、BUG-363
- 复发自:BUG-362(C/D 无证据后验已走确定性写入,A/B 点选仍被当成聊天)
- 修复版本:3659519bd00e05ffa88e83823ac56ad4d4a747ab
BUG-366 | 方法覆盖完成被当成候选收敛,职业笔记又让候选卡立刻失效
- 状态:resolved
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:
decideNextAction、conversationalSessionOutcome、isOfferBlockingFollowup、method_followup_plan、rectification-offer-candidates、候选 snapshot 指纹、生时纠正系统提示、验证报告 - 用户现象:用户一句话里同时给出长期职业和带日期创业事件后,状态终于会动,但直接跳到
adopt_candidate。Agent 按协议出牌。职业无日期occupation_note不参与评分却把方法覆盖标成完成;候选仍是 34/33/33。随后候选卡被前端判定失效,提示「出生资料已变化」。系统没有按 D9/D10 差异反问前事。 - 触发条件:经典方法层已齐或职业笔记刚补齐;引擎
propose_allowed已开;候选相对支持只差 1 分;Agent 在同一轮先写带日期事件再写occupation_note再offer-candidates。 - 根因:两层概念被焊在一起。(1)
methodCoverageAll && propose_allowed直接变成session_outcome=adopt_representative,覆盖完成被当成收敛;盘面差异只写进最终报告,不进入决策。(2) 无评分occupation_note推进工作流版本,候选 snapshot 仍按上一版证据读取,失效文案又把所有 409 翻译成出生资料变化。 - 修复:新增
decideNextAction。覆盖完成只进入ask_candidate_discriminator。34/33/33 为not_separated。D9/D10 差异生成CandidateContrastPacket。未通过 holdout 不得ready_to_adopt。候选卡只认出生资料 + 可评分证据 + 推断修订 + 候选集;occupation_note仍覆盖职业层,但不让 snapshot 过期。失效原因拆开。不改已哈希 Skill10.0.11。 - 验证:
frontend/tests/rectification-decide-next-action.test.ts锁定覆盖≠收敛、并列必须出区分探针、无评分职业笔记不改 scoreable fingerprint、holdout 未过不得 adopt、报告不得无calculationResultId写 executed;frontend/tests/rectification-eight-method.test.ts锁定职业笔记覆盖后不再直接 adopt、覆盖后剩余探针仍走区分。 - 防复发:禁止
methodCoverageAll直接变成adopt_representative。禁止把事件拟合率当成候选区分。禁止无评分证据让候选卡失效。禁止把所有 stale 提示写成「出生资料已变化」。不得改已哈希 Skill10.0.11。 - 相关记录:BUG-361、BUG-362、BUG-363
- 复发自:BUG-361(职业笔记覆盖和出牌门修了,但覆盖完成被当成可以 adopt)
- 修复版本:待发布
BUG-365 | staging web 镜像 next build 被推断类型检查挡住
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-08-24
- 最近更新:2026-08-24
- 影响面:
deploy/railway-web.Dockerfile的RUN npm run build、probeFromEngine、method-followup反推探针、rectification-v9-toolswindow scan - 用户现象:Linux
xiaoxin离线时在本机构建linux/amd64web 镜像,next build在 TypeScript 阶段失败,无法把909de8b8换上 staging。 - 触发条件:当前
staging头含推断 ledger,且走 Dockernext build。staging push 的 validate 跳过npm run build,因此测试可绿、镜像红。 - 根因:
EngineProbeFields把expected_outcomes.answer_class收成AnswerClass,与引擎探针的string相交后不能map(probeFromEngine)。反推探针用字面量比较dasha_boundary,在source收窄成age_band后被判无交集。windowScanFromDecisionReceipt可为null,调用方仍读.transitions。 - 修复:
probeFromEngine接受引擎探针并校验AnswerClass。反推探针改走Set<string>。window scan 用可选链,缺省空数组。 - 验证:本机
npx tsc --noEmit仅余测试夹具多余字段,已改为Object.assign;Dockernext build待用新 SHA 重跑。 - 防复发:staging push 的类型回归仍只在 publish Docker 暴露;本机/xiaoxin 镜像构建失败不得改 SHA 蒙混上线。
- 相关记录:BUG-355、BUG-363、BUG-364
- 复发自:BUG-355(validate 跳过
next build,publish 才暴露类型错误) - 修复版本:待发布
BUG-398 | 已持久化点选题因不是全局最高探针被误判 stale
- 状态:resolved(本地修复,待发布)
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
applyChoiceWithoutEvidence、生时纠正 A/B/C/D 点选、推断 revision ledger - 用户现象:页面刚读取到的当前点选题提交后返回
stale_probe,即使focusId、probeId与 revision 都没有变化。 - 触发条件:数据库已持久化的 active focus 不是当前推断状态按信息增益重新计算出的第一名探针。
- 根因:点选入口已经校验 active focus,但
applyChoiceWithoutEvidence又用nextProbe(state)建立第二套“当前题”真源,把合法的低信息增益已打开题判成过期;探针 schema 的多个 identity 字段又是逐个命中,互相矛盾时仍可能落到其中一个探针。 - 修复:移除对重新计算全局最高探针的限制;只要显式
probe_id、semantic_key、candidate_split_hash同时匹配同一个 state probe 就允许应用。显式 identity 矛盾或找不到时仍返回stale_probe;无显式 identity 才保留原 domain/highest-gain fallback。ledger 的 open probe 与 revision 校验不变。 - 验证:
npm_config_cache=/tmp/jyotisha-npm-cache npx --yes tsx --test tests/rectification-inference-machine.test.ts,14/14 通过。 - 防复发:已持久化 focus 可以回答低于全局最高信息增益的探针;互相矛盾的 schema identity 必须 fail closed。
- 相关记录:BUG-367
- 复发自:无
- 修复版本:待发布
BUG-399 | 感情区分探针可落在未成年年份
- 状态:resolved(本地修复,待发布)
- 首次发现:2026-08-26
- 最近更新:2026-08-28
- 影响面:
scripts/rectification/event_probes.py、relationshipdasha_boundary探针 - 用户现象:感情卡可能询问出生后很早、明显不符合“认真关系进入或结束”语义的年份。
- 触发条件:候选在未成年年份附近存在高区分度 Vimshottari 或 Narayana 边界,且该年份没有已记录事件挡住。
- 根因:boundary 候选只使用全局
birth_year + 5下限,没有应用 relationship catalog 的年龄下限。 - 修复:relationship 的 dasha boundary 从
birth_year + age_lo开始;不使用age_hi作硬上限,避免排除成年后的真实关系事件。其他允许童年事件的领域保持原范围。 - 验证:
/opt/anaconda3/bin/python3.12 -m pytest tests/test_rectification_event_probes.py -q,15/15 通过;回归锁定未成年 relationship 年份被排除且 family 童年边界行为不回退。 - 防复发:relationship boundary 不得早于 catalog
age_lo;不得把age_hi当事实有效期。 - 相关记录:BUG-379、BUG-417
- 复发自:无
- 修复版本:待发布
BUG-400 | 已打开点选题时 Agent 只确认事实,没有问出题干
- 状态:resolved(本地提示契约补强,待发布)
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:生时纠正 Agent 系统提示、口语与 choice card prompt 投影
- 用户现象:工具已经持久化下一道点选题,但 Agent 正文只说已记下事实,没有自然语言追问;卡片只能显示服务器的“年份 · 事件家族”锁标签。
- 触发条件:工具返回
open_question/current_question后,模型只输出确认语,没有输出带问号且符合锁定年份/领域的问句。 - 根因:BUG-391/392 已保证服务器不代写问题并优先投影 Agent 问句,但提示只要求“自己写一句自然语言追问”,没有明确禁止该轮仅确认事实。
- 修复:补强现有同一条提示:有 open/current question 时,本轮正文必须包含对应自然语言追问,不能只回复“记下了”或只做事实确认。不新增服务器题干模板,不改变 A/B/C/D 评分语义。
- 验证:
./node_modules/.bin/tsx --test tests/rectification-v9-agent.test.ts tests/rectification-choice-card.test.ts,42/42 通过;静态契约锁定强制追问语句,既有 Agent 问句投影测试保持通过。 - 防复发:有 active open question 的轮次必须口语问出同一年份和事件家族;服务器锁标签仍只作为 fail-closed 显示,不得伪装成 Agent 问句。
- 相关记录:BUG-391、BUG-392
- 复发自:BUG-391
- 修复版本:待发布
BUG-401 | 零信息增益质量卡伪造 probe,点选永远 stale 且一条线索就打断采集
- 状态:resolved(本地修复,待发布)
- 首次发现:2026-08-26
- 最近更新:2026-08-26
- 影响面:
method_followup_plan、stampChoiceSchemaWithProbe、persistServerOwnedFocus、生时纠正 A/B/C/D 卡片 - 用户现象:只记入一条「2016 年 9 月上大学」后就出现高考发挥选择题;题干在 Agent 正文和卡片重复,点击任一选项返回
stale_probe。 - 触发条件:账本只有一条学业事件;引擎 receipt 生成
known_event_quality,但该条目information_gain=0、candidate_ids=[]、expected_outcomes=[],且没有进入inference_state.probes。 - 根因:BUG-389 让
known_event_quality在 3 条训练事件 / 2 个领域门槛之前插队;stampChoiceSchemaWithProbe在已有 inference state 却找不到匹配探针时仍合成probe_id。持久化 focus 因而引用不存在的探针,点选链路按正确的 fail-closed 校验返回stale_probe。卡片又可视化重复渲染 Agent 已问出的题干。 - 修复:删除采用门前的零增益质量卡分支;所有 scoring focus 统一要求正信息增益、真实候选映射和同一 inference probe,匹配失败不再合成 identity、也不持久化。读取旧案例时忽略遗留
clarify_eventfocus 并继续收集其他领域事件。卡片 legend 保留给无障碍读取,但视觉上只显示 A/B/C/D,题干由 Agent 正文展示。 - 验证:6 个定向套件 152/152;
npm run lint0 error(17 条既有 warning);next build --webpack完整通过。默认 Turbopack 在隔离 worktree 因node_modules指向工作区外的符号链接而报环境错误,非源码错误。 - 防复发:scoring choice schema 在已有 inference state 时必须绑定真实 probe;零增益或没有候选映射的条目不得成为点选题;一条事件后继续跨领域收集,题干不得在正文与卡片重复。
- 相关记录:BUG-389、BUG-390、BUG-398、BUG-400
- 复发自:BUG-389
- 修复版本:待发布
BUG-402 | 生时纠正点选成功后不续问,确认文案永久停在“正在准备下一步”
- 状态:resolved(代码修复完成,staging 发布与验收待确认)
- 首次发现:2026-08-26
- 最近更新:2026-08-27
- 影响面:
POST /api/rectification/agent的answer_choice、生时纠正聊天续跑、区分题自然语言、Skill 包注册 - 用户现象:点击 A/B/C/D 后服务端已记录选择并更新候选,但页面只显示“正在准备下一步”,不再出现下一问;职业题干容易直接复述“入职、升职或职责明显加重”等固定标签。
- 触发条件:结构化选择成功后决策器返回
ask_fact_collection、ask_candidate_discriminator或ask_holdout_validation;或 Agent 根据event_probes.py的固定事件家族 brief 写点选题。 - 根因:前端只消费
narration,忽略成功响应里的nextAction,且把临时 loading 句持久化成最终 assistant turn。探针 brief 又要求围绕固定事件家族写一句是/否题,模型容易轻度改写后原样输出。 - 修复:选择仍保持独立、幂等、非模型事务;仅当
nextAction需要继续提问时复用现有read_onlyAgent turn,其他动作只刷新 Case 快照。删除持久化 narration 中的“正在准备下一步”。探针 brief 改为锁定时间范围、领域和语义目标,要求结合最近对话只选一个口语入口,不逐字复述或堆叠标签示例。发布 immutable Skill10.0.12,10.0.11保留为 deprecated 供旧 Case 精确解析。 - 验证:
frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-agentic-entry.test.ts、frontend/tests/skill-registry.test.ts、相关 V9 Skill 合同测试、tests/test_rectification_event_probes.py。 - 防复发:结构化命令成功响应新增或变更决策字段时,客户端必须有消费合同;不得把 loading 状态写成持久化回答。服务器探针可锁定语义,不得把内部分类标签当成最终题干模板。
- 相关记录:BUG-391、BUG-392、BUG-394、BUG-398、BUG-401
- 修复版本:10.0.12
BUG-403 | 生时纠正候选后验、展示排名和停止语义不一致
- 状态:resolved(本地修复,未提交)
- 首次发现:2026-08-27
- 最近更新:2026-08-27
- 影响面:生时纠正停止意图、方法追问、候选区分探针、结构化选择评分、候选结果投影、Profile freshness、候选采用 RPC、采用后反向核验续跑、动态选项、唯一分钟确认门、VedAstro 缓存验证
- 用户现象:用户在当前领域回答「没有了」后可能误结束整个 Case 并提前展示候选;已有感情起止证据仍重复询问是否谈过恋爱;既有教育证据误抑制尚未问过的 D24 高信息量区分机会;一次结构化选择几乎淘汰全部候选;候选卡排名、
representative_time、credible_range与 inference 后验互相矛盾;首次采用候选返回candidate_profile_changed;采用后虽已有verify_adopted_time计划但前端没有发起下一轮;选择题依赖静态模板兜底;确认链路又被入口硬编码的false永久关闭,并显示机械式代表时间收口提示。 - 触发条件:局部否定被当作全局停止;方法覆盖只按领域而不看已确认事件语义;候选对比把 Evidence 内容推断成已问 probe;单轮冲突使用硬淘汰;持久化候选与 inference 各自投影;Profile freshness 把展示地点和历史
active_birth_time当作本轮计算输入,却漏掉声明时间窗;候选采用只刷新快照,没有复用结构化选择的read_only续跑;动态选项缺失时回退固定职业、感情、学业或考试文案;Agentic 决策入口和 VedAstro 投影无条件写死不允许精确分钟确认。 - 根因:停止语义、语义去重、asked-probe 账本、评分更新、公开候选投影、Profile freshness、外部验证和确认门分别维护了不兼容规则。尤其 Evidence 只能证明事件存在,不能证明某个候选分组问题已经问过;展示地点和历史
active_birth_time也不是本轮计算输入变化;临时外部验证失败不应永久污染同一候选结果。 - 修复:停止整个校正不再靠词表正则猜测,新增 Agent 语义工具
rectification-stop-and-review,由服务端持久化paused;「没有 / 不记得 / 这方面没有」由 Agent 针对当前 active focus 调用rectification-resolve-focus,不会直接终止 Case,后续普通消息可恢复暂停 Case。选择题只在引擎返回完整、合法且恰好覆盖yes / weak_yes / no / unsure的动态style_options时显示;缺项、重复、空文案或非法事件家族直接不出卡,不再回退任何职业、考试、感情或学业静态模板。已确认感情证据抑制泛化 D9 复问,但不抑制真实候选区分 probe;structured/varga probe 只按 inference receipt 的已答 key 去重,普通事件 probe 按 live evidence 的domain.year去重。结构化答案改为累计软评分,累计 3 次唯一 strong conflict 才淘汰,并保证至少一个候选存活。UI、API、Mastra 和报告统一从 active inference 候选投影排名、代表时间和可信区间;候选映射、cluster range、代表时间或 receipt range 任一不一致时隐藏候选并禁止采用。统一 fingerprint 忽略展示地点与历史active_birth_time,纳入出生日期、原始时间、时间来源/时段、声明时间窗、不确定范围、经纬度和时区等真实计算输入。采用期间复用现有pending/busy锁定输入,采用后由既有 continuation effect 发起read_only,进入verify_adopted_time反向前事核验。删除 Agentic 入口的confirmationAllowed: false和 VedAstro 投影的canConfirmExactMinute: false,统一走buildConfirmationGate();真实证据门仍 fail closed,不把代表分钟伪装成唯一分钟。VedAstro 失败只持久化安全失败码;相同 evidence/range 缓存再次比较时可重试验证,并通过独立 service-role RPC 仅刷新当前 Result 的验证 receipt,不重算或改写候选、排名、inference 和 selection。删除「本轮校正已收口」「本会话以代表性时间收口」及采用后的机械式代表时间提示;只有真实唯一分钟确认后才显示确认时间。发布 immutable Skill10.0.13,10.0.12保留为 deprecated。 - 验证:新增/更新回归覆盖 Agent 语义暂停与恢复、局部否定 focus 关闭、动态选项 fail closed、感情泛化复问、D24 structured probe 不被普通教育 Evidence 屏蔽、累计冲突软淘汰、候选投影不变量、latest inference transition 权威采用、候选 schema 缺失时 fail closed、采用/普通消息互斥、Profile freshness、确认门真实输入、VedAstro 安全失败码及缓存重试、验证刷新 RPC 的归属/指纹/Profile freshness/最小更新权限;最终本地类型检查与聚焦套件结果见本次变更验收记录。
- 防复发:停止整个流程必须由显式语义工具落库,不得增加停止词正则;动态选项不完整时宁可不出卡,不得恢复任何静态题干或 A/B/C/D 文案;Evidence 语义覆盖不得替代 structured probe receipt;单个启发式选择不得一票淘汰候选;公开候选卡、代表时间、可信区间与采用入口必须共享 active inference 投影;外部验证临时故障必须可重试且不得改写候选状态;入口不得硬编码绕过真实确认门,也不得删除 holdout、相邻分钟、外部验证和用户同意等证据门;代表性采用和唯一分钟确认必须使用不同状态与文案。
- 相关记录:BUG-366、BUG-367、BUG-398、BUG-401、BUG-402
- 复发自:BUG-366(覆盖完成被当成收敛)、BUG-367(结构化选择旁路)、BUG-398(probe freshness)、BUG-401(伪 probe 与过早出卡)、BUG-402(点选后续跑)
- 修复版本:10.0.13(未提交)
BUG-404 | 区分题未落 active Focus 仍被说出,自由文本否定误入完整 Agent
- 状态:resolved(修复已验证,待 staging 发布)
- 首次发现:2026-08-27
- 最近更新:2026-08-27
- 影响面:
POST /api/rectification/agent、active Focus、动态选择题、自由文本回答、候选后验更新 - 用户现象:Agent 正文询问候选区分题,但页面没有选择卡;用户随后输入「那段时间没有变化」一类自然语言回答时,系统没有关闭当前问题,而是启动完整 Agent/候选比较长链,最终超时并显示校正暂时不可用。
- 触发条件:服务端没有成功持久化对应 active Focus,或 active Focus 已存在但用户没有点击卡片、改用自由文本回答。
- 根因:正文问题与 durable Focus 曾是两套真源;自由文本消息没有 active Focus 的轻量结构化意图入口;旧路径还能按 A/B/C/D 位置、focus status 或选项引文猜答案语义。结果是未落库的问题仍可见,而自然语言局部否定无法复用结构化点选 resolver。
- 修复:只有
created/already_open且 Focus UUID、questionId、semantic_key、candidate_split_hash和完整动态选项 schema 一致时才暴露区分题;持久化失败、重复冲突或 schema 非法时同时隐藏卡片和 Agent 可见 follow-up。active Focus 下的自由文本先交给轻量模型输出结构化 intent 与answer_class,模型只分类不改状态;当前问题答案通过动态选项自带的answer_class反查 option key,并与点击卡片统一调用applyRectificationChoice()。分类不清或模型异常时保留 Focus 并提示点选,不再落回完整 Agent。删除用户语义正则、选项引文解析及 status/位置到答案的推断;动态 schema 不完整时 fail closed,不恢复静态题干或选项 fallback。 - 验证:相关回归
144/144通过;tsc --noEmit通过;完整 ESLint 无 error(仅保留仓库既有 warning);完整前端套件遍历发现的旧静态选项/旧提示词断言已改为动态 schema 契约并逐项通过。回归覆盖乱序 option key、自由文本分类、Focus 持久化不变量、卡片身份、点选与文本共用 resolver、非法 schema fail closed、无语义正则及 inference fixture 动态 schema。 - 防复发:候选区分题必须以持久化 active Focus 为唯一真源;模型分类结果不能直接写状态;所有答案必须经过动态
answer_class和同一确定性 resolver;不得增加语义词表、正则或 A/B/C/D 位置 fallback;分类失败时保持当前 Focus,不得启动完整 Agent 长链。 - 相关记录:BUG-367、BUG-398、BUG-400、BUG-401、BUG-402、BUG-403
- 复发自:BUG-367(结构化点选旁路)、BUG-400(正文与卡片题干分离)、BUG-403(局部否定仍依赖 Agent 工具)
- 修复版本:10.0.13(待 staging 发布)
- 后续复发:BUG-405
BUG-405 | 区分探针问题合同未统一,低信息量事件题压过可渲染高信息量分盘题
- 状态:resolved
- 首次发现:2026-08-27
- 最近更新:2026-08-28
- 影响面:生时纠正候选区分探针、点选题四选项合同、方法追问排序、active Focus 持久化、Agent 可见投影、
POST /api/rectification/agent - 用户现象:候选比较阶段出现无法点选的自由文本问题,或先问信息量很低的事业存在题,而更高信息量的 D24 等分盘区分机会被跳过;点选「没有」或自然语言回答无法更新后验,流程退回泛问经历。
- 触发条件:Python 区分探针只给出 yes/no 结局、不带完整
style_options;TypeScript 曾用测试夹具补四选项,生产路径却 fail-closed 不出卡;method-followup在冲突事件探针与 contrast 探针之间按 if/else 分支优先,而不是按信息量全局排序;ask_candidate_discriminator且没有合法 active Focus 时仍可能进入完整 Agent。 - 根因:问题合同、可渲染性、探针排序和 Focus 持久化是四套规则。BUG-403/BUG-404 要求动态四选项,但 Python 从未发出完整
style_options,存在题无法服务端补全;冲突事件探针因分支顺序可以压过更高information_gain的 contrast 探针;Agent 仍能看见原始candidate_contrast_packet/current_probe,并在没有卡片时把区分题说出来。 - 修复:新增跨运行时
probe-question-v1合同。存在题/质量题可由服务端补全为yes / weak_yes / no / unsure;分盘风格标签保持动态,并补都不是这些特质与这段记不清楚。只有isRenderableProbe通过的探针进入排序;冲突事件探针与 contrast 探针按information_gain × novelty × coverage - penalty全局取最高。先持久化 Focus 再暴露问题;compare-candidates 对 Agent 隐藏原始探针包。缺少合法 Focus 时先修复或收口到可信区间,不进入完整 Agent;流结束后若仍是 discriminator 且没有合法 Focus,记state_invariant_failed。Skill 版本保持10.0.13。 - 验证:
frontend/tests/rectification-probe-question-contract.test.ts、choice-card / server-focus / contrast-packet / eight-method 排序回归、turn-decision 隐藏current_probe、route 在runV9AgentTurn前修复 Focus、Pythontests/test_probe_question_contract.py与tests/test_rectification_event_probes.py四选项合同。Gitea Independent Staging Quality Gate3ae22f576842310e6e43de497bf76714b614c7ed通过后,Deploy staging run 2121 成功;https://staging.jyotisha.chat/api/health报告同一 SHA,rectificationMigrations=ok。 - 防复发:区分题必须同时满足四选项合同、可渲染、全局信息量排序、Focus 先于提问。不得恢复冲突探针优先于 contrast 的分支;不得把原始探针包交给 Agent;不得在
current_question=null时把 discriminator 当成成功口语轮次。 - 相关记录:BUG-367、BUG-398、BUG-400、BUG-401、BUG-403、BUG-404
- 复发自:BUG-403(动态四选项合同未贯穿 Python/排序)、BUG-404(Focus 与口语问题仍可分裂)
- 修复版本:10.0.13
- 后续复发:BUG-407
BUG-406 | 点选题干只在无障碍 legend 里,卡片上看不到
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
rectification-choice-card、GETchoice_card.prompt、生时纠正 Agent 口语 - 用户现象:区分阶段出现 A/B/C/D,气泡只说“请看下面的选项”,卡片上方没有年份和事件家族;用户看不到要核对的是哪一类经历。
- 触发条件:服务器已持久化
choice_card.prompt(年份 · 事件家族),Agent 按 P0 只写承接句、不复述题干。 - 根因:BUG-401 把卡片 legend 设为
sr-only,当时题干由 Agent 正文展示。BUG-405 改为正文只做承接、题干以 Focus/卡片为真源,但没有取消sr-only,题干两边都不出现。 - 修复:卡片 legend 恢复为可见题干,与 GET
choice_card.prompt同一份文案。不改 Skill10.0.13。 - 验证:
frontend/tests/rectification-agentic-entry.test.ts锁定可见<legend>{props.card.prompt}</legend>,禁止sr-only。 - 防复发:有点选卡时题干必须在卡片上可见。Agent 承接句不得替代卡片题干。不得再把
choice_card.prompt藏进sr-only。 - 相关记录:BUG-400、BUG-401、BUG-405
- 复发自:BUG-401(卡片隐藏题干)叠加 BUG-405(正文不再复述题干)
- 修复版本:待发布
- 后续复发:BUG-471
BUG-407 | 区分题目录被快照投影饿死,低分职业题压过已评分的高分 D24
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:生时纠正 GET 点选卡、Mastra 工具读路径、
POST /api/rectification/agent、contrast packet、method-followup - 用户现象:训练事件已经够、推理层已有高信息量 D24 探针,界面仍出低信息量事业存在题;题干修复后选题内容仍不对。
- 触发条件:快照
latest_result.candidates为空,或可信区间与推理活跃时刻不一致导致authoritativeCandidateProjectionfail-closed;Pythondiscriminating_event_probes仍给出低分事业题;已打开的事业区分卡把后续排序锁在原题上。 - 根因:BUG-405 把排序公式改成按信息量取最高,但读路径组包不读
inference_state.probes,remaining D24 又依赖快照时刻。时刻被投影饿死后目录只剩职业题。评分落库用score.candidates能把 D24 写进推理层,GET/工具随后丢掉。已打开的低分 distinguish Focus 还在 method-followup 里优先于重新排序。 - 修复:GET、Agent、工具共用一份
rectificationFollowupCatalog。合并未回答的推理探针与 Python 事件探针,按semantic_key去重留更高信息量。拼 remaining splits 用推理未淘汰时刻,不把 adopt 投影的空scores当成出题目录。已有日期证据的领域不再出存在题(例如已记事业就不再问另一年入职);D9/D10/D24 等分盘区分题仍进池,按信息量全局取最高,不绑定某个领域。varga.d24/d5按质量题、d9/d10按风格题补全。已打开但 semantic_key 不是当前目录赢家的区分卡让位。评分落库仍用当次score.candidates。Skill 版本保持10.0.13。 - 验证:
frontend/tests/rectification-decision-authority.test.ts空快照仍选出目录最高分;frontend/tests/rectification-eight-method.test.ts已覆盖领域的存在题让位,D10 与 D24 谁分高问谁。 - 防复发:出题目录必须来自推理探针加引擎探针,不得只吃 Python 事件探针或快照投影时刻。Adopt/展示投影 fail-closed 不得饿死出题。已打开的低分区分卡不得挡住更高分目录赢家。
- 相关记录:BUG-405、BUG-406
- 复发自:BUG-405(排序公式对,目录被投影饿死,已打开低分卡锁题)
- 修复版本:待发布
BUG-411 | 已持久化的 D24 区分卡在 GET 时被 active_focus 重建丢掉
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
buildMethodFollowupPlan已打开区分焦点、GETchoice_card、生时纠正点选卡 - 用户现象:训练事件已齐,助手记下感情经历后只说“对候选的区分有帮助”,不再追问,界面也没有点选卡。服务端其实已经创建了 D24 区分焦点。
- 触发条件:目录赢家是推理层
varga_contrast(不在 Pythondiscriminating_event_probes里),该焦点已持久化,随后 GET 刷新卡片。公开分数字段可因credible_range与全窗候选不一致而被清空。 - 根因:已打开的区分焦点只要还没被更高分探针替换,计划就改写成
source=active_focus。重建时只在 Python 事件探针里按targetDomain找 live probe。D24 只存在于对比包,education 域又没有 dasha 事件探针,于是semantic_key丢失、choice_frame对不上已持久化的questionId,GETchoice_card变成 null。Agent 被禁止在没有公开卡片时口述区分题,正文只剩确认句。 - 修复:打开的区分卡若已经是当前目录赢家,继续用该赢家的 ranked discriminator 出题,不要改写成 active_focus。
- 验证:
rectification-choice-card锁定「Python 事件探针没有教育行、D24 只在推理探针、焦点已持久化」时 GET 仍返回点选卡。 - 防复发:不得把已打开且仍是目录赢家的分盘区分题改写成没有
semantic_key的active_focus。GET 有合法持久化区分焦点时不得把choice_card置空。 - 相关记录:BUG-407、BUG-410
- 复发自:BUG-410(决策已进入区分并持久化焦点,GET 仍把卡片藏掉)
- 修复版本:待发布
BUG-412 | 反推点选卡丢掉已记下的年份月份,后两个选项收成一排小按钮
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
choice-cardperiod/prompt、GETchoice_card、rectification-choice-card点选样式 - 用户现象:区分卡出现后题干写成「当前这几个候选 · 学业…」,已记下的入学年份和月份精度出不来。A/B/C 是整行主按钮,D「这段记不清楚」和「先这样」挤在下一排、更小、居中、颜色变淡。
- 触发条件:目录赢家是没有
year的 D24varga_contrast,账本里已有同学业带日期经历;点选卡把unsure和停止键标成secondary。 - 根因:
periodFor只要找到探针就采用year_label,对比包探针年份为 0 时标签是占位「当前这几个候选」,不再回落到同学业已确认经历。GET 又优先用已持久化的占位题干。选项按unsure=secondary拆到special-choices,和停止键共用 flex 小按钮。 - 修复:探针没有具体年份时,题干锁定同学业已确认经历的「YYYY 年前后」或月份精度的「YYYY 年 M 月前后」。GET 用更具体的服务端题干覆盖占位题干。A/B/C/D 和停止键同一列主按钮。
- 验证:
rectification-choice-card锁定 D24 占位探针 + 2016 年学业经历 → 题干带「2016 年前后」;月份精度经历带出「M 月前后」;GET 仍把占位题干升级成年份锁。rectification-eight-method锁定高分 D24 的 period 是已记学业年。rectification-agentic-entry锁定点选卡不再拆special-choices。 - 防复发:不得在对比包年份为 0 时把「当前这几个候选」压过账本已确认年份。点选卡四个答案不得因为
unsure改成另一套小按钮。 - 相关记录:BUG-406、BUG-408、BUG-411
- 复发自:BUG-411(卡片能投影后,占位年份和 secondary 拆行才被看见)
- 修复版本:待发布
BUG-413 | 点选后卡片消失,只留下一条自动发出的选项气泡
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:生时纠正点选卡停留与选中态、
choice_click用户气泡、D24/发挥质量题干 - 用户现象:点 A/B/C/D 或「先这样」后卡片立刻从对话里消失,底下多出一条用户自己点出来的
A. 发挥明显失常…。同一张卡不能再看见选中项。题干写成「2016 年前后 · 学业、考试发挥或学习压力出现明显变化」,像目录锁标签。 - 触发条件:GET 已出示点选卡;用户点击任一选项。
showChoiceCards在busy时卸载卡片,并把choiceCardUserMessage追加成用户消息。 - 根因:点选被做成「发一条用户消息 + 等下一轮」,卡片只挂在最新未 busy 的助手气泡上。题干走
period · family锁标签;对比包发挥质量又不分领域,一律套学业考试目录句。 - 修复:点选后把卡片钉在出题的那条助手消息上,保留
data-selected,禁用再点;不再插入自动用户气泡,刷新时也藏掉A. …/ 「先这样」这类合成行。发挥质量题干改成年份锁定的口语问句,并按领域区分家族,不再用「当前这几个候选 · 学业…」目录锁。 - 验证:
rectification-choice-card锁定 D24 题干是「2016 年前后,有没有学业或考试发挥失常、压力特别大的时候?」;GET 把已持久化的目录锁升级成这句。rectification-agentic-entry锁定choiceAttachment停留、不再创建v9-choice-user-气泡。rectification-answer-choice锁定合成点选行不是聊天用户句。 - 防复发:点选卡不得因为
busy从已回答的助手气泡上卸掉。点选不得再追加A. 标签用户气泡。题干不得再把period · family目录锁直接展示给用户。 - 相关记录:BUG-405、BUG-406、BUG-412
- 复发自:BUG-412(卡片能看清后,点选消失和目录题干才被看见)
- 修复版本:待发布
BUG-414 | 无年份家人反推仍出点选卡
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:生时纠正点选卡
eventQuestionPrompt、buildChoiceFrame、rankRenderableDiscriminators、家人存在性家族、GET 题干覆盖 - 用户现象:D24 点选之后出现「那段时间,有没有家人相关的明显变化?」。没有具体年份,事件也不是一件能点是/否的家事。改成「有没有家人结婚、添丁或住院?」后仍缺时间,无法反推。
- 触发条件:家人域没有已记带日期经历;区分探针年份为 0(D12 分盘对比);或目录家族仍是「家人相关的明显变化」。
- 根因:口语题干用
period + 有没有 + event_family拼接。D12 对比探针没有年份,同域也没有带日期证据,period落成占位「那段时间」。第一轮误修成去掉年份、只问「有没有发生过」。反推点选必须锁在具体年/月上,没有时间就不能计分。 - 修复:存在性 / 发挥质量点选在锁不住具体年份时失败关闭,不出卡。目录跳过无年份 D12,改问下一条有年份的区分题;若没有,用自然语言采集一件记得住时间的家事。已记家人年份时题干为「2018 年前后,有没有家人结婚、添丁或住院?」。各域存在性家族改成可核对的口语事件。GET 把已持久化的「那段时间 / 目录锁」升级成带年份的新句。
- 验证:
rectification-choice-card锁定无年份家人帧为null;有引擎年份的家事探针才带年。rectification-eight-method锁定无年份 D12 让位给有年份的事业探针,或退回家人自然语言采集;已开的无年份家人卡不再保持评分帧。无年份 D24 不得借用账本学业年。方法层齐后剩下无年份 D4 时,先用自然语言问记得住时间的搬家,不出无年份点选卡。 - 防复发:评分用 A/B/C/D 反推不得用「那段时间」或无年份的「有没有发生过」顶时间。不得用年龄带中点发明年份。家人存在性家族不得再展示「家人相关的明显变化」。
- 相关记录:BUG-412、BUG-413
- 复发自:BUG-413(锁标签改成「有没有 + 家族」后,空年份仍被当成可问的点选卡)
- 修复版本:待发布
BUG-415 | 反推点选借用账本年份,点选后又多一条确认气泡
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:生时纠正点选卡
periodFor、冲突探针去重、answer_choice续跑、bindSpokenToOpenQuestion - 用户现象:点选卡问「2016 年前后有没有学业或考试发挥失常」。该年不是候选分钟反推出来的,而是账本里已记的学业年。点选 A/B/C/D 后先出现「已记录你的选择,并更新了候选比较。」,再出现工具「读取校正记录」和第二条确认/下一问。
- 触发条件:D24
event_quality探针年份为 0,同领域已有带日期证据;同时 Python 已给出另一领域带月份的 dasha 冲突探针。点选成功且nextAction仍要继续提问。 - 根因:评分反推把账本同领域年份当成锁年。冲突探针按整个领域去重,已有事业经历后不再问 2012 年 11 月事业边界。点选成功会持久化确认 turn,客户端再把它落成助手气泡,随后
read_only续跑又写第二条。 - 修复:存在性 / 发挥质量点选只认引擎探针年或月,不再回落到账本同领域年份。冲突探针按年去重,同年跳过、不同年继续问。无年份 D24 让位给带年份的 dasha 题。点选后续跑时不写确认 turn,客户端丢掉点选占位气泡,只保留续跑那一条;续跑正文去掉「已记录选择 / 请看下方选项」。
- 验证:
rectification-choice-card锁定无年份 D24 不得借用 2016 学业年,GET 展示 2012 年 11 月事业题。rectification-eight-method锁定同年事业探针跳过、不同年仍问,无年份 D24 让位给 2023 事业 dasha。rectification-answer-choice锁定续跑不写append_agentic_rectification_turn,「先这样」仍写。rectification-v9-stream锁定点选确认句被剥掉。 - 防复发:评分 A/B/C/D 反推不得用账本同领域已记年份顶引擎时间。冲突探针不得按领域整段跳过。点选后续跑不得再展示独立确认气泡。
- 相关记录:BUG-402、BUG-411、BUG-412、BUG-414
- 复发自:BUG-414(无年份家人卡修好后,无年份 D24 仍借用学业年);BUG-402(续跑补上后多写了一条确认)
- 修复版本:待发布
BUG-416 | 生时纠正口语被攒到整轮结束,点选后续跑不再逐字出现
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
runV9AgentTurn、step-answer、公开answer.delta、生时纠正聊天气泡 - 用户现象:工具活动仍会更新,但助手正文不再边生成边出现;点选卡回合尤其明显,进度走完后整段或占位句一次性弹出。
- 触发条件:生时纠正对话里模型在工具之后写口语;当前有
open_question/ 点选卡时更重。 - 根因:BUG-373 把含工具调用的 step 的
text-delta整段丢掉,只在step-finish发布终端步,口语无法按 token 直播。随后open_question把已写出口语放进heldSpoken,等到流结束再bindSpokenToOpenQuestion一次性发出。点选续跑还会把确认句剥掉,用户先看到长时间空白。 - 修复:终端中文在比对/结果类工具之后按
text-delta直播;未解锁时等到中文句末再开始直播。规划英文和工具步正文仍丢弃。有点选锁时边生成边绑定,缩短则发replace覆盖,不再等到 flush。客户端按replace重写气泡。 - 验证:
rectification-step-answer锁定比对后分片直播、工具步英文不进answer.delta、直播后若再调工具则 retract。rectification-v9-stream锁定比对后两段answer.delta按序到达,点选锁下确认句直播且不问句、timeout 仍用占位句。rectification-activity-receipt/rectification-agentic-entry锁定客户端replace。 - 防复发:有
open_question时不得把口语攒到flushPersistedPrompt。禁止把含工具调用的 step 的规划句留给浏览器。answer.delta若会缩短必须带replace,客户端不得只做raw +=。 - 相关记录:BUG-373、BUG-377、BUG-415
- 复发自:BUG-373(隔离终端步后不再按 token 发布);BUG-415(点选绑定把已推迟的口语又剥成空白)
- 修复版本:待发布
BUG-417 | 未成年事业/搬家点选,点选后续跑复述旧经历并因截断报未完成
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
event_probes.pydasha boundary、method-followup年龄下限、bindSpokenToOpenQuestion、点选后续跑answer_truncated - 用户现象:1997 年出生后被问「2012 年 11 月有没有入职升职」「2005 年 5 月有没有搬家离乡」。点选后续跑正文反复说「实习和离职都记下了」。选 C 后出现「回答未完成,已保留现有内容;本次不会扣点。」
- 触发条件:候选在童年/少年有高区分度大运边界;账本已有事业或感情经历;点选后续跑模型复述旧确认并写满长度。
- 根因:BUG-399 只给感情加了 catalog
age_lo,事业/搬家/学业仍从出生后 5 年起算。邻近年去重只覆盖学业。点选锁只剥问句,不剥跨领域确认。finish_reason=length即使已有open_question也走失败,超时则不会。 - 修复:成人语义领域(学业/搬家/感情/事业/财务/健康)的 dasha 边界都从
birth_year + age_lo起算,仍不用age_hi作硬上限。家人事件仍从出生后 5 年起算。感情/事业/搬家存在性探针跳过邻近年。点选锁下跨领域确认句丢掉。有点选锁时长度截断与超时一样收成成功占位句,不报未完成。 - 验证:
tests/test_rectification_event_probes.py锁定 1997 年出生不得出 2012 事业、2005 搬家,已记 2024 感情不得再问 2023。rectification-eight-method锁定出生日期过滤童年事业探针、邻近年感情探针。rectification-v9-stream锁定搬家/感情锁下丢掉实习确认,以及open_question+length仍run.completed。 - 防复发:成人语义事件家族不得用出生后 5 年当地板;家人事件仍允许童年年。点选续跑不得把上一件经历的确认句带到另一领域卡片。有
open_question时answer_truncated不得再把整轮标失败。 - 相关记录:BUG-399、BUG-415、BUG-416
- 复发自:BUG-399(只修了感情未成年年)
- 修复版本:待发布
BUG-418 | 点选后刷新,助手说请点选但 GET 没有卡片
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
applyRectificationChoice、GETchoice_card、点选后续跑 - 用户现象:点完区分题后助手仍写「接下来有一个问题需要您点选一下」,刷新后这条还在,下方没有点选卡。
GET的choice_card为null。账本里该问已经答过。 - 触发条件:训练门已开、当前区分焦点被点选关闭;同一请求没有写下一条焦点。客户端按
nextAction再开一轮只读续跑。刷新发生在续跑写完新焦点之前。 - 根因:GET 投影卡片必须有已持久化的焦点 UUID。点选只关闭当前焦点,下一条区分题或带年份的采集问要等后续 Agent 轮。续跑失败、截断或用户刷新时,界面只剩「请点选」占位,没有可点的卡。无年份 D24 也不能顶成评分卡。
- 修复:点选成功后在同一请求里按更新后的推断状态排出下一问。下一条是带年份的区分题就写入新焦点,正文改成「接下来请点选下面这一问。」;下一条是家人等采集就写入带年份的口语问,不出无年份 D24 卡。这两种都不再自动续跑。
- 验证:
rectification-answer-choice锁定答完搬家区分题后持久化 2014 学业卡,刷新 GET 仍有focus_id。只剩已答区分题时写入 2021 年家人采集口语,不调用 set-focus。rectification-eight-method锁定家人采集带上 collection-probe 年份。shouldContinueAfterStructuredChoice在nextInterviewPersisted时为 false。 - 防复发:点选关闭焦点后,同一请求必须留下下一问(新焦点或口语采集)。不得在 GET
choice_card为空时继续展示「请点选」。无年份 D24 不得在点选后变成评分卡。 - 相关记录:BUG-411、BUG-410、BUG-413、BUG-414、BUG-415、BUG-417
- 复发自:BUG-411(已持久化的卡会被丢掉;这里是下一问根本没持久化)
- 修复版本:待发布
BUG-419 | 生时纠正 holdout 落到年份事件,无星座 D9 被丢掉,题干把写作说明给用户
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
split-holdout、candidate-contrast-packet、method-followup出题目录 - 用户现象:训练/盘外划分时可能把只有年份的经历留作 holdout,把日/月精度事件拿去打分。D9/D10 窗口扫描没有星座名时整题消失。区分题题干出现「不得发明年份」「Opportunity」。账本里写过「程序员」一类职责说明后,剩余 D10 区分题被当成已经问过。
- 触发条件:至少两个领域都有两条带日期经历、其中只有事业带月/日精度;或 D9 只有换盘时刻没有 from/to 星座;或剩余分盘题进入点选;或职业备注含职责关键词而 D10 尚未真正答过。
- 根因:holdout 回退把月日事件与全部事件拼在一起再取最后一个,年份事件排在后面。D9/D10 缺星座时仍坚持
varga_style,选项凑不齐就被丢掉。remainingQuestion把给模型的写作说明写进question。账本关键词生成的varga.d10被当成已答 asked key,前缀匹配跳过整层剩余题。 - 修复:没有单领域孤立事件时,holdout 取最后一个月/日精度事件。D9/D10 凑不齐风格选项时降为存在题,不删题。
question改为对人的短问,写作说明放进authoringHint。账本关键词只作 mentioned/新颖度惩罚,不跳过剩余分盘题;只有已答semantic_key或显式varga.d10才跳过。存在题weak_yes仍与yes同向半权、一次作答不淘汰。 - 验证:
rectification-split-holdout锁定无孤立领域时 holdout 是日精度事业而不是年份感情。rectification-candidate-contrast-packet锁定无星座 D9 降为存在题、题干不含 Opportunity、程序员提及仍保留 D10 但选未提及的 D4。rectification-distinguish-contract锁定存在题weak_yes与yes同映射半权。rectification-decide-next-action/rectification-eight-method改为 mentioned 与 answered 分集。 - 防复发:不得把账本关键词生成的
varga.d*写进 packetaskedKeys。不得在 D9/D10 缺星座时继续要求varga_style四个风格选项。不得把不得发明年份写进对人的question。holdout 回退不得再把年份事件排在月日事件后面取最后一个。 - 相关记录:BUG-410、#39
- 复发自:无
- 修复版本:待发布
BUG-420 | 候选快照缺指纹被当成 current,stale 时把区分打回采集
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
decideFromDossier、scoreableSnapshotIsCurrent、decideRectification、GET/工具candidate_snapshot - 用户现象:已进入区分的 Case 在快照过期或缺证据指纹时,下一问变回采集,点选卡消失。工具侧和 dossier 侧对同一份账本可能一个判 stale、一个判 current。
- 触发条件:已有区分探针和训练门;
latest_result缺少evidence_ledger_fingerprint,或账本指纹已变但决策器仍走snapshotCurrent === false → collect。 - 根因:dossier 用
!storedFingerprint || stored === currentfail-open;工具用!storedSnapshot || !fingerprint || scoreableSnapshotIsCurrent同样 fail-open。decideRectification把snapshotCurrent === false与方法覆盖缺失绑在一起打回event_collection。 - 修复:两处都走
candidateSnapshotSource+storedSnapshotIsCurrent。指纹缺失判fingerprint_missing,不得当 current。没有已存快照仍不算 stale。快照 stale 且仍有区分探针时保持ask_candidate_discriminator,receipt 写stale_reason,要求重算;不把区分打回采集。不改 Skill。 - 验证:
rectification-decide-next-action锁定缺指纹为 stale;训练已开+探针时snapshotCurrent: false仍是ask_candidate_discriminator;训练未开仍采集。rectification-decision-authority无指纹的事业/感情训练账本仍区分。rectification-eight-method锁定缺指纹拒绝offer-candidates;暂停逃生口和覆盖后的 34/33/33 平局只在指纹匹配时出牌。 - 防复发:不得把空指纹写成 current。不得在已有区分探针时用 stale 快照把
nextAction改成ask_fact_collection。不得为了暂停出牌或平局出牌把空指纹 fail-open。occupation_note仍不得单独让 scoreable 快照失效。 - 相关记录:BUG-410、#40
- 复发自:无
- 修复版本:待发布
BUG-421 | 工具投影与 turn_decision 各判一次,单候选被写成并列区间
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
safeCaseProjection、latestResultToolProjection、evaluateCandidateSeparation、GET/工具session_outcome - 用户现象:同一 Case 的 turn_decision 与 full_diagnostics 可能给出不同的
session_outcome/selection_allowed。只剩一个候选时仍被说成基本并列。 - 触发条件:read-case 同时被问 turn_decision 与 full_diagnostics;或候选集收敛到 1 个分钟后继续出牌/写报告。
- 根因:
safeCaseProjection自己跑decideConversationalSession并可能第二次buildMethodFollowupPlan,latestResultToolProjection可在没有决策时把会话猜成collect_evidence。单候选把 lead 伪造为 8,状态仍是not_separated,报告按并列区间写。 - 修复:
safeCaseProjection开头decideFromDossier,方法计划只编一次。latestResultToolProjection必收RectificationDecision并用overlayPublicDecision。单候选状态为sole_candidate,不再伪造 lead。canConfirmExactMinute仍只由确认门打开。不改 Skill。 - 验证:
rectification-decision-authority锁定projectTurnDecision与safeCaseProjection的session_outcome/selection_allowed相同,工具源不再decideConversationalSession。rectification-decide-next-action锁定单候选是sole_candidate、可收口为代表时间、报告不含「基本并列」、确认门仍关。 - 防复发:不得在投影层再猜
sessionOutcome ?? "collect_evidence"。不得为单候选伪造MIN_SEPARATION_LEAD。不得把单候选写成并列区间。 - 相关记录:BUG-410、#41
- 复发自:无
- 修复版本:待发布
BUG-422 | 丢题不可见、契约无 golden、工具表与提示词重复
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
completeStyleOptions/isRenderableProbe、GETcurrent_question、Mastra Agent 工具表、contracts/probe-question-v1.json - 用户现象:区分探针因外貌词、标签不足或无法渲染被静默丢掉,界面和工具投影都看不到原因。选择题 schema 坏了时
current_question变成null,模型继续自拟题。采集阶段的提示词重复 Skill 里已有的外貌/财务禁令和工具调用表。 - 触发条件:varga 风格标签含禁词或不足两项;已持久化 Focus 的 choice schema 缺四选项;
proposeAllowed为 false 时模型仍看到offer-candidates。 - 根因:选项补全和可渲染检查只返回成功数组或
null,没有reason。读路径把坏 schema 当成「没有题」。Agent 工具表不看decideFromDossier。TS/Python 合同没有 byte-equal golden。uniquify曾用answer_class当可见后缀。 - 修复:补全/可渲染返回
{ ok, options|reason }。inspectDiscriminatorProbes把丢题写入decision.droppedProbes和 receiptdropped_probes。GET/turn_decision对坏选择题返回{ unrenderable: true, reason },采集类 Focus 仍为null。createRectificationV9AgentTools(ctx, decision)按 phase /proposeAllowed/selectionAllowed/canConfirmExactMinute过滤工具。合同 golden 在contracts/probe-question-v1.json,引擎 client 拒不匹配的question_contract_version。重复标签用·2而不是weak_yes。Mastra 提示词删掉已由代码执行的外貌/财务/工具禁令,Skill 保持 10.0.13。lib/birth-time-*仍被app/与components/的 guided/journey/intake 与/api/birth-time-journey、/api/birth-time-guide引用,本批不删。 - 验证:
rectification-probe-question-contract锁定 result type、index uniquify、golden bytes、forbidden_copy 丢题。Pythontest_probe_question_contract同样对齐 golden。rectification-answer-choice锁定坏 schema 为 unrenderable、采集 Focus 仍 null。rectification-server-focus锁定不完整 choice schema 为 unrenderable 而不是可点选 prompt。rectification-v10-tool-contract锁定proposeAllowed为 false 时没有offer-candidates。rectification-v9-engine-contract锁定错误合同版本 fail-closed。rectification-v9-agent/rectification-agentic-entry/rectification-ingest-p0/rectification-confirmation-gate锁定瘦身后的提示词(confirmation_allowed、不得宣称唯一出生分钟、新事件走 rectification-record-evidence-batch),不再要求 Mastra 提示词复述confirmation_gate。Independent Staging Quality Gate 必须通过后才算部署。 - 防复发:不得把
completeStyleOptions/isRenderableProbe改回只返回数组或 boolean。不得把坏选择题投影成current_question: null。不得在无决策时按 phase 过滤工具后,再把offer-candidates写进提示词禁令。不得用answer_class当可见标签后缀。不得在本路径删除仍被 journey/guide UI 引用的birth-time-*。不得把 Skill 升出版本。 - 相关记录:BUG-403、BUG-421、#42
- 复发自:BUG-403(动态四选项合同未贯穿丢题原因)
- 修复版本:待发布
BUG-423 | 写盘 still-valid 区间与读盘全体跨度不一致,区分永远收不了口
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
authoritativeCandidateProjection、GET/工具候选投影、decideFromDossier的candidateScores - 用户现象:区分环节答再多题也走不到
ask_holdout_validation或ready_to_adopt,最后落到offer_provisional_range且representative_time为 null。第一次打分、两候选 58/42 时投影就已经是空列表。 - 触发条件:receipt 由
buildInferenceState写入unionStillValidRange(落后峰值 ≥ 8 的候选不进区间),读盘却要求credible_range等于全体 active 跨度。lead ≥ 8 时两者必然分叉。 - 根因:写盘口径是 still-valid 并集,读盘校验口径是全体 active 跨度,阈值同为
MIN_SEPARATION_LEAD = 8,构成闭环死锁。consistent = false清空scores,evaluateCandidateSeparation判不出已拉开。既有 fixture 把分差写成 < 8 或手写宽 range 冒充生产者输出,所以测试红不了。 - 修复:
receiptRangeMatches改与unionStillValidRange(inference.candidates)比对,投影对外的credibleRange返回该 still-valid 并集。每个 activecluster_range落在全体跨度内的包含性校验保留。没有inference_state、坏 candidate set、或窗口外/颠倒的 range 仍 fail-closed。不改阈值、不改确认门、不改 Skill。 - 验证:
rectification-credible-range-projection用buildInferenceState/buildCaseInferenceState锁定 58/42、50/30/20、单候选、无 inference;58/42 决策层separation.sufficient且ask_holdout_validation或ready_to_adopt,canConfirmExactMinute === false。rectification-eight-method把「窄 range 当损坏」改为窗口外["04:00","04:10"]。rectification-decision-authority的损坏 range 同样改为窗口外或首尾颠倒。 - 防复发:不得让写盘与读盘对
credible_range使用不同口径。不得用手写credible_range字面量的 fixture 覆盖这条生产者路径。不得把consistent = false改成 fail-open。不得用改MIN_SEPARATION_LEAD绕过死锁。 - 相关记录:BUG-403、BUG-421
- 复发自:BUG-403(候选卡、代表时间、可信区间必须共享 inference 投影,但读写口径未钉成同一函数)
- 修复版本:待发布
BUG-424 | quarter/range 精度训练门计入、推断账本却标 unused
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
datedPrecision、buildCaseInferenceState的asPrecision - 用户现象:账本允许
quarter/range精度。训练门把它们当成带日期事件开门,推断状态却把同一批事件标成unknown/unused,既不打分也不进 holdout。 - 触发条件:录证据工具写入
datePrecision: "quarter"或"range"后重建inference_state。 - 根因:
evidence-model.datedPrecision把quarter/range映射成year,inference-adapter.asPrecision映射成unknown。 - 修复:导出同一个
datedPrecision,推断适配器也走它。空值仍是unknown。 - 验证:
rectification-v9-contracts锁定quarter/range→year。rectification-credible-range-projection锁定quarter事件usage !== "unused"。 - 防复发:训练门与推断账本不得对同一
datePrecision使用两套映射。 - 相关记录:BUG-423
- 复发自:无
- 修复版本:待发布
BUG-425 | varga 风格题按 answer_class 半权,时间靠前的组被系统性抬高
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-29
- 影响面:
ConflictProbe.choice_kind、conflictProbesFromContrast、applyProbeOutcome/directionFor - 用户现象:D9/D10 风格点选里,选项是并列的盘面风格,不是“有/弱有”。选后一组只得一半分,更早分钟被系统性抬高。
- 触发条件:剩余候选按 varga 风格分组出题;组 0 映射
yes(±2),组 1 映射weak_yes(±1)。 - 根因:
directionFor只看answer_class。风格题复用weak_yes当第二组标签,却走了存在题的半权。引擎若发来无style_options的varga.d9/varga.d10题,打分仍用原始choiceKind,渲染已降为existence。 - 修复:探针带显式
choice_kind。varga_style各组满权 ±2,unsure仍为 0。存在题 /event_quality的weak_yes仍半权且一次作答不淘汰。缺字段的旧收据保持旧分。不改SCORE_DELTA数值。conflictProbesFromContrast改用effectiveContrastChoiceKind,与渲染降级一致。 - 验证:
rectification-varga-style-weight锁定两组/三组风格等权、旧收据无choice_kind仍半权、replay 时candidate_set_id不变且revision单调;varga_style的weak_yes计入strong_conflict_count且连答 3 次可淘汰对面分组;引擎varga.d9/varga.d10无style_options时打分choice_kind与渲染 effective kind 一致。rectification-distinguish-contract锁定存在题weak_yes半权。rectification-coverage-collect锁定分数已拉开且家人/职业未覆盖时仍停在采集,且采集下一问是未覆盖的 blocking method;补齐或拒绝家人+职业后离开采集;canConfirmExactMinute === false。 - 防复发:不得从
semantic_key前缀猜测风格题权重。不得把存在题weak_yes改成淘汰或反向。varga_style的 B 选项weak_yes必须与 A 一样计入强冲突。打分用的choice_kind必须等于渲染用的 effective kind。旧收据缺choice_kind必须仍能加载。不得打开 unique-minute 门。 - 相关记录:BUG-419、BUG-410
- 复发自:无
- 修复版本:待发布
BUG-426 | 无年份 sticky holdout 仍进入核对,点选卡却渲染不出来
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-29
- 影响面:
holdoutStatusFromInference/holdoutStatusFromState、decideFromDossier、holdout 点选卡 - 用户现象:保留的 holdout 后来变成未知精度、没有年份后,决策仍要
ask_holdout_validation,出题计划却给不出可渲染卡片。 - 触发条件:
stickyHoldoutEvents把已保留 holdout 钉住;该事件year === null或precision: "unknown";或validate_holdout时既没有oosBlindPrompts也没有带年份的 holdout。 - 根因:holdout 状态只看
usage === "holdout"。能否出题看的是带年份的事件或 oos 提示。两套口径不一致。 - 修复:抽出共用 helper:能问(oos 提示或 dated holdout)才是
not_started;否则unavailable。unavailable且用户未停走既有adopt_representative,不当成passed。带年份 holdout 追问补上存在题选项和probe_year,好渲染 choice frame。不改applyHoldoutAnswer,不改 stickiness。 - 验证:
rectification-holdout-renderable锁定未知精度 sticky holdout 不进入ask_holdout_validation且可ready_to_adopt;带日期 holdout 有可渲染choice_frame;通过为validated_range;失败路径不变;nextAction === ask_holdout_validation时 followup 与choice_frame均非空。canConfirmExactMinute === false。 - 防复发:不得只凭
usage === "holdout"打开核对。不得把unavailable当成已通过。ask_holdout_validation必须能渲染出卡(next_followup与choice_frame均非空)。不得打开 unique-minute 门。 - 相关记录:BUG-424、BUG-410
- 复发自:无
- 修复版本:待发布
BUG-427 | 密封 holdout 合同把冻结 scorer 的成绩读成当前引擎成绩
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
references/rectification_sealed_holdout.v1.json、确认门 holdout 合同 - 用户现象:合同已回写 v3 的
top_1_rate=0.15,但当前树 sidecar 诊断是 0.45。读者会把冻结 scorer 的成绩当成当前引擎发布指标。 - 触发条件:读密封 holdout 合同、对照当前树
implementation_sha256,或把 sidecar 非盲诊断数字当成发布门槛。 - 根因:合同只写了指标数字,没有绑定产生这些数字的
implementation_sha256,也没有标明当前树是否仍匹配、更新的非盲诊断在哪。 - 修复:补充
metrics_produced_by/current_tree_scorer/current_tree_unfrozen_diagnostic。六个既有键的键名、类型、数值不动。sidecar 0.45 只记在带限定条件的诊断对象里。删除未引用的 v2PILOT_REPORT_PATH。确认门保持关闭。 - 验证:
holdout_passed()为 False;test_rectification_confirmation_and与rectification-confirmation-gate锁定身份哈希来自报告文件、sidecar 不得写入六个键、门仍关闭。 - 防复发:评测指标必须与产生它的
implementation_sha256绑定记录。当前树哈希漂移时不得把合同数字读成当前引擎成绩。不得把非盲、结果已被看过、scorer 未冻结、source audit 未过的 sidecar 数字写入发布六键。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-428 | v4 intake 把已看过、迁移审阅的案例标成密封盲测材料
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
minute_rectification_holdout_v4_intake.json、intake 脚本、holdout validator 人审安全条款 - 用户现象:4 例带着
independent_human_reviewed=true和holdout_partition=sealed_evaluation,但adjudicator是自动化迁移,评分结果已在 v3 盲测回放和 post-audit sidecar 里被看过。 - 触发条件:把 v3 密封集迁入 v4 intake 后,用
case_errors(..., require_review_safeguards=True)当盲测资格。 - 根因:人审安全条款只看布尔量;intake 接受该布尔量即可写成已审。曝光记录和人审来源没有一等字段。
- 修复:4 例留在队列并标
prior_score_exposures、human_review_kind=migrated_v3_record、frozen_before_scoring=false、holdout_partition=exposed_awaiting_human_rereview。带曝光记录的案例不得过盲测校验;只有fresh_biography_audit满足人审条款。intake 不再在缺少该字段时把案例写成已审。盲测可用 0 例、已曝光待复审 4 例。生产调参与精确分钟声称仍为 false。 - 验证:
tests/test_minute_rectification_holdout_intake.py、tests/test_minute_rectification_holdout_validator.py。 - 防复发:结果已被看过的案例不得再计入盲测。人审安全条款不得由自动化脚本自行置真。迁移来的审阅记录不满足新的传记级人工核查。
- 相关记录:BUG-427
- 复发自:无
- 修复版本:待发布
BUG-410 | 训练已齐仍因家人/职业方法层停在采集,Agent 只确认后截断
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
decideRectification、GETinterview、Mastra 出题计划、生时纠正 Agent 口语 - 用户现象:事业和感情各记下两件带日期经历后,助手只说“记下了”,不再追问,也没有点选卡。引擎已经算出高信息量 D24 区分题。
- 触发条件:训练事件达到 3 条/2 个领域(holdout 不计),家人与职业方法层仍未覆盖,候选尚未拉开,目录里已有可渲染区分探针。
- 根因:
methodCoverageAll把relatives和occupation当成进入区分的硬门槛。决策器因此一直返回collect_evidence。出题计划却按信息量选出 D24。Agent 被禁止在没有open_question时口述区分题,采集下一问又不是家人/职业,正文只剩确认句,choice_card为空。 - 修复:训练门已开、候选未拉开、且存在区分探针时,不再被未覆盖的家人/职业挡回采集。分数已经拉开时仍不得因缺方法层而直接采用。提示禁止因家人或职业未覆盖改回收集。
- 验证:
rectification-decide-next-action锁定未拉开或公开分数字段被清空时,训练已齐+探针 →ask_candidate_discriminator;已拉开+方法层未齐仍采集。rectification-decision-authority用事业+感情训练账本锁定 D24 区分题和 choice frame。rectification-eight-method锁定训练已齐后 dasha 冲突探针的会话结果是discriminate_candidates,不再停在collect_evidence。 - 防复发:不得把家人/职业方法覆盖当成区分阶段的前置条件。不得在
session_outcome=collect_evidence且下一问是区分题时把追问整段丢掉。 - 相关记录:BUG-400、BUG-405、BUG-407
- 复发自:BUG-407(目录能选出 D24,决策器仍被方法覆盖挡在采集)
- 修复版本:待发布
BUG-409 | staging publish 的 next build 因 answered_probes.id 与 optional receipt 失败
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:Gitea
backend-quality-gate.ymlpublish、deploy/railway-web.Dockerfile的RUN npm run build、contrastPacketFromLatestResult、Mastrarectification-v9-tools - 用户现象:向
staging推送月份精度修复后,run2128validate 通过,publish 在 web 镜像next build失败。没有 dispatchDeploy staging。公网仍停在上一成功 SHA。 - 触发条件:staging push 的 validate 跳过
npm run build;publish 才在 Docker 里跑 TypeScript。 - 根因:
answered_probes的字段是probe_id,目录过滤写成了不存在的item.id,tsc报 TS2339。工具侧latestResult.decisionReceipt可为undefined,目录函数要求| null,报 TS2345。BuildKit 日志末尾仍是skill-package-registry.ts的 Import traces,真正失败是Failed to type check。 - 修复:已答探针按
probe_id过滤。目录函数接受undefinedlatest,并把缺省 receipt 收成null再往下传。 - 验证:
npx tsc --noEmit;rectification-decision-authority锁定已答探针按probe_id退出目录。 - 防复发:改生时纠正目录或 Mastra 工具类型后,推 staging 前必须跑
npx tsc --noEmit或等价next build,不能只跑tsx --test。 - 相关记录:BUG-309、BUG-315、BUG-321、BUG-371、BUG-408
- 复发自:BUG-309(validate 跳过
next build,publish 才暴露类型错误) - 修复版本:待发布
BUG-408 | 反推题丢掉大运起点的月份,同年 3 月对 9 月问不出来
- 状态:resolved
- 首次发现:2026-08-28
- 最近更新:2026-08-28
- 影响面:
scripts/rectification/event_probes.py、点选卡choice_frame.period、Skill10.0.13区分阶段时间范围 - 用户现象:反推点选卡只写「YYYY 年前后」。候选分钟只差十几三十分钟时,大运/副运起点其实已经错开几个月,但题目仍按年问;同一年里 3 月对 9 月这种差被丢掉。
- 触发条件:训练事件已够、剩余候选的 Vimshottari/Narayana 大运或副运起点相差不足一年,但月份已分开。
- 根因:探针只取起点
.year,边界还要求两边年份至少差 1 年,评分固定落在当年 7 月 1 日。引擎算到的月日被扔掉。 - 修复:边界改用大运/副运实际起点。年份不同,或同年且相差至少 45 天,按该月出题并按该月计分。标签写成「YYYY 年 M 月前后」。年龄带兜底仍只锁年。点选卡直接用探针
year_label,不得再从年份拼回「年前后」。开场仍不诱导用户猜月份。Skill 版本保持10.0.13。 - 验证:Python 事件探针回归锁定同年 3 月/9 月边界、微小同年漂移仍不出边界题、年龄带仍是年精度;前端点选卡与 method-followup 锁定「2018 年 3 月前后」。
- 防复发:
dasha_boundary必须带引擎月份;不得把起点截成年后再比。年龄带/无起点时不得伪造月份。 - 相关记录:BUG-405、BUG-407
- 复发自:无
- 修复版本:待发布
BUG-429 | 同一账号资料变化时星盘库被两个 effect 各写一遍 localStorage
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/主对话页星盘库 hydration 与本人记录 upsert - 用户现象:进入对话或改一次出生资料时,主线程同步
JSON.stringify并写localStorage两次;账号切换时两条路径靠 ref 提前 return 分工,没有显式分支。 - 触发条件:
accountId或profile引用变化。 - 根因:两个
useEffect依赖数组都是[accountId, profile],一个负责首次本地+云端,一个无条件 upsert 本人。行为能跑通,但每次资料变化都重复序列化。 - 修复:合并为一个 effect,按
clear/hydrate-then-persist/persist-self显式分支。云端失败仍保留本地库;本人记录仍 upsert;切账号仍清空。 - 验证:
frontend/tests/chart-library-session.test.ts覆盖清空、切账号 hydration、同账号 persist-self、云端只保留 other、云端失败保留本地。既有chart-library-other-profile源码锁未改。 - 防复发:同一份资料不得用两个同依赖 effect 靠 ref 提前 return 分工;分支名必须写在代码里。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-430 | 字体栈声明了 Inter 却从未加载,Windows/Linux 静默落到系统黑体
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-09-05
- 影响面:全站
--font-body、后台 antdfontFamily - 用户现象:设计稿写了 Inter,实际请求里没有 woff2;非苹果系统落到 Segoe UI / 微软雅黑。
- 触发条件:打开任意带根布局的页面。
- 根因:仓库 0 个字体文件、0 处
@font-face、0 处next/font,CSS 与 admin token 却把 Inter 写在 StyreneB 之后。 - 修复:根布局用
next/font/local加载仓内InterVariable-latin.woff2(display: "swap"、--font-inter)。早期曾用next/font/google,构建期会访问 Google Fonts,见 BUG-543。--font-body与 adminfontFamily改为var(--font-inter, Inter)。Tiempos / 宋体栈未改。构建产物与/HTML 可见 woff2 preload。 - 验证:
frontend/tests/site-style-isolation-contract.test.ts;next build的/HTML 含 Inter woff2 preload。 - 防复发:字体栈里出现的西文家族必须有
next/font/local(仓内字体文件)或@font-face;不得用next/font/google。缺授权的展示字体(Tiempos)不得用同一套办法偷偷补上。 - 相关记录:BUG-543
- 复发自:无
- 修复版本:待发布
BUG-431 | 自托管 PostgreSQL 缺库时仍对用户写“Supabase 尚未配置”
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
GET /api/account、会员余额、登录失败提示、会话/报告/星盘库等 503 - 用户现象:运行时已是 Better Auth + 本地 PostgreSQL,缺业务库 URL 或未设
AUTH_PROVIDER=self-hosted时,会员页“当前余额”和首页仍显示“Supabase 尚未配置”。 - 触发条件:未注入
APP_DATABASE_URL/SERVICE_DATABASE_URL,或AUTH_PROVIDER未设为self-hosted因而落到旧适配器路径。 - 根因:BUG-010 已禁止浏览器直连 Supabase,但 API 与
friendlyError仍把配置失败翻译成 Supabase 文案和SUPABASE_NOT_CONFIGURED。产品已不用 Supabase。 - 修复:对外统一为“数据库尚未配置” /
DATABASE_NOT_CONFIGURED,以及“请先配置数据库环境变量。”。内部类名仍叫SupabaseConfigurationError,避免本轮改适配器标识。 - 验证:
frontend/tests/self-hosted-error-copy.test.ts扫描frontend/src禁止上述旧文案,并锁定错误码与内部消息。 - 防复发:用户可见中文不得再出现“Supabase 尚未配置”;缺库 503 必须说数据库,不得说已退役的托管服务。
- 相关记录:BUG-010
- 复发自:BUG-010
- 修复版本:待发布
BUG-432 | 生时校正区分题出题层静默丢弃探针后无卡无记账死锁
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
POST /api/rectification/agent消息分支、decideFromDossier、buildMethodFollowupPlan、口述采集 focus - 用户现象:点选两道区分题后,助手口述家人采集问但没有选项卡。用户用自由文本回答后,服务端回没有出路的终结话术;
current_question与choice_card均为 null,case 停在collecting_evidence;该回合 receipt 的 phases / tools / tool_activities 全空,用户这句话里的新证据没有入账本。 - 触发条件:训练证据已齐、已答 dasha 边界探针、剩余探针含无年份 varga 与缺
style_options的varga_style;方法覆盖未齐时出题层回落到口述采集;随后用户在无 active choice focus 时发自由文本。 - 根因:
inspectDiscriminatorProbes用withCompletedContrastOptions把缺style_options的varga_style降级后仍视为可渲染,并选出无年份 varga 探针进入ask_candidate_discriminator。renderableContrastProbe仍走未降级的completeStyleOptions,把同一探针静默丢弃。无年份桶只在覆盖齐时使用,于是回落到source=method_coverage的家人采集且不带choice_frame。路由只信决策层:persistServerOwnedFocusskipped、openQuestionFromPersistedFocus为 null 后直接返回终结话术,既不落证据也不跑 Agent。口述采集题不落 focus,刷新后问题丢失;卡片持久化失败时返回无正文下一步。 - 修复:出题层与决策层共用
withCompletedContrastOptions;丢弃探针写入dropped_probes。仅当出题层给出intent=distinguish_candidates且带choice_frame的 followup 时才进入ask_candidate_discriminator,否则落到采集。消息分支拿不到可渲染卡且仍有 followup 时落入runV9AgentTurn;仅当next_followup为 null 时才收口,话术给出继续采集或按区间看盘。口述采集持久化不带 choice 的 collect focus;卡片持久化失败时落到口述采集下一步。冲突探针写回时保留style_options。收尾加固:口述采集投影带kind=collect_spoken,current_probe/inference.next_probe只对可渲染选择题放行;降级口述采集剥掉区分探针身份(semantic_key/ 年份 /probe_id),正文与 schema prompt 同一次生成;卡片失败后的假分支删除,focus 持久化失败按safeErrorCode/ caseId 打脱敏日志;decideFromDossier把可选birthDate传给门控 plan,门控通过时decision.probe取 plan 真正选中的探针。点选路径persistApplied先加载一次 compute,把同一birthDate喂给decideAfterInferenceChange与后续出题 plan。Skill 版本保持10.0.13。未放宽 confirmation gate,未增加「已确认唯一分钟」路径。 - 验证:
frontend/tests/rectification-discriminator-followup-consistency.test.ts锁定同一探针池上决策层与出题层一致、decision.probe与plan.next_followup.semantic_key一致、缺style_options的 D9/D10 风格题在方法覆盖未齐时仍出区分卡、无星座时钟键 fail-closed 到采集、低龄年份探针传入birthDate时门控与真实 plan 结论一致、消息路径不再返回死路话术。rectification-answer-choice.test.ts锁定口述采集后仍有 collect focus 与current_question,collect 时current_probe为空、选择题时非空,降级采集不含区分题身份,点选路径persistApplied在decideAfterInferenceChange之前加载 compute 并传入同一birthDate。rectification-server-focus.test.ts锁定 collect schema 不是未渲染区分卡,降级采集的 questionId / schema 不含区分题键且同一探针随后可出卡。rectification-turn-intent-classifier.test.ts按新收口话术改断言。npx tsc --noEmit、npx tsx --test tests/rectification-*.test.ts tests/birth-time-rectification-contract.test.ts tests/agentic-rectification-*.test.ts与npx tsx --test tests/*.test.ts。 - 防复发:可渲染判定必须共用选项补全,不得一边降级接受一边静默丢弃。无卡时不得把用户消息当终结话术丢掉。有合法 choice schema 的 active focus 仍走轻量分类 +
applyRectificationChoice。不得引入语义正则、A/B/C/D 位置推断或静态选项 fallback。口述采集题不得占用区分探针身份。无选择卡的问题必须由 Agent 正文问出来,current_probe只对可渲染选择题放行。 - 相关记录:BUG-404、BUG-407、BUG-410、BUG-411
- 复发自:BUG-404
- 修复版本:待发布
BUG-433 | 根错误页和 404 在摘掉 globals 后变成裸 HTML
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
src/app/error.tsx、src/app/not-found.tsx;任意未匹配路由与根渲染崩溃 - 用户现象:访问不存在的路径或触发根错误边界时,页面不居中、无配色、无间距,按钮塌成无样式文字。
- 触发条件:根布局不再 import
globals.css之后,这两页仍用 Tailwind 工具类和Button。 - 根因:它们与 admin 共享根布局段,不能 import
site-styles;工具类定义只在站点 CSS chunk 里。上一轮把“必须另开布局链”写成唯一出路,漏了仓库里global-error.tsx已有的内联样式模式。 - 修复:照
global-error.tsx改为模块常量 + 内联style,fallback 色值与globals.csstoken 一致。错误页“重试”用原生 button,“返回对话”用next/link。404 仍保留既有源码锁render={<Link href="/" />},用cloneElement套内联样式,不引入Button。 - 验证:
frontend/tests/site-style-isolation-contract.test.ts新增断言(既有断言未改);frontend/tests/root-error-boundaries-contract.test.ts保持绿灯;next build的_not-found.htmlbody 无min-h-svh等工具类;admin 路由 CSS 仍不含.message-list。 - 防复发:根布局段页面(
error.tsx/not-found.tsx/forbidden.tsx/global-error.tsx)只能用内联样式,不得依赖工具类或globals.css/site-styles。 - 相关记录:BUG-219、BUG-430
- 复发自:无
- 修复版本:待发布
BUG-434 | 入门付费墙的关闭按钮掉到标题下面,弹窗头部从未有过样式
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/入门问题完成后弹出的「解锁完整咨询」弹窗,移动端最明显 - 用户现象:标题「解锁完整咨询」独占一行,关闭「×」以灰色圆角块的形式落在标题下方靠左,和礼物图标那段挤在一起;头部与正文之间没有间距。
- 触发条件:打开该弹窗即出现,与账号状态无关。
- 根因:
onboarding-redeem-paywall.tsx写的是<div className="dialog-header">,而dialog-header这个类在全仓 CSS 里没有任何定义。同类弹窗(account-dialog-overlay.tsx、membership/page.tsx)用的都是<header className="account-modal-header">(display:flex; justify-content:space-between; padding-bottom)。付费墙自a17ff258引入起就拼错,未被任何测试或样式覆盖,因此一直是无样式块级 div,标题与按钮竖排。 - 修复:改为
<header className="account-modal-header">,与另外两处弹窗完全一致。未新增任何 CSS,未改文案、aria-label="关闭"、aria-modal、焦点陷阱与 44px 触达。 - 验证:
frontend/tests/membership-page.test.ts新增两条 —— 一条锁付费墙必须复用account-modal-header且不得再出现dialog-header;一条通用守卫,抽出该组件所有静态className逐个断言在globals.css里有对应选择器。反向验证:把dialog-header放回去,两条同时变红;还原后 38 pass / 0 fail。 - 防复发:弹窗头部一律用
account-modal-header,不得为单个弹窗另造头部类名。新增组件若引入新类名,必须同时在globals.css落规则 —— 上面那条通用守卫会挡住这类拼写漂移。 - 相关记录:BUG-433
- 复发自:无
- 修复版本:待发布
BUG-435 | 删除会话确认弹窗的两个按钮没有任何样式,"确认删除"也不是红色
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/侧边栏删除会话时弹出的「删除聊天记录?」确认框 - 用户现象:「取消」和「确认删除」渲染成浏览器原生灰色按钮 —— 没有圆角、没有 44px 高度、没有内边距,破坏性操作也没有任何红色提示。
- 触发条件:打开该确认框即出现。
- 根因:
page.tsx:4000的「取消」完全没写 class;page.tsx:4001的「确认删除」写的是danger-button,而这个类在全仓 CSS 里没有定义。全局只给button设了font: inherit; color: inherit,没有任何兜底规则,所以两个按钮都是裸原生按钮。同一文件 60 行之前的退出登录确认框用的是正确写法button-secondary+button-primary danger-primary。 - 修复:两个按钮改成与退出登录框完全一致的
button-secondary/button-primary danger-primary。未新增 CSS,未改文案与role="alertdialog"。星盘库「删除」上同样无定义的danger-button修饰符一并去掉(它本就没生效,渲染不变)。 - 验证:
./node_modules/.bin/next build后用类名扫描脚本核对,danger-button已从"无规则类名"清单消失;tsc --noEmitexit 0、eslint0 error、全量测试无新增失败。 - 防复发:破坏性确认框一律复用
button-secondary+button-primary danger-primary,不得临时造修饰符。新类名必须同时在globals.css落规则 —— 参见 BUG-434 在membership-page.test.ts里加的通用守卫。 - 相关记录:BUG-434
- 复发自:无
- 修复版本:待发布
BUG-436 | 主对话消息间距规则从 8-19 起失效,消息比设计稿挤
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/主对话(非空、非生时校正会话)里相邻消息之间的竖向间距 - 用户现象:消息之间只剩气泡自身的内边距,比设计稿定的
--space-8(移动端--space-6)窄。 - 触发条件:任何有历史消息的普通咨询会话。
- 根因:
globals.css的.conversation:not(.is-empty):not(.is-rectification) .message + .message { margin-top: var(--space-8) }写于200c39d2(2026-07-22),当时消息是.message-list的直接兄弟。7d667fec(2026-08-19)给每条消息套了一层<div className="message-entry">,而这个类在 CSS 里没有任何定义,两条.message从此不再相邻,选择器永久不匹配。同一规则组里.message又被覆盖成padding: 0,于是间距归零。3198fb6b把同一层包装搬进ChatTranscript,未改变这一点。 - 修复:选择器改为
.message-entry + .message-entry,桌面--space-8、移动--space-6数值不变。.message-list里除.message-entry外只有可选的.error-message,不会误伤。引导语消息在.welcome内且只在.is-empty时渲染,本就被选择器排除。 - 验证:
frontend/tests/class-name-definition-contract.test.ts(本次新增的通用守卫)反向验证 —— 把选择器改回.message + .message后message-entry立即被判为无规则类名,测试变红;改回则绿。 - 防复发:给消息列表加包装层时必须同步迁移依赖相邻兄弟的选择器。新类名漏定义由上述守卫兜底;该守卫会剥掉 CSS 注释再判定,避免"只在注释里出现过"被当成已定义。
- 相关记录:BUG-434、BUG-435
- 复发自:无
- 修复版本:待发布
BUG-437 | 区分卡因过期候选集前缀被二次加前缀,界面无卡且 Agent 只承接不提问
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
probeFromEngine的candidate_split_hash归一化、projectRectificationChoiceCard身份校验、scoreAndPersistCurrentEvidence组包的candidateSetVersion、GETchoice_card - 用户现象:
current_question是合法选择题(kind=choice、focus_id与probe_id齐全、题干已生成),choice_card却是 null。Agent 按提示第 4 条只承接不复述,正文只剩「请你凭印象选一个」,界面没有卡片,用户无法继续。dropped_probes为空,看不出被抑制的原因。 - 触发条件:某一轮打分改变了候选分钟列表(例如 05:05 换成 05:00),此前铸出的 varga 对比探针仍带旧候选集前缀,随后任意一次读路径重建对比包。
- 根因:
scoreAndPersistCurrentEvidence组对比包时candidateSetVersion取上一轮inference_state.candidate_set_id,candidateTimes取本轮score.candidates,于是新写入的探针 hash 带着旧集合前缀,而同一份 state 的candidate_set_id按新候选生成。之后probeFromEngine用hash.includes(candidateSetVersion)判断是否已有前缀,旧前缀不含新集合 id,于是再加一层前缀得到双前缀 hash。落 focus 时stampChoiceSchemaWithProbe写的是 state 里探针的单前缀原值,projectRectificationChoiceCard的candidate_split_hash全等校验因此永远不成立,直接 return null,且不记入任何丢弃账本。 - 修复:新增
splitHashForCandidateSet,hash 为<候选集 id>:<语义键>形态时按当前候选集重铸,已带当前前缀时保持不变,不透明引擎 hash 只加一次前缀,杜绝前缀叠加。新增isSameCandidateSplit,两侧同为<任意候选集 id>:<同一语义键>时视为同一 split(语义键本身已编码候选分组),projectRectificationChoiceCard改用它做身份校验,语义键不同或不透明 hash 不同仍然 fail closed。组包candidateSetVersion改为按本轮candidateRange与score.candidates计算的candidateSetId,与写入 state 的候选集同源。Skill 版本保持10.0.13,未放宽 confirmation gate。 - 验证:
frontend/tests/rectification-choice-card.test.ts锁定「探针 hash 带旧候选集前缀时 GET 仍返回卡片」(去掉修复即失败)与「持久化 split 属于另一探针时仍隐藏」;rectification-candidate-contrast-packet.test.ts锁定前缀重铸幂等、不透明 hash 只加一次、split 身份忽略前缀但不忽略探针、组包按本轮候选集作用域。npx tsc --noEmit;npx tsx --test tests/rectification-*.test.ts tests/birth-time-rectification-contract.test.ts tests/agentic-rectification-*.test.ts658 项 0 失败;全量npx tsx --test tests/*.test.ts失败集合与改动前一致(23 项数据库/部署环境类)。 - 防复发:探针
candidate_split_hash只能有一层候选集前缀,任何读路径不得对已带前缀的 hash 再加前缀。打分写入的对比包必须与本轮候选集同源,不得沿用上一轮candidate_set_id。卡片身份校验不得因候选集前缀差异静默吞掉合法卡片。 - 相关记录:BUG-410、BUG-411、BUG-432
- 复发自:无
- 修复版本:待发布
BUG-438 | 移动端侧边抽屉里「新建对话」「我的报告」居中悬空,与其余左对齐项不齐
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/移动端(<768px)侧边抽屉顶部两个导航项 - 用户现象:品牌行、星盘列表、收藏对话、历史对话全部左对齐,只有「新建对话」和「我的报告」的图标+文字组居中飘在抽屉正中,视觉上完全不成列。
- 触发条件:在移动端打开侧边抽屉即出现。
- 根因:
.new-chat/.report-nav-button的基础样式是justify-content: center(为桌面折叠图标栏准备),左对齐靠顶层的[data-state="expanded"] … { justify-content: flex-start }翻回来。但data-state取的是open ? "expanded" : "collapsed",open是桌面折叠状态;移动抽屉由独立的openMobile控制。组件侧isCollapsedDesktop = state === "collapsed" && !isMobile,移动端恒为 false,所以文字标签照常渲染;CSS 侧却因data-state="collapsed"匹配不上覆盖规则,按钮保持居中。JS 与 CSS 对"展开"的定义不一致。品牌行没被带歪,是因为[data-state="collapsed"] .brand-row那整套图标栏规则写在@media (min-width: 768px)里,移动端本就不生效。 - 修复:把默认翻过来 —— 基础样式改为
justify-content: flex-start(移动抽屉与桌面展开态都正确),删掉依赖data-state的顶层覆盖,居中改为@media (min-width: 768px)内的[data-state="collapsed"]规则,与既有的.brand-row、[data-sidebar="menu-button"]、.profile-trigger等图标栏规则并列。全仓仅此一处顶层[data-state=…]选择器,其余均已按视口收口。 - 验证:
frontend/tests/sidebar-contract.test.ts新增一条 —— 锁基础层左对齐、禁止再出现[data-state="expanded"]版覆盖、并要求居中规则位于 768px media 内。反向验证三次(基础样式改回 center / 覆盖加回来 / 居中规则移出 media),每次都变红。 - 既有断言改动(唯一一处):
frontend/tests/personal-report-entry.test.ts原先断言.report-nav-button必须justify-content: center,那正是本条缺陷本身,改为flex-start并注明原因。该断言其余部分(display: flex、align-items: center)未动。 - 防复发:
data-state只描述桌面折叠态,不得用它决定移动抽屉的布局;折叠图标栏的样式一律写进@media (min-width: 768px),不得作为基础层默认值再由覆盖翻回。 - 相关记录:BUG-434、BUG-436
- 复发自:无
- 修复版本:待发布
BUG-439 | 回答里的围栏代码块没有任何样式,长代码行撑破消息栏
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
/主对话中 Agent 回答里的 ``` 围栏代码块 - 用户现象:代码块没有容器,行内代码的小胶囊底色套在整块代码上;一行长代码不换行也不滚动,直接溢出消息栏,移动端会把版面顶横。
- 触发条件:回答里出现围栏代码块。
- 根因:
globals.css只有.message-markdown code(行内胶囊),没有任何pre规则。react-markdown 输出的裸<pre>保留浏览器默认的white-space: pre且无overflow,内容宽度不受.message-content的max-width约束。.message-content上的min-width: 0只能让 flex 收缩,挡不住块内溢出。 - 修复:新增
MarkdownCodeBlock组件接管pre,输出「语言标签 + 复制按钮」的头条和可横向滚动的代码区;.markdown-code pre > code设overflow-x: auto并重置行内胶囊底色,容器overflow: hidden收住圆角。未引入语法高亮依赖。 - 验证:
frontend/tests/markdown-code-block-contract.test.ts锁overflow-x: auto与white-space: pre必须同时存在、胶囊底色必须重置、pre必须走该组件、复制按钮有 aria-label、剪贴板被拒时不得误显示「已复制」、卸载时清定时器。 - 防复发:给 markdown 新增块级元素时必须同时给出容器与溢出策略;行内与块级代码的样式不得共用一条规则。
- 相关记录:BUG-434
- 复发自:无
- 修复版本:待发布
BUG-440 | 生时校正采集阶段否定后无下一问,对话空转
- 状态:resolved
- 首次发现:2026-08-29
- 最近更新:2026-08-29
- 影响面:
buildMethodFollowupPlan方法覆盖口述题、classifyRectificationTurnIntent采集焦点、applyCollectFocusDenial/persistNextInterviewIfIdle、POST/api/rectification/agent自由文本路径、GETcurrent_question - 用户现象:点完若干区分卡后,助手口述问家里有没有结婚、添丁或住院这类记得住时间的事。用户回「没有」。助手只确认「记下了」,随后
current_question与choice_card均为 null,interview.type = ask_fact_collection,对话停住,无法继续。 - 触发条件:方法覆盖未齐(尤其 relatives),剩余可渲染探针都是无年份分盘题;用户对家人口述采集题给出明确否定,且该轮走自由文本而不是点选。
- 根因:三段同时成立才会空转。(1) 「没有」既不是证据也不是计分答案,
answered_probes与 revision 不变;若焦点以resolved关闭,declined_skipped_topics只聚合status in ('declined','skipped'),buildMethodFollowupPlan又只认target_domain,覆盖度不前进。(2) 无年份分盘题被coverageComplete挡在 dated 排序之后,计划回落到同领域口述采集题;家人覆盖要求「有家人证据或有 declined 记录」,家里确实没发生过事的用户两条都不满足,而varga.d12本就是这道家人题、no分支可以计分淘汰候选,却被当成不计分采集题问出去并丢失。(3) 点选路径有persistNextInterviewAfterChoice保证下一问落库;自由文本路径跑完runV9AgentTurn后没有同等兜底。Agent 不重复刚被否掉的问题(这点是对的),于是只确认不提问,current_question变成 null。 - 修复:P0:即将返回某领域
intent=collect_method_evidence+source=method_coverage口述题时,若rankRenderableDiscriminators的 yearless 桶里有同领域可渲染探针,改出该 distinguish 卡(followupFromRanked,按既有rankDiscriminatorScore取最高)。未放宽 coverageComplete 对 yearless 的总排序,避免回归 BUG-405/407。无年份 varga 探针允许铸出 choice_frame,不得向题干借用账本年份。P1:采集焦点自由文本接入同一套轻量分类器;明确否定时服务端把焦点 resolve 成declined(不是resolved),并按点选路径持久化下一问。同步修正rectification-resolve-focus工具描述。未引入语义正则、关键词表或 A/B/C/D 位置推断。focusStatusForAnswer的no → declined语义未改。P2:runV9AgentTurn成功后若无 open focus 且 plan 仍有 followup,按点选路径补落下一问。收尾:(a) 分类器新增可选has_new_dated_event,缺失按 false;选择题与采集题快路径在该字段为 true 时先确定性地应用答案(计分 / declined),不落确定性正文、不 return,继续runV9AgentTurn记同一句里的新带时间事件;为 false 时行为与原先逐字一致。应用答案时deferFollowup,让 Agent 看到没有 active focus,不得再 resolve。(b)persistNextInterviewIfIdle预检改为先decideFromDossier,用同一个decision.sessionOutcome建 plan,并复用该 decision 做publicNextAction,不再写死collect_evidence。Skill 版本保持 10.0.13,未放宽 confirmation gate。 - 验证:
frontend/tests/rectification-collect-stall.test.ts锁 D12 卡、答 C declined 计分、occupation 否定推进 horary、自由文本后current_question非 null、有年份区分题仍优先。新增:缺失has_new_dated_event按 false;采集/选择题快路径在该字段为 true 时应用答案后进入 Agent 且不落确定性正文;采集否定 + deferFollowup 不抢先落下一问;选择题 deferFollowup 仍计分且不落下一问/正文;persistNextInterviewIfIdle同一 dossier 只调用一次decideFromDossier,预检 sessionOutcome 与决策层相同。npx tsc --noEmit通过;eslint 改动文件 0 error。指定套件tests/rectification-*.test.ts tests/birth-time-rectification-contract.test.ts tests/agentic-rectification-*.test.ts672 项、0 失败。全量tests/*.test.ts2266 项、0 失败。失败集合未扩大到产品逻辑。 - 防复发:同一领域的问题若存在可渲染计分卡,不得改用不计分口述题。采集题的明确否定必须产生 declined 记录,不得写成 resolved。出题层还有 followup 时
current_question不得为 null。不得为了救这条空转而取消 coverageComplete 对 yearless 的总限制。确定性快路径不得吞掉同一句话里的新带时间事件。同一次调用里出题层与决策层必须用同一个 sessionOutcome。 - 相关记录:BUG-404、BUG-405、BUG-407、BUG-411、BUG-432、BUG-437
- 复发自:无
- 修复版本:待发布
BUG-441 | 口述采集题已落库但界面看不到,助手只说看下面这一问
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:GET
current_question、openQuestionFromPersistedFocus、agentVisibleLatestProjection、stableFollowupQuestionId、POST/api/rectification/agent出卡判定与空正文兜底、Agent 系统提示第 4 条。可见性策略已被 BUG-449 取代。 - 用户现象:助手正文只写「接下来看下面这一问。」,界面没有任何问题。GET 里
choice_card为 null,current_question已是合法口述采集题(kind=collect_spoken、prompt 非空),问题只存在于数据库和 API。 - 触发条件:方法覆盖回落到不带 choice_frame 的口述采集焦点;Agent 按选择卡承接措辞说话;前端只解析
choice_card。 - 根因:三段同时成立。(1) 前端从不渲染
current_question:applyCaseSnapshot只解析latest_result/choice_card/case,kind=collect_spoken在界面上没有承载位置。(2) 面向 Agent 的两套投影自相矛盾:projectTurnDecision给出current_question.kind=collect_spoken与 prompt;记完证据后的agentVisibleLatestProjection取自openQuestionFromPersistedFocus,后者对 collect focus 一律返回 null。Agent 收到打架信号,退回「卡片会显示」的措辞。(3)stableFollowupQuestionId在 followup 既无 semantic_key 也无 probe_year 时退到${method_id}:${ask_theme};persistPlanFocus传入的 active_focus 计划正好两者都没有,questionId 退化成active_focus:active_focus。 - 修复:P1/P2 仍在:
openQuestionFromPersistedFocus对 collect focus 返回带kind=collect_spoken的同形结果;choiceReady/ 出卡判定只对kind=choice为真;current_probe与inference.next_probe仍只对可渲染选择题放行;collect 在无 semantic_key / probe_year 时用collect:<domain>:<intent>,active_focus:active_focus不得与新 id 互相 already_open。P0 独立提示条已撤回:前端不再解析或渲染current_question。口述采集题改由 Agent 用自己的话在正文里问出来(该可见性策略已被 BUG-449 取代:题干改由服务器按焦点生命周期确定性接在正文之后,模型不得自行写题干)。「请点选」「看下面这一问」仍只允许在确实有选择卡时使用。服务端 collect focus 继续持久化,GET 仍返回current_question。Skill 保持 10.0.13,未放宽 confirmation gate。 - 验证:
frontend/tests/rectification-spoken-collect.test.ts锁:GETcollect_spoken不再渲染独立块、.rectification-spoken-collect-prompt不存在、选择卡分支仍在。collect openQuestion 不为卡片、probe 在 collect 下为空、active_focus 派生 questionId 带 domain。可见性回归见 BUG-449。BUG-440 collect-stall 全绿,D12 同领域计分卡仍优先于家人口述题。 - 防复发:服务端问题的可见性由正文与确定性兜底保证,不得为无选项的问题新造视觉容器,更不得借用选择卡的样式类。同一焦点在所有面向 Agent 的投影里形态必须一致。「请点选」类措辞只允许在有选择卡时使用。不得用正文文本判断助手有没有问出来。collect questionId 不得退化为
active_focus:active_focus。口述采集题的可见性策略见 BUG-449,不得再改回「指望模型问」。 - 相关记录:BUG-404、BUG-411、BUG-432、BUG-437、BUG-440、BUG-449
- 复发自:BUG-432(无选择卡的问题曾要求由 Agent 正文问出,前端仍不渲染
current_question) - 修复版本:待发布
BUG-442 | 职业采集关不上、无年份卡出不来、覆盖门压掉区间出口
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
applyOccupationCollectLedgerNorm/occupationCovered、buildMethodFollowupPlan无年份分盘卡、decideRectification覆盖门、POST/api/rectification/agent区间出口、houseTableAlignedToMinute/publicChartForMinute - 用户现象:账本已有足够可计分事件和多个领域,助手仍反复追问职业口述题;候选几乎均匀,
margin_percent停在门槛以下,entropy 不降;can_offer_range/can_adopt/propose_allowed/selection_allowed全为 false,用户从头到尾没被告知可以按当前区间收口。对外代表分钟与宫位表也可能不是同一时刻。 - 触发条件:职业采集焦点下用户给出职业性质描述,模型把证据记成
domain=career的入职/换工作;训练事件已达标,剩余高信息量探针全是无年份分盘题;引擎回执已accept_allowed/propose_allowed,决策层仍因 occupation 覆盖 flag 落collect()。 - 根因:四段同时成立。(1) 覆盖判定
isOccupationNote要求domain=occupation,职业口述回答被记成 career 域,occupation 方法层永不关闭,next_followup恒为职业口述采集题。(2) 无年份分盘卡的出题条件绑在coverageComplete上,覆盖不齐则 d24/d7/d4/d5 一道都出不来,margin 无法被这些探针拉开。(3)decideRectification撞上coverageBlocks直接collect(),把canOfferRange压成 false;同一份引擎回执已经放行 accept/propose,决策层因覆盖 flag 单方面压掉区间出口。(4) 对外投影的representative_time取推理层代表分钟,house_table/natal_recast却可能是另一分钟算出的盘面。 - 修复:P0:occupation 采集焦点下,服务端把该轮职业性质描述归一成
domain=occupation/eventKind=occupation_note(无日期允许,occupation_note 不参与计分)。覆盖端:已有确认 career 证据且 occupation 采集焦点已按 questionId / target_domain 关闭时也算覆盖,不读正文、不用关键词。P1:训练门开时允许出无年份分盘卡,不再要求覆盖齐;训练门未开仍不出,避免回到 BUG-405/407。同领域可渲染计分卡仍优先于不计分口述题(BUG-440)。P2:训练门已开且出题层给不出带 choice_frame 的可渲染区分卡时,允许canOfferRange并播报「可以先按当前区间看盘,也可以再补一件记得时间的经历」;引擎 accept/propose 已放行而覆盖 flag 要压掉时至少放行canOfferRange。canAdopt与 confirmation gate 维持 fail-closed。P3:对外representative_time、house_table、natal_recast必须是同一分钟;对不上则取house_tables_by_time对应表,否则标成不可用。Skill 保持 10.0.13。未引入语义正则、关键词表、A/B/C/D 位置推断或轮次状态机。 - 验证:
frontend/tests/rectification-occupation-coverage-exit.test.ts用本例 15 件可计分、4 域账本锁:occupation 采集回答落 occupation_note;career 证据 + 已关闭 occupation 焦点覆盖成立;career 单独仍不覆盖;训练门开且覆盖未齐能出无年份分盘卡,训练门关仍不出;训练门开且无可渲染区分卡时canOfferRange为真、canAdopt/canConfirmExactMinute为假;路由播报二选一;代表分钟与house_table/natal_recast同源,构造不一致回执不得直接发出。collect focus 仍不是选择卡。BUG-440 collect-stall、BUG-437 hash、D12 同领域卡优先保持绿。 - 防复发:覆盖判定不得依赖模型选对 domain。同一个覆盖 flag 不得同时锁死出题与出口。训练门已开且无可渲染区分卡时必须给用户出路。对外代表分钟与盘面必须同源。不得用正文文本判断覆盖或意图。
- 相关记录:BUG-404、BUG-407、BUG-410、BUG-432、BUG-437、BUG-440、BUG-441
- 复发自:BUG-440(覆盖未齐时的收口分支存在,但 occupation 记账域错误导致永远走不到;无年份卡仍被同一覆盖门锁死)
- 修复版本:待发布
BUG-443 | D9/D10 风格卡用了类型表原文、题干贴了年份、题干贴着选项
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
varga-type-tables的d9StyleLabel/d10StyleLabel、completeStyleOptions风格题固定项、choice-card题干、birth-time-choice.csslegend、.rectification-choice-why - 用户现象:D9/D10 风格点选卡选项是「和谐、美感、合作」「照顾、情感」这类类型表词条,三个抽象名词并列、维度不统一,无法对号入座。题干出现「2024–2026 年这段里,做事风格更接近其中一种?」;风格题没有「这段」可指。题干与第一个选项几乎没有间隔。
- 触发条件:候选在 D9 或 D10 上升上分开,出
choice_kind=varga_style点选卡。 - 根因:三段同时成立。(1)
d9StyleLabel/d10StyleLabel直接输出D9_TYPE_TABLE[sign].trait/D10_TYPE_TABLE[sign].style,解读用的占星类型表字段被当成用户选项。(2)choice-card.ts的periodFor对varga_style走lifePeriodLabel(evidence, domain),eventQuestionPrompt再把时间段贴到长期风格题干上;风格描述的是平时怎么做 / 怎么相处,不随年份变化。(3).birth-time-choice-question用 gridgap分隔子项,但题干是<legend>,legend 不参与 fieldset 的 grid 布局,gap 对题干与选项不生效。 - 修复:类型表为 D9/D10 每行新增面向用户的
userChoice,不改trait/occupation/style/spouse/marriage。标签函数改输出userChoice;answer_class与 sign 的映射不变。风格题no改为「都不太像我」,unsure 单独为「一时说不好」;existence / event_quality 固定选项文案不变。varga_style不再返回时间段,题干写成长期表述。legend 与.rectification-choice-why用--space-3下外边距,不把 legend 改成p、不新增视觉容器。Skill 保持 10.0.13。未改计分、探针身份或 hash。 - 验证:
frontend/tests/rectification-varga-style-copy.test.ts锁:12×2userChoice非空且不等于trait/style;sign →answer_class仍按组序;风格卡题干无年份/「这段」;existence / event_quality 仍锁年份且固定选项不变;四选项isRenderableStyleOptions;legend / why 源码具有非零下边距。修复前:标签等于类型表原文、题干带lifePeriodLabel年份、「这段记不清楚」用在风格题、legend 仅margin-bottom: var(--space-1)。合同金标contracts/probe-question-v1.json同步。 - 防复发:面向用户的选项文案不得直接复用占星类型表字段。长期风格题不得锁定时间段。卡片内题干与选项必须有独立于 grid gap 的间距。existence / event_quality 的年份月份锁定(BUG-408、BUG-412)不得被风格题改动带回。
- 相关记录:BUG-408、BUG-412
- 复发自:无
- 修复版本:待发布
BUG-444 | 无年份分盘对比题重新出成无星座依据的点选卡
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
isRenderableProbe/inspectDiscriminatorProbes/rankRenderableDiscriminators/buildChoiceFrame/shouldAttachChoiceFrame、无年份 remaining-layer 分盘对比题 - 用户现象:家里有没有结婚添丁住院、有没有搬家、学业压力、收入变化、生病受伤这类题可以在题干没有任何时间范围的情况下出成四选一点选卡;选项仍写「明确发生且时间吻合」「这段记不清楚」。答了以后按分组顺序灌进后验,候选被无依据地拉开或压掉。
- 触发条件:
REMAINING_LAYERS产出year: 0的分盘对比探针;训练门已开;出题层把无年份varga.*一律当成可渲染计分卡。 - 根因:三段同时成立。(1) 为满足 BUG-440「同领域可渲染计分卡优先于不计分口述采集题」,把
choice-card的无年份豁免从性格倾向题放宽到所有semantic_key以varga.开头的探针,重新打开了 BUG-414 关掉的门。(2) 存在类和质量类固定选项预设了题干里没有的时间范围(「时间吻合」「这段」)。(3) 非varga_style的分盘对比题remainingStyleOptions不绑定星座,remainingOutcomes按分组顺序映射 yes / weak_yes / no,属于无依据计分。 - 修复:没有具体年份或月份时,只有
choice_kind === varga_style且计分选项带sign才允许铸卡。同一条判定走共享的isRenderableProbe,同时作用于inspectDiscriminatorProbes与rankRenderableDiscriminators。被排除的探针进入dropped_probes,理由yearless_ungrounded_contrast。题干无时间范围时选项不得含「时间吻合」「这段」。oos_blind且无年份的 followup 显式不得挂choice_frame。覆盖门与区间出口保持 BUG-442:训练门已开且没有可渲染区分卡时仍可canOfferRange。计划回落到带年份的采集题。未改 confirmation gate、answer_class 映射、探针身份或 hash。Skill 保持 10.0.13。 - 验证:
frontend/tests/rectification-yearless-ungrounded.test.ts逐层锁无年份 d4/d7/d12/d2/d11/d30(existence)与 d24/d5(event_quality)不出卡;带 sign 的无年份 d9/d10 仍出卡,缺 sign 的 varga_style 不出卡;带年份的 dasha_boundary / age_band / known_event_quality / reserved_event 仍出卡;被排除探针出现在dropped_probes;决策层与出题层同一探针池结论一致;oos_blind 无年份无卡。修复前:上述无年份 existence/quality 层会铸卡,决策可能落ask_candidate_discriminator。BUG-440 collect-stall、BUG-437 hash、BUG-442 occupation 覆盖与区间出口、BUG-414 无年份家人卡、BUG-443 风格文案保持绿。 - 防复发:只有选项绑定星座的性格倾向题可以没有时间范围;其余分盘对比题必须有具体年份或月份;无时间范围的卡不得有预设时间的选项;被规则排除的探针必须记入
dropped_probes;不得为了「有题可问」重新放宽本条。判定必须看kind与sign字段,不得按层名白名单,不得对题干做语义分析。 - 相关记录:BUG-432、BUG-440
- 复发自:BUG-414
- 修复版本:待发布
BUG-445 | 深色主题次要字色与行动色在卡片底上对比度不足 4.5:1
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:深色主题 token、围栏代码块语言标签、根边界页行动色
- 用户现象:深色模式下卡片底上的次要说明文字和黏土橙行动色发灰发糊,代码块语言标签是其中一处已上线的实例。
- 触发条件:系统偏好或账户菜单选择深色;任何把
ink-tertiary或action画在canvas-muted上的界面。 - 根因:对比度合同只验了字色对阅读面
--color-canvas,漏掉了卡片底--color-canvas-muted。实测--color-ink-tertiary#928e84对#30302d为 4.05:1,--color-action#d4785a为 4.18:1,均低于 WCAG AA 4.5:1。 - 修复:两个深色块把
--color-ink-tertiary改为#9c988e、--color-action/--color-focus/--report-accent改为#d78064(色相 15°、饱和度不变,只提亮明度)。四个根边界页内联 token 与DESIGN.md对照表同步,清掉#d4785a。 - 验证:
frontend/tests/dark-theme-contract.test.ts把对比度断言扩成 8 种字色 × 4 种底色,失败信息打印具体配对和比值;反向把 tertiary 改回#928e84后该测试变红。 - 防复发:新增字色或底色 token 时必须进入这张 32 组矩阵,不得只验阅读面;不得靠放宽 4.5 阈值让测试通过。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-446 | 回答操作等控件热区小于文档承诺的 44px
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:回答下方赞/踩/复制/重新生成、侧栏星盘 chip、登录页链接按钮、模型选择项、生时跳过、报告中心章节按钮
- 用户现象:回答下方四个图标按钮只有 26×26px 且没有 padding,很难点准;若干列表项和链接也低于 44px。
- 触发条件:任意一条助手回答下方的操作行;侧栏星盘 chip;登录页链接;onboarding 下拉;报告中心章节操作。
- 根因:
DESIGN.md写了 44px,样式却把高频图标做成 26px/padding: 0,chip 32px,若干文字按钮 40px。 - 修复:视觉尺寸不动。图标行与 chip、登录链接用透明伪元素扩大热区。选择项、跳过、报告章节按钮把
min-height提到 44px。回答操作四个按钮中心距只有 27px、下方 follow-up 只有 8px 边距,44×44 会互相吃点击,热区停在不重叠的 27×34;chip 与登录链接因 8px 间距停在 40px。见BLOCKED.md。 - 验证:
frontend/tests/touch-target-contract.test.ts锁 26px 视觉、伪元素尺寸、以及三处min-height >= 44。改前改后截图对比视觉尺寸。 - 防复发:新增可点元素必须满足 44px 命中区;视觉小于 44px 时用伪元素补,且扩大后的热区不得与相邻可点元素重叠。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-447 | 字数上限只有 maxLength,到顶后界面毫无反应
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:主对话输入框、姓名输入、生时引导经历框、选择题补充说明
- 用户现象:打到字数上限后键盘像失灵,没有提示说明已经到顶。
- 触发条件:主输入写满 500 字(入门称呼为 80)、姓名 80、引导经历 500、补充说明 240。
- 根因:四处只有
maxLength,没有接近上限时的可见反馈,也没有aria-describedby。 - 修复:剩余字数进入阈值(500→50,短输入按 10% 且不低于 8)才挂载计数;平时不显示。计数用
ink-tertiary,到顶转danger。aria-live="polite"且aria-atomic="true",可见数字不进 live 文本,避免每字播报。主输入计数放进已有.composer-footer,不新造一层把输入框顶高。 - 验证:
frontend/tests/character-remaining-contract.test.ts锁阈值、两句 live 文案、四处aria-describedby接线。 - 防复发:带
maxLength的产品输入必须在阈值内给出计数,并只在阈值内挂载 live region。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-448 | 生时引导框用 ⌘/Ctrl+Enter 发送且没有任何说明
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
birth-time-guide-turn经历输入框 - 用户现象:主对话和生时校正对话都是 Enter 发送,到引导框按 Enter 只会换行,界面没有任何键位说明。
- 触发条件:生时引导流程里在「说说这段经历」多行框按 Enter。
- 根因:该框绑定了修饰键发送,且未处理
isComposing;另外两处已经是 Enter 发送并忽略组字中的 Enter。 - 修复:统一为 Enter 发送、Shift+Enter 换行,并加上
!event.nativeEvent.isComposing。保留「整理为经历草稿」按钮。在既有#birth-time-guide-hint补一句键位说明。选择统一发送键而不是只补提示,是因为用户已在另外两处养成 Enter 习惯,漏掉isComposing会让中文选词误发送。 - 验证:
frontend/tests/composer-ime-contract.test.ts锁三处onKeyDown都判断isComposing,引导框不再看metaKey/ctrlKey。 - 防复发:产品内多行发送框必须同一套 Enter / Shift+Enter /
isComposing合同;新的输入框不得只改发送键不处理组字。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-449 | 口述采集题已落库但自由文本后助手只说记下了,用户看不到下一问
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:POST
/api/rectification/agent自由文本路径、persistNextInterviewIfIdle、persistCollectSpokenAssistantIfNew、runV9AgentTurn口述题可见性、Agent 系统提示第 4 条 - 用户现象:答完一件事后助手只说「记下了:…」,之后没有任何问题也没有卡片,但 GET 里
current_question已是合法collect_spoken题。 - 触发条件:自由文本记下带年份经历(本例唯一证据为 2022 搬家、
precision=year);轮内rectification-record-evidence-batch→autoRescoreAfterEvidenceChange→persistPlanFocus新建collect:relationship:collect_method_evidence;助手正文非空。 - 根因:五段同时成立。(1)
persistNextInterviewAfterChoice对 collect 焦点返回hostNarration = spokenFollowupForUser(followup);点选路径把它写进正文,所以点选没坏。(2)persistNextInterviewIfIdle调用同一个函数,却丢弃hostNarration,只返回{persisted, choiceReady}。自由文本路径因此没有题干输出。(3) 它还在开头if (dossier.conversationSummary.activeFocus) return提前返回;本 case 的焦点是轮内persistPlanFocus建的,所以这条兜底连跑都没跑。(4) route 唯一的兜底persistEmptyCollectSpokenAssistant只在!result.answerText.trim()时触发;正文非空一律不补。(5) 选择卡不受影响(卡片由current_question渲染);区分题有discriminatorInvariantfail-closed。collect 两样都没有。BUG-441 把可见性改成「指望模型问」后,非空正文不再由服务器保证。 - 修复:P0:在
runV9AgentTurn已有的首份 dossier 上快照activeFocus.id;轮后若projectCurrentQuestion为collect_spoken且 focus id 与轮前不同(含轮前为 null),服务器补题干。同一 id 不补。choice 永不补。P1:题干复用spokenFollowupForUser/ schemaprompt;非空正文作为独立一段接在同一条助手消息后并answer.delta;空正文整条消息就是题干。补出文本过INTERNAL_TOKEN_RE,不得含「请点选」。agent-run 与 route 共用 helper + 本轮collectSpokenEmitted,同一轮最多一次。P2:persistNextInterviewIfIdle返回hostNarration,交给 P1;activeFocus 提前返回保留,但不吞掉 P0。P3:撤回「口述采集题必须由你用自己的话问出来」;有持久化当前问题时题干一律不由模型写。Skill 保持 10.0.13。未放宽 confirmation gate / coverageComplete,未回退b43808b0无年份收窄。未用正文文本判断是否已问,未新造视觉容器。 - 验证:
frontend/tests/rectification-spoken-collect.test.ts锁:2022 relocation 自由文本后正文含感情采集题干;同一 focus id 下一轮不重复补;choice 正文无题干;空正文整条即题干;in-turn / idle 共用同一句题干且每轮一次;无内部 token、无「请点选」。改写「非空不补」与 routeif (!answerText.trim())旧锁。assert.doesNotMatch(/answerText\.(?:includes|match|search)\(/)仍绿。BUG-440 / BUG-441 / BUG-442 /b43808b0无年份收窄回归保持绿。修复前:persistEmptyCollectSpokenAssistant({ answerText: "记下了:2022年搬家。" })返回null。 - 防复发:口述采集题的可见性由服务器确定性输出保证,不得依赖模型是否照做,也不得用正文文本反推;点选路径与自由文本路径必须共用同一句题干来源(
spokenFollowupForUser/ schema prompt)。不得为口述题新造视觉容器或复用选择卡样式。 - 相关记录:BUG-404、BUG-432、BUG-440、BUG-441
- 复发自:BUG-441
- 修复版本:待发布
BUG-450 | 点选题答完后的非收敛区间提议卡死
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
offerRangeWithoutAdopt、persistNextInterviewIfIdle、projectRectificationChoiceCard、buildCandidateContrastPacket、出题层rankRenderableDiscriminators、record-evidence-batch、POST/api/rectification/agent消息快路径 - 用户现象:点选题答完后,助手说「可以先按当前区间看盘,也可以再补一件记得时间的经历」;界面既没有可采用的时间卡,也没有下一道口述题。用户随后用真实带时间经历回答时,旧焦点仍指向已经回答的采集题,对话到此停止。
- 触发条件:训练门已开、occupation 等方法覆盖未齐、
datedMethodCollectOpen为假、引擎已放行 accept/propose、probe === null、canOfferRange=true但canAdopt/selectionAllowed/proposeAllowed全为假;随后collect_spoken焦点收到已成功入账的真实事件,且本轮工具调用可能先执行record-evidence-batch。 - 根因:五段同时成立。(A) 探针耗尽是假耗尽:带年份探针的分支只把已淘汰候选切出,当前活跃候选在所有
answer_class分支中待遇相同,按活跃集合重算信息量为零;旧出题层沿用全窗口信息量,不重算,也不把该探针写入dropped_probes,于是以probe=null静默失效。(B)probe=null且方法覆盖未齐时,rectification-decision.ts的offerRangeWithoutAdopt仍把canOfferRange设为真,但保持canAdopt/selectionAllowed/proposeAllowed为假并关闭 focus;这把本应继续补覆盖的状态送进没有承载的区间出口。(C)provisional_range的deferAdoption原本连intent=collect_method_evidence的覆盖追问一起吞掉,把它从next_followup挪到deferred_followup;消费端不读后者,因覆盖不足进入的状态反而结构性阻止补覆盖,形成自锁。(D) 用户用真实事件回答口述采集焦点并成功落库后,服务端没有按 active focus 确定性执行resolved;焦点继续存在,persistNextInterviewIfIdle因 active focus 提前返回,旧题仍出现在current_question。这与「确实没有」必须走declined的语义不同。(E) 本轮工具顺序可能是record-evidence-batch先于rectification-read-case,模型未拿到服务端 focusId 时仍能记证据;旧实现把工具顺序当成硬前置,既没有先补读 dossier,也没有保证后续焦点关闭和下一问落库。 - 修复:P0:
provisional_range只对intent=distinguish_candidates延后 followup;collect_method_evidence覆盖题继续从next_followup返回并正常落焦点。adopt_representative/validated_range/exact_minute_confirmed/provisional_range_user_stopped/completed_with_range的既有收口行为不变。P1:对比包和rankRenderableDiscriminators均按当前活跃候选重算探针信息量;零区分力探针显式写入dropped_probes.reason=no_split_among_active,不放宽MIN_BOUNDARY_DAYS、distinguish 合同或确认门。P2:record-evidence-batch成功且当前 active focus 是collect_spoken时,服务端按 dossier 中的 focus 自行resolve(status=resolved, evidenceId=acceptedEvidenceId),不依赖模型传focusId;否定回答仍走declined。关闭后继续走下一问持久化。P3:offerRangeWithoutAdopt使用统一的nonConvergingRangeNarration输出可信区间、代表分钟及「代表分钟不是已确认的唯一出生分钟」口径,并在同一轮持久化至少一个可执行的collect_spokenfocus;不为无选项问题新造视觉容器,也不为看盘按钮放宽canAdopt/confirmationAllowed。P4:若本轮尚未成功执行rectification-read-case,record-evidence-batch先自行读取一次 dossier 再落库,不因模型工具顺序错误打断用户。 - 验证:修复前基线(
79f65ac8)实际回归输出:record-before-read 时第一条 RPC 为insert_agentic_rectification_tool_receipt而非 dossier read;provisional_range.next_followup为undefined而不是collect_method_evidence。修复后tests/rectification-eight-method.test.ts为 62/62;该套件同时锁定首条 RPC、accepted evidence 使用其 evidence ID 关闭 spoken collect focus、resolve 发生在 record 之后且不再返回旧题;tests/rectification-range-offer-deadend.test.ts覆盖活跃候选零信息量 drop、仍有真实 split 时继续出区分卡、数字区间与代表分钟叙述、口述 focus 落库、userStopped 行为;tests/rectification-spoken-collect.test.ts与 BUG-440 / BUG-442 /b43808b0无年份收窄回归保持绿。npx tsc --noEmit通过;改动文件 ESLint 0 error(6 条既有 unused-vars warning);focused rectification/contract suite 727/727;全量tests/*.test.ts2339/2339;/opt/anaconda3/bin/python3.12 -m pytest tests/test_rectification_event_probes.py tests/test_candidate_discriminator_contract.py35/35。 - 防复发:任何因缺某项覆盖而进入的状态,不得阻止获取该项覆盖;探针信息量必须针对当前活跃候选计算,失去区分力的探针必须显式 drop;口述采集焦点被真实证据回答后必须由服务端确定性关闭,不依赖模型传
focusId;resolved与declined语义不得互换;助手正文承诺的每个出口,同一轮必须有对应承载,区间提议必须带数值与代表分钟口径;record-evidence-batch不得因未先 read-case 而丢弃用户证据。不得用answerText文本判断问题是否已经问出,不得引入语义正则、关键词表或 A/B/C/D 位置推断,不得回退无年份varga_style收窄或放宽 confirmation gate。 - 相关记录:BUG-405、BUG-407、BUG-432、BUG-440、BUG-441、BUG-442、BUG-449
- 复发自:BUG-432(出题层/决策层/可见承载再次分裂)、BUG-440(覆盖不足状态再次空转)、BUG-441/BUG-449(口述焦点可见性与服务端兜底边界未覆盖本路径)
- 修复版本:
fix(rectification): prevent collect focus dead-end after choice answers(Skill 保持10.0.13)
BUG-451 | 个人报告整份调用失败掩盖单章失败与截断边界
- 状态:mitigated
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:个人报告 worker、
createPersonalReportAgent、/reports/[reportId]、personal_report_jobs - 用户现象:一次模型输出承载整份报告;任一主题写坏或最终 JSON 解析失败,整份报告显示
report_schema_invalid,用户看不到已成功生成的主题。生成日志也无法区分模型停止、输出截断和校验失败。 - 触发条件:完整报告包含多个 write 主题,单次 writer 输出超过 provider 默认预算或任一主题未通过绑定/最终 schema 校验。
- 根因:报告 writer 只做整份报告的一次结构化调用加一次修复重试,没有章节级持久化、重试预算或
finishReason/token telemetry;report_document也没有可安全表达半成品的中间语义。 - 修复:先上线并采集
finishReason、inputTokens、outputTokens;随后改为串行 plan → section → summary → assemble。每章按evidenceRefs过滤 bundle 并独立落库/重试,耗尽后生成明确 blocked disclosure;摘要最后只接收章节标题与claimStatus;仅最终 assemble 写report_document,并把 job progress phase/percent 返给等待页。 - 验证:任务 0 真实 staging 观测为 1 次报告失败、
finishReason=length占比 0%、outputTokensp50/p95 为 3069/3069,失败归因为 report-level parse 而非截断;分章节单元测试、续做/blocked 测试、真实 Docker 数据库 RLS/权限测试均通过;TypeScript、ESLint(0 error)通过。 - 防复发:模型调用必须记录 allowlist 指标字段且不得记录 prompt、模型原文或用户资料;章节中间结果只能写
personal_report_sections;章节生成保持串行;摘要不得接收正文全文;blocked 章节必须显式渲染,不能静默丢失;最终成品仍须通过服务端 canonical parse。 - 相关记录:BUG-352
- 复发自:无
- 修复版本:待 staging 部署;本地提交待生成
BUG-452 | 采集焦点落库失败后静默结束
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
persistNextInterviewAfterChoice、persistFocusAfterChoice、persistExhaustionCollect、POST/api/rectification/agent非终态轮末出口 - 用户现象:答完家人采集题后助手只说「记下了,这方面先跳过。」,没有下一问也没有时间卡。
- 触发条件:上一采集焦点刚被关闭,下一条
collect_method_evidence焦点落库返回duplicate_focus或skipped;该轮同时没有可采用的候选承载。 - 根因:采集焦点落库失败时
persistNextInterviewAfterChoice返回hostNarration=null,随后被 denial 兜底文案「记下了,这方面先跳过。」吞掉;轮末也没有结构化不变量检查current_question或真实可采用承载,最终形成非终态但无可继续交互入口的静默死路。 - 修复:采集焦点首次落库失败后重读 dossier 并重试一次;若已存在同
questionId的 active focus,则按already_open复用。重试仍失败时复用persistExhaustionCollect口径,正文写出可信区间、代表分钟和口述下一问,并尝试落一个可回答的采集焦点。自由文本轮末新增结构化非终态出口检查:若未 accepted、未 confirmed、未 user-stopped,且既无current_question也无真实可采用承载,则确定性补口述采集焦点、把题干写入正文,并记录不含用户内容的rectification_nonterminal_exit_repaired日志。删除会成为终局的 denial 单句兜底。 - 验证:修复前新增 fixture 证明
duplicate_focus与skipped均会得到hostNarration=null、current_question=null;修复后两条路径均有问题正文并持久化或复用非空焦点。新增非终态出口不变量回归,锁定服务端补出collect_spoken焦点和题干。复刻五证据、五条已答探针、活跃候选05:00/05:07/04:53、family declined 的 case,锁定career.2023.dasha_activation以no_split_among_activedrop、nextAction=offer_provisional_range、canAdopt=false,轮末current_question.question_id=collect:occupation:collect_method_evidence且正文含「你长期做什么工作?」。assert.doesNotMatch(afterRun, /answerText\.(?:includes|match|search)\(/)保持通过 本地npx tsc --noEmit、改动文件 ESLint、目标四组回归122/122通过;focused 全套730/731,唯一 Docker/PostgreSQL migration fixture 失败单独重跑1/1通过;全量基线2342/2347,五项失败均为并行数据库容器/migration 环境波动或本机缺少 PyYAML,未扩大到产品逻辑。 - 防复发:非终态轮次结束时必须有
current_question或真实可采用承载;任何采集焦点落库失败都不得静默降级为一句确认话。该不变量只看焦点与 decision/承载字段,不得用正文文本判断助手是否问出问题。不得为无选项题新造视觉容器或复用选择卡样式;不得引入语义正则、关键词表或 A/B/C/D 位置推断;resolved与declined语义不得互换。 - 相关记录:BUG-441、BUG-449、BUG-450
- 复发自:BUG-450
- 修复版本:
fix(rectification): prevent silent collect focus stalls(Skill 保持10.0.13)
BUG-453 | 可采用区间已由服务端放行但前端不渲染时间卡
- 状态:resolved
- 首次发现:2026-08-30
- 最近更新:2026-08-30
- 影响面:
rectification-agentic-chat.tsx时间卡渲染门控、choice-action点选快路径、Case GET 默认投影 - 用户现象:答完最后一道选择题后助手只说「已记录你的选择,并更新了候选比较。」;界面没有时间卡也没有下一问,流程停住。
- 触发条件:服务端已给出
can_adopt=true、selection_allowed=true、precision_stage=ready_to_adopt,但当前轮是确定性点选路径,没有产生rectification-offer-candidates模型工具 receipt;前端仍以历史offeredSelectionOnce作为时间卡前置条件。 - 根因:服务端允许采用,前端却把承载绑定到模型是否调用某个工具;确定性点选路径不会产生该 receipt step,因此
selectionAllowed=true仍无法渲染时间卡。点选快路径原正文也只保留确认句,没有区间、代表分钟和采用提示。 - 修复:P0:时间卡改为只依赖服务端
selectionAllowed=true与canAdopt=true,保留最新已结算助手消息、忙碌/只读/重生成状态和选择卡互斥门;不再依赖offeredSelectionOnce。P1:点选进入offer_provisional_range + can_adopt=true时由服务端确定性写出可信区间、代表分钟、代表分钟免责声明和「可以从下面的时间里选一个采用」;该正文同时落入确定性 turn。P2:非终态轮末只看current_question或决策canAdopt,缺失时确定性补口述采集焦点并记录结构化日志。P3:Case GET 默认只保留活跃候选/代表分钟对应宫位表与当前焦点 probe;detail=full保留完整 receipt;decision、gates、inference_state不被瘦身修改。模型侧继续使用既有的精简 projection。 - 验证:新增/更新前端门控与
canAdoptcamel/snake 解析回归;保留 duplicate focus、skipped focus、非终态出口和五证据 case 回归;P1 断言确定性落库正文包含区间、代表分钟、免责声明和采用提示;P3 断言默认投影保留决策字段与完整inference_state,并移除非当前 probe 与非活跃宫位表。提交前运行 TypeScript、改动文件 ESLint、rectification/agentic 回归及全量测试;真实线上模型 74 秒路径耗时未在本轮复测。 - 防复发:UI 承载只看服务端状态,不得绑定模型工具 receipt;ready-to-adopt 的确定性正文必须写区间、代表分钟和采用提示。非终态轮次必须有
current_question或真实可采用承载;所有承载判断只看焦点与 decision 字段,不得用正文文本判断。不得为无选项问题新造视觉容器或复用选择卡样式,不得引入语义正则、关键词表或 A/B/C/D 位置推断;不得改变 confirmation gate、resolved/declined语义或 Skill10.0.13。 - 相关记录:BUG-441、BUG-449、BUG-450、BUG-452
- 复发自:BUG-450
- 修复版本:待发布
BUG-454 | 新增后台页面未同步 capability audit 路由契约
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:
backend-quality-gate.yml的 Python quality gate、_capability_audit()应用路由扫描 - 用户现象:推送
53450f5bf362c85d8b3bfbae7f2ab724e30f3208到staging后,quality gate run2217的validate失败,部署未继续。 - 触发条件:新增
frontend/src/app/admin/feature-pricing/page.tsx与frontend/src/app/admin/pricing-simulator/page.tsx后,运行tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources。 - 根因:
_scan_app_routes()按文件系统扫描真实page.*路由,而测试在tests/test_api_server_security.py中维护旧的硬编码完整列表,未加入两个新路由。 - 修复:在路由快照断言中加入
admin/feature-pricing与admin/pricing-simulator,保持测试与真实页面树的排序结果一致。未修改 workflow 配置或路由扫描逻辑。 - 验证:针对性 capability audit 测试通过;随后执行 quality gate 关注的 Python 测试集合,确认原失败断言已消除。
- 防复发:新增或删除前端
page.*路由时,必须同步 capability audit 的路由契约测试;quality gate 失败时先查看失败测试,不把下游publish的附带状态当成根因。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-455 | 定价配置异常被通用运行失败吞掉
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:POST
/api/rectification/agent、POST/api/consult、POST/api/reports、FeaturePricingError错误映射 - 用户现象:环境没有发布对应 feature/model tier 定价时,生时校正显示通用“暂时不可用,请稍后再试”,咨询和报告也只落入通用 503;文案暗示重试,但实际需要运维发布定价配置。
- 触发条件:
resolve_feature_pricing返回feature_pricing_missing、feature_pricing_model_unavailable或其他 fail-closed 定价错误。 - 根因:三个付费入口没有保留
FeaturePricingError.code;生时校正把 reserve 异常统一改成billing_unavailable,随后又未在外层显式映射而落到run_failed,consult/reports 则由通用 catch 吞掉。 - 修复:三个入口均只把
FeaturePricingError.code写入脱敏服务端日志;生时校正保留内部feature_pricing_*reason,并统一映射为公开billing_unavailable;咨询和报告统一返回pricing_configuration_unavailable。用户文案明确“计费配置不可用/尚未完成,请联系支持”,不返回内部错误原文。 - 验证:新增三入口路由契约,断言内部 code 被保留、公开错误不落入
run_failed、响应不包含error.message;TypeScript、ESLint、billing/consult/report 聚焦回归通过。rectification 聚焦套件705/705;全量2368/2369,唯一失败为默认python3缺少 PyYAML,指定已有 PyYAML 的解释器后原失败文件39/39通过。真实 staging 定价行与日志因本地无环境凭据未验证。 - 防复发:付费入口必须把配置缺失与瞬时运行错误分开;内部日志记录稳定 code,公开响应只使用安全错误码和可执行文案。部署完成不等于定价可用,仍须通过 admin flow 发布当前环境实际 model tier 的定价。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-456 | opening 成功后未持久化当前问题槽
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:POST
/api/rectification/agentopening 成功路径、persistNextInterviewIfIdle、ensureNonTerminalTurnExit - 用户现象:开场回复正文看起来提出了问题,但服务端
current_question仍为空,页面显示“当前没有可回答的问题,正在等待服务端更新”。 - 触发条件:新 Case 执行
action=opening且 Agent 没有自行调用可持久化 focus 的工具。 - 根因:成功轮后的问题槽持久化与非终态出口检查都被限制在
action === "message";opening 只保存散文回复,没有建立服务端拥有的可回答问题槽。 - 修复:成功的
opening与message一起执行persistNextInterviewIfIdle;随后执行ensureNonTerminalTurnExit,在仍无问题且没有已确认、用户停止或真实可采用承载时确定性补出问题。read_only保持无副作用,不纳入该分支。 - 验证:回归锁定 opening/message 共用问题槽持久化与非终态出口路径,并以真实
persistNextInterviewIfIdlefixture 断言投影后的current_question非空;TypeScript、ESLint 与 rectification 聚焦套件705/705通过。未进行真实 staging opening smoke。 - 防复发:任何会启动或推进 Case 的成功非终态轮都必须以服务端状态结束:存在
current_question或真实可采用承载;不得以模型正文是否包含问句代替结构化状态。 - 相关记录:BUG-452、BUG-453
- 复发自:BUG-452(非终态出口只覆盖 message,未覆盖 opening)
- 修复版本:待发布(Skill 保持
10.0.13)
BUG-457 | 功能定价草稿保存成功但列表为空且菜单入口被隐藏
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:
/admin/feature-pricing、/admin/pricing-simulator、GET /api/admin/feature-pricing、后台功能定价可读性 - 用户现象:管理员保存定价草稿后接口没有报错,但列表仍为空;左侧菜单找不到“功能定价”;功能和模型档位只显示内部英文键,非研发人员无法判断含义。
- 触发条件:以
admin_runtime读取启用 RLS 的public.feature_pricing;或由 Refine 根据resourcePermissions生成后台菜单;或打开功能定价、定价测算列表。 - 根因:建表迁移只向
admin_runtime授予了SELECT,没有为启用 RLS 的表创建读取策略,因此安全定义者函数可以保存草稿,但后台直查被 RLS 静默过滤为 0 行;同时feature-pricing和pricing-simulator未加入菜单权限映射;界面直接渲染稳定内部键值。 - 修复:新增向前迁移,为
admin_runtime添加feature_pricing只读 RLS 策略;补齐两个 Refine 资源的权限映射;统一提供中文标签并保留括号内原始键值,供功能定价和定价测算共同使用,同时把草稿、已发布、已停用状态本地化。 - 验证:契约测试锁定菜单映射、中文标签/原始值以及只读 RLS 策略;本地 PostgreSQL fixture 插入
rectification/standard草稿后,以admin_runtime成功读回该行;TypeScript、ESLint 与聚焦测试通过。 - 防复发:任何后台运行角色直查启用 RLS 的新表时,表权限和对应只读 policy 必须成对交付并用该真实角色查询验证;新增 Refine resource 必须同步
resourcePermissions;后台稳定键值必须显示可读标签且不得改变提交值。 - 相关记录:BUG-454、BUG-455
- 复发自:无
- 修复版本:待发布
BUG-458 | 订阅续费测试依赖固定时钟导致 staging quality gate 过期失败
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:
frontend/tests/database-billing-admin.test.ts、Giteabackend-quality-gatestaging validate job - 用户现象:功能定价修复推送后 workflow run
2232的validate失败,后续publish、迁移和部署未执行;前端全量测试2370/2371通过,仅数据库计费契约失败。 - 触发条件:CI 在
2026-08-31 10:00 UTC之后执行订阅续费测试;测试把上一份月订阅结束时间和下一份订阅期望开始时间固定为该时刻。 - 根因:测试期望没有遵守
settle_order的真实契约greatest(v_now, max(existing_subscription.ends_at))。当数据库当前时间晚于写死的旧订阅结束时间时,新订阅会正确地从当前时间开始,固定字符串断言却仍期待过去时间。 - 修复:将已有月订阅结束时间设为相对当前时间的一天后,并直接断言续费订阅的
starts_at等于上一份订阅的ends_at、ends_at等于starts_at + interval '1 month';不修改正确的计费函数。 - 验证:
database-billing-admin.test.tsPostgreSQL 聚焦测试1/1通过;使用已安装 PyYAML 的 Python 解释器运行前端完整测试2371/2371通过;远端 workflow 结果另行记录。 - 防复发:涉及
clock_timestamp()、now()或续费基准的契约测试不得把当前日期附近的绝对时间作为期望;应断言记录间关系和周期不变量。 - 相关记录:BUG-455、BUG-457
- 复发自:无
- 修复版本:
test(billing): make renewal timing deterministic
BUG-459 | Skill 升版身份同步不完整:SHA 目标目录易错且身份分散在五处
- 状态:resolved
- 首次发现:2026-08-31
- 最近更新:2026-08-31
- 影响面:
skills/skill-package-registry.json、skills/jyotish-birth-time-rectification/**、RECTIFICATION_SKILL_VERSION、frontend/tests/skill-registry.test.ts、openRectificationCase - 用户现象:升版后新建 Case 直接失败(
skill_registry_version_mismatch),或skill-registry.test.ts在发布门禁处失败。 - 触发条件:升级 rectification Skill 版本时只改了部分身份位点;或对 Skill 根目录而非
versions/<version>/计算 SHA256。 - 根因:Skill 身份分散在五处——版本目录
SKILL.mdfrontmatter、根目录SKILL.md副本、registry 条目的version/sha256/packagePath、RECTIFICATION_SKILL_VERSION常量、测试内硬编码 SHA。registry 的packagePath指向versions/<version>/,但根目录存在同名SKILL.md副本,容易让人误以为 SHA 应对根目录计算;根目录含versions/整棵子树,对它计算会把全部历史版本吃进哈希,且能正常返回一个值、不会立刻报错。openRectificationCase(v9/case-service.ts)校验 registry active 版本必须等于RECTIFICATION_SKILL_VERSION,任一处不同步即导致新建 Case 整体失败。 - 修复:升版按固定顺序执行——先建
versions/<new>/并改完全部字节(含 frontmatter 与根目录副本),最后统一对versions/<new>/调用computeSkillPackageSha256(),再回填 registry 与测试。10.0.13 → 10.0.14 按此流程完成。 - 验证:对
versions/10.0.14/实算 SHA 与 registry、skill-registry.test.ts三处一致;registry active 条目唯一;根目录与版本目录SKILL.md字节一致;rectification-*与skill-registry套件 730 项 0 失败;tsc --noEmit通过。未进行真实 staging smoke。 - 防复发:SHA256 只能对 registry
packagePath指向的目录计算,绝不对 Skill 根目录计算;改字节与算 SHA 不得交错,必须先定稿再统一重算;升版必须在同一变更内同步全部五处身份位点;skill-registry.test.ts是发布门禁,不得跳过。存量 Case 绑定各自的skill_version,resolveExact对deprecated放行,因此升版不需要数据迁移,但旧 Case 不会获得新版 prompt 约束。 - 相关记录:BUG-456
- 复发自:无(提交
797a423a曾出现同类的"改了 Skill 字节但未刷新 registry hash",当时未单独立项) - 修复版本:Skill
10.0.14
BUG-460 | 非终态轮缺少统一出口闸导致 answer_choice 与消息早退进入无问题死路
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:POST
/api/rectification/agent的opening、message、answer_choice、stop_and_review推进轮,decideRectification的 probe 耗尽转向,以及服务端问题在 turn 历史中的可见性 - 用户现象:真实校正会话已有多条证据、多个领域并答完多道区分题后,仍保持正确的
can_adopt=false,但current_question=null,界面永久显示等待服务端更新。 - 触发条件:最后一步走
answer_choice,或message命中任一 HTTP 200 确定性早退;同时普通 discriminator 已耗尽、候选分离不足,原决策链直接返回区间而未继续尝试 holdout、带日期收集或 Nakshatra 边界题。 - 根因:BUG-456 只在当时命中的 opening/message 分支附近补了兜底调用,没有建立所有推进轮共享的后置条件;后来新增或既有的 structured choice 与多个消息早退仍可绕过。选择题历史另以「接下来请点选下面这一问。」代替服务端 focus prompt,导致回看时看不到真实问题。
- 修复:所有成功的推进轮统一经过 awaited 公共出口闸;immediate 路径必须等 focus 持久化完成后才返回 HTTP 200,stream 路径必须在
done前完成。read_only显式无副作用。闸门最终重读 Case 并验证current_question || canAdopt || 已终态,无法建立后置条件时 fail-closed。probe 耗尽按普通 discriminator → holdout → dated collect → Nakshatra 边界题 → 明确区间出口转向,所有路径继续使用既有 delivery capability,事故 Case 的canAdopt=false不变。服务端 focus prompt 直接写入 turn 历史,不让模型复述。 - 验证:任务 0 三条不变量覆盖四种推进 action 与 answer_choice 响应可见时序;上一轮五条收紧不变量继续通过;rectification、skill registry 与 TypeScript 最终数字见本次交付报告。未进行真实 staging smoke。
- 防复发:新增 action 必须先加入穷举的 execution-kind 映射,否则 TypeScript/结构契约失败;所有 HTTP 200 推进出口只能在公共闸门之后可见,所有流式成功出口只能在公共闸门之后发送
done。不得用正文、问号或 A/B/C/D 文本推断当前问题,也不得通过放宽采用门槛消除无下一步状态。 - 相关记录:BUG-459
- 复发自:BUG-456
- 修复版本:待发布
BUG-461 | 口述采集卡重复助手正文,选择题卡题干与气泡不对齐
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
rectification-agentic-chat当前问题槽、RectificationChoiceCardlegend、.rectification-question-slot对齐 - 用户现象:口述采集时卡片把助手已经说过的题干再显示一遍,并多出灰色提示「请在下方输入框回答…」。选择题卡在未点选时与助手气泡左缘错位,点选后回看才对齐。
- 触发条件:当前问题是
collect_spoken;或当前问题是选择题且卡片仍在问题槽、尚未变成消息附件。 - 根因:BUG-449/460 已把服务端题干写入助手正文,BUG-441/452 也禁止为无选项问题新造视觉容器,但后续测试把口述采集卡和可见 legend 锁成了必有。问题槽是消息列表的兄弟节点,没有
--assistant-content-inset;已回答卡片在.rectification-message-wrap内,所以只有回看才对齐。 - 修复:口述采集不再渲染第二块视觉卡,题干只留在正文,
collectSpokenPrompt只驱动输入框占位。选择题 legend 改为sr-only。问题槽与助手内容使用同一 inset,且不给槽内选择题再套一层 inset。 - 验证:
rectification-spoken-collect、rectification-agentic-entry、rectification-varga-style-copy改为锁新行为;口述采集不得再出现__spoken/__prompt/aria-describedby。未做登录后的真实页面点选。 - 防复发:不得为
collect_spoken新造视觉容器或复用选择卡样式;选择题可见题干不得与助手正文重复;问题槽与已回答卡片必须共用--assistant-content-inset。不得用正文文本判断助手有没有问出来。 - 相关记录:BUG-441、BUG-449、BUG-452、BUG-460
- 复发自:BUG-441
- 修复版本:待发布
- 后续复发:BUG-471
BUG-462 | 区间出口采集撞上开场题号后,成功计费轮被误报 run_failed
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
exhaustionSpokenCollectFollowup、persistCollectFocus、POST/api/rectification/agent流式成功出口 - 用户现象:助手已经记下带年份事件并完成计费后,界面同时出现「回答未完成,已保留现有内容;本次不会扣点。」和「生时校正暂时不可用,请稍后再试。」
- 触发条件:非收敛区间出口后需要再问口述采集,且开场通用采集题号
collect:unknown:collect_method_evidence已作为历史行存在;或职业/健康压力等域不在 SQLtarget_domain白名单里。 - 根因:耗尽采集只轮转家庭/升学/财务后就退回
domain: null,题号与开场题相同。unique (case_id, question_id)含已结束行,落库报focus_idempotency_conflict。出口闸因此认为没有下一步并抛agentic_rectification_nonterminal_exit_missing。该异常发生在billing.settled与run.completed之后,被映射成run_failed;前端只要看到error就把本轮标成失败并提示不扣点。 - 修复:耗尽采集在家庭/升学/财务之后问职业,再问健康压力/搬家/事业/感情,最后用
domain: "other"。职业落库映射为other,健康压力映射为health。同一题号冲突时改写questionId:next。流式result.ok后出口闸失败只记日志,仍发送done,不再发run_failed。 - 验证:
rectification-range-offer-deadend锁职业与other回退;rectification-server-focus锁域映射与:next重试;rectification-spoken-collect锁成功出口不send error;rectification-collect-stall冲突路径改为两次落库。未做登录后的真实页面复现。 - 防复发:耗尽回退不得再用
collect:unknown:*。SQL 不允许的域必须在落库前映射。流式成功轮在计费完成后不得把出口闸失败发成用户可见的run_failed。出口闸仍须在send done之前 awaited。不得放宽canAdopt。 - 相关记录:BUG-450、BUG-452、BUG-460
- 复发自:BUG-460
- 修复版本:待发布
BUG-463 | 采用门把分离与 holdout 绑死导致引擎可出牌时用户无法采用
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
deliveryCapability/decideRectification、GET case overlay、persistNextInterviewIfIdle、候选卡采用 RPC 投影 - 用户现象:多条可评分证据、多领域、区分题已答完,引擎 receipt 已允许 acceptance/selection/propose,界面仍是
can_adopt=false、没有时间卡、不能保存,并继续轮询财务/职业等采集题。 - 触发条件:方法覆盖(含职业确认或拒答关闭)已满足,候选领先不足
MIN_SEPARATION_LEAD,或 holdout 未通过/不可用;引擎四门自洽且confirmation_allowed=false。 - 根因:策略死结,不是实现回归。引擎 receipt 与 Skill
10.0.14都把分离/holdout 留在唯一分钟确认门,交付物是代表分钟加可信区间。TypeScriptdeliveryCapability却把这两项写进canAdopt,overlay 再与引擎取交集,于是引擎的“可以”永远被 TS 盖成“不行”。上一轮TASK-rectification-nonterminal-exit-20260901.md与 BUG-460/462 的防复发写了“不得放宽canAdopt”;该红线已由产品负责人于 2026-09-01 显式推翻,本条记录授权变更。 - 修复:
locallySelectable只保留候选存在、方法覆盖完成、以及证据下限keep_collecting。分离充分、holdout passed 与引擎confirmation_allowed只进入canConfirmExactMinute。覆盖完成后 idle 持久化不再追问,GET overlay 放出候选卡;采用 RPC 仍读引擎 snapshot 的selection_allowed,写入accepted不写confirmed。 - 验证:事故形状 1a–4 与交付 B1–B3;授权组断言(分离不足/holdout/并列/
offer_provisional_range不采用)改为 provisional 采用且确认门仍关;证据下限、引擎 ceiling、确认门、非终态出口闸与read_only保持全绿。rectification-*.test.ts与skill-registry.test.ts最终数字见本次交付;./node_modules/.bin/tsc --noEmit必须通过。未进行真实 staging smoke。 - 防复发:
canAdopt/selectionAllowed/proposeAllowed/canConfirmExactMinute只在deliveryCapability计算并经...capability展开,出口不得覆写。不得把分离或 holdout 重新绑回采用门。不得放宽确认门、表达边界、3/2 证据下限或引擎 ceiling fail-closed。不得 bump skill 版本或改 Python 引擎来绕过本条。 - 相关记录:BUG-456、BUG-460、BUG-462
- 复发自:无(授权策略变更,不是同一实现回归)
- 修复版本:待发布
- 后续复发:BUG-472
BUG-464 | 客户端全量覆盖 chat_sessions.messages 导致列表膨胀、写满失败与双标签页丢消息
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
GET /api/sessions、GET/PATCH /api/sessions/[id]、POST /api/consult、append_consultation_question、首页会话列表与对话区 - 用户现象:会话一多首页变慢;对话变长后发问提示“问题保存失败”;两个标签页各问一句,刷新后只剩后写的那一轮;中止或失败的半截回答会留下可刷新的记录。
- 触发条件:打开带多段对话的账户首页;长会话继续发问;同一会话在两个标签页交替发送;停止生成或 run.failed 后刷新。
- 根因:
chat_sessions.messages由浏览器整包 PATCH 覆盖,列表 GET 又把全部消息一次性带回。服务端结算已经 append assistant,客户端再 last-write-wins 覆盖。列表无分页、无上限。 - 修复:列表 GET 去掉
messages;详情 GET 按会话加载。咨询在 reserve 成功后由append_consultation_question写入用户提问,满 200 条或 200,000 字符返回session_full并退回预扣。consult 忽略请求体history,改读库内最近 12 条。客户端persistSession只写元数据;旧 bundle 的messagesPATCH 接受并忽略。中止与失败的部分回答只留在当前页。 - 验证:合同测试锁列表/详情拆分、伪造 history 不进模型上下文、PATCH 忽略 messages、SQL 幂等与
session_full、双 request_id 可叠加。./node_modules/.bin/tsc --noEmit、npm test与npm run test:db必须通过。 - 防复发:不得把
messages加回列表 GET 或校正 agent select。不得恢复客户端 transcript PATCH。consult 不得再读取parsed.data.history。append_consultation_question必须保持 advisory lock、request_id 幂等与满员拒绝。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-465 | 会话选择只活在内存里,刷新、后退和登录回跳都会丢掉当前对话
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:首页
activeSessionId、浏览器历史、redirectToLogin后的登录回跳 - 用户现象:无论当前在哪个对话,刷新后回到最近一条;会话间切换后按后退会直接离开站点;登录过期后再登回来落在空白首页,对不上刚才那条对话。
- 触发条件:在侧边栏点开非默认会话后刷新;连续切换几个会话后按浏览器后退;带着
?c=会话地址遇到 401 被送去登录。 - 根因:
activeSessionId只存在 React state。地址栏没有会话痕迹,登录页的successPath也只允许回到/或/admin。 - 修复:合法会话 id 写入
?c=。用户主动切换、新建、打开校正时pushState;删除/归档当前会话或伪造 id 时replaceState。默认选中和生成恢复不写 URL。popstate复用selectSession且不再二次 push。401 把当前?c=暂存 sessionStorage,登录落回/后启动逻辑读回并清暂存。登录页零改动。 - 验证:合同测试锁启动读参、默认不写 URL、popstate 不二次 push、删除清死链、401 暂存、首页不用
useSearchParams。./node_modules/.bin/tsc --noEmit、前端测试与next build的/○ Static必须通过。 - 防复发:首页不得为读 query 引入
useSearchParams或服务端searchParams,以免/从 Static 退化。不得给登录页加开放重定向next参数。默认选中不得pushState。 - 相关记录:BUG-464
- 复发自:无
- 修复版本:待发布
BUG-466 | 星盘库、合盘历史和会话置顶/归档仍有一份会骗人的本地真相
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:星盘库保存/删除、合盘历史、会话置顶与归档、
chat_sessions列 - 用户现象:云端保存失败仍提示已保存,刷新后记录消失;云端删除失败后刷新会复活;合盘历史按 id 并集,服务端删掉的记录会从本机回来;置顶和归档只活在这台浏览器里,换设备全部归零。
- 触发条件:断网或接口失败时保存/删除其他人的星盘;云端删除合盘历史后刷新;在一台设备置顶或归档后再换设备打开。
- 根因:星盘库和合盘历史同时写 localStorage 与云端,启动时又用云端替换或并集覆盖;置顶/归档只有
jyotisha-session-controls本地键,没有账户数据。仓内曾用20260718100000_repair_missing_chart_profiles.sql与20260718101000_repair_missing_synastry_reports.sql修过同类双份真相。 - 修复:星盘库与合盘历史只信云端列表;写失败保留表单或当页报告并明确报错,不再写本地副本。启动清除旧库/历史键。
chat_sessions增加pinned、archived_at,置顶/归档走既有 PATCH;本地控制键一次性导入后删除。每日星语缓存与activeChartId不动。 - 验证:合同测试锁保存/删除失败不再写本地、合盘不再并集、列表/详情带上两列、PATCH 元数据接受
pinned/archived_at、旧messagesPATCH 仍忽略。npm run test:db重放加列迁移并验证 RLS。./node_modules/.bin/tsc --noEmit、前端测试与next build的/○ Static必须通过。 - 防复发:不得把
jyotisha_chart_library/jyotisha_synastry_history/jyotisha-session-controls写回作为权威存储。云端写失败不得再提示已保存到本地。不得把业务迁移复制进frontend/db/migrations。列表 GET 仍不得带回messages。 - 相关记录:BUG-464、BUG-465
- 复发自:无
- 修复版本:待发布
BUG-467 | 引擎算出的解读事实被报告提取层全部丢弃,claim card 只剩执行回执
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
buildReportEvidencePacket、buildReportEvidenceBundleV2的 claim card 构造、filterReportEvidenceBundleForSection、ReportEvidenceBundleV2schema、报告 writer 指令与jyotish-personal-reportSkill - 用户现象:个人报告读起来空洞、各主题高度雷同,说不出这张盘特有的任何东西;报告正文只能围绕"服务器已闭合本主题所需的最低证据组"这类回执文案展开。
- 触发条件:任意一次个人报告生成。
/api/consultation_workflow响应里已经带有解读层,但报告路径不读。 - 根因:结构性矛盾。引擎响应的
chart.ai_prompt_pack.evidence_snapshot.functional_benefic_malefic、.strength.shadbala_ranking、chart.modules.ashtakavarga.sav、chart.modules.dasha_sub_periods.current、chart.yogas、chart.modules.guided_topics都存在,但提取层只保留行星/宫位/上升、两条 Dasha 时间线、varga 宫位占据和技法执行回执。于是 claim card 的conclusion是模板句、supportingFacts是"XX 已执行并纳入本主题证据计划"。写作端合同又规定 claimCards 是唯一结论来源——结论来源里没有结论,模型只能输出空洞合规文本或悄悄违规自行解读原始盘面(schema 查不出来)。同时写作端拿不到任何解读方法论。 - 修复:
ReportEvidenceBundleV2新增interpretiveFacts(yogas / functionalRoles / shadbalaRanking / savScores+savTotal / currentDasha / convergenceDomains)与themeNarrativeSeeds,均必填、允许为空、保持.strict()、进入 canonical 排序与bundleHash,并对悬空 ref、重复 rank/house/theme、越界文本 fail closed。提取走 allowlist:yoga 分类与功能吉凶角色是闭合枚举,行星过safeCelestialName,SAV 按上升整宫把星座分投影成宫位分,叙事种子逐行过违禁词清洗(含外部供应商名与内部路由词一律丢行)。claim card 的conclusion/supportingFacts改为由这些事实确定性拼装,risks 进counterFacts;assertionLevel推导规则一字未改,只在完全没有种子时把 consensus 压到single_system_inference。filterReportEvidenceBundleForSection按主题裁剪种子与 SAV 宫位,同时让盘面通用层(功能吉凶、yoga、八分力、当前大运)的 receipt 随每章下发。新增frontend/src/lib/report-interpretation-packs/:通用包进cachedSystemMessage缓存前缀,本章主题包放缓存边界之后;包内只写措辞与解释方式,不含路径、内部名词、供应商名与任何用户数据。Skill 升到1.1.0(1.0.0转 deprecated)并写明知识包不是事实来源。telemetry 只补记interpretiveFactCount与knowledgePackCharacters两个数值。 - 验证:先用虚构 smoke 出生数据真实调用本地
/api/consultation_workflow九个主题各一次,把字段存在性写进PROGRESS-report-skill-parity-20260901.md(timing.vimshottari、timing.convergence_top_domains、strength.sav_scores、三个*_narrative在该响应里确实不存在,按缺失处理,未伪造)。frontend/tests/report-interpretive-facts.test.ts用合成夹具锁住每层提取条数、hit:false的候选 yoga 不算成盘、非法行星名被丢弃、超长种子截断、外部供应商行被丢、hash 在字段顺序扰动下不变、越界输入 fail closed、有种子/无种子两种 claim card 行为、consensus 防升级回归、按主题裁剪。frontend/tests/report-interpretation-packs.test.ts锁每主题可解析、单包 ≤3000 字符、违禁词/路径扫描、缓存前缀逐字节稳定。./node_modules/.bin/tsc --noEmit、eslint0 error、next build通过,前端全量tsx --test与基线同一失败集(均为本机缺 Docker/PostgreSQL 的数据库与部署夹具)。 - 防复发:bundle 内自由文本只允许来自服务器自产生成器,用户 question 与模型历史输出一律不得写入。新增字段必须有长度上限、数量上限与字符净化,schema 保持
.strict(),hash 必须覆盖。不得因内容变丰富而提升assertionLevel,不得删改 consensus ≥2 verified 校验。modules.yogas.yogas里hit=false的规则行不是成盘 yoga,不得当证据。知识包只约束措辞,不得成为 bundle 之外新占星断言的来源;包内不得出现路径、内部模块名、引擎/供应商名或 prompt 模板。写作 agent 仍然 no skills / no tools / no memory,章节仍串行。 - 相关记录:BUG-457
- 复发自:无
- 修复版本:待发布
BUG-468 | 填报精确出生时间后咨询仍按未校正范围交互
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
prepareConsultationRoute/serverChartFromProfile、runConsultationWorkflow请求体、toAgentConsultationContext、run_rectification_gate/_build_consumer_context、咨询模型指令 - 用户现象:初始化已填医院记录级精确出生时间,或已付费采用校正时间的用户,咨询时模型仍说没做过生时校正、要按时间范围交互。
- 触发条件:
hospital_record未校正填报分钟进入咨询;或birth_time_status为accepted/confirmed且存在active_birth_time的verified_chart咨询。 - 根因:前端星盘
toolInput不传declared_accuracy/time_source,引擎默认minute+family_clear得到5min且矩阵缺档;mastra 又无条件把rectification.boundary写成not_auto_rectified。 - 修复:按档案真值映射精度字段;
rectified只在accepted/confirmed且有 active time 时声明。workflow 透传引擎 gate 边界。补5min分盘档与分级文案。declared_birth_window/general_no_birth_time与输出守卫不改。 - 验证:任务 0 不变量先红后绿;
consultation-birth-time-mode守卫 9 条保持全绿;聚焦consultation-*/consult-*/chat-*/rectification-*与tests/test_rectification_*.py、tests/test_consultation_birth_accuracy.py。 - 防复发:不得对未采用校正的档案声明
rectified;不得把not_auto_rectified写回每次咨询;不得改输出守卫或 window/general 两档指令来“修好”精度传递。 - 相关记录:BUG-009、BUG-073
- 复发自:无
- 复发自:无
- 修复版本:待发布
BUG-469 | opening 轮把采集题干再写成第二条助手消息
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:POST
/api/rectification/agentopening 成功出口、finalizeSuccessfulTurnExit、口述采集当前问题槽 - 用户现象:开场模型正文已经自然引导用户说一件事,紧接着又出现一条服务端采集题干,两条语义重复、语气断裂。
- 触发条件:新 Case
action=opening,本轮已有非空 assistant 正文,且 focus 为 collect 类。 - 根因:BUG-460 为了让题干进历史,对所有成功推进轮都把
current_question.prompt追加成独立 assistant 消息。opening 的模型正文本身已承担引导,再追加就变成双重提问。 - 修复:focus 仍照常持久化。仅当
action === opening、focus 为 collect、且本轮已有非空 assistant 正文时,不再把题干写成第二条 turn 消息。判定只看 action / kind / intent / 正文是否非空,不读正文内容。 - 验证:
shouldPersistFocusPromptTurn矩阵;既有 server-focus / question-ownership / 非终态出口闸测试保持;agent-voice-copy-contract.test.ts。 - 防复发:不得用正文字符串判断“是否已问过”。非 opening 轮仍把服务端题干写入历史。
- 相关记录:BUG-456、BUG-460、BUG-461
- 复发自:BUG-460
- 修复版本:待发布
BUG-470 | Home 拆分后 eslint 开始分析 page.tsx,staging quality gate 因 16 条 react-hooks 失败
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:Gitea
backend-quality-gatevalidate、frontend/src/app/page.tsx、账户 overlay、首页 lint - 用户现象:向
staging推送124d3990后 quality gate run2280失败。validatejob5496前端测试 2460/2460 通过,随后npm run lint --prefix frontend报✖ 86 problems (16 errors, 70 warnings)。publish因needs: validate跳过。 - 触发条件:含第三批 Home 拆分的提交进入完整 runner 的 frontend lint。
eslint-plugin-react-hooksv7 的 compiler 规则开始分析已缩到约 2000 行的Home。 - 根因:拆分前
Home过大,同一套规则不分析该组件,render 里写 ref、effect 同步 setState、模块级{ current }赋值和Date.now()都不会报错。拆分后这些写法变成 16 个 error:react-hooks/refs、react-hooks/immutability、react-hooks/set-state-in-effect、react-hooks/purity。模块级 holder 是第三批为打通 hook 循环新加的;其余模式在拆分前已存在,只是当时扫不到。 - 修复:循环回调改为
useRef,在产出它们的自定义 hook 体里写入(与已有resumeRectificationSession.current相同)。sessionsRef改在 session hook 内同步。preview改为 state,不再在 render 读uiPreview.current。账户 overlay 改为传入model对象,去掉 render 里累加 epoch / 写modelRef。聊天 actions 改到copyAssistantMessage之后的 effect。effect 里同步 setState 改queueMicrotask。合盘卡片时间改走timestamp()。 - 验证:
npm run lint --prefix frontend0 error;./node_modules/.bin/tsc --noEmit;frontend/tests/account-dialog-overlay.test.ts;相关首页/账户合同测试。 - 防复发:
Home不得在 render 里写ref.current或改模块级{ current };账户 overlay 不得再靠openEpoch+modelRef在 render 里刷 memo;源码合同锁定 overlay 走modelprop、chat actions 只在 effect 里写入。 - 相关记录:BUG-326、BUG-339、BUG-343、BUG-383
- 复发自:BUG-326(render 里写 ref);BUG-339(effect 同步 setState)
- 修复版本:待发布
BUG-471 | 选择题干与口述采集题在直播界面消失
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
rectification-choice-cardlegend、.rectification-question-slot口述题干、shouldPersistFocusPromptTurn、生时纠正直播对话 - 用户现象:助手说「界面上继续有下一问 / 有下一步」,下方没有可见问句;D9/D10 风格卡只有 A/B/C/D,看不到题干。用户无法判断要回答什么。
- 触发条件:Agent 正文只写承接句;
current_question已是choice或collect_spoken;直播send()不重拉 turns。 - 根因:BUG-461 为去掉重复采集卡,把选择题 legend 重新设为
sr-only,并拆掉口述题槽。题干只指望助手正文或第二条 persist turn。Agent 被禁止复述题干,直播又不合并额外 turn,于是两边都空。 - 修复:选择题 legend 恢复可见 prompt。
collect_spoken在问题槽渲染 prompt 段落(不加灰色「请在下方输入框回答…」、不套选择卡样式)。collect 且本轮已有非空助手正文时,不再追加第二条题干消息。不改 Skill 版本,不打开确认门。 - 验证:
rectification-agentic-entry、rectification-varga-style-copy、rectification-spoken-collect、agent-voice-copy-contract。未做登录后的真实页面点选。 - 防复发:有点选卡时题干必须在卡片上可见,不得再把
choice_card.prompt藏进sr-only。口述采集的真源是 GETcurrent_question.prompt,必须在问题槽可见。不得用助手正文字符串判断「有没有问过」。不得恢复灰色 hint。 - 相关记录:BUG-406、BUG-441、BUG-461、BUG-469
- 复发自:BUG-406(可见 legend)被 BUG-461 回退;BUG-461 拆掉口述槽后直播无题干
- 修复版本:待发布
BUG-472 | 职业与财务采集挡住已放行的临时采用卡
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
BLOCKING_COVERAGE_IDS、persistNextInterviewIfIdle、persistNextInterviewAfterChoice、GET overlaycan_adopt - 用户现象:区分题已答完、引擎
accept_allowed=true、区间已收到约 8 分钟,界面没有时间卡。用户说家里「没有」后只看到一句范围旁白,流程像停住。 - 触发条件:带日期的 dasha/D9/D10/relatives 已覆盖或拒答,
occupation仍 uncovered;耗尽采集按 family→education→finance 继续追问。拒答家人走applyCollectFocusDenial→persistNextInterviewAfterChoice,不走 idle 早退。 - 根因:BUG-463 已把分离/holdout 移出采用门,但仍把
occupation留在 blocking 集合。idle 覆盖完成后不再追问,拒答采集路径仍会落财务题。公开层can_adopt=false,前端不渲染时间卡;口述槽当时也是空的。 - 修复:blocking 采用门只保留
dasha_events、d9_relationship、d10_career、relatives。occupation仍可采集,不再挡住canAdopt。persistNextInterviewAfterChoice与 idle 共用同一早退:已可采用且下一步不是采集/区分/holdout 时,不再落下一问。确认门、3/2 证据下限、引擎 ceiling 不变。不 bump Skill,不改 Python 引擎。 - 验证:
rectification-provisional-adopt事故形状 1c 与 after-choice 早退;rectification-eight-method、rectification-coverage-collect、rectification-occupation-coverage-exit、rectification-range-offer-deadend、rectification-collect-stall。./node_modules/.bin/tsc --noEmit通过。未做登录后的真实页面点选。 - 防复发:不得把
occupation、财务或健康重新写进BLOCKING_COVERAGE_IDS。覆盖完成后persistNextInterviewAfterChoice与 idle 都不得再追问 OOS 采集。不得放宽确认门或 3/2 下限。 - 相关记录:BUG-453、BUG-463
- 复发自:BUG-463(idle 不再追问,拒答路径与 occupation blocking 仍挡住采用卡)
- 修复版本:待发布
BUG-473 | 流式回答每个网络事件都全量重渲染,长回答越写越卡
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
use-consultation-run.tsNDJSON 回调、rectification-agentic-chat.tsxsend循环、chat-message-content.tsx - 用户现象:回答越长越卡,文字一坨一坨蹦出来而不是流出来;思考步骤更新时整条消息跟着抖。
- 触发条件:任何一次流式咨询或校正回合,回答超过一两千字后明显。
- 根因:服务端每个模型 text-delta 立即发一条
answer.delta,客户端每条事件各自setStreamingReply/setMessages;每次提交都对整段部分回答跑parseAgentReply、applyThinkingSectionProgress、splitSpokenAnswerAndTechniqueAudit和react-markdown全量 parse,随回答长度呈平方级增长。 - 修复:新增
lib/stream-frame-buffer.ts,所有流式事件先落到累加器,requestAnimationFrame合并成每帧最多一次提交;正文与思考文本按max(2, ceil(积压/12))每帧匀速释放,突发积压约十二帧追平,页面隐藏时退化为 250ms 定时并一次放完,run.completed/失败/中止时同步冲干净。chat-message-content.tsx在流式时把已完成段落(最后一个空行之前,不切进代码围栏、列表项之间或表格)交给按内容记忆的前缀组件,只有尾块每帧重 parse;结算后仍整篇一次 parse。 - 验证:
tests/stream-frame-buffer.test.ts(释放公式、200 token/50 帧只提交 50 次、突发 12 帧追平、隐藏退化、reset/dispose)、tests/chat-markdown-split.test.ts(切分规则、前缀稳定、前缀与尾块分开 parse)、tests/home-streaming-render-split.test.ts新增按帧驱动的渲染次数上限。 - 防复发:流式状态必须经
stream-frame-buffer提交,不得在事件回调里直接setState;流式期间的 Markdown 渲染必须走前缀/尾块切分。 - 相关记录:BUG-474
- 复发自:无
- 修复版本:待发布
BUG-474 | 回答结算瞬间整条消息闪一下,步骤时间线从展开直接跳成折叠
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
chat-transcript.tsx、chat-message-row.tsx、consultation-run-timeline.tsx、globals.css.message - 用户现象:流式回答写完的那一刻,正在读的整条回答淡出再淡入一次;上方「正在分析」步骤块瞬间收成「已完成 N 步」,下方正文向上跳一段。
- 触发条件:任何一次咨询流式结束。
- 根因:流式中的最后一条由
StreamingMessageEntry渲染,结算后由SettledMessageEntry渲染,两者是不同组件,renderKey相同也会卸载重挂,ChatMessageRow的 GSAP 入场在新挂载的<article>上重放;ConsultationRunTimeline用key={live ? "live" : "settled"}强制重挂,原生<details>的open没有过渡;.message上另有一份 160ms CSS 入场关键帧与 GSAP 的 180ms 叠加。 - 修复:
latestAssistantView派生最后一条 assistant 视图(流式或刚结算),LatestAssistantEntry一个组件、一个 key 负责两个状态;历史列表按excludeLatestAssistant排除尾条。删除.message的 CSS 入场与message-enter关键帧,GSAP 时长改为 0.16s 与 DESIGN.md §6 一致。时间线去掉key,改成button[aria-expanded]+grid-template-rows 0fr→1fr180ms 过渡,折叠后内容inert;summary 文案换行时 120ms 淡入。 - 验证:
tests/chat-stream-settle-contract.test.ts(视图 key 跨结算一致、只有一处渲染尾条、.message无 animation、时间线过渡与 reduced-motion、live 空行「正在处理…」);session-conversation-layout与chat-bundle-splitting-contract的 0.18 锁改为 0.16 并注释原值。 - 防复发:尾条 assistant 必须由
LatestAssistantEntry单点渲染;不得给时间线加随状态变化的key;入场动画只允许 GSAP 一份。 - 相关记录:BUG-473、BUG-475
- 复发自:无
- 修复版本:待发布
BUG-475 | 流式期间步骤时间线无法折叠,请求已发出但第一个事件到达前没有任何进行中反馈
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
consultation-run-timeline.tsx - 用户现象:回答生成中点「正在分析」想收起步骤,下一个 token 又把它撑开;发送后到服务端第一条事件之间只有头像和空白。
- 触发条件:流式期间点击时间线 summary;服务端首事件延迟超过一两秒时。
- 根因:
<details open={live ? true : undefined}>由live受控,用户的开合没有进入状态;rows为空时组件直接返回null。 - 修复:
open = userOpen ?? live,用户切换后记入 state,结算时若用户未动才程序折叠;rows为空且 live 时渲染一条QUEUED_TIMELINE_ROW(「正在处理…」+ 行内 spinner + shimmer 文案)。 - 验证:
tests/chat-stream-settle-contract.test.ts。 - 防复发:时间线开合必须以用户选择优先;live 状态下不得渲染空壳。
- 相关记录:BUG-474
- 复发自:无
- 修复版本:待发布
BUG-476 | 生时校正会话的 Agent 活动 UI 与普通会话是两套,live 标记被 14px 格子裁切
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-01
- 影响面:
rectification-agentic-chat.tsx、agent-activity-status.tsx、chat-message-row.tsx、globals.css、package.json(thinking-orbs) - 用户现象:校正会话里步骤标记是一个 20px 的 canvas 小球,被压在 14px 的格子里边缘裁切;结算后步骤列表永远全展开,下面还多挂一个「本轮完成 · N 个步骤」折叠块;失败时消息上方多一行红字;重新生成时先出现一个假的「正在组织回答…」;普通会话则是行内 spinner、结算收成「已完成 N 步」一行。两边看起来不像一个产品。
- 触发条件:任何一次校正回合与任何一次咨询回合并排对比。
- 根因:校正走
activityTrace+AgentActivityStatus的 trace 分支,普通会话走ConsultationRunTimeline;chat-message-row.tsx里并存三条思考渲染路径(timeline、ConsultationThinkingReport+ThinkingStepTree、trace panel),其中 sections report 已无任何调用方可达;.conversation.is-rectification .agent-thinking-marker { 14px }覆写与ThinkingOrb size={20}冲突。 - 修复:新增
lib/rectification-timeline-adapter.ts,把 trace/receipt/activity 映射成ConsultationTimelineRow(tool → calculate 行;失败 tool 标签加「未完成」;回执方法作为最后一条完成行的 sources chips,去重 ≤8;无 live 行时把「正在…」类活动文案作为 live 行,answer-composition 归 write)。校正消息由此走同一个ConsultationRunTimeline。删除consultation-thinking-report.tsx、thinking-step-tree.tsx、completed-activity-receipt.tsx及其 CSS;AgentActivityStatus只保留无 timeline 的兜底,live 标记统一InlineSpinner12px,移除thinking-orbs依赖;删除 14px 覆写;失败态改走既有 error 通知;重新生成显示 queued 行由真实事件填充。校正thinking.delta仍在服务端公开边界丢弃,本轮未动。 - 验证:
tests/rectification-timeline-adapter.test.ts;chat-stream-layout、chat-bundle-splitting-contract、rectification-agentic-entry、rectification-activity-receipt中锁死路径的断言按「注明原值与错因」改为锁新路径。 - 防复发:任何会话面的 Agent 活动都必须投影成
ConsultationTimelineRow交给ConsultationRunTimeline;live 标记只允许InlineSpinner;不得再引入第二种等待词汇。 - 相关记录:BUG-474、BUG-475
- 复发自:无
- 修复版本:待发布
BUG-477 | 生时校正会话的输入框没有字数上限,也没有接近上限的计数
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-02
- 影响面:
rectification-agentic-chat.tsx、chat-composer.tsx - 用户现象:校正会话里可以无限输入;主对话在剩余 50 字时会出现计数并在到顶时变红(BUG-447),校正会话什么都没有。停止按钮、发送按钮、textarea 也是另一份手写的。
- 触发条件:任何一次校正会话输入。
- 根因:校正面用裸
<Textarea>+ 两个<Button>自己拼输入框,没有maxLength,也没有接CharacterRemaining;ChatComposer只会读主会话的composer-draft外部 store,校正面有自己的draftstate,所以一直没复用。 - 修复:
ChatComposer新增可选受控value与remainingId:有value时以其为准(store 订阅仍在组件内,不改主会话行为),校正面传value={draft},不写主会话 store。校正面渲染ChatComposer(maxLength=500,与主对话同上限)并在composer-footer放CharacterRemaining;三段 placeholder、Enter/Shift+Enter/isComposing处理、停止按钮文案原样保留。 - 验证:
composer-isolation-contract(校正传value、不 importcomposer-draft)、character-remaining-contract、composer-ime-contract、rectification-agentic-entry。 - 防复发:不得再手写第二个聊天输入框;新增会话面一律渲染
ChatComposer,自有草稿走value。 - 相关记录:BUG-447、BUG-478
- 复发自:无
- 修复版本:待发布
BUG-478 | 滚动跟随与「跳到最新」在两个会话面各写一份,主会话每个 token 都触发一次 scrollTo
- 状态:resolved
- 首次发现:2026-09-01
- 最近更新:2026-09-02
- 影响面:
use-conversation-scroll-anchor.ts、page.tsx、rectification-agentic-chat.tsx、rectification-sticky-scroll.ts(删除)、globals.css - 用户现象:主会话流式期间每个 token 触发一次
scrollTo,回答结束瞬间再补一次 smooth 滚动,看起来"先跳到底再滑一下";校正面是另一套 rAF 跟随;「跳到最新」按钮两份不同样式(主会话内联 Tailwindshadow-md、校正面--shadow-soft,文案一个「跳到最新」一个「回到最新」)。 - 触发条件:任意流式回答;向上翻阅历史。
- 根因:
page.tsx的滚动 effect 以activeStreamingText为依赖;校正面自带followTailRef/followLatestContent/rectification-sticky-scroll.ts;按钮各写各的。 - 修复:跟随并入
useConversationScrollAnchor:ResizeObserver观察滚动容器的子元素、MutationObserver跟踪子元素增删,anchored 时下一帧scrollTop = scrollHeight;resetKey变化立即落底;不再在结算时补 smooth 滚动。page.tsx的滚动 effect 删除;校正面接入同一 hook(resetKey = caseId),删除rectification-sticky-scroll.ts及其全部胶水。新增JumpToLatestButton(.jump-to-latest,--shadow-elevated、44px、focus ring),两面共用,文案统一「跳到最新」带箭头;page.tsx内联 Tailwind 版与.rectification-jump-latest删除。校正.message-list720px 覆写删除,与主会话同 900px。 - 验证:
chat-notice-and-scroll-contract(follow 在 hook 内、anchored 守卫在赋值前、page.tsx无scrollTo)、chat-navigation-a11y-contract、chat-stream-layout、rectification-agentic-entry、rectification-answer-choice。 - 防复发:任何会话面不得自行监听 scroll 或按内容变化调
scrollTo;跟随只经useConversationScrollAnchor,跳到最新只经JumpToLatestButton。 - 相关记录:BUG-477
- 复发自:无
- 修复版本:待发布
BUG-479 | 首页揭幕后仍有组件级加载:外层加载屏只等三样,推荐问题、今日星语、校正摘要各自转圈
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
page.tsx启动 effect 与揭幕条件、starter-home.tsx、globals.css(.starter-loading)、lib/home-bootstrap.ts(新增)、DESIGN.md§9 - 用户现象:登录后先看一次整页加载屏,进入界面后推荐问题位置再转一次(
starter-loading)、今日星语卡aria-busy并显示「正在写下今天的星语。」、切会话消息区出现 spinner;用户感受是"进来了又在等"。 - 触发条件:任何一次进入首页;切换到消息尚未缓存的会话。
- 根因:
setHydrated(true)只等账户、模型目录、会话列表;推荐问题、今日星语、校正入口摘要三个 effect 都以hydrated为门槛,只能在揭幕之后才发请求,于是各自带一套等待表现。这些请求彼此无数据依赖,串行体验是历史堆出来的。 - 修复:启动分两阶段(
bootstrapPhase: "account" → "prepare")。账户阶段完成后不揭幕,改进入 prepare:三个 effect 改以bootstrapPhase为门槛在揭幕前并行发起,并预热校正分包import();揭幕 effect 在全部适用项就绪(bootstrapPrepareSettled)或 4 秒预算(BOOTSTRAP_PREPARE_TIMEOUT_MS,从进入 prepare 起算)到期时setHydrated(true)。加载屏文案按阶段切换(bootstrapLoadingCopy)。揭幕后删除starter-loading分支、今日星语aria-busy与「正在写下」文案(pending 显示静态「今天的星语还没写出来。」,到达后静默替换)、会话切换的InlineSpinner(保留sr-only文案)。揭幕后按侧栏顺序后台预取最近 5 条会话消息(sessionIdsToPrefetch)。账户阶段失败仍直接揭幕到错误屏;8 秒 bootstrap 超时不变。 - 验证:
home-bootstrap-reveal.test.ts(纯函数 + 源码合同:prepare 门槛 ×3、揭幕延时、预热、预取、揭幕后无 spinner、加载屏 DOM 合同);daily-starlanguage.test.ts/starter-questions.test.ts按例外条款改读新门槛;tsc、eslint0 error、next build/Static、首屏 gzip-9 513570 → 513889 B(+0.06%)。 - 防复发:任何首页数据拉取不得以
hydrated为门槛再在揭幕后展示等待态;新增的揭幕前依赖加入bootstrapPrepareSettled的输入,而不是各自转圈。main.app-loading[aria-busy="true"]/main.app-loading-error选择器被stale-client-recovery依赖,不得改。 - 相关记录:BUG-464(消息按需加载引入的首屏消息等待,流式轮已把当前会话消息前移到揭幕前)、BUG-470(effect 内 setState 规则)
- 复发自:无
- 修复版本:待发布
BUG-480 | 第一道 D9 选择卡只有 A-D、没有题干
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
persistServerOwnedFocus、persistPlanFocus、finalizeSuccessfulTurnExit、选择题choice_frame.prompt - 用户现象:走查第一道 D9 相处方式卡只有 A–D,题干不在卡片上,也不在 turn 历史里。后续点选题走 answer_choice 路径则正常。
- 触发条件:message/agent 路径首次为
distinguish_candidates建 focus;serverOwnedChoiceCopy因空 prompt/period 返回 null,或 turn-exit 只在 idle 新建 focus 时写题干 turn。 - 根因:copy 为 null 时仍可能留下不可渲染 choice;agent
persistPlanFocus没有 spoken collect 回退;turn-exit 在 agent 已建 focus 时跳过题干持久化。 - 修复:copy 为 null 的区分题 fail-closed,回退为
spokenCollectFallbackFollowup,不建空题干 choice。agent 路径同样回退。turn-exit 只要当前问题有 prompt 就按既有结构规则写题干 turn。D9varga_style优先接上探针问句。不改 Python 引擎,不 bump Skill。 - 验证:
rectification-walkthrough-polishD9 不变量与 unwired 回退;rectification-server-focus;agent-voice-copy-contract。未做登录后的真实页面点选。 - 防复发:任何可渲染
choice_card必须有非空 prompt;不得再把空 prompt 的 choice focus 当成功创建。agent 与 idle 创建链都要走同一 fail-closed。 - 相关记录:BUG-460、BUG-461、BUG-469、BUG-471
- 复发自:BUG-471(可见 legend 修好后,空 prompt 创建链仍会丢题干)
- 修复版本:待发布
BUG-481 | 正文说「界面上有下一问」时问题槽还是空的
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
agentic-rectification指令、VOICE.md、流结束后loadCaseSnapshot - 用户现象:助手说界面上已有下一问,用户当下看不到题干。题干 turn 在流结束后的 turn-exit 才写入。
- 触发条件:Agent 正文断言界面当前状态;前端在
done后才刷新 case 快照。 - 根因:指令允许「过渡到界面上的下一步」;模型把尚未刷新的槽位说成已经出现。
- 修复:指令改为中性过渡(「接下来我们继续」),明确禁止断言界面当前状态。时序沿用已有
await loadCaseSnapshot(),不新增question.ready事件。 - 验证:
agent-voice-copy-contract;rectification-walkthrough-polish指令合同。未做登录后的真实页面点选。 - 防复发:正文不得写「界面上有/出现了下一问」。题干真源仍是服务端问题槽。
- 相关记录:BUG-441、BUG-449、BUG-471
- 复发自:BUG-471(直播看不到题干时,模型仍被要求过渡到「界面上的下一步」)
- 修复版本:待发布
BUG-482 | 同域采集问句逐字复读
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
USER_COLLECT_QUESTION_RETRY、spokenFollowupForUser、buildMethodFollowupPlan - 用户现象:「感情这边,还记得哪年认真在一起、分开,或结婚吗?」在历史里逐字出现两次。第一次用工作事件回答后重问正确,但措辞完全一样。
- 触发条件:该域 collect focus 已关闭且未被拒答,覆盖仍未完成,下一问再次采集同一域。
- 根因:采集 copy 每域只有一句;没有用结构化「已问过」状态切换第二措辞。
- 修复:每域增加 retry 变体。
closedCollectFocuses里该域 collect 已建立且 status 不是 declined 时设collect_retry。不做正文字符串匹配。 - 验证:
rectification-walkthrough-polish同域 retry;agent-voice-copy-contract可见文案词表。未做登录后的真实页面点选。 - 防复发:同域重问必须换第二措辞;判定只用 focus 结构化状态,不读正文。
- 相关记录:BUG-028、BUG-087
- 修复版本:待发布
BUG-483 | 采用后仍挂着采用前的采集题,没有切入前事核对
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
persistNextInterviewIfIdle、accept 路由、.rectification-question-slot渲染位置 - 用户现象:采用 04:53 成功后,当前问题仍是采用前的财务采集,显示在候选卡下方、脱离对话流。预期是最多两件前事核对。
- 触发条件:accept RPC 成功,active focus 仍是 collect;idle 见任何 activeFocus 就直接返回。
- 根因:
buildMethodFollowupPlan在 accepted 时会出 reverse_verify,但 idle 被旧 collect 挡住;accept 成功后也不替换 focus。问题槽是消息列表的兄弟节点。 - 修复:
accepted_time存在且当前 focus 不是 reverse_verify/oos 时,先 skipped 关闭再 persist 下一问。accept 成功后调用同一 idle。问题槽改到最新一条消息内、候选卡之后。GET 不作为主变更路径。 - 验证:
rectification-walkthrough-polish采用后不变量;rectification-eight-methodreverse_verify;rectification-spoken-collect槽位合同。未做登录后的真实页面点选。 - 防复发:
accepted_time非空时current_question不得仍是采用前 collect。问题槽必须留在对话流内。 - 相关记录:BUG-117、BUG-120
- 修复版本:待发布
BUG-484 | overlay can_adopt 与引擎出牌/采用不一致
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
deliveryCapability、GET overlaycan_adopt、offer/accept 可执行性 - 用户现象:职业从未被问,overlay 判
can_adopt:false,但 offer 工具与引擎accept_allowed=true仍出牌且采用成功。 - 触发条件:blocking coverage 未齐(含 occupation 路由缺口),引擎 ceiling 已放行采用。
- 根因:
deliveryCapability把methodCoverageAll和训练门绑进locallySelectable。BUG-472 已把 occupation 移出 blocking 集合,其余 coverage 仍挡 overlay 采用。 - 修复:方案 1(对齐引擎)。coverage 从采用门移除,只保留为问询路由优先级。训练门 3/2、
keep_collecting证据下限、引擎 ceiling、确认门不变。canAdopt为真且 nextAction 已是出牌/采用时,仍持久化deferred_followup里的剩余区分题与未覆盖 blocking collect;点选回执不得用采用口播覆盖已写入的下一题题干。职业/财务/已覆盖层的耗尽采集在出牌态仍跳过。不 bump Skill 10.0.14;「职业挡出牌」的 skill 文本偏差记在本条,下次 skill 版本再改。 - 验证:
rectification-walkthrough-polishoverlay 与引擎对齐;rectification-occupation-coverage-exit;rectification-decide-next-action;rectification-range-offer-deadend;rectification-answer-choice点选后仍持久化下一张带年区分卡与家人采集;rectification-decision-authority非终态出口仍等待家人采集写入。./node_modules/.bin/tsc --noEmit与四组测试见当轮记录。未做登录后的真实页面点选。 - 防复发:overlay
can_adopt必须与 offer/accept 实际可执行性一致。不得把 coverage 重新写回采用门。不得放宽确认门。不得因为canAdopt就跳过剩余区分题或未覆盖的 dasha/D9/D10/家人采集。点选回执在下一题已写入时不得改写成采用口播。 - 相关记录:BUG-453、BUG-463、BUG-472
- 复发自:BUG-472(occupation 移出 blocking 后,deliveryCapability 仍用 coverageComplete 挡采用)
- 修复版本:待发布
BUG-485 | 生时校正开场正文与问题槽同时提问
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:POST
/api/rectification/agentopening、openingBrief、.rectification-question-slot__prompt、口述采集直播界面 - 用户现象:开场助手气泡已经邀请用户说一件经历(例如哪年上大学、哪年换工作),气泡下方问题槽再用一句几乎相同的采集题再问一遍。用户发出「2016 年 9 月上大学」后,分析中的时间线下面仍挂着同一句旧题。
- 触发条件:新 Case
action=opening且current_question.kind=collect_spoken;或在该采集题仍有效时提交下一条自由文本。 - 根因:两条规则并行。BUG-471 规定口述题干真源是 GET
current_question.prompt,必须在问题槽可见。开场模型却在current_question落库之前生成正文,opening brief 还写「当前可询问范围」,于是正文自己提问。轮末persistNextInterviewIfIdle再写入GENERIC_COLLECT_QUESTION。shouldPersistFocusPromptTurn只挡住第二条助手消息,挡不住问题槽。采集题槽也没有选择题那样的!busy门,分析中仍渲染旧题。 - 修复:开场流开始前先
persistNextInterviewIfIdle,让 read-case 能看到已持久化题干。opening brief 与系统指令改为:题干由问题槽承担,正文只打招呼、不得提问或举大学/工作/搬家的例子。问题槽口述题干与输入框 describedBy 在busy/ 重生成时隐藏,与选择卡一致。不 bump Skill,不改 Python 引擎,不放宽确认门。 - 验证:
rectification-spoken-collect锁开场先持久化、brief 不再写「可询问范围」、槽位showCollectSpokenPrompt含!busy;rectification-v9-agent锁 opening brief;agent-voice-copy-contract锁开场不得举例提问。 - 防复发:口述采集直播题干只由问题槽展示。开场正文不得并行提问。采集槽与选择卡一样在忙碌时隐藏。不得用正文字符串判断「有没有问过」。不得把题干再追加成第二条 opening 消息。
- 相关记录:BUG-441、BUG-449、BUG-461、BUG-469、BUG-471
- 复发自:BUG-469(opening 正文仍提问)与 BUG-471(槽位必须可见题干)同时生效
- 修复版本:待发布
BUG-486 | 个人报告财富章有 D2/D11 回执却无结构化分盘,终态 parse 整份失败
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
buildReportEvidenceBundleV2、generatePersonalReport、readAllVargaCharts/readVargaHouses、POST /api/reports后台生成、report_schema_invalid - 用户现象:计费配置可用后,完整个人报告创建成功但生成停在失败;列表
status=failed、failureCode=report_schema_invalid。日志内层是final_parse_rejected,进度可到约 85%,财富章可能已写成 ready,事业/婚恋/时机没有章节行。 - 触发条件:请求含财富主题;引擎
modules.varga_full使用D2_Hora/D11_Rudramsa这类带流派后缀的键;工作流把 D2/D11 标成已执行。财富章因此被标成 write,终态文档却没有charts[].id为 D2/D11。 - 根因:两层叠加。提取只把
^D\d{1,3}$当成分盘 id,丢掉D2_Hora/D11_Rudramsa,readVargaHouses也只认 D9/D10。主题计划仍可用 D2/D11 回执闭合财富章。组装后findThemeCoverageViolations要求财富主题必须带结构化 D2 与 D11,最终 parse 失败,公开码压成report_schema_invalid。 - 修复:把文档允许的分盘键归一成 D2/D9/D10/D11/D24(含
D2_Hora等别名),占据星体走 celestial allowlist。write 主题若缺少对应结构化分盘,降成 blocked disclosure,不再组装成会解析失败的文档。终态 parse 失败时把缺分盘与 hash 失配从笼统的final_parse_rejected分出来;日志仍只写 allowlist token,不含用户或模型原文。不放宽.strict()文档 schema,也不改 assertionLevel 规则。 - 验证:
frontend/tests/personal-report-generation.test.ts用D2_Hora/D11_Rudramsa抽出 D2/D11 且财富章成为 claim;frontend/tests/personal-report-generation-v2.test.ts财富 write 只有 D1 时 ready 且财富进 blocked;frontend/tests/personal-report-contract.test.ts财富主题缺 D2/D11 仍拒绝。Gitea run 2298 在report-interpretive-facts.test.ts6 fail:夹具只有 D2/D11/D24 回执、没有varga_full,降级后 wealth/education 卡被拿掉。补上结构化 D2/D11/D24 后该文件本地绿,并锁「仅回执则 demote」。未用登录会话在 staging 重生。 - 防复发:文档分盘 id 必须从引擎
varga_full别名归一,不得只认D2这种短键。财富/事业/婚恋/教育 write 主题缺对应结构化分盘时必须降级 blocked,不得把final_parse_rejected当正常完成。新增分盘别名必须同时覆盖提取与文档CHART_IDS。改REQUIRED_THEME_CHARTS或 demote 后必须跑frontend/tests/report-interpretive-facts.test.ts;期望 write 主题 claim card 的夹具必须带对应结构化分盘,不能只标 machine section used。 - 相关记录:BUG-352、BUG-451、BUG-467、BUG-489
- 复发自:无
- 修复版本:待发布
BUG-487 | 口述采集题干在动作图标下方,刷新后聊天历史丢失
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:POST
/api/rectification/agentopening/evidence、finalize_agentic_rectification_turn、重生成、.rectification-question-slot__prompt - 用户现象:开场助手气泡只打招呼,采集题干出现在点赞/复制/重生成图标下面的问题槽。刷新页面后气泡里没有那句题干,因为它从未写入
assistant_message。 - 触发条件:新 Case
action=opening且current_question.kind=collect_spoken;或同一采集题仍有效时刷新/重进会话。 - 根因:BUG-471 / BUG-485 把口述题干真源放在 GET
current_question.prompt的直播槽。槽位不是 turn 历史。shouldPersistFocusPromptTurn在已有非空正文时禁止第二条助手消息,所以刷新只能看到打招呼。 - 修复:
composeCollectSpokenAssistantText按题干字符串精确身份接到同一条正文末尾。opening/evidence 在finalizeTurn之前 idle-persist 当前题、用answer.delta replace替换直播正文,并把组合文本写入p_assistant_message。重生成同样回贴当前collect_spoken题干。前端仅在最新助手正文尚未带上该精确题干时才渲染采集槽;选择卡仍走问题槽。不 bump Skill 10.0.14,不放宽确认门,不用正文字符串判断「有没有问过」。 - 验证:
rectification-collect-prompt;rectification-spoken-collect锁槽位兜底与composeCollectSpokenAssistantText;rectification-v9-agent锁 finalize 前写入组合正文;rectification-v9-regenerate锁重生成回贴;agent-voice-copy-contract锁服务器接题干并写入聊天历史。 - 防复发:口述采集题干必须出现在同一条已完成助手消息里,刷新后仍在
turns[].text。不得只靠问题槽直播。不得再追加第二条 opening 题干消息。不得用includes/ 正则扫描正文决定是否已提问。 - 相关记录:BUG-449、BUG-461、BUG-469、BUG-471、BUG-485
- 复发自:BUG-485(槽位可见但不是聊天历史)
- 修复版本:待发布
BUG-488 | 口述采集题干仍画在动作图标下方,没有进气泡正文
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
rectification-agentic-chat最新助手气泡、.rectification-question-slot__prompt - 用户现象:开场气泡只有打招呼,采集题干仍出现在点赞/复制/重生成图标下面。staging 已是 BUG-487 的 SHA。
- 触发条件:新 Case opening,
current_question.kind=collect_spoken,GET 已有 prompt。 - 根因:BUG-487 把题干接到
assistant_message并允许槽位兜底。直播正文仍可能只有打招呼(replace 未进客户端 raw、或 GET 后才有 current_question),于是showCollectSpokenPrompt把同一句画在图标下。用户要的是气泡正文,不是槽。 - 修复:不再渲染采集题槽。最新助手气泡按题干精确身份
composeCollectSpokenAssistantText;定稿后的 Case snapshot、发送下一条、以及挂载后的 snapshot 回调把组合文本写进该条消息状态,复制走组合文本。选择卡仍走问题槽。模型仍不自己提问,避免气泡里两句相近问法。不 bump Skill,不放宽确认门。 - 验证:
rectification-spoken-collect锁气泡拼接、禁止rectification-question-slot__prompt;既有 collect-prompt / v9-agent / regenerate / voice 锁保持。staging 门禁 run 2304 因showActivity={bubbleMessage.state…}对不上rectification-agentic-entry源码锁失败;showActivity改回displayedMessage.state(气泡仍走bubbleMessage)。run 2305 前端 2509/2509 后 ESLint 在currentQuestionRef.current = currentQuestion(render)和 snapshotsetMessageseffect 上报react-hooks/refs/react-hooks/set-state-in-effect;ref 改到useLayoutEffect,题干写入改到 snapshot/send 回调。 - 防复发:口述采集题干不得作为动作图标下的兄弟节点。不得用正文字符串判断「有没有问过」。不得同时让模型提问又拼接同一句服务端题干。
- 相关记录:BUG-471、BUG-485、BUG-487、BUG-585
- 复发自:BUG-487(服务端拼接后槽位兜底仍可见)
- 修复版本:待发布
BUG-489 | 个人报告四主题全部 blocked:分盘值形状、Transit 回执与 Chara Karaka 层缺失
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
deriveVargaHousesFromEngine、buildReportEvidenceBundleV2、_attach_local_consultation_layers、personal_fullcareer/marriage/timing/wealth - 用户现象:staging 真实 personal_full 报告四个请求主题全部 blocked,摘要退化为「报告主题尚未生成」。blocked 理由分别为缺 AmK+Transit、缺 DK、缺 Transit、缺结构化 D2/D11。
- 触发条件:个人报告按主题调用
/api/consultation_workflow,提取层组ReportEvidenceBundleV2。与出生数据或线上环境无关,本地 smoke 出生可逐字复现。 - 根因:三层叠加。
modules.varga_full真值是小写ascendant/sign_index/planets字典,提取层只读旧形状Ascendant/sign_idx/ 顶层行星键,所有分盘图提取失败;modules.transits.status=executed存在但从不 upsert Transit 回执;consultation 响应的modules.jaimini只有arudha_padas,没有 Chara Karaka,AmK/DK 回执无法闭合。BUG-486 的45f190a1/643f6f7a只修了键名别名、demote 兜底和手造旧形状 fixture,测试绿、线上仍挂。 - 修复:提取层双形状兼容真实 varga;
transits.status==="executed"时 upsert Transit(partial+executed,缺或非 executed 不产回执);引擎把jaimini.calc_chara_karaka_7挂到chart.modules.jaimini.chara_karakas(不改arudha_padas),前端按 AmK/DK 存在性 upsert 回执。不放宽主题最低证据组,不改既有字段形状。 - 验证:golden fixture 形状契约;
frontend/tests/report-blocked-repairs.test.ts锁双形状、Transit 三态、AmK/DK、四主题 claimCards 4 / blocked 0、九主题全 claim;tests/test_consultation_consumer_context.py::test_local_consultation_layers_attach_chara_karakas_from_jaimini;既有 personal-report generation / interpretive / contract / v2 回归绿。本地tsc --noEmit/ eslint 0 error /next build --webpack/ pytest 28。全量tsx --test2514/2515,唯一失败是既有 Docker 争用 flake,同文件单独 7/7。Gitea gate 2308 与 Deploy staging 2309 成功;https://staging.jyotisha.chat/api/health的deployment.gitCommit=7faf8555a589a0bdf14a5b17323352470816a7ee。登录后的 staging 重生与 writer/token 抽查仍需委托方(本环境 401,无模型凭据)。 - 防复发:分盘 fixture 必须来自真实引擎响应,不得再手造
Ascendant/sign_idx形状当 live 契约。Transit / AmK / DK 回执必须来自模块数据存在性,不得用available_layers名目冒充已执行。新增chara_karakas不得改写既有arudha_padas。改提取形状后必须跑report-blocked-repairs与report-interpretive-facts。 - 相关记录:BUG-486、BUG-467、BUG-352
- 复发自:BUG-486(键名别名与 demote 治标,值形状与缺失层未修)
- 修复版本:
7faf8555(staging;未提升 main)
BUG-490 | 校正题目必须在同一条助手消息里,删除问题槽
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
agentic_rectification_conversation_focuses.asked_turn_id/answer_option、GET/api/rectification/cases/[caseId]turns[].question、rectification-set-focus.spokenPrompt、rectification-agentic-chat、.rectification-question-slot - 用户现象:采集题、区分题、前事核对出现在助手气泡下面的独立问题槽,或刷新后只剩正文。产品要求每一问都是同一条助手消息:正文 → 题干 → 选项;已答原地变灰;刷新从 turn 重建。
- 触发条件:任意校正 opening / message / answer_choice / accept 后仍有 active focus。
- 根因:focus 没有挂到 asked turn,也没有记录所选选项。前端只能从 GET 直播槽或正文拼接恢复题目,所以必须另画
.rectification-question-slot。Agent 被禁止调用 set-focus,题干只能用服务端模板。 - 修复:focus 增加
asked_turn_id与answer_option。GET 按asked_turn_id把question接到 assistant turn。Agent 用rectification-set-focus.spokenPrompt写题干(校验失败两次落模板)。题干和选项画在同一条<article>里。删除问题槽与把题干拼进assistant_message的路径。旧 turn 仅在精确后缀\n\n${prompt}且asked_turn_id命中时 detach。不 bump Skill 10.0.14,确认门仍 fail-closed。 - 验证:
rectification-question-in-message、rectification-spoken-prompt、rectification-spoken-collect/rectification-collect-prompt/rectification-walkthrough-polish/rectification-answer-choice/rectification-decision-authority/rectification-varga-style-copy迁到新合同;rectification-v10-tool-contract锁 Agent 可调用 set-focus,两次invalid_spoken_prompt后retryable=false落模板。 - 防复发:DOM 中不得再出现
.rectification-question-slot。运行时不得用正文字符串判断「有没有问过」。任意 active focus 必须能在某条 assistant turn 的question.focus_id上找到。缺题状态只出现在输入框上方。本题废止 BUG-441/461/471/485/487/488 中与「槽」或「正文不得含题干」绑定的条款;其余条款保留(有选项时题干可见、共用--assistant-content-inset、不得用正文判断状态、开场不得并行提问、不得追加第二条 opening 题干消息)。 - 相关记录:BUG-441、BUG-461、BUG-471、BUG-480、BUG-485、BUG-487、BUG-488、BUG-491
- 复发自:BUG-488(拼接进正文后选择卡仍走槽,刷新合同仍不完整)
- 修复版本:待发布
BUG-491 | message 路径预建 choice focus 不挂 asked_turn_id,刷新丢题干
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
persistNextInterviewIfIdle、finalizeSuccessfulTurnExit、agent-run message 路径预建 choice focus - 用户现象:用户回答采集题后进入区分题时,在线会话也许能看到下一问;刷新后对应助手消息没有题干和选项。
- 触发条件:message 路径上 agent-run 已为下一问建好 choice focus,随后
finalizeSuccessfulTurnExit因已有 focus 早退,不写题干 turn。引入提交约为39c30b55。 - 根因:题干曾依赖第二条 assistant turn 或正文拼接。已有 active focus 时 idle persist 直接返回,不把 focus 链到当前 turn。
- 修复:题干改为 turn 上的
question。idle persist 若已有 active focus,仍用当前turnId回写asked_turn_id。GET 按该列重建。不再需要第二条题干 turn,早退不再丢题。 - 验证:
rectification-question-in-message锁「预建 choice focus + askedTurnId → set_focus.p_asked_turn_id」;GETturns[].question按asked_turn_id连接,禁止按时间戳推断。 - 防复发:opening / message / answer_choice / accept 建立的每个 focus 都必须有
asked_turn_id,且 GETturns[]恰有一条 assistant turn 带该question。不得按asked_at与created_at对题。 - 相关记录:BUG-480、BUG-490
- 复发自:无
- 修复版本:待发布
BUG-492 | 消息路径 set-focus 用 Agent 选项覆盖服务端 choice schema,探针不计分
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
rectification-set-focus、expectedAnswerSchemaFor、projectCurrentQuestion、method-followup Agent hint - 用户现象:答完采集题后的区分题没有 A–D,或点选后候选比较不更新。刷新后同一条助手消息里仍可能只有题干没有选项。
- 触发条件:message 路径 Agent 只传
spokenPrompt(或只传choice.option_a..d、或自写options[].answer_class)调用rectification-set-focus,且next_followup带choice_frame。 - 根因:
d9404976把 set-focus 重新交给 Agent 写题干,但execute仍用parseAgentChoiceCopy(Agent expectedAnswerSchema)建 choice。只写spokenPrompt时 schema={prompt},投影成采集/空题,idle persist 见 active focus 早退,服务端 choice 永远建不出,该探针不计分。Agent 自写answer_class则模型拥有映射,违反选项与计分服务端所有。 - 修复:set-focus 按
next_followup.choice_frame强制expectedAnswerSchemaFor/ collect schema,忽略 Agentchoice。无下一问返回no_pending_question。hint 改为「你只写 spokenPrompt」。同探针再 set-focus 走 prompt 外 identity 幂等。 - 验证:
rectification-question-ownership锁只传 spokenPrompt → 四选项与serverOwnedChoiceCopy相等、probe_id非空、kind===choice;额外 Agent choice 被忽略;采集题collect:true;无下一问不写 focus;第二次 set-focus 幂等且选项不丢。rectification-question-in-message的 choice fixture 补options[].answer_class并断言kind与四选项。 - 防复发:任何路径不得再从 Agent 输入读
choice。choice fixture 必须能过parseAgentChoiceCopy。已知边界:用户不答上一题直接发新消息时,旧 focus 仍挂在旧消息、新 turn 无 question(不变量 1 短暂不成立),旧卡仍可点,属可接受。 - 相关记录:BUG-490、BUG-491
- 复发自:BUG-490(题目进消息后开放 Agent set-focus,未改 schema 所有权)
- 修复版本:待发布
BUG-493 | 题目列表 RPC 失败时 fail-open,聊天里全部题目消失
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
listV10ConversationFocuses、GET/api/rectification/cases/[caseId]question_source、rectification-agentic-chat - 用户现象:迁移未跑或 RPC 权限错误时,历史消息上的题干和选项全部消失,界面只剩「等待服务端更新」,没有失败提示。
- 触发条件:
list_agentic_rectification_conversation_focuses返回 error 或抛异常。 - 根因:列表函数失败时静默
return [],GET 把空列表当成「没有题」,前端无法区分加载失败与真的缺题。 - 修复:失败时
console.warn并返回{ focuses: [], available: false }。GET 带question_source: "unavailable"|"focus"。前端文案「题目加载失败,请刷新。」与「正在等待服务端更新」分开。 - 验证:
rectification-question-ownership锁 RPC error → 列表available===false、turns 上 question 全 null、question_source==="unavailable"、warn 含 case 与 reason。GET 仍走 200 组装路径,不把列表失败提升为 503。 - 防复发:题目加载失败不得再吞成空数组。
question_source为unavailable时不得显示「服务端更新中」。 - 相关记录:BUG-490、BUG-492
- 复发自:无
- 修复版本:待发布
BUG-494 | 题干可省略探针年份,嵌入卡又隐藏 why
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
validateSpokenPrompt、rectification-set-focus、VOICE.md、嵌入RectificationChoiceCard - 用户现象:区分题看起来像「你有没有一段认真开始或结束的关系?」,用户看不到是哪一年,答错年份会记到错误探针上。
- 触发条件:
followup.probe_year或choice_frame.period含年份,但 AgentspokenPrompt不写该年份。嵌入卡variant="embedded"隐藏why。 - 根因:
year_mismatch只在题干已经写出 19xx/20xx 时比对;缺年份直接放过。 - 修复:有
probe_year时题干必须包含该年,否则year_missing;无probe_year但 period 含年份时每个年份都要出现。采集题不加年份要求。VOICE 增加漏写年份坏例。 - 验证:
rectification-spoken-prompt锁year_missing/year_mismatch/ 通过;嵌入卡测试断言question.prompt含探针年份(结构断言,不扫正文)。 - 防复发:区分题题干必须能单独看出年份/期间,不得依赖已隐藏的
why。 - 相关记录:BUG-490、BUG-492
- 复发自:无
- 修复版本:待发布
BUG-495 | 选择题选中项外圈粗描边溢出气泡
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
.birth-time-choice-option:focus-visible、嵌入RectificationChoiceCard、.message-bubble - 用户现象:点选 A 后(或新题自动聚焦第一项时),选项外侧出现一圈粗的红棕色描边,圆角对不齐,看起来像边框溢出。已有选中底色和边框已经够用。
- 触发条件:校正聊天里嵌入选择题;选项在
.message-bubble { overflow: hidden }内。新题会focus()第一项,点击后按钮仍保持:focus-visible。 - 根因:
:focus-visible使用outline-offset: 2px(会话页还有3px),描边画在按钮外面,被气泡裁切。选中态border-color: var(--color-action)与焦点色相同,叠成双层粗边。 - 修复:焦点环改为
outline-offset: -2px画在按钮内。已选中项不再叠加 outline(高对比模式除外)。嵌入卡不再自动聚焦第一项。 - 验证:
rectification-varga-style-copy锁 inset offset、选中无 outline、卡片不.focus()。 - 防复发:选择题焦点环不得使用正
outline-offset。选中态只保留data-selected的边框和底色。 - 相关记录:BUG-490
- 复发自:无
- 修复版本:待发布
BUG-496 | 个人报告写作失败被压成 report_schema_invalid,首章失败后其余主题未开始
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
generateSectionedPersonalReport、createPersonalReportAgent、classifySectionErrorCode、GET /api/reports、GET /api/reports/[reportId]、报告中心失败卡片 - 用户现象:blocked-repairs 部署后,真实
personal_full仍显示生成失败,可见码只有report_schema_invalid。四主题里只有事业章留下失败记录,婚恋/财富/应期未开始。 - 触发条件:章节 writer 输出未通过
assertWriterOutput(常见为evidenceRefs与 plan 集合不等),或输出在 token 预算内被截断;随后整份报告失败。 - 根因:三层叠加。旧错误分类把任何含
evidence的消息打成section_evidence_insufficient,report_writer_evidence_refs_mismatch被误标;章节输出预算用英文「字符 ÷ 2」,standard 上限 1200 字中文只分到 1024 token;一章 blocked 或未捕获抛错可以中止整份,job 停在约 43%。失败详情只有聚合码,页面无法区分截断、引用不齐或未开始。staging 事故窗口的 writer telemetry 在容器 recreate 后丢失,本事故不能写成finishReason=length。 - 修复:中文口径重算每章/摘要
maxOutputTokens,去掉 ÷2;length 修复要求压到字数下限且预算不低于首次。Prompt 要求逐字复制plan.evidenceRefs;repair 只加失败类别。refs/identity 先于泛化evidence分类。单章失败后继续其余主题;all_sections_blocked走统一失败日志。详情与列表聚合已有 section 行,展示可读摘要与错误码,不改表、不放宽 writer schema。 - 验证:预算表锁定 concise 1218 / standard 2128 / deep 3072 / research 3072 / summary 3072。refs mismatch 分类为
section_refs_mismatch且其余主题仍交付。repair 提示含类别词、不含内容。失败详情从 section 行汇总;四章 ready 但整份失败时摘要为「已写成,未完成装配」。tsc --noEmit、改动文件 ESLint、个人报告聚焦测试。staging5c0bec0c真实 standard 四章均 ready(无finishReason=length),整份因onProgress抛错被标成report_schema_invalid;进度投影失败改为不中止装配。 - 防复发:输出预算不得再用字符 ÷ 2。
assertWriterOutput保持 id/theme 全等与 refs 集合相等。section 错误码不得把 refs mismatch 归进 evidence insufficient。一章失败不得中止其余 write 主题。用户可见失败必须有错误码级摘要,不得只展示report_schema_invalid。日志与 PROGRESS 不得写入 prompt、bundle、模型原文或用户资料。 - 相关记录:BUG-352、BUG-451、BUG-486、BUG-489
- 复发自:BUG-451(分章后仍把写作失败压成整份 schema 码,截断预算与失败分类未按中文口径收口)
- 修复版本:待发布
BUG-497 | 采集阶段同时出现采集题和采用卡
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
publicDecisionFields、POST .../candidates/accept、parseRectificationCandidateResult、rectification-agentic-chat、OPENING_COLLECT_DOMAIN - 用户现象:采集题还在问时,同一条助手消息下面出现三张「采用此时间」卡。开场「随便一件带时间的经历」被跳过或答成搬家后,真正的教育采集题变成「再补一件」,或教育域被当成已拒。
- 触发条件:
session_outcome=collect_evidence,内部capability.canAdopt=true,当前题是collect_spoken。开场题曾以collect:education:collect_method_evidence持久化。 - 根因:
collect()把canAdopt原样投影成公开can_adopt。前端showSelectionCards只靠!showLiveChoiceCard,采集题没有 A–D 卡,采用卡就露出来。v9 删掉 offer 门后,BUG-313 / BUG-323 的防复发条款失效。开场题不是教育题,却以education持久化。 - 修复:公开
can_adopt只在adopt_representative/provisional_range_user_stopped/awaiting_confirmation/validated_range为真(后者是 holdout 已过、ready_to_adopt 的既有语义,不是新开口)。accept 在can_adopt=false时 409;前端再按session_outcome双保险。采集阶段只显示只读范围行,composer 旁提供「先这样」。开场域改为other(idcollect:other:collect_method_evidence);declinedDomains/domainCollectFocusAsked忽略collect:other:开场行。occupation 仍走collect:occupation:。 - 验证:
rectification-adopt-flow-20260902、rectification-decide-next-action、rectification-confirmation-gate、rectification-candidate-result;accept 源码锁 409 在 RPC 之前。rectification-adopt-flow-fix-20260903跳过开场后 exhaustion 仍给 education 且无collect_retry。 - 防复发:
can_adopt只能收紧,不得在collect_evidence/discriminate_candidates/validate_holdout放行。exact_minute_confirmed/completed_with_range不得新增放行。开场题不得占用 education/career/relationship/family,不得为 unknown。不改 DB check 约束。 - 相关记录:BUG-313、BUG-323、BUG-501
- 复发自:BUG-313、BUG-323
- 修复版本:待发布
BUG-498 | 采用后核对题 choice_card 为空导致不可答
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
keepAcceptedFocus、projectRectificationChoiceCard、rectification-set-focus、expectedAnswerSchemaFor - 用户现象:点「采用此时间」后出现核对题,嵌入卡 disabled,点不了 A–D。题干有两个年份时沿用错年;schema 缺
probe_year时从文案反推。 - 触发条件:已采用,active focus 为
reverse_verify:education_style:score,随后 GET / set-focus 重算 followup。schema 无probe_year且题干含19xx/20xx。 - 根因:承接分支用
method_id: active_focus重建question_id,与已持久化 id 不一致;projectRectificationChoiceCard按设计拒绝串题,返回 null。核对题 schema 还被最高增益区分探针盖上 relocation identity。keepProbeYear用题干正则兜底,和「不得用字符串反推状态」同向。 - 修复:
keepAcceptedFocus/out_of_sample_check沿用已持久化questionId、ask_theme、domain和选项。不放宽 choice-card 校验。reverse_verify schema 不盖区分探针。spokenPrompt 含钟点时保留服务端题干以免卡片不可解析。区分题 keep 仍走probe:semantic_key。reverse_verify / oos schema 写入followup.probe_year;keep 只取匹配探针或 schema;缺年则省略。删除题干正则。 - 验证:
rectification-adopt-flow-20260902、rectification-question-ownership采用后续跑 set-focus。rectification-adopt-flow-fix-20260903无 schema 年且无匹配探针时 followup 不带probe_year,method-followup.ts无年份正则。 - 防复发:无 semantic_key 的 followup 必须
focus.questionId === frame.question_id。不得从题干/正文抠年份或其它状态。 - 相关记录:BUG-492、BUG-497
- 复发自:无
- 修复版本:待发布
BUG-499 | 采用卡不沉淀进历史,列表末尾裸按钮
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
rectification-agentic-chat、attachOfferResultToTurns、核对题 skip 文案 - 用户现象:采用后三张卡消失,列表末尾出现「用这个时间看盘」;续跑结束后卡又出现在新消息下,与核对题并存。被关掉的采集题无痕消失。状态条「改选」曾是不可点的
<span>。 - 触发条件:采用后续跑
busy;savedStatus=accepted渲染rectification-consult-handoff。 - 根因:卡永远锚在最新助手消息;采用后的界面动作散落在列表末尾,不跟出卡消息走。「改选」赋了
offerSectionRef但从不清滚动。 - 修复:出卡消息拥有
candidateOffer;采用后原地结算「已采用 / 改选」;状态条承接「改选」;核对题「这题跳过」;被关掉的采集题标「已跳过」。「改选」改为<button type="button">,点击scrollIntoView到出卡消息并闪一次rectification-message-flash。showSelectionCards为 false 时不渲染。「用这个时间看盘」见 BUG-502。 - 验证:
rectification-agentic-entry、rectification-activity-receipt、rectification-adopt-flow-20260902组件源码锁。rectification-adopt-flow-fix-20260903锁「改选」为 button +scrollIntoView。 - 防复发:已采用后不得把卡锚到最新消息;不得再渲染
rectification-consult-handoff。「改选」必须是 button。 - 相关记录:BUG-497、BUG-490、BUG-502
- 复发自:无
- 修复版本:待发布
BUG-500 | 校正运行耗时不可观测
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-02
- 影响面:
get_agentic_rectification_turn_receipt、tool-service、rectification-compare-candidates、聊天进度文案 - 用户现象:采用后续跑约三分钟,回执只有 tool/status/methods;开场 set-focus 失败原因也看不见。
- 触发条件:compare-candidates 走引擎;set-focus
invalid_spoken_prompt;超过 45 秒仍显示「正在处理」。 - 根因:回执不暴露
started_at/elapsed_ms;失败只有safeErrorCode;进度文案不跟活跃步骤切换;read_only仍可能重跑 vedastro-validate。 - 修复:回执增加每步耗时和 fingerprint detail;compare 记引擎各段;45 秒后按当前工具换文案;minute-sensitive 已
passed时跳过 vedastro-validate,failed仍重试且不重算排名。 - 验证:
rectification-adopt-flow-20260902的 elapsed_ms / 45 秒文案;rectification-eight-method失败校验重试。 - 防复发:失败行不得用 GET 时的
now()计算耗时;确认门语义不因沿用校验而放宽。 - 相关记录:BUG-497、BUG-498
- 复发自:无
- 修复版本:待发布
BUG-501 | 采集阶段关采用卡后无题无出口
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
inspectNonTerminalTurnExit、persistNextInterviewIfIdle、persistExhaustionCollect - 用户现象:采集阶段把题问完后 composer 空着,没有下一问、没有采用卡、也没有「先这样」。
- 触发条件:
session_outcome=collect_evidence,内部canAdopt=true,无 active focus,next_followup/deferred_followup都空。 - 根因:
35e5781e收紧公开can_adopt后,非终止轮出口仍把内部decision.canAdopt当成「已有出口」。persistNextInterviewIfIdle不建题,ensureNonTerminalTurnExit又跳过persistExhaustionCollect。此前漏出的采用卡(BUG-497)掩盖了这个死角。 - 修复:
satisfied与 idle 跳过持久化改看publicCanAdopt(decision)。公开不能采用时走persistExhaustionCollect,兜底采集题自带「先这样」。 - 验证:
rectification-adopt-flow-fix-20260903采集期ensureNonTerminalTurnExit建collect_spoken并打rectification_nonterminal_exit_repaired;adopt_representative对照不建题。 - 防复发:没有可回答问题且用户不能采用时,不得把内部
canAdopt当成非终止轮已满足。公开can_adopt只能收紧。 - 相关记录:BUG-497
- 复发自:无
- 修复版本:待发布
BUG-502 | 状态条「用这个时间看盘」无实际作用
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
rectification-agentic-chat、conversational-birth-time-rectification、page.tsx、use-session-management - 用户现象:采用后点「用这个时间看盘」,只开一个空会话并往输入框塞一句服务端不读的话,与自己点「新对话」无异。
- 触发条件:
savedStatus=accepted,状态条渲染onStartConsultation按钮。 - 根因:采用时 RPC 已写
profiles.active_birth_time;按钮没有刷新档案、不带回原问题,只做startNewChat()+ 填 draft。 - 修复:删除按钮与
onStartConsultation/startConsultationAfterRectification链。状态条改为「已采用 … · 改选」加「之后新建对话即按此时间排盘。」;待回问题横幅改为结束后新建对话再问。onSaved仍refreshAccount。 - 验证:
rectification-adopt-flow-fix-20260903扫描frontend/src;rectification-adopt-flow-20260902/rectification-agentic-entry改为doesNotMatch。 - 防复发:不得再提供「用这个时间看盘」或
rectification-consult-handoff。新对话按verified_chart取采用时间。 - 相关记录:BUG-499
- 复发自:无
- 修复版本:待发布
BUG-503 | 一道题选「一时说不好」就交付
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
uncertaintyHighFromAnswers、decisionBudgetFromInference、composeChoiceNarration、publicNextAction、user-copy - 用户现象:整场只收到一道选择题,选 D「一时说不好」后直接交付几十分钟区间;旁白还说「更新了候选比较」。
- 触发条件:
classified_from=choice的用户答题数为 1,answer_class=unsure。连续两道 D 也会因maxPlateauRounds=2交付。 - 根因:
uncertaintyHighFromAnswers没有样本下限,1 答即1 >= ceil(1/2)。applyProbeOutcome把 unsure 记成low_information,平台期从第一道就开始计数。上游 Stop Rule 是「样本够多且大半答不上来」,实现成了任意一道答不上来。 - 修复:
references/rectification_policy.v1.json增加minUncertaintyAnswers: 3。不足 3 答不停。达到下限后仍用「≥ 一半」判定。平台期在decisionBudgetFromInference用同一下限才统计连续low_information,maxPlateauRounds仍为 2。unsure 旁白改为「已记录。这题先不计分,换一件事问。」appliedInference仍持久化。交付话术在进度句前说明user_uncertainty_too_high/tied_first。publicNextAction只读暴露stop_reason。用户主动「先这样」仍立即交付。 - 验证:
rectification-convergence-budget1 答不停且继续出区分题;3 答 2 unsure 停;4 答 2 unsure 停;2 答连续 unsure 不因平台期停,5 答后连续 2 道 unsure 停。rectification-answer-choiceunsure 旁白与 §0 形状(答 D 持久化下一问、不采用)。agent-voice-copy-contract锁停止原因文案。 - 防复发:不确定度停止必须先过
minUncertaintyAnswers。该数字只在references/rectification_policy.v1.json定义。不得把maxPlateauRounds/ 8 轮 / 10 答外层熔断改掉。不得用题干字符串反推停止原因。 - 相关记录:BUG-463
- 复发自:无
- 修复版本:待发布
BUG-504 | 开场采集题用「再说一件」措辞,用户不知从何说起
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
spokenFollowupForUser、GENERIC_COLLECT_QUESTION - 用户现象:新用户开场招呼后看到「也可以再说一件你记得大概时间的事。」不知道该写什么;招呼段按设计不提问、不举例。
- 触发条件:零证据开场,
OPENING_COLLECT_DOMAIN = other,followupsource=method_coverage。 - 根因:
e8c98c37把开场 domain 改成other后,spokenFollowupForUser按 domain 查USER_COLLECT_QUESTION。other是给已有证据后的补问兜底(「也可以再说一件」),真正开场文案GENERIC_COLLECT_QUESTION只在 domain 查不到时才用。domain→文案查表把兜底文案当开场。 - 修复:
source === method_coverage && domain === other && collect_retry !== true时用GENERIC_COLLECT_QUESTION。文案补上「一件事」和「哪年搬的家」。oos_blind兜底与collect_retry的other仍用原补问。不改OPENING_COLLECT_DOMAIN/ questionId / 记账。 - 验证:
rectification-server-focus零证据开场 spoken prompt 等于GENERIC_COLLECT_QUESTION,不含「也可以再」,含「比如」;exhaustionoos_blind与collect_retry仍走USER_COLLECT_QUESTION.other/ retry。源码锁至少两个具体例子。 - 防复发:开场题文案必须带具体例子;
USER_COLLECT_QUESTION.other只用于已有证据后的补问。 - 相关记录:BUG-501
- 复发自:无
- 修复版本:待发布
BUG-505 | 生时校正面进入时先闪普通对话、再空白、再整面重挂;问题槽用「等待服务端更新」裸文案
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
use-rectification-surface、use-session-management、page.tsx(面板 key、prepare 阶段)、rectification-agentic-chat、starter-home、sidebar-session-row - 用户现象:点首页卡片没有任何反馈;从侧栏进已有校正会话先闪一帧普通对话、再是一块空白面板、记录到了再整面重挂;新建校正第一轮结束时画面闪一下、滚动归零;恢复会话后快照没回来前问题区空着;答完题后 composer 上方是「当前没有可回答的问题,正在等待服务端更新。」之类没有动作、没有时限的文案。
- 触发条件:任何进入校正面的路径(卡片、侧栏、深链/刷新);任何一轮结束到下一题到达之间。
- 根因:
selectSession先setActiveSessionId再 open,rectificationSurfaceOpen在 open 返回前为 false;面板 key 带rectificationTurns.length > 0 ? "ready" : "loading",turns 到达即重挂;面板挂载后自拉快照;问题缺口只有裸文案。 - 修复:
/cases/open后用hydrateRectificationCase一次读取 turns + 快照(与BOOTSTRAP_PREPARE_TIMEOUT_MS同一 4 秒上限)再切会话、写 URL;selectSession对未打开的校正会话不先切;启动时选中的校正会话在 prepare 阶段完成 hydration 再揭幕;key 只剩 session/Case 绑定,后到 turns 经渲染期 prop 调整只填空 transcript;卸载中止流与快照读取。入口卡片脚注静态「正在打开…」、侧栏行「打开中」,不转圈。缺口改为rectificationQuestionGapState:preparing(时间线 live 行「正在准备下一个问题…」+ 2s 定时重拉 ≤2 次)→unavailable(「没有拿到下一个问题。」+ 44px「重新加载」);hydration 超时也走同一缺口。 - 验证:
rectification-surface-contract(一次揭幕、无重挂、prepare 阶段 hydration、静态入口反馈、缺口四态、无「等待服务端更新」);rectification-surface-state(hydration 超时/失败、缺口纯函数、turn 解析);rectification-agentic-entry/rectification-question-in-message/rectification-spoken-collect的旧锁按红线改注。 - 防复发:面板一个会话只挂载一次,turns 与快照都是 prop/state 更新;任何等待必须是时间线 live 行或带动作的状态,不得出现「等待服务端更新」类文案;揭幕后不得出现非生成中的 spinner。
- 相关记录:BUG-479(首页两阶段揭幕)、BUG-473(校正面接入共享时间线)
- 复发自:无
- 修复版本:待发布
BUG-506 | 选择题点击后有空帧并闪下一题卡片;采用候选只有按钮变灰
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
rectification-agentic-chat(send、submitStructuredChoice、acceptCandidate) - 用户现象:点完选项,「正在记录本次选择…」那行消失一帧,下一题卡片闪现又消失,再出现「正在处理…」;点「采用此时间」只看到按钮变灰,页面其它部分静止好几秒。
- 触发条件:选择题
willContinue;采用候选。 - 根因:
willContinue分支删掉 thinking 行、置choiceContinuationPending、finally setPending(false),下一次 effect 才send("read_only")再追加新行;中间那一帧busy=false且快照刚装进新choiceCard。采用流程不追加任何 live 行。 - 修复:
send(action, text, continuation),continuation 复用已有 live 行并跳过 busy 守卫;选择与采用在同一个 async 链里await send("read_only", …, { reuseAssistantRenderKey });busy全程为 true;采用点击即追加「正在采用 HH:MM…」行;choiceContinuationPending与 effect 删除。 - 验证:
rectification-surface-contract「一条 live 行贯穿」;rectification-agentic-entry/rectification-answer-choice的旧锁按红线改注。 - 防复发:后续轮不得经 effect 中转;一条链里
busy不得掉回 false。 - 相关记录:BUG-505
- 复发自:无
- 修复版本:待发布
BUG-507 | 记录为空的已有校正会话进入后永远空白
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
rectification-agentic-chat、rectification-surface-state - 用户现象:某个开场当时失败/未持久化的校正会话,从侧栏再进来什么都不显示,composer 可用但不知道先说什么。
- 触发条件:
initialTurns=[]且服务端对 resumed case 一律should_start_opening=false。 - 根因:面板只在
shouldStartOpening时自动开场,空 turns 无任何 CTA。 - 修复:
rectificationConversationState判empty→ 「这段校正还没有开始。」+「开始提问」(send("opening"),openingStarted守卫;服务端幂等抑制重复 opening,不改服务端)。 - 验证:
rectification-surface-state会话态纯函数;rectification-surface-contract空态源码锁。 - 防复发:无 turns 且不自动开场时必须给起点。
- 相关记录:BUG-505
- 复发自:无
- 修复版本:待发布
BUG-508 | 选择题选中无确认感、记录中只有沙漏光标
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
rectification-choice-card - 用户现象:点了选项只看到底色变化,卡里没有进度,不知道有没有点上。
- 触发条件:任何选择题点击。
- 根因:
is-pending只改cursor: wait;选中项无对勾。 - 修复:选中项加
Check+ 「已选择」;pending 时卡顶一行InlineSpinner+ shimmer「正在记录…」(与时间线 live 行同形)。35688015已把选中态收进按钮、d9404976已内嵌进消息,本轮只补差额;用户选择的回显由内嵌卡的answer_option承担(刷新前后一致),不再另加用户气泡。 - 验证:
rectification-surface-contract。 - 防复发:卡内等待只能是时间线 live 行同形。
- 相关记录:BUG-506
- 复发自:无
- 修复版本:待发布
BUG-509 | 校正盘面首态是一整块空面板
- 状态:resolved
- 首次发现:2026-09-02
- 最近更新:2026-09-03
- 影响面:
rectification-board、rectification-board-model、page.tsx、globals.css - 用户现象:桌面端右侧 18–22.5rem 的面板第一阶段只有一句「补充经历后…」,移动端 peek 同样;用户明明填过出生时间。
- 触发条件:
candidateResult为空。 - 根因:首态不用 profile 的填报时间。
- 修复:
declaredTime由page.tsx从 profile 派生(reportedTime || time,须为H:MM)传入;头部时钟位显示填报时间,正文「填报出生时间 HH:MM」+「回答几个问题后…」;peek「填报 HH:MM」;is-board-empty收窄到minmax(16rem, 18rem)。快照 API 不提供填报时间的宫位表,不造数据。 - 验证:
rectification-surface-state板文案纯函数;rectification-surface-contract。 - 防复发:首态必须显示已知的填报时间;不得为填报时间伪造宫位表。
- 相关记录:BUG-505
- 复发自:无
- 修复版本:待发布
BUG-510 | 采集阶段盘外核对选择题画出 A–D 却点不了
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
projectRectificationChoiceCard、GET/api/rectification/cases/[caseId]choice_card、rectification-agentic-chat内嵌卡 - 用户现象:助手记下带月份的职场事后,下面出现「某年前后,有没有开始一段认真关系、分手或结婚?」和 A–D,点任何一项都没有反应。
current_question已是选择题,choice_card为 null。 - 触发条件:采集阶段已有带年份事件,下一问是
intent=out_of_sample_check的四点选存在性题;schema 有合法 A–D,但没有probe_year。该回合未调用rectification-set-focus,题目由 idle persist 挂到最后一条助手消息。selection_allowed仍为 false(采用门,不是点选门)。 - 根因:keep 路径
forceChoice会尝试重铸choice_frame。缺probe_year时hypothesisFor没有具体时期,frame 为空,GET 按设计把choice_card置空。界面仍从 turn 上的question.options画出 A–D,但liveQuestion要求 GET 卡与focus_id一致,disabled={!liveQuestion},submitChoice在没有choiceCard时直接 return。 - 修复:
projectRectificationChoiceCard在重建 followup 没有choice_frame时,若当前焦点已是reverse_verify/out_of_sample_check且 schema 能解析出四点选,按已持久化文案投影choice_card。口述采集 schema 仍不发卡。未放宽区分题身份校验,未打开selection_allowed。 - 验证:
rectification-choice-card锁定无probe_year的盘外存在性卡仍可投影、口述采集盘外题仍不发卡;rectification-agentic-entry锁内嵌卡disabled={!liveQuestion}且点选要求 GET 卡focus_id一致。 - 防复发:GET 已有合法持久化 reverse_verify / out_of_sample_check 四点选 schema 时,不得因重建缺
choice_frame把choice_card置空。界面画出 A–D 时 GET 必须给出可点的卡。不得把selection_allowed当成 A–D 点选门。不得从题干正则反推年份。 - 相关记录:BUG-416、BUG-431、BUG-498
- 复发自:BUG-431
- 修复版本:待发布
BUG-511 | 公开案例复验因默认岁差改 Raman 而失败,标签与计算不一致
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
tests/run_real_case_revalidation.py、scripts/jyotish_api_server.py岁差回执、账户profiles.ayanamsa、咨询/报告/校正/每日星语请求口径 - 用户现象:发布门真实案例复验
valid=false,gated 通过率掉到门槛以下。同一张虚构盘在旧生产与当前默认下 Moon nakshatra / 首段大运不同。回执上的岁差名有时写成 Lahiri 或 Raman,与该次实际计算不是同一套。 - 触发条件:
80102459把引擎默认改成 Raman 后,参考集调用没有显式锁定 Lahiri;VedAstroayanamsa_policy与部分回执字段仍写死字面量。 - 根因:参考集对照依赖「默认值恰好是 Lahiri」;产品默认改为 Raman 后口径漂移。三处标签用字面量兜底,不读该次请求实际岁差。账户没有用户可选岁差。
- 修复:参考集与夹具大运显式
--ayanamsa lahiri。API 回执与 VedAstro 对照改用该次请求实际岁差。profiles.ayanamsa默认raman,设置里可选四个值;咨询、报告、校正、星盘库、每日星语走resolveAyanamsa。DEFAULT_AYANAMSA_NAME本轮不改。 - 验证:夹具大运日期不变;前端
resolveAyanamsa、账户 PATCH 四值校验、咨询 toolInput 带 profile 岁差、校正快照带岁差但不进 fingerprint;npm run test:db跑迁移。公开案例复验命令见本轮 PROGRESS。 - 防复发:参考集与外部对照必须显式传岁差,不得依赖默认值。回执只显示实际名。岁差不进校正
baselineFingerprint,不重算历史报告。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-512 | 岁差设置组件携带无样式类名导致前端发布门失败
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
frontend/src/components/ayanamsa-preference-field.tsx、frontend/tests/class-name-definition-contract.test.ts - 用户现象:staging quality gate 的前端测试
2609 pass / 1 fail,唯一失败为ayanamsa-preference (src/components/ayanamsa-preference-field.tsx)。 - 触发条件:通用类名契约扫描岁差设置组件。
- 根因:外层
<section>同时使用已有样式类sheet-section和没有 CSS、没有行为用途的冗余类ayanamsa-preference。 - 修复:删除冗余
ayanamsa-preference,保留承担实际样式的sheet-section;不新增空 CSS,也不扩大 allowlist。 - 验证:
cd frontend && npx tsx --test tests/class-name-definition-contract.test.ts。 - 防复发:组件新增项目类名必须有真实 CSS 或明确的无样式行为用途;纯冗余 modifier 直接删除。
- 相关记录:BUG-511
- 复发自:无
- 修复版本:待发布
BUG-513 | Gulika 计算泄漏进程级岁差模式,Raman 请求回执与后续计算可能漂移
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
scripts/gulika.py、scripts/jyotish_api_server.py、scripts/prashna_context.py - 用户现象:请求选择 Raman 时 Gulika 曾按其他岁差返回或污染后续恒星黄道计算;同一常驻进程的下一次计算可能继承错误模式。
- 触发条件:Gulika / Upagraha 计算在已设置另一岁差的进程内调用
houses_ex(..., FLG_SIDEREAL)。 - 根因:Swiss Ephemeris 岁差模式是进程全局状态;Gulika 设置请求岁差后没有恢复调用前模式,API 与 Prashna 调用方也没有始终传入本次请求岁差。
- 修复:
ayanamsa_utils.temporary_ayanamsa在finally恢复前一模式;Gulika 的恒星上升计算统一使用该上下文;API 与 Prashna 显式透传并回执实际岁差。 - 验证:Raman→Lahiri、Lahiri→Raman 两向同进程回归测试均保持调用前 Moon 黄经;模式设置所有权测试锁定只有
ayanamsa_utils.py可直调set_sid_mode;API Raman smoke 回执为raman/Raman。 - 防复发:临时切换进程级岁差必须通过
temporary_ayanamsa;业务模块不得直接调用set_sid_mode。 - 相关记录:BUG-511、BUG-512
- 复发自:无
- 修复版本:待发布
BUG-514 | 专业报告导出测试使用 Node 24 模块 mock 选项,Node 22 发布门加载为空模块
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-03
- 影响面:
frontend/tests/professional-report-reference-route.test.ts、Independent Staging Quality Gate - 用户现象:staging quality gate Run 2362 的前端测试
2627 pass / 1 fail;专业报告导出路由测试报does not provide an export named 'ACCOUNT_BIRTH_SELECT',镜像发布被跳过。 - 触发条件:Gitea 的 Node 22 执行新增的
mock.module(..., { exports: ... })测试;本地 Node 24 会通过。 - 根因:Node 22 的实验性模块 mock 使用
namedExports;未知的exports选项被忽略,生成不含命名导出的 mock 模块。本地只在 Node 24 验证,未覆盖发布门运行时。 - 修复:改用 Node 22/24 均支持的
namedExports,按路由实际别名注册 mock;删除共享出生资料 helper 的 mock,改用真实 helper 和虚构数据库行。 - 验证:Node 22.14 与 Node 24.15 分别运行
tsx --test tests/professional-report-reference-route.test.ts,均为 4/4 通过;Node 22.14 全量前端测试 2628/2628 通过,TypeScript 0 错。 - 防复发:使用实验性 Node API 的发布门测试必须在仓库门禁主版本 Node 22 至少跑一次;模块 mock 不得使用仅新版本接受的选项名。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-515 | 缺少 D11 被误判为 finance 必需证据,完整 Shadbala 的 confidence cap 降为 low
- 状态:resolved
- 首次发现:2026-09-03
- 最近更新:2026-09-04
- 影响面:
mcp_server.py::_collect_strict_evidence、finance strict workflow - 用户现象:财富严格工作流在 Shadbala 分量完整、原本应保持
medium-high置信度上限时,仅因没有 D11 Rudramsa 就返回confidence_cap=low。 - 触发条件:finance 证据包没有
modules.varga_full.D11_Rudramsa,其余财富主判据与完整 Shadbala 证据可用。 - 根因:上游加入
d11_rudramsa后,finance 的missing_evidence过滤表没有同步把它列为非阻断支持项;空 D11 因而被计入缺失的必需证据,触发通用missing -> low降级。 - 修复:把
d11_rudramsa加入 finance 非阻断证据集合。D11 仍可进入证据包,但只作支持项;wealth_promise_strength保持主判据,完整 Shadbala 用例的medium-high断言不改。 - 验证:聚焦回归集合 88 passed(31.71s);模板 validator
valid=true, problem_count=0, template_count=15;本地 quick gate 为 Python CORE 585 passed / 1 skipped、前端 2628 passed、lint 0 errors、Next build 通过,退出 0(575.45s);git diff --check通过。 - 防复发:新增证据字段必须同时声明 required / supporting 角色;支持项不得因缺失进入
missing_evidence,也不得单独压低 confidence cap。 - 相关记录:
TASK-upstream-sync-fix-20260903.md - 复发自:
45d13258/f2241463上游同步批次 - 修复版本:待发布
BUG-516 | 校正工具完成后「正在分析」没有实时步骤,看起来卡住
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:生时校正对话时间线、
rectificationTimelineRows、采集输入栏 - 用户现象:用户回答采集题后,分析面板只显示已完成的「读取校正记录」,标题仍是「正在分析」,没有转圈或闪动的实时步骤,会误以为卡住。采集输入栏下方另有「先这样,先看当前范围」出口。
- 触发条件:校正公开流完成一个工具、尚未开始下一工具或写出回答;同一回合仍处于 thinking。
- 根因:公开流故意丢掉
thinking.delta。工具完成后界面把当前活动改成完成文案,时间线因此没有live行。标题虽带微弱闪动,步骤列表全是对勾。 - 修复:工具完成后把当前活动改成「正在分析…」,并在未结束且没有实时步骤时补一条思考行。共享时间线在
live且全是完成步骤时同样补这条行。去掉采集输入栏的rectification-collect-stop按钮。卡片上的同文案按钮见 BUG-518。 - 验证:
rectification-timeline-adapter、chat-stream-settle-contract、rectification-spoken-collect、rectification-adopt-flow-20260902。 - 防复发:未结束的校正时间线在已有完成步骤时必须仍有一条
live行和 spinner。不得把已完成工具名留作当前活动。不得把 token 级 thinking 重新放到公开流。 - 相关记录:BUG-473
- 复发自:无
- 修复版本:待发布
BUG-517 | 选择题选中态右边的对勾「已选择」是多余确认文案
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
rectification-choice-card - 用户现象:点了 A–D 后,选中项右侧再出现对勾和「已选择」。底色和边框已经标明选中,这组标记没有新信息。
- 触发条件:点校正选择题任一选项,或点卡片上的停止项。
- 根因:BUG-508 为了补确认感,在
data-selected底色之外又加了Check+ 「已选择」。 - 修复:去掉选中徽标和对应 CSS。选中仍靠
data-selected="true"的底色与边框;pending 时卡顶「正在记录…」保留。 - 验证:
rectification-surface-contract。 - 防复发:选择题选中态不得再渲染对勾或「已选择」。确认感只来自选项底色和卡顶记录行。
- 相关记录:BUG-508
- 复发自:无
- 修复版本:待发布
BUG-518 | 选择题卡片仍画出「先这样,先看当前范围」
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
rectification-choice-card - 用户现象:A–D 下面还有一颗「先这样,先看当前范围」。输入栏那颗同文案按钮已经去掉,卡片上这颗还在。
- 触发条件:校正判断题(非 reverse_verify)画出选择题卡片。
- 根因:卡片把
stop_label一律画成第五个选项。采集输入栏的rectification-collect-stop去掉后,卡片出口还在。 - 修复:
stop_label为「先这样,先看当前范围」时不画这颗按钮。盘外核对的「这题跳过」仍保留。服务端 stop 语义和文案常量不改。 - 验证:
rectification-surface-contract、rectification-adopt-flow-20260902。 - 防复发:校正判断卡不得再渲染「先这样,先看当前范围」。不得把盘外核对的「这题跳过」一并删掉。
- 相关记录:BUG-516
- 复发自:无
- 修复版本:待发布
BUG-519 | 探针池耗尽出采用卡时旁白说「继续往下收」,并被无 frame 的 nakshatra 绕过采用早退
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/src/lib/rectification-agentic/v9/method-followup.ts、decision-from-dossier.ts、answer-choice.ts、server-focus.ts、user-copy.ts、adopt-narration.ts、adopt-narration-agent.ts、/api/rectification/agent - 用户现象:采集题答「没有」后,助手说「我按现有材料继续往下收」,同一轮却弹出采用卡;composer 没有下一问,采用原因不明。
- 触发条件:区分探针已答完,receipt 仍带 nakshatra 探针与
window_scan.d*_candidates_differ=true,决策已是offer_provisional_range/adopt_representative/ready_to_adopt。 - 根因:
buildMethodFollowupPlan的 nakshatra 分支只看「有没有探针」,不看决策层是否已丢弃;无choice_frame的distinguish_candidates被放进deferred_followup。isRemainingDiscriminatorFollowup见source=nakshatra_boundary就当成剩余区分题,BUG-472 的采用早退被绕过。随后persistableFocusDomain("appearance")原样返回领域名,DB check 不含该值,写焦点失败,旁白落到hostNarrationFallback。 - 修复:
rectificationFollowupCatalog只在inspectDiscriminatorProbes选中 nakshatra 时传入;无 frame 的 nakshatra followup 丢进dropped_probes(not_renderable)。deferred_followup不再收无 frame 的 distinguish。isRemainingDiscriminatorFollowup对无 frame 的 distinguish 返回 false。persistableFocusDomain对非白名单领域返回null;无 frame 的 appearance distinguish 直接skipped且零次 RPC。采用卡首次打开时由无工具旁白 Agent 写 2–4 句,校验失败/超时走模板,模板在 BUG-503 的stopReasonPrefix之后补「分不开 A 和 B」。 - 验证:
frontend/tests/rectification-adopt-narration-20260904.test.ts;live five-evidence fixture 补 nakshatra 与window_scan后原断言仍过;rectification-server-focus锁appearance/horary/nakshatra → null。 - 防复发:采用早退只留在
shouldSkipFollowupPersist。不得为collect:appearance放宽 DB check。旁白 Agent 说出的每个HH:MM、四位年份、以及去掉这两类之后剩下的整数(含相对支持度)必须能在AdoptDeliveryFacts里找到,否则整段丢弃。Agent 调用与意图分类器相同,不计费。 - 相关记录:BUG-440、BUG-444、BUG-463、BUG-472、BUG-503、BUG-520
- 复发自:BUG-472
- 修复版本:待发布
- 编号说明:任务书撰写时最大号为 BUG-515。同步
cfa82499后 staging 已占用 BUG-516~518,本条落在 BUG-519。
BUG-520 | 区分题答「明确没有发生」被当成整条领域拒答
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/src/lib/rectification-agentic/v9/method-followup.tsdeclinedDomains、answer-choice.tsdossierWithClosedFocus - 用户现象:带年份的事业/搬家区分卡选 C「明确没有发生」后,同领域后续有年份探针不再出现;D10/D4 观察分支也会被跳过。本案因探针池已空,结局没被改写。
- 触发条件:
focusStatusForAnswer把区分题的「没有」写成declined;v_declined_skipped投影带target_domain但不参与计分身份;declinedDomains把任意 declined 焦点的领域当成拒答。 - 根因:BUG-440 为 D12 同领域卡把「答 C declined 计分」写成领域覆盖。对有年份的
distinguish_candidates,C 只是这一题的计分答案,不是「这条线以后都别问」。 - 修复:
declinedDomains只把intent === "collect_method_evidence"(缺 intent 的旧行仍按采集)算成领域拒答;distinguish_candidates的 declined 不计覆盖。dossierWithClosedFocus追加行带intent。focusStatusForAnswer仍返回declined,以保留 DB 状态与不确定度计数。 - 验证:
rectification-collect-stall「denying the dated family collect declines relatives」原断言不变;rectification-adopt-narration-20260904锁 career distinguish declined 不覆盖d10_career,且有年份事业探针仍可被选中;本案 family collect 与 family collect + career/relocation distinguish 两种 declined 输入决策与旁白相同。 - 防复发:领域拒答只看采集题 intent。不得把区分题的 C 当成整域 skipped_by_policy。
- 相关记录:BUG-440、BUG-519
- 复发自:无
- 修复版本:待发布
- 编号说明:与 BUG-519 同批;任务书原写 BUG-517,因编号冲突改为 BUG-520。
BUG-521 | 点选最后一道区分题时采用旁白 Agent 从不运行
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/src/lib/rectification-agentic/v9/answer-choice.tspersistNextInterviewAfterChoice - 用户现象:点完最后一道区分题才出采用卡时,旁白仍是模板句;只有采集题答「没有」和空闲兜底才会走 Agent。
- 触发条件:
applyRectificationChoice→persistApplied传入的 dossier 仍是点选前的decisionReceipt;本轮新状态在decisionState,本轮决策在nextDecision。 - 根因:采用早退分支从旧 dossier 再算一遍
decideFromDossier。旧决策还是「还有题要问」,precisionStage !== "ready_to_adopt",shouldWriteAdoptNarration为 false,模型一次都没调。模板范围也来自这份过期决策。同一次调用里决策层算了两遍(BUG-440 防复发条款)。 - 修复:
persistNextInterviewAfterChoice接受调用方传入的decision;未传时用decideAfterInferenceChange({ dossier, state: decisionState })。函数体内不再出现decideFromDossier(。adoptDeliveryFacts/ 模板兜底使用合成后的 receipt(本轮inference_state)。三条调用方都传入本轮决策。 - 验证:
rectification-adopt-narration-20260904点选用例改为点选前 dossier(revision 5、分数 23/16/7)+ 答完后decisionState/nextAction;断言模型调用 1 次、facts 为 05:00 与 21/18/9、ready_to_adopt。非法模型输出时模板范围仍来自本轮决策。源码断言该函数体内无decideFromDossier(。 - 防复发:
persistNextInterviewAfterChoice不得再从旧 dossier 重算决策。点选路径测试不得用已答完 dossier 冒充点选前状态。 - 相关记录:BUG-440、BUG-519、BUG-522
- 复发自:BUG-440
- 修复版本:待发布
BUG-522 | 采用旁白校验把「不是确认」整段打回,且真实环境无法证明 Agent 有没有写
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
adopt-narration-agent.ts提示词、deliverAdoptNarration超时与诊断字段 - 用户现象:校验器全文匹配「确认|精确」,模型写「这不是确认的分钟」会被静默退到模板;部署后日志看不出 Agent 路径命中还是模板兜底。
- 触发条件:采用旁白 Agent 生成含「确认」或「精确」的否定句;或模型挂起超过等待时间。
- 根因:提示词只禁止承诺确认/精确,校验器却禁这两个字。分类器同款调用没有超时。结果没有可观测枚举。
- 修复:提示词改为「不要出现『确认』『精确』这两个词」,与校验器同口径;校验器本身不放宽,「这不是确认的分钟」仍打回。
deliverAdoptNarration打adopt_narration=agent | template:<reason> | template:model_error | template:not_ready(不含模型原文),并用AbortSignal.timeout(8000)超时走模板。意图分类器超时仍是既有缺口,本单不修。 - 验证:提示词源码断言含「不要出现」;
deliverAdoptNarration对 agent / template:unknown_minute / template:model_error / template:not_ready 各有断言;短 timeout 挂起 generateText 走模板且不抛。 - 防复发:采用旁白结果必须留下
adopt_narration=日志。Agent.generate 必须带超时。不得把「确认」从校验器删掉却不改提示词。 - 相关记录:BUG-521、BUG-523
- 复发自:无
- 修复版本:待发布
BUG-523 | 采用旁白超时用 unref 计时器,测试挂死事件循环
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/src/lib/rectification-agentic/v9/adopt-narration-agent.tscomposedAbortSignal - 用户现象:staging 门禁
npm test退出码 1,13dded9f不能部署。生产请求有监听 socket 撑住事件循环,超时在真实流量里仍会触发;但每次调用都会留下一个无法clear的 8 秒计时器。 - 触发条件:
deliverAdoptNarration用AbortSignal.timeout();测试把generateText挂成永不 resolve 的 Promise。 - 根因:Node 的
AbortSignal.timeout()内部计时器始终 unref,空事件循环会在 abort 前排空。测试运行器判cancelledByParent,同文件后三条用例一并取消。验收只看了# fail,没看# cancelled与退出码。 - 修复:改为
setTimeout+AbortController(计时器保持 ref),返回{ signal, dispose };模型返回、校验完成或外部 abort 后clearTimeout并移除监听。超时仍走template:model_error。 - 验证:
rectification-adopt-narration-20260904超时用例不再 cancelled;新增「调用完不留活跃计时器」用例;源码不含AbortSignal.timeout。定向套件与该文件摘要须含完整六行且# cancelled 0、退出码 0。 - 防复发:采用旁白超时不得再用
AbortSignal.timeout。验收必须贴 Node 摘要完整六行(tests / pass / fail / cancelled / skipped / todo)与进程退出码,不得只报# fail 0。 - 相关记录:BUG-522
- 复发自:BUG-522
- 修复版本:待发布
BUG-524 | consultation_workflow 在 float 时辰上崩溃,全量 HTTP 500
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
POST /api/consultation_workflow、execute_consultation_workflow、_build_birth_time_sensitivity、_birth_datetime_from_args;报告 worker 与聊天深度咨询共用该端点 - 用户现象:staging 上 standard personal_full 生成失败,
failureCode = calculation_unavailable。同一端点对 provisional 出生时间返回 HTTP 500。 - 触发条件:API 出生 payload 的
hour/minute为 float(_high_rigor_birth_payload的既有口径),且birth_time_accuracy为provisional或approximate,因而会走到_birth_datetime_from_args。 - 根因:上游同步把
_build_birth_time_sensitivity无条件接入 consultation_workflow。CLI argparse 的 hour/minute 是 int;API 路径是 float。datetime()不能接受 float,抛TypeError。异常只被except ValueError包住,于是冒成 500。 - 修复:
_birth_datetime_from_args对年月日时分做int()规范化,不改变 API float 口径。敏感度构建失败时写入 blocked/not_available状态对象并继续 workflow,不再打死整个端点。 - 验证:
tests/test_consultation_workflow_birth_time_sensitivity.py(已列入CORE_PYTEST_TARGETS);本地 HTTP 对 career/marriage/wealth/timing/health 与不带敏感度字段的聊天形状均为 200/success=true;五份响应喂给buildReportEvidenceBundleV2得到 5 张 claim card、blockedSections为空。staging quick 门585 passed, 1 skipped。staging 上从 web 容器对虚构 smoke 盘POST http://api:5200/api/consultation_workflow(provisional + float 时分)HTTP 200、success=true、birth_time_sensitivity.status=candidate_window_only,约 74s。产品侧真实 personal_full 仍失败,见 BUG-526,不是本条 TypeError。 - 防复发:float hour/minute 的 API body 必须能走
execute_consultation_workflow且不 500;敏感度层失败必须降级,不得再变成未捕获TypeError。不得把_high_rigor_birth_payload的 hour/minute 改成 int。 - 相关记录:BUG-526
- 复发自:无
- 修复版本:
7b1354a79bb8e9b92c3c816fdc01f5f8de2860b7(含本修复的 staging 部署 SHA)
BUG-525 | 采集题答「没有」后「正在准备下一个问题…」不消失
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
applyCollectFocusDenial、persistCollectDenialTurn、/api/rectification/agentcompletedMessageResponse、rectification-agentic-chat问题缺口重试 - 用户现象:采集口述题答「没有」后,助手气泡已经给出下一问,底部仍转圈显示「正在准备下一个问题…」,不会变成「没有拿到下一个问题」。范围条「目前范围 … 还在收窄」是采集阶段的只读提示,不是本缺陷。
- 触发条件:当前焦点为
collect_spoken;意图分类answer_current_focus+answer_class: "no";走采集拒答快路径(不进 Agent)。 - 根因:拒答快路径把下一问题干整段当成正文流出去,但 turn 上
question为空、新焦点没有asked_turn_id,run.completed也不带turnId。前端liveQuestionOnMessages要求已结算消息的question.focus_id等于current_question.focus_id,缺口因此一直是preparing。applyCaseSnapshot只要快照里有current_question就把重试次数清零,定时重拉永远到不了unavailable。 - 修复:拒答后先落确定性 turn,再
linkFocusAskedTurn。库内正文为确认句 + 精确题干后缀(GET 仍按asked_turn_iddetach);直播只推确认句。run.completed带上turnId。快照里出现current_question不再重置缺口重试。 - 验证:
rectification-collect-stall锁 family collect 拒答 → occupation 题干写入 turn、p_asked_turn_id、直播确认句;route 源码锁persistCollectDenialTurn与finished.turnId。rectification-surface-contract禁止if (nextQuestion !== null) setQuestionRetryAttempts(0)。 - 防复发:采集拒答快路径建立的下一焦点必须有
asked_turn_id,且run.completed必须带该 turn。不得把「快照已有 current_question」当成缺口已闭合。直播不得把下一问题干当作整段回复(题干走 turn.question)。 - 相关记录:BUG-440、BUG-490、BUG-491、BUG-505、BUG-520
- 复发自:BUG-491
- 修复版本:待发布
- 编号说明:rebase 到
origin/staging时 BUG-524 已被 consultation_workflow 占用,本条落在 BUG-525。
BUG-526 | accepted 生时的个人报告 worker 直接读已收回权限的校正表,16 秒 calculation_unavailable
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/src/lib/personal-report-worker.tscreateProductionWorker的generate;frontend/src/app/api/reports/route.ts的loadCandidateRange;public.agentic_rectification_cases - 用户现象:staging 上 standard personal_full 创建成功后约 16 秒失败,
failureCode = calculation_unavailable,无章节行。表现与 BUG-524 事故码相同,但引擎访问日志里没有POST /api/consultation_workflow。 - 触发条件:资料
birth_time_status为accepted(不是confirmed),worker 与 create route 因此去查agentic_rectification_cases的candidate_accepted行。 - 根因:
20260814010000_immutable_skill_registry.sql已从该表收回service_role的表级权限,只许走 security definer RPC。报告链路仍from("agentic_rectification_cases").select(...)。Postgres 三次permission denied for table agentic_rectification_cases(与 job 三次 attempt、默认 5s/10s 重试对齐)。查询失败被映射成可重试calculation_unavailable,报告从未调用引擎。第二读者:create route 同样if (error) throw error,会把创建请求打成calculation_unavailable。 - 修复:新增只读 RPC
read_report_candidate_range(不 re-grant 表权限)。worker 与 create route 经共享 helper 调用;任何读失败降级为无窗口并继续生成。calculation_unavailable只保留给引擎调用失败。legacy 表终态以库约束为准:confirmed与completed(20260720已纳入枚举;前端不再.in("status", ["confirmed", "completed"])直查)。 - 验证:
tests/report-candidate-range.test.ts三态降级;tests/personal-report-api.test.tsnull 窗口仍 201;tests/database-report-candidate-range.test.ts权限矩阵与列白名单。staging @e27d5dc5:accepted 档案两次 standard personal_full 均 5×POST /api/consultation_workflow200,五章有正文;不再出现 16 秒calculation_unavailable。终稿被 BUG-535final_parse_rejected拦住,见该条。 - 防复发:报告 worker 与 create route 不得再直接 SELECT 已收回
service_role权限的校正表;候选窗读失败不得再变成calculation_unavailable。 - 相关记录:BUG-524、BUG-534、BUG-535
- 复发自:无
- 修复版本:
7baf2300 - 编号说明:rebase 到
origin/staging时 BUG-525 已被采集拒答占用,本条落在 BUG-526。
BUG-527 | 可评分事件不足 3 条时盘外核对抢跑到已拒答领域
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
buildMethodFollowupPlan、holdoutFollowupFor、oos_blind/ holdout 提问 - 用户现象:账本只有 2 条可评分带日期事件时,家人采集刚答「没有」,下一问却变成盘外核对,焦点落在刚拒答的 family。
- 触发条件:
holdoutValidation === "not_started"或sessionOutcome === "validate_holdout",引擎给了oos_blind_prompts,训练门event_quality.passed=false。 - 根因:计划层的两处 OOS 分支只看 holdout 状态和引擎提示,不过
meetsAcceptanceEventQuality,也不跳过declinedDomains(BUG-520 口径)。 - 修复:新增
holdoutFollowupFor:训练门未开返回null;否则取第一个未拒答的 OOS 提示,没有则退到带年份 holdout 事件,再没有为null。两处 OOS 分支都走它。不改holdoutValidationStatus/decideRectification的采用与确认门。 - 验证:
frontend/tests/rectification-collect-direction-20260904.test.ts(2 条事件不出 OOS;4 条事件跳过 family 后问 education holdout;全部拒答且无 holdout 事件时validate_holdout的next_followup为 null)。 - 防复发:OOS 提问必须同时满足训练门与未拒答领域。不得只凭引擎给了
oos_blind_prompts就提问。 - 相关记录:BUG-396、BUG-520
- 复发自:无
- 修复版本:待发布
- 编号说明:任务书写 BUG-524;rebase 后最大号已是 BUG-526(报告 worker),本条从 BUG-527 起。
BUG-528 | 缺第三件带年份的事时先问职业或泛问
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
buildMethodFollowupPlan、nextDatedCollectFollowup、exhaustionSpokenCollectFollowup、persistNextInterviewAfterChoice - 用户现象:2 条可评分事件、家人拒答后,决定路径问「你平时主要做什么工作?」;职业没有日期,答完也不推进训练门。
- 触发条件:
!meetsAcceptanceEventQuality,方法覆盖轮转里感情/事业已有记录、家人已拒答,剩下职业。 - 根因:带年份的学业/财务/搬家/健康只存在于穷尽路径;主流程在方法轮转之后才落到
domain: null泛问。 - 修复:
DATED_COLLECT_ORDER(家人 → 学业 → 财务 → 搬家 → 健康 → 事业 → 感情)由主流程与exhaustionSpokenCollectFollowup共用。训练门未开时,在方法轮转之前取下一件带年份采集题;职业与other泛问排在最后。datedCollectFollowup的source为method_coverage(采集,不是盘外核对)。 - 验证:新文件锁 education → finance → occupation → other;拒答路径落下
USER_COLLECT_QUESTION.education。rectification-eight-method中训练门未开的下一问从感情改为家人(或财务),每条改断言附原值/新值/原因。4 条可评分事件时本分支不触发(rectification-collect-stall、rectification-yearless-ungrounded的 OOS 结论不变)。 - 防复发:训练门未开时不得先问职业或无领域泛问。顺序只许有一处定义。
- 相关记录:BUG-426、BUG-442、BUG-472、BUG-527、BUG-531
- 复发自:无
- 修复版本:待发布
BUG-529 | 采集题干可以不指向任何领域
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
validateSpokenPrompt、rectification-set-focus、COLLECT_DOMAIN_KEYWORDS - 用户现象:Agent 把家人盘外核对改写成「除了工作这条线,还有哪件事你能记起大概的年份?」,用户不知道该往哪想。
- 触发条件:
intent=collect_method_evidence、无choice_frame、领域在关键词表内;题干不含该领域日常词。 - 根因:校验器只查长度、选项字面、内部 token、
domain_mismatch,不要求题干提到领域。 - 修复:采集题必须命中目标领域至少一个关键词,否则
domain_missing。第二次仍不合格时服务端用USER_COLLECT_QUESTION[domain]落焦点。工具描述要求写出领域。区分题与 OOS 题不套这层关键词。 - 验证:
rectification-spoken-prompt(education 泛问 →domain_missing;含「上学」→ ok;OOS 不受影响);新文件两次泛问后焦点 prompt 为USER_COLLECT_QUESTION.education。 - 防复发:采集题方向由服务端定。不得把关键词校验套到带
choice_frame的区分题或 OOS 题上。 - 相关记录:BUG-527、BUG-528
- 复发自:无
- 修复版本:待发布
BUG-530 | 采集进度句没有数字
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:读盘投影
collection_progress、agentic-rectification.ts提示词、MACHINE_VOICE_LEXICON - 用户现象:助手写「当前范围还在继续收窄中,接下来我们继续。」没有任何数值。
- 触发条件:下一问是采集题;Agent 在没有
collection_progress或不用该字段时自行写进度。 - 根因:读盘投影没有给出可评分件数/下限/还差几件;提示词允许空进度句;词表不拦「继续收窄」。
- 修复:
collectionProgressFromReceipt从gates.event_quality取{ scoreable, minimum, missing },缺字段为null。读盘与 GET interview 都带上。提示词要求数字只来自该字段,没有就不说进度。词表加入「继续收窄」「范围还在」,只对spokenPrompt校验生效(旁白无同一套校验)。 - 验证:投影锁 2/3/1 与缺 gate 时
null;提示词测试含collection_progress与「不得写『范围在收窄』」。 - 防复发:没有
collection_progress不得写没有数字的收窄句。数字不得自算。 - 相关记录:BUG-527
- 复发自:无
- 修复版本:待发布
BUG-531 | 新案例第二问被家人抢占感情 / 事业
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
buildMethodFollowupPlan里nextDatedCollectFollowup的插入位置 - 用户现象:新案例已有 1 件事业(或 1 件感情)时,第二问变成家人,而不是感情 / 事业。
- 触发条件:训练门未开(前 3 件事几乎总是如此);
3847e9c9把 dated 补采集插在方法覆盖轮转之前。 - 根因:上游任务书把「缺第三件带年份的事」的顺序写成了训练门未开阶段的总顺序。执行方按字面把
nextDatedCollectFollowup放在!dashaCovered之后、感情 / 事业 / 家人轮转之前。这是任务书顺序错位,不是执行方偏离。 - 修复:删掉轮转前分支。轮转里
!familyCovered之后、!occupationCovered之前,训练门未开且nextDatedCollectFollowup有结果时取它。此时感情 / 事业 / 家人已 covered 或 declined,补采集只会给出学业 → 财务 → 搬家 → 健康。DATED_COLLECT_ORDER与exhaustionSpokenCollectFollowup不动。 - 验证:
rectification-collect-direction-20260904锁 1 事业 →d9_relationship、1 感情 →d10_career、事业+感情 →relatives、再拒答家人 →d5_education、再拒答学业 →d2_finance。家人答「没有」后焦点仍是学业。3847e9c9改写的感情 / 事业断言回到45bdb63e。 - 防复发:dated 补采集不得插在感情 / 事业轮转之前。训练门未开时新案例前两问仍是 D9 / D10。
- 相关记录:BUG-528、BUG-463
- 复发自:BUG-528
- 修复版本:待发布
BUG-532 | 原生 input 间距合同测试仍锁 12px
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
frontend/tests/admin-model-management-ui-contract.test.ts;挡住backend-quality-gate的npm test --prefix frontend - 用户现象:无直接用户可见故障。
7ee7f825之后门禁因这一条失败,staging 部署停在更早的 SHA。 - 触发条件:
globals.css原生 input 为padding: 0 var(--space-3),合同测试仍匹配padding: 0 12px;。 - 根因:
7ee7f825(fix(ui): collapse product weights and radii onto the shared Button)把间距收敛到 token,未改合同测试。本单开工时origin/staging上这条仍红,别处未修。 - 修复:测试接受
padding: 0 var(--space-3);。不改 CSS。 - 验证:
admin-model-management-ui-contract全过;全量失败清单只剩 Docker 并发争用,隔离重跑通过。 - 防复发:改
globals.css原生控件尺寸时同步合同测试。不得为迁就测试把 token 改回裸像素。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-533 | 开场打招呼被 set-focus 空 replace 清掉
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
applyStepAnswerChunk、retractSpoken、开场轮answer.delta - 用户现象:开场先流出「你好,很高兴一起做这次生时校正…」,页面最终只剩「我们慢慢来就好…」和采集题干。
- 触发条件:模型在
rectification-set-focus之前写出带句号的中文正文;该步已live播出。 - 根因:任一公开工具的
tool-call都会retractLive,发出answer.delta{text:"", replace:true}。set-focus 只是把题干挂到同一条消息,前面的打招呼不是过程自述。 - 修复:
rectification-set-focus的 call/result 不再收回已播出正文。读诊断、记证据仍收回。 - 验证:
rectification-step-answer:打招呼 + set-focus 为none,其后短句仍live;read-diagnostics 仍retract。 - 防复发:set-focus 不得把同轮已播出的正文 replace 成空。不得把这个例外扩到 record/compare/diagnostics。
- 相关记录:BUG-584
- 复发自:无
- 修复版本:待发布
BUG-534 | 分章进度 section: 让 job 行无法 round-trip,心跳中断、阅读页误报失败
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
personal-report-job-service-corerecordFromRow;心跳 RPC 返回解析;GET /api/reports/:id读 generating 行 - 用户现象:standard personal_full 已经调到引擎并写出至少一章后,阅读页变成
report_generation_failed/「报告暂时无法读取」。后台 job 停在section:theme-*,心跳不再刷新,直到租约过期才重试。 - 触发条件:worker
onProgress把progress_phase写成section:<section_id>(SQL 与isValidPersonalReportJobProgressPhase允许冒号)。随后心跳或 GET 把该行再读进recordFromRow。 - 根因:
recordFromRow用operationalCodePattern(^[a-z][a-z0-9_]{0,63}$)校验progress_phase,不接受冒号。updateProgress写入成功后解析 RETURNING 行抛storage_invalid;onProgress吞掉非租约错误,生成继续。下一次心跳解析同一行再抛,worker 中止。GET 同一路径变成 HTTP 500。 - 修复:
progress_phase改走已有的isValidPersonalReportJobProgressPhase(与 SQL check、写入校验同一套,允许section:)。 - 验证:
personal-report-job-service:写入section:theme-health_pressure后心跳与getOwnedByRequestId仍成功。staging @e27d5dc5干净一次跑完五章到 assemble 85%,心跳未再中断。 - 防复发:job 行 round-trip 不得比写入校验更严。分章进度相位必须能读回。
- 相关记录:BUG-526、BUG-535
- 复发自:无
- 修复版本:
e27d5dc5
BUG-535 | 五章写完后终稿 final_parse_rejected,accepted 报告仍无 ready 文档
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
assembleReportDocumentV2之后的safeParseServerReportDocument;standardpersonal_full终装 - 用户现象:报告中心显示生成失败。错误码
report_schema_invalid,日志innerReason=final_parse_rejected。五章其实都已写完。 - 触发条件:accepted 生时、默认五主题、staging @
e27d5dc5。干净 attempt 1 与此前被租约打断后恢复的 attempt 都复现。 - 根因:
c2f23131把CHART_IDS扩到 9 个(加 D6/D8/D30),文档 v2 仍charts.max(6)。分盘提取修好后,五主题装配 9 张图,终稿 parse 拒绝。四主题一直 ≤6 所以从未踩中。随后真实五章又踩中actionNotes.max(24),再踩中匿名 contract guard。 - 修复:v2
charts.max(CHART_IDS.length);JSON Schema / Python 合同同步;装配允许集改为CHART_IDS。装配把actionNotes截到合同上限 24,不放宽 schema。final_parse_rejected日志parsePaths仅 path + code;guard 失败改为字段路径 + 种类。blocked 段去掉确定性用语。 - 验证:合同测试 9 图过 parse;五主题分章夹具 READY;每章 6 条行动仍 READY 且 notes=24。staging request
31025c49:actionNotes/too_big。a75929c1requestf10daf6d:三条(guard)/guard。4ef4c406request0679321b:ready,v2 文档 9 张图、五章有正文、actionNotes=22、blocked 披露为空,阅读页可打开。 - 防复发:上限必须绑定
CHART_IDS.length,不得再写裸 6/9。扩枚举必须同时改 Zod / JSON Schema enum / PythonCHART_IDS。装配侧截断须跟合同上限同一常量。终稿失败必须带可区分的 parse path,不得只写(guard)。 - 相关记录:BUG-526、BUG-534;引入半拉子改动的提交
c2f23131 - 复发自:无
- 修复版本:
4ef4c406
BUG-536 | 采用后第一道核对题重复已问过的采集题
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
remainingReverseVerifyProbes、采用后buildMethodFollowupPlan的accepted分支 - 用户现象:采用代表分钟后,第一道核对题与采集阶段已经问过并回答过的带年份题几乎逐字相同。
- 触发条件:采集阶段已按
semantic_key问过该 (domain, year);采用后反向核对从eventProbes再取第 0 条。 - 根因:冲突探针去重用了
askedKeys;BUG-350 单独写下的反向核对漏接。过滤了拒答领域、年份已覆盖、成年下限,没有用semantic_key/candidate_split_hash/domain.year。 - 修复:
remainingReverseVerifyProbes增加askedKeys,与remainingConflictProbes同口径。已问或已跳过的探针不再作为核对题。 - 验证:
rectification-post-adopt-verify-20260904:askedProbeKeys含第一条semantic_key时下一问是另一条;两条都在则next_followup === null且next_user_action.id === "start_consultation";只含candidate_split_hash也能去重。rectification-eight-method采用后有剩余探针 / 无剩余探针两条既有断言仍过。 - 防复发:采用后核对必须走
askedKeys。不得只靠probeYearAlreadyCovered当已问去重。 - 相关记录:BUG-350
- 复发自:无
- 修复版本:待发布
BUG-537 | 核对卡「这题跳过」等于整案停止,已采用仍念采用提示,前端报没有下一问
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
choice-card核对卡 stop 槽、SKIP_PROBE_ACTION、applyRectificationChoice、rectificationQuestionGapState、校正聊天面 - 用户现象:点「这题跳过」后助手又说可以从下面选一个先用着;随后出现「没有拿到下一个问题。」和重新加载。
- 触发条件:已采用,当前焦点是反向核对卡;用户点跳过。
- 根因:三层叠加。(1) 核对卡只把停止按钮改了标签,动作仍是整案
STOP_ACTION,userStopped:true。(2) 结构化旁白只看can_adopt,已采用后publicCanAdopt仍为 true,再念一遍adoptCue。(3) 服务端没有核对结束状态,前端把「无焦点 + 可续」当成问题还没准备好。 - 修复:核对卡绑定
skip_probe:焦点skipped、探针进answered_probes(不计分)、不置userStopped,再取下一道核对题。已采用禁止adoptionNarration/adoptCue。GET 与结构化答题暴露next_user_action;已采用且无下一问时为start_consultation。前端该态为verified_idle:一行postAdoptVerifyDone,无重载、无 handoff。关闭核对结束时不得把已跳过的焦点重新挂到收尾消息上。 - 验证:两道核对题跳第一道 → 下一焦点是第二道,
sessionOutcome不是user_stopped,旁白不含adoptCue。跳最后一道 → 无新焦点,next_user_action.id === "start_consultation",旁白等于postAdoptVerifyDone。verified_idle无 gap copy、无重载、无 consult handoff。采用前区分题停止按钮仍是STOP_ACTION。 - 防复发:核对卡「这题跳过」不得走
STOP_ACTION。已采用旁白不得再念采用提示。start_consultation不得进 preparing/unavailable。 - 相关记录:BUG-350、BUG-499、BUG-502、BUG-518、BUG-520
- 复发自:无
- 修复版本:待发布
BUG-538 | 采用旁白承诺的核对与采用后计划不同源
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
adoptDeliveryFacts.post_adopt_verification、采用旁白模板与 Agent 校验器 - 用户现象:采用卡旁白说会拿若干年前事和几类域外题来核对;采用后一道都没问。
- 触发条件:旁白事实包按 holdout 事件与
oos_blind_prompts生成;采用后实际计划走remainingReverseVerifyProbes。 - 根因:
TASK-rectification-adopt-narration-20260904.md§4 把post_adopt_verification写成 holdout + OOS。BUG-350 已定采用后不得把不计分盘外核对当默认下一步。两套规格矛盾,旁白说的是不会发生的事。这是任务书错误,不是执行方偏离 BUG-350。 - 修复:
post_adopt_verification改为与采用后计划同一过滤(同一函数、同一askedKeys、同一declined),项为{kind:"reverse_verify"; domain; year_label}。空列表时模板说没有还能核对的前事;校验器把「会拿……核对」判为promise失败。holdout / OOS 只留在决策收据,不进旁白。 - 验证:
rectification-adopt-narration-20260904原 holdout/oos 三项改写为 reverse_verify 且与buildMethodFollowupPlan({accepted:true})首题一致。空列表 +「会拿……核对」→promise。模板含「没有还能核对的前事」。 - 防复发:旁白承诺必须与
remainingReverseVerifyProbes同源。空列表不得承诺会核对。不得把 holdout/OOS 写回旁白事实包。 - 相关记录:BUG-350、BUG-519、BUG-520、BUG-521、BUG-522、BUG-523
- 复发自:无
- 修复版本:待发布
BUG-539 | 家庭采集题带年份前缀,又让用户报年份
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-10
- 影响面:
collectionYearFields、spokenFollowupForUser对collect_method_evidence - 用户现象:家庭采集题写成「某年前后,家里如果有结婚、添丁或住院这类事,记得大概哪年就行。」既指定了年份又让用户报年份。
- 触发条件:家庭轮转把 dated 探针的
year_label拼进USER_COLLECT_QUESTION.family。 - 根因:
68f0759e为 BUG-414/418「带年份的口语问、不出无年份分盘选择卡」把 dated 探针年份塞进采集口语。USER_COLLECT_QUESTION.family本身以「记得大概哪年就行」结尾,拼在一起自相矛盾。本单只去年份前缀。家人拒答后若还有未问的财务/搬家/健康/职业采集,不得直接给结果——见 BUG-546(推翻本条原先「出采用卡不是缺陷」的收口)。 - 修复:采集口语不再拼「某年前后,」。
probe_year/semantic_key仍留给计分与去重。不出无年份分盘选择卡的半边保留。 - 验证:家庭采集口语等于
USER_COLLECT_QUESTION.family,同时probe_year > 0。答完最后一道区分题后仍落家庭采集焦点,旁白不含探针年份。BUG-418 无年份分盘卡测试仍过。 - 防复发:采集口语不得既指定年份又说「记得大概哪年就行」。口径被 BUG-642 改为带线索模板:有
probe_year时用「某年前后有没有…别的年份也行」;无线索模板仍用「记得大概哪年就行」。不得为去前缀而丢掉probe_year或放回无年份分盘选择卡抢在采集前面。 - 相关记录:BUG-414、BUG-418、BUG-546、BUG-642
- 复发自:无
- 修复版本:待发布
BUG-540 | 候选区分阶段同一道「上大学」发挥题问两次,第二次绑错证据
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
choice-card.ts::pickProbe、ChoiceCardFollowup.probe_id - 用户现象:候选区分阶段连续两张选择卡题干、选项完全相同(「某年某月那次上大学,更接近如愿、将就调剂、发挥失常还是说不清」)。用户两次都作答,第二次答完范围继续收窄。
- 触发条件:学业域已核实入学与毕业两条事件;引擎对两条各发一条
known_event_quality;第二张卡的 followup 指向毕业探针。 - 根因:
pickProbe在choice_kind === "event_quality"时取池里第一条质量探针就返回,semantic_key匹配写在后面,永远轮不到。periodFor与eventQuestionPrompt都从这条错探针取日期和题干。用户第二次是对着入学题给毕业探针打分。 - 修复:匹配顺序改为
probe_id→semantic_key→ 无键时才按种类兜底。有键但找不到对应探针时返回null,不出卡,不再退回同种类第一条。question_id在有键时带上该键,避免两张质量卡共用一个 id。 - 验证:同域两条质量探针、followup 指向第二条时,
buildChoiceFrame的question_id、日期标签、user_meaning全部来自第二条;指向第一条不受影响。有键找不到探针时buildChoiceFrame为null。rectification-choice-card既有 varga_style / existence 断言仍过。 - 防复发:不得先按
choice_kind取第一条质量探针再匹配semantic_key。有probe_id/semantic_key时不得退回同种类第一条。 - 相关记录:BUG-390、BUG-541
- 复发自:无
- 修复版本:待发布
BUG-541 | 毕业等学业 kind 套高考发挥题,同切分质量探针占满名额
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-04
- 影响面:
event_probes.py::_quality_distinguish_probes - 用户现象:毕业事件也被写成「上大学 / 调剂 / 发挥失常」。即便卡面绑对了,第二道仍不带来新切分信息。
- 触发条件:学业域同时有入学与毕业;两条
known_event_quality的 yes/no 分钟集合相同(同一 D24 星座切分),只有semantic_key/target_evidence_id/candidate_split_hash不同。 - 根因:(1)
_quality_user_meaning与QUALITY_DISTINGUISH_OPTIONS["education"]只看domain == "education",不看event_kind。采集线会产生education_completion,模板仍是 BUG-390 的高考发挥题。(2)candidate_split_hash掺了年份,同分组拦不住;MAX_QUALITY_DISTINGUISH_PROBES = 2被两条信息相同的探针占满。 - 修复:质量探针只对
education_start/education_change/education_interruption发出;education_completion与其它 kind、以及非学业域不发。同域同 yes/no 集合只保留信息增益最高、并列取时间最早的一条;被去掉的不计入名额。不改四选项文案,不改candidate_split_hash,不新造毕业体验模板。 - 验证:入学 + 毕业 → 一条质量探针且
target_evidence_id指向入学。仅毕业 → 零条质量探针;训练门仍开时仍有 dasha 存在性探针。两条可发事件同分组 → 一条;_select_quality_distinguish_rows不同分组 → 两条,第三条不超过上限 2。test_probe_question_contract四选项合同仍过。 - 防复发:学业质量探针必须看
event_kind(或kind),不得对education_completion发。同域同 yes/no 集合不得发第二条。不得改QUALITY_DISTINGUISH_OPTIONS文案或candidate_split_hash算法来「修」去重。 - 相关记录:BUG-390、BUG-540
- 复发自:BUG-390(质量探针只对学业发出,但学业内 kind 未收紧)
- 修复版本:待发布
BUG-542 | 部署窗口数据库瞬断被报成「服务尚未配置」
- 状态:resolved
- 首次发现:2026-09-04
- 最近更新:2026-09-06
- 影响面:16 处 API 路由的 Supabase/数据库客户端创建兜底;共享 helper
frontend/src/lib/api/service-unavailable.ts - 用户现象:staging 刚切完部署时,生时校正点选答案返回
503 {"error":"服务尚未配置"}。同一时刻/api/health显示数据库检查 degraded,数分钟后自愈。环境变量从未缺失。 - 触发条件:部署切换窗口内数据库短暂不可用;
createServerSupabaseClient()在自托管分支会先读身份会话(数据库查询),连接错误从这里抛出。 - 根因:16 处
catch把任意创建失败一律翻译成「服务尚未配置」。该文案只对应SupabaseConfigurationError(缺环境变量)。 - 修复:共享
jsonForSupabaseSetupFailure:仅配置错误返回「服务尚未配置」;其它异常返回503 {"error":"服务暂时不可用,请稍后重试","code":"service_unavailable"},并console.error路由名与error.name(不打印堆栈/连接串)。不改客户端抛错语义、不加重试、不改 health。 - 验证:
npx tsx --test tests/api-service-unavailable-20260904.test.ts4/4。mockcreateServerSupabaseClient抛配置错误 → 503「服务尚未配置」;抛Error("connection refused")→code=service_unavailable,文案不含「尚未配置」。覆盖POST /api/rectification/agent与GET /api/rectification/cases/[caseId]。grep -rn "服务尚未配置" frontend/src只命中 helper。tsc --noEmit0 错;改动文件 eslint--quiet0 error。 - 防复发:新路由创建数据库客户端失败不得手写「服务尚未配置」;必须走 helper。发布门模块 mock 继续用 Node 22 的
namedExports(BUG-514)。 - 修复版本:
5483649b
BUG-543 | staging 镜像构建因 Google Fonts 拉不到 Inter 失败
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:Gitea
backend-quality-gate.ymlpublish、deploy/railway-web.Dockerfile的RUN npm run build、frontend/src/app/layout.tsx - 用户现象:无终端用户可见现象。门禁 validate 通过后 publish 失败,staging 不发布新镜像。
- 触发条件:推送触及门禁路径后,publish 在 Docker 内执行
npm run build。构建环境访问不到fonts.googleapis.com。 - 根因:BUG-430 用
next/font/google在构建期下载 Inter。validate 跑在 hostexecutor,能出网;publish 的镜像构建不能。Gitea run 2408(SHA40c623ed)报Failed to fetch Inter from Google Fonts,railway-web.Dockerfile:26退出 1。 - 修复:把 latin 可变 Inter(OFL)放进
frontend/src/app/fonts/InterVariable-latin.woff2,根布局改next/font/local。不改 workflow、不设构建期代理。字体栈与--font-inter不变。 - 验证:
site-style-isolation-contract锁定next/font/local、仓内 woff2 魔数、以及frontend/src不再出现next/font/google/fonts.googleapis.com。本机npx tsx --test tests/site-style-isolation-contract.test.ts;tsc --noEmit。 - 防复发:不得把西文正文字体改回
next/font/google。新字体必须是仓内文件 +next/font/local或@font-face。 - 相关记录:BUG-430
- 复发自:BUG-430(加载方式在无 Google Fonts 网络的镜像构建里不成立)
- 修复版本:待发布
BUG-544 | 采用卡钉在历史采集题下,最新旁白却说从下面选
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
attachOfferResultToTurns、applyLiveCandidateOffer、rectification-agentic-chat的candidateOffer锚点 - 用户现象:最新助手说「可以从下面选一个先用着」,下面没有时间卡。往上翻,卡钉在更早的「工作上呢,还记得哪年入职…」采集题下面。
- 触发条件:公开
can_adopt曾为真时,当时最新助手消息还是未答完的采集题;之后继续问了家人/核对,再出采用旁白。 - 根因:三层叠加,复发 BUG-497/499。(1) 客户端
useEffect只要某条消息已有同一resultId就不再改锚点。(2)messages.find取第一条candidateOffer,旧采集题永远赢过后面的采用旁白。(3) GET 把offer_result_id写在最新回合上时,mergeTurnQuestions在旧消息offer_result_id为空时仍保留客户端脏锚点。 - 修复:未采用时卡只挂在最新已落地、且没有未答采集/区分题的助手消息上;未答采集/选择题在场则不出卡。已采用后仍不把卡挪到核对题上。GET 水合时空的
offer_result_id清掉该回合的客户端脏锚点。 - 验证:
rectification-candidate-offer-anchor.test.ts:采集题 + 后续旁白 → 卡在旁白;只有未答采集 → 无卡;已采用后卡留在原消息。agent-voice-copy-contract、rectification-agentic-entry、rectification-adopt-flow-20260902既有合同仍过。 - 防复发:未采用不得把采用卡锚在未答
collect_spoken/choice上。不得用messages.find取第一条 offer。GET 空offer_result_id不得保留客户端脏锚点。已采用后仍不得把卡锚到最新核对题。 - 相关记录:BUG-312、BUG-497、BUG-499、BUG-545
- 复发自:BUG-497、BUG-499
- 修复版本:待发布
BUG-545 | 单分钟采用旁白把同一时刻念两遍,并说「分不开当前候选」
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
nonConvergingRangeNarration、templateStopExplain、RECTIFICATION_USER_COPY.adoptCue - 用户现象:交付句为「剩下的题分不开当前候选。更站得住的范围是 05:15,代表分钟 05:15。这只是代表性候选,不是已确认的唯一出生分钟。可以从下面选一个先用着。」听起来像内部状态,不像对人说话。
- 触发条件:可信区间收成同一分钟,且
stop_facts没有可并列的第二分钟。 - 根因:模板把「范围」和「代表分钟」无条件拼在一起;
formatClockRange把05:15–05:15收成05:15后仍套「范围是 X,代表分钟 X」。停问原因用「当前候选」这种内部词。adoptCue说「选一个」,卡片却不在这句下面(见 BUG-544)。 - 修复:区间收成单分钟时改口「眼下更站得住的是 HH:MM」,不再重复。停问改成「再问下去也分不开 A 和 B」或「再问下去也分不出更准的时间了」。
adoptCue改为「我按你说的经历认真分析过了,下面是这次的结果。」不得说「先用着」。边界句「不是已确认的唯一出生分钟」保留。 - 验证:
agent-voice-copy-contract锁单分钟句式与adoptCue;rectification-answer-choice最后一题采用旁白匹配新句式或跨分钟「代表分钟」。机器词表加入旧「当前候选 / 可以从下面选一个先用着 / 下面的时间可以先用着」。 - 防复发:单分钟区间不得把范围和代表分钟念两遍。用户可见文案不得出现「当前候选」。交付收尾不得说「先用着」。交付/采用轮仍须出现代表性分钟边界语义。
- 相关记录:BUG-544、VOICE.md
- 复发自:无
- 修复版本:待发布
BUG-546 | 家人采集答「没有」后直接给结果,未继续收能区分分钟的经历
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
buildMethodFollowupPlan、shouldSkipFollowupPersist、deferAdoption、isRemainingEvidenceCollect - 用户现象:学业、感情、事业都已有带年份经历,家人题答「没有」后,助手直接给出代表分钟和采用旁白。剩下两三个分钟还分得开,财务、搬家、健康、职业都还没问。
- 触发条件:训练门已开(至少 5 条带日期事件、2 个以上领域),家人采集拒答,剩余区分探针被标成
no_split_among_active,决策层canAdopt=true/sessionOutcome=adopt_representative。 - 根因:两层早退叠在一起。(1)
buildMethodFollowupPlan的 dated 补采集只在训练门未开时运行;门已开就跳到职业,再被deferAdoption清掉next_followup。(2)shouldSkipFollowupPersist见canAdopt且阻断方法已覆盖、当前题不是剩余区分题,就跳过写焦点、改写采用旁白。BUG-539 曾写「家人没有后出采用卡不是缺陷」,那是把采用能力门和「还有未问的经历域」混成一件事。 - 修复:家人覆盖或拒答后总是走
nextDatedCollectFollowup(学业 → 财务 → 搬家 → 健康 → 感情/事业补洞)。isRemainingEvidenceCollect覆盖这些域和职业口述。deferAdoption与shouldSkipFollowupPersist都不得把这类采集早退掉。canAdopt能力仍可开着;未答采集在场时采用卡仍按 BUG-544 不出。不放宽精确分钟确认门。职业仍不是 adopt 能力门槛。 - 验证:
rectification-collect-stalllive five-evidence 家人拒答后持久化财务采集;rectification-adopt-narration-20260904仅家人拒答仍问财务;rectification-provisional-adopt1c / after-choice / B2 锁财务采集写焦点;rectification-eight-method家人之后先财务再职业再占问。 - 防复发:训练门已开不得跳过未用的 dated collect。
canAdopt不得单独构成跳过财务/搬家/健康/职业采集的理由。不得把 BUG-539「家人没有后采用」读成「未问完的经历域也可以结束」。 - 相关记录:BUG-519、BUG-539、BUG-544
- 复发自:BUG-519(采用早退把未用采集域一并吃掉);BUG-539 把该收口写成非缺陷
- 修复版本:
998406ce - 修正:
998406ce把家人之后的 dated 补采集做成无条件优先,现成搬家 A/B 卡被财务口述挤掉。见 BUG-547。 - 修正:剩余采集豁免了
deferAdoption,但决策层在分离度够时仍判adopt_representative,出牌与追问同轮。见 BUG-550。
BUG-547 | 现成区分卡被口述补采集挤掉
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
buildMethodFollowupPlan、nextDatedCollectFollowup、精度层 / yearless 区分卡 - 用户现象:学业、感情、事业、家人都已有带年份经历,引擎已经给出搬家 A/B 探针,下一问却变成财务口述采集。
- 触发条件:经典八法覆盖、精度阶段
d4_refine、存在可渲染choice_frame的搬家探针,同时DATED_COLLECT_ORDER里还有未覆盖域。 - 根因:BUG-546 把家人之后的
nextDatedCollectFollowup从「训练门未开才走」改成无条件执行,且排在精度层搬家卡和 yearless 卡之前。BUG-546 的触发条件本是「没有可用区分题」,实现扩成了「不管有没有区分题都先采集」。 - 修复:家人之后若 yearless 或精度层能拿出
choice_frame,先问那张区分卡;只有本轮没有任何可渲染区分卡时才走 dated 补采集。isRemainingEvidenceCollect对deferAdoption的豁免保留。不放宽精确分钟确认门,不改DATED_COLLECT_ORDER。 - 验证:
rectification-choice-card搬家 A/B 卡用例原样通过;无探针时仍问财务采集;occupation 顺序断言改为d2_finance并写三栏说明。 - 防复发:dated 补采集不得抢在可渲染区分卡之前。没有区分卡时仍必须继续收未问过的带年份域(BUG-546)。
- 相关记录:BUG-546、BUG-519
- 复发自:BUG-546
- 修复版本:
cd704a92
BUG-548 | 全量附录表未写入 public 表白名单,staging 质量门失败
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:Gitea
Independent Staging Quality Gate/validate、frontend/tests/database-local-business.test.ts - 用户现象:
bab07187推 staging 后质量门失败,镜像未发布。用户看不到新的全量数据附录入口。 - 触发条件:新增
public.personal_report_longform_appendices后跑npm test --prefix frontend。 - 根因:
database-local-business把pg_tables的 public 表名单锁死。附录迁移已应用,名单仍缺personal_report_longform_appendices,唯一失败项是not ok 949。附录定向库测本身通过。 - 修复:把该表按字母序写入白名单;断言
20260905010000_personal_report_longform_appendices.sql已应用且未双写冻结的frontend/db/migrations。 - 验证:Gitea run 2416 日志仅此 1 fail;本机重跑
database-local-business。 - 防复发:新增
public表必须同步改这条string_agg(tablename)白名单,并写原值/新值/原因。npm test会跑tests/*.test.ts,含这条库测。 - 相关记录:无
- 复发自:无
- 修复版本:
52a4570c
BUG-549 | 区分卡答「发生了」后仍问同一领域哪年采集
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
buildMethodFollowupPlan、datedCollectDomainBlocked、rectificationFollowupCatalog - 用户现象:刚在事业区分卡上点选「发生了」,下一问又是「工作上呢,还记得哪年入职、换工作…」。
- 触发条件:账本没有该领域带年份证据;区分卡答案只进推断层;本轮已无剩余区分卡,计划层按账本判断领域未覆盖。
- 根因:点选答案不写证据账本(BUG-389–393 分层,本单不推翻)。计划层
careerCovered/datedCollectDomainBlocked只看账本。BUG-547 之后区分卡问完才进采集链,刚答过的领域会被再问一次。 - 修复:
yes/weak_yes的领域视为采集已覆盖(用eventProbes按semantic_key/probe_id回查 domain,回查不到则忽略)。不写账本,不改meetsAcceptanceEventQuality,不改确认门。no/unsure不算覆盖。 - 验证:
frontend/tests/rectification-collect-direction-20260904.test.ts:账本无事业证据、事业探针答 yes 后下一问不是事业采集;答 no 仍问。BUG-546/547 既有用例仍过。 - 防复发:不得把点选答案写入证据账本来「修」重复采集。不得用
semantic_key前缀硬拆领域。不得加「除了刚才那次以外」这类补丁文案。 - 相关记录:BUG-389、BUG-546、BUG-547
- 复发自:无
- 修复版本:
988d97ae
BUG-550 | 带年份采集没问完就出采用卡,同一轮又被下一道采集题挤掉
- 状态:resolved
- 首次发现:2026-09-05
- 最近更新:2026-09-05
- 影响面:
decideRectification、decideFromDossier、decideAfterInferenceChange;next_user_action、offer 工具、采集焦点持久化、客户端时间卡四个消费者 - 用户现象:家人题答「没有」后出现财务口述题,下面已经挂着可点的候选时间卡;答完财务后助手写出整份生时校正验证报告并说可以采用某个钟点,紧跟着又问搬家,时间卡消失。
- 触发条件:学业 / 感情 / 事业已有带年份经历,家人已拒答,财务 / 搬家 / 健康 / 职业口述还没问也没拒答;候选分离度已经够给代表时间。
- 根因:决策层与计划层各说各的。
datedMethodCollectOpen只数四个阻断方法(Dasha / D9 / D10 / 家人),不数DATED_COLLECT_ORDER里还没问的学业、财务、搬家、健康与职业口述。分离度够时直接adopt_representative。BUG-546 只让剩余采集豁免deferAdoption,所以session_outcome已经是可采用,next_followup却仍是下一道采集。四个消费者听了不同的一层:next_user_action让模型写出牌报告并调 offer;offer 工具放行;焦点持久化仍写下道采集题;客户端按session_outcome出卡,未答题在场时才被 BUG-544 藏掉。 - 修复:传给
decideRectification的datedMethodCollectOpen叠加「采集计划的下一问仍是剩余带年份采集」。分离度够、尚未采用、用户未停止、也未耗尽时,保持collect_evidence,公开can_adopt=false。问完(覆盖、拒答)之后同一轮才adopt_representative,报告和卡片一起出,下面不再跟采集题。不改 Skill、不改引擎、不改 BUG-544 隐藏规则。用户说「没有更多」、已采用、已耗尽仍走原出口。 - 验证:
rectification-decide-next-action:分离且采集仍开 →collect_evidence/ask_fact_collection/ 公开不可采用;userStopped与accepted不因此改口。rectification-collect-direction-20260904:家人已拒、财务未问 → 决策采集、持久化财务、offer 工具offer_not_allowed;财务 / 搬家 / 健康 / 职业补齐后adopt_representative且next_followup为空。npx tsx --test tests/rectification-*.test.ts tests/agent-voice-copy-contract.test.ts904/904。 - 防复发:剩余带年份采集未完时,不得因分离度够就判
adopt_representative。不得只改客户端藏卡或只改 offer 工具描述来掩盖决策与计划矛盾。不得把「卡先给、问题照问」当成默认;那条路要产品另开单。 - 相关记录:BUG-544、BUG-546、BUG-547、BUG-549
- 复发自:BUG-546(计划层继续采集,决策层已经可采用)
- 修复版本:
ca4e2408
BUG-551 | 生成中禁用输入框,焦点丢失,回车消息被丢弃
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
ChatComposer、主咨询page.tsx、use-consultation-run.ts、生时校正rectification-agentic-chat.tsx - 用户现象:发出消息后输入框变灰,不能继续打字;回答结束后必须再点一次输入框。生成中按回车,消息被丢掉。
- 触发条件:任一对话面正在生成回答时打字或按回车。
- 根因:两个对话面把
isLoading/busy写进<textarea disabled>。浏览器会立刻移走焦点且解禁后不归还。send()在已有请求时直接return false,没有任何排队。 - 修复:输入框只在只读、会话加载、onboarding 或另一面打开时禁用。生成中回车把文字排成一条待发送,正常结算后自动发出;停止、失败或断线恢复时放回输入框。发送键在有排队时禁用。
- 验证:
frontend/tests/chat-composer-queue.test.ts;composer-ime-contract、composer-isolation-contract、rectification-surface-contract。 - 防复发:不得再用生成中状态禁用 textarea;生成中的发送必须进入排队或明确放回草稿,不得静默丢弃。
- 相关记录:BUG-216、BUG-329
- 复发自:无
- 修复版本:
055b7adc
BUG-552 | 生时校正停止穿报错衣服,选择题与采用等待中停止无效
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
rectification-agentic-chat.tsx、ChatMessageRow、选择题与采用fetch - 用户现象:生成中点停止后对话区出现红色告警,气泡按失败处理;还没有正文时思考行消失。回答选择题或点采用后的等待里,停止键在但点了没反应。
- 触发条件:生时校正流式回答中点停止;或选择题 / 采用请求尚未回到
send()时点停止。 - 根因:
AbortError把气泡写成failed并setError(RECTIFICATION_STOPPED_NOTICE),唯一出口是role="alert"的.error-message。空正文时flatMap丢掉该行。选择题与采用两条fetch没有挂AbortController到runAbort,停止是空操作。BUG-329 的防复发只锁了send()那一路 fetch abort。 - 修复:停止后气泡
settled+stopped,灰字说明已停止且不扣点,不再setError。无正文也保留气泡。选择题与采用的 fetch 挂上同一个runAbort。 - 验证:
frontend/tests/chat-composer-queue.test.ts、rectification-agentic-entry.test.ts、rectification-surface-contract.test.ts、chat-notice-and-scroll-contract.test.ts。 - 防复发:停止不得走
role="alert";busy时可见的停止键必须 abort 当前 fetch,包括选择题与采用,而不仅是send()。 - 相关记录:BUG-329、BUG-551
- 复发自:BUG-329(停止按钮只绑了
send()的 abort,没覆盖选择题与采用) - 修复版本:
055b7adc
BUG-553 | 历史列表只按置顶排,改名收藏后旧会话跳到最顶
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-15
- 影响面:侧栏历史列表、
GET /api/sessions、PATCH /api/sessions/[id]、use-session-management - 用户现象:刚聊过的会话不在最上面;改名、收藏、换模型或换资料后刷新,这条会话反而排到最顶。
- 触发条件:登录后打开侧栏历史;对已有会话做元数据 PATCH,或不发新消息就刷新。
- 根因:
visibleSessions只按pinned排,开着页面时updatedAt变化不改位置。元数据 PATCH 和服务端metadataUpdateValues一律写updated_at = now(),非对话操作也会把会话顶到列表头。真正该 bump 的是发问 / 回答落库。 - 修复:
sortSessions置顶优先、组内updatedAt倒序且稳定。元数据 PATCH 不再写updated_at;客户端改名 / 收藏 / 归档 / 换模型 / 换资料不再timestamp()。列表改为游标分页,历史按本地日期分组,侧栏标题去掉资料名前缀。 - 验证:
frontend/tests/session-groups.test.ts、session-cursor.test.ts、chat-session-write.test.ts(metadataUpdateValues不含updated_at)、sidebar-state.test.ts(改名不 bumpupdatedAt)、sidebar-contract.test.ts、chart-library-session.test.ts。 - 防复发:元数据 PATCH 不得写
updated_at。updatedAt只由对话活动推进,校正答题也算(BUG-704)。侧栏历史排序必须置顶 +updatedAt倒序,不得只按置顶。打开已有会话不得改title/updatedAt,由 BUG-699 与frontend/tests/session-open-preserves-identity.test.ts执行。?c=不在已加载页时先问服务端(BUG-705)。 - 相关记录:BUG-024、BUG-699、BUG-704、BUG-705
- 复发自:无
- 修复版本:
a1956deb
BUG-554 | 设置弹窗随分区跳变,星盘资料铺开整张表单,账户与点数离开首页
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:设置弹窗、
chart-library-panel、billing-panel、首页page.tsx、/membership路由 - 用户现象:三个设置分区弹窗尺寸不同;星盘资料列表下面无条件铺开添加表单;点「账户与点数」会离开首页打开整页会员。
- 触发条件:登录后打开设置弹窗,切换个人资料 / 星盘资料 / 通用设置,或从余额、头像菜单、余额不足进入充值。
- 根因:
accountDialogClasses给每个分区不同宽度,高度随内容撑开。星盘资料没有列表/详情视图,添加表单始终渲染。第四个导航项直接router.push到/membership。 - 修复:四个分区共用
.settings-modal固定尺寸。星盘资料改为列表→详情。账户与点数成为弹窗第四分区;删除/membership与/membership/orders,旧链接重定向到/?settings=billing。七处入口改为openAccountDialog("billing")。 - 验证:
frontend/tests/account-dialog-overlay.test.ts、billing-panel.test.ts、membership-page.test.ts、membership-navigation-contract.test.ts、chart-library-other-profile.test.ts、settings-url.test.ts、beam-avatar.test.ts、class-name-definition-contract.test.ts、tests/test_api_server_security.py -k capability_audit。next build --webpack中/为 Static,无/membership页面。 - 防复发:设置弹窗必须同时声明 width 与 height;星盘资料默认视图不得无条件渲染
<form;/保持 Static,不得useSearchParams;收银台仍window.open;硬跳转清单不得因本单变长。(2026-09-16 修正,见 BUG-698):「必须同时声明 width 与 height」这条防不住复发——它检查的是声明存在性,不是声明是否生效。.settings-modal后来 width 与 height 都老实写着,却因为 height 只用dvh、不认识该单位的引擎整条作废而再次随分区跳变。固定高度的单位回退口径以 BUG-698 为准。 - 相关记录:BUG-434、BUG-251、BUG-143
- 复发自:无
- 修复版本:
dc6598d7
BUG-555 | 普通咨询历史只留每条开头 4000 字,下一轮看不到应期和审计表
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
POST /api/consult、consultation-session-history.ts、session-context-summary.ts、chat_sessions.context_summary - 用户现象:追问「你上次说的应期」时,模型像没看过上一轮。Level 2 报告的应期、综合判断和技法审计表在下一轮消失,也没有任何截断提示。上下文超限时走普通失败,没有缩窗重试。
- 触发条件:普通咨询多轮追问;单条助手回复超过 4000 字;或累计历史超过模型窗口。
- 根因:历史窗口写死为最近 12 条 × 每条开头 4000 字,不知道模型
context_window,也不保留结论。超限错误没有识别为context_overflow。 - 修复:历史改为摘要之后的 append-only 尾巴,按模型窗口算预算,超长从最旧整条丢。单条上限 12,000 字并写明省略字数。尾巴超过 16,000 字时在结算后异步做检查点摘要(不扣点、15 秒 ref 超时、乐观并发)。识别上下文溢出后同一请求内缩到「摘要 + 最后一对」重试一次。
- 验证:
frontend/tests/consultation-session-history.test.ts、session-context-summary.test.ts、consultation-context-cache-contract.test.ts。 - 防复发:咨询历史不得再按固定 12 条 × 头部截断静默砍结论。摘要超时不得用
AbortSignal.timeout()。溢出重试不得新增用户可见等待态。 - 相关记录:BUG-523
- 复发自:无
- 修复版本:
bf8ad0d1
BUG-556 | 缓存命中已写入账本,后台看不到;Anthropic 历史没有第二断点
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
api/admin/usage/aggregate、api/admin/usage、定价测算页、用量列表、cachedHistoryMessage、skill-binding共享方法段 - 用户现象:后台用量页没有缓存命中率或缓存读 tokens。Anthropic 只缓存了系统块,历史每轮全价重算。跨领域不变的 Full-spectrum 与 Event judgment skeleton 每次跟着工具结果重发。
- 触发条件:普通咨询跑过后打开用量聚合或用量列表;使用 Anthropic 或多领域咨询。
- 根因:
complete_usage已把metadata.cache写入usage_ledger,聚合与列表都不读。历史消息没有 AnthropiccacheControl。共享方法段放在工具结果里,不在可缓存系统块。 - 修复:聚合按模型给出 7 天 / 30 天命中率与读写未命中;列表加「缓存读 tokens」。历史尾巴最后一条在 Anthropic 上打第二断点。共享两段搬进
<jyotish-shared-method>。 - 验证:
frontend/tests/admin-usage-aggregate-contract.test.ts、agent-generation-settings.test.ts、skill-binding.test.ts、consultation-methodology.test.ts。 - 防复发:有
metadata.cache的运行必须进入命中率分母;readTokens = 0计未命中。无缓存数据的行不得进分母。系统块 9 段节选与providesSkillDiscovery: "on-demand"不得回退。 - 相关记录:BUG-555
- 复发自:无
- 修复版本:
bf8ad0d1
BUG-557 | 会话标题守卫比较 RPC 前快照,模型标题写库恒 0 行
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
POST /api/consult首轮会话标题 - 用户现象:新会话首轮模型起的标题,刷新、关标签或换设备后变回问题截断标题。
- 触发条件:普通咨询新会话发第一问,在客户端 PATCH 标题之前刷新或离开。
- 根因:
append_consultation_question会把空标题 / 「新对话」改成问题前 14 字。守卫却用 RPC 前快照做.eq("title"),更新恒 0 行且不报错。 - 修复:
shouldGenerateSessionTitle仍看 RPC 前标题。RPC 成功后再读一次当前标题做守卫。更新带.select("id"),0 行console.warn("session title guard missed")。 - 验证:
frontend/tests/session-title-persist-contract.test.ts、application-billing-contract.test.ts。 - 防复发:标题守卫必须比较 RPC 后的
chat_sessions.title,并断言影响行数。不得用 TS 重算 SQL 的截断规则。 - 相关记录:BUG-553
- 复发自:BUG-553(
a1956deb的测试没断言守卫会命中) - 修复版本:
0102a973
BUG-558 | 采集问完落到「也可以再说一件事」,没有交付出口
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
persistExhaustionCollect、exhaustionSpokenCollectFollowup、ensureNonTerminalTurnExit、口述采集 UI、POST /api/rectification/agent - 用户现象:七个带年份领域和职业都问过之后,助手仍问「也可以再说一件你记得大概时间的事」,界面只剩不可点的范围小字,没有候选卡、没有停止按钮、没有下一步。
- 触发条件:方法覆盖完成、带年份领域都问过或拒过、职业已覆盖;非收敛出牌或非终止修复走穷尽采集。
- 根因:
exhaustionSpokenCollectFollowup在 dated / occupation 之后无条件落到otherCollectFollowup。domain="other"不在剩余采集集合里,也不挡出牌,但作为collect:other:*焦点被写回后下一轮还回到同一句,没有终止条件。口述采集没有停止按钮;ask_about_result只在点选焦点处理。 - 修复:穷尽采集不再问
other。还有 dated / occupation 就继续问;否则acceptance_allowed且训练门开且可采用则adopt_representative(stopReason=probe_pool_exhausted),否则写门槛翻译句、不写焦点,并用terminalNote满足非终止出口。口述采集在输入框上方放与点选卡同文案的停止按钮;只读范围改为同一停止动作。ask_about_result与stop_rectification在点选和口述焦点都映射到停止。keep_collecting仍继续采集,不得劫持成出牌。 - 验证:
frontend/tests/rectification-exhaustion-exit-20260906.test.ts;既有 adopt / spoken-collect / timeline / hidden-e2e 三栏更新。 - 防复发:
exhaustionSpokenCollectFollowup不得再返回other。穷尽日志必须有rectification_exhaustion_collect且不含证据摘要或年份。不得把keep_collecting改成强制采用。 - 相关记录:BUG-550、BUG-546、BUG-586
- 复发自:无
- 修复版本:
150d7ef1
BUG-559 | 同年月拆成多道「有没有 X」,同域同年再问,无锚点性格卡
- 状态:mitigated(点选同年复发见 BUG-592)
- 首次发现:2026-09-06
- 最近更新:2026-09-08
- 影响面:
scripts/rectification/event_probes.py、asked_probe_keys、existenceProbeAsked、rankRenderableDiscriminators - 用户现象:同一大运边界月按领域各问一次「那时候有没有入职 / 有没有搬家」;已问过某年 5 月后又问同年无月份;没有对应经历的「平时做事更接近哪一种」性格卡也会出。
- 触发条件:引擎按领域扫描边界月;去重只看账本年份不看已问探针;
varga_style不要求同域带年月证据。 - 根因:existence 探针按
(domain, year, month, source)独立出题;_existence_blocked_years不并入已问探针年份;性格对照卡没有锚点门槛。 - 修复:请求可带
asked_probe_keys(不进结果指纹)。已问domain.Y.MM.*后,同年与相邻年不再出该领域 existence / activation。varga_style无同域 confirmed 带年月事件则dropped_probes(unanchored_varga_style);有锚点时 hint 带年月、分数 ×0.8,用户可见题干仍不写年份。邻近账本事件以领域+年月(无摘要)写入user_prompt_hint。B2 同年月合并卡choice_kind=boundary_pair本单未做。 - 验证:
tests/test_rectification_event_probes.py已问同年相邻年不再产出;frontend/tests/rectification-probe-year-dedupe-20260906.test.ts。 - 防复发:不得把
asked_probe_keys写入结果指纹。点选答案仍不写证据账本。不得为性格卡在用户可见题干里写年份。 - 相关记录:BUG-540、BUG-390、BUG-592
- 复发自:无
- 修复版本:
3a9ae736
BUG-560 | 相对支持度正比例归一把引擎证据压成 7–9,lead 8 不可达
- 状态:blocked
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
scripts/rectification/decision_policy.py::_relative_support、公开簇代表 prior、前端unionStillValidRange - 用户现象:多件带年月经历之后,公开候选相对支持度仍是 7–9,范围几乎不收;之后只靠点选卡 ±2 推分,问尽仍收不窄。
- 触发条件:公开最多 12 个簇代表按原始分正比例把 100 分掉;原始分底座远大于事件极差。
- 根因:
_relative_support按原始分正比例归一。TASK-rectification-provisional-adopt-20260901.md§2 已看到 lead 停在约 5,当时只改门、四个常数不动,没有改 prior 尺度。 - 根因升级(2026-09-06):校准门「覆盖 ≥ 旧方案 − 1 且中位宽度更窄」设计有缺陷。旧方案 proportional 在 20 例公开 holdout 上中位宽度 21 分钟 = 整个候选窗口(半径 10),覆盖 17/20 只是因为不收窄。先验一旦按引擎原始分拉尖(offset),真实分钟落在领先集合里只有 12/20(中位宽 10),与随机取 10 分钟(≈48%)相当。引擎原始分在分钟级几乎没有区分力;现有八法计分不足以在 20 分钟窗口内可靠收窄。「收敛」只能来自用户答题和更强的引擎证据,不能靠换尺度。本条状态保持 blocked,未启用新尺度。
- 修复:实现 offset(分−全网格最低分)与 softmax,由校准脚本在 20 例公开 holdout 上比覆盖与中位宽度。合入门:覆盖 ≥ 旧方案 − 1 且中位宽度更窄。实测无方案过门(offset 覆盖 12/20 < 16;softmax T=2 中位宽度 21 不小于旧值 21)。默认仍
proportional,POLICY_VERSION/ALGORITHM_VERSION不 bump,旧 Case 不因此自动重算。 - 验证:
scripts/rectification_prior_calibration.pyholdout 表见进度记录;tests/test_rectification_relative_support.py锁定默认 proportional,并断言显式 offset 在拉开的格子上领先不差于 proportional。 - 防复发:不得在校准门未过时启用 offset/softmax 或 bump 政策版本。不得改
MIN_SEPARATION_LEAD=8或采用/确认/熔断常数来「先让分看起来够」。 - 相关记录:BUG-463、
TASK-rectification-provisional-adopt-20260901.md§2 - 复发自:无
- 修复版本:未启用新尺度;代码与校准脚本
62afa521
BUG-561 | 长报告 KP Lord/Sub 与专题 KP 表在失败信封下整段消失
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
scripts/jyotish_engine.pypl9 长报告、专业参考 Markdown - 用户现象:三年 KP 月表还在,但 KP Lord/Sub 原始表和事业/财运/关系章的 KP 结构小表整段不见,页面上也没有 blocked 说明。
- 触发条件:专业参考路径把
cmd_kp收成{status: blocked, reason}且没有 planets/houses;渲染层遇到该信封直接返回空列表。鉴别条件是候选窗/blocked KP 信封,不是 Raman 本身。 - 根因:
_kp_lord_sub_section等装配函数在 KP 模块失败时return [],计划段被静默跳过。 - 修复:失败路径改为输出显式 blocked 标题和原因码;最终 Markdown 再扫一遍计划标题,缺席则补
planned_section_unavailable。 - 验证:
tests/test_report_longform_gaps2.py。 - 防复发:计划内段必须 present 或 blocked,不允许缺席。禁止再对失败 KP 信封直接
return []。 - 相关记录:无
- 复发自:无
- 修复版本:
cfcd369d
BUG-562 | 专业参考路径剥掉 raw_full_reading 后模块审计表只剩表头
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
scripts/jyotish_engine.py长报告审计表、力量章 Yogini/Narayana/Kala/Bhava Bala - 用户现象:逐模块执行审计只有表头、零数据行。力量章缺 Bhava Bala 与三套辅助大运明细。
- 触发条件:professional 路径 sanitize 掉
raw_full_reading;worksheet 只抄了 dasha 家族状态、没有 periods。 - 根因:审计行只从将被剥离的 raw 模块读;辅助大运渲染拿不到规范化 periods。
- 修复:sanitize 前抽出
_iter_audit_modules;规范化 dasha family periods,并从本地 yogini/narayana/kala/bhava_bala 模块补装,缺数据走 blocked 行。 - 验证:
tests/test_report_longform_gaps2.py。 - 防复发:full pack 下审计表数据行必须达到模块执行数下限;力量章六子块缺数据必须有 blocked 标题。
- 相关记录:BUG-561
- 复发自:无
- 修复版本:
cfcd369d
BUG-563 | 报告 MD 详情页在无 IntersectionObserver 时同步 setState,staging 门禁 lint 失败
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
frontend/src/components/personal-report/personal-report-markdown-view.tsx、Giteabackend-quality-gaterun 2446 - 用户现象:staging 未部署本轮报告 MD 页;门禁
npm run lint1 error 后publish/deploy-staging未跑。站点/api/health的deployment.gitCommit仍停在此前 SHA。 - 触发条件:
LazyMarkdownSection在IntersectionObserver未定义时(SSR / 部分测试环境)于useEffect内同步setVisible(true)。 - 根因:react-hooks 编译器规则禁止 effect 内同步 setState,以免级联渲染。仓库红线是 lint 0 error。
- 修复:无观察器时改为
requestAnimationFrame后再setVisible,并在清理函数里cancelAnimationFrame。SSR 首屏仍只渲染 eager 段。 - 验证:
npm run lint -- src/components/personal-report/personal-report-markdown-view.tsx0 error;frontend/tests/personal-report-markdown-view.test.ts仍断言懒加载段不进 SSR 标记。 - 防复发:报告 MD 视图 effect 不得同步 setState;后续 lint 0 error 才允许合入 staging。
- 相关记录:BUG-561、BUG-562
- 复发自:无
- 修复版本:
ebc8380f
BUG-564 | 长报告 heading-ensure 之后仍丢逐月 KP 标题、时间来源与分钟排行
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
scripts/jyotish_engine.pypl9 长报告、scripts/flexible_birth_time_report_section.py、scripts/run_quality_gate.py快速门 - 用户现象:有 KP 月份数据时,事业/财务/关系三条「逐月 MD/AD 支持」标题仍不见;时间节点归因表的来源列被脱敏成空;校时附录没有分钟排行。快速门也不跑 gaps2 合同。
- 触发条件:
kp_monthly_report.months非空;professional 路径 sanitize 掉source_path;候选窗存在但渲染层不读candidate_times。 - 根因:
_kp_monthly_theme_brief成功路径不写标题,空包才靠文末 heading-ensure 补 blocked 标题;parity_gates 用source_path,sanitize 会剥掉任何_path键;CLI provenance 只读显式uncertainty_minutes;分钟排行未装配。顺带d1_d60_ledger/support_topics.kp为 None 时.get会在有月份数据的渲染里炸掉。 - 修复:逐月 brief 始终输出标题,空数据走 blocked;parity_gates 改
source_label;窗口宽度回填 uncertainty;校时附录列出候选分钟;None 守卫;快速门列入 gaps2 / parity / timing / flexible 合同。 - 验证:
tests/test_report_longform_gaps2.py、tests/test_flexible_birth_time_report_contract.py、tests/test_timing_precision_contract.py。 - 防复发:有月份数据时三条逐月标题必须各出现一次,且不得靠文末 blocked 标题顶替;sanitize 后 Markdown 仍须保留可读 source_label;快速门必须跑上述四个测试文件。
- 相关记录:BUG-561、BUG-562
- 复发自:BUG-561(
cfcd369d的 heading-ensure 只覆盖空包缺席,未覆盖成功路径漏标题) - 修复版本:
e4d16b75
BUG-565 | 穷尽门槛句每个回合写成两条相同助手消息
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
persistExhaustionCollect、finalizeSuccessfulTurnExit、runV9AgentTurnopening/evidence、applyRectificationChoice点选路径、/api/rectification/agent - 用户现象:方法覆盖完成、无剩余采集、引擎采用门关闭时,同一段「范围已经收到…还差带月份的经历」连出两三遍。
- 触发条件:A4 用例 2 形状上
finalizeSuccessfulTurnExit({ action: "message" })、action: "evidence"经 agent-run,或点选最后一张关闭天花板的卡。 - 根因:门槛翻译句被当成独立消息。
persistExhaustionCollect自己persistV9DeterministicTurn(requestId: randomUUID())写第 1 条;inspectNonTerminalTurnExit.satisfied不认门槛句为载体,再写第 2 条。opening/evidence 在 finalize 前还会先 idle 一次。点选路径一条独立门槛消息,又把同一句拼进「已记录你的选择」。 - 修复:门槛句是回合正文,写入方只能有一个。
persistExhaustionCollect只返回{ hostNarration, terminalNote: true, persisted: false },不再写 turn。idle 返回terminalNote且本回合尚未写过门槛时,由finalizeSuccessfulTurnExit/ agent-run / route 用幂等requestId写一次并跳过非终止修复。点选路径只把门槛句并入该回合正文。 - 验证:
frontend/tests/rectification-exhaustion-exit-20260906.test.ts:message finalize、evidence agent-run、最后一张卡各计 1 次含门槛句的助手 append;A4 用例 2 后再调ensureNonTerminalTurnExit,append 不增加。非 DB 校正套件 910/910。 - 防复发:
persistExhaustionCollect不得再调用persistV9DeterministicTurn。门槛requestId必须可幂等。inspectNonTerminalTurnExit必须把穷尽态视为已交付。 - 相关记录:BUG-558、BUG-566
- 复发自:BUG-558(穷尽交付把门槛句做成独立消息,出口修复未锁单一写入方)
- 修复版本:待合入
origin/staging(codex/rectification-convergence-exit-fix-20260906)
BUG-566 | 穷尽分支排在问题持久化之前,吞掉仍可问的区分卡和 holdout
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
persistNextInterviewIfIdle、isExhaustedGateState、决策层ask_candidate_discriminator/ask_holdout_validation - 用户现象:引擎采用门关闭(例如
low_date_quality)后,聊天只剩门槛句,不再出现还能分开候选的选择题或盘外核对。 - 触发条件:无剩余采集、方法覆盖完成、
canAdopt=false,但决策层仍给出可渲染区分卡或 holdout 题。 - 根因:idle 穷尽分支只看「无剩余采集且不可采用」,不看
decision.nextAction,排在persistNextInterviewAfterChoice之前。 - 修复:抽
isExhaustedGateState。idle 穷尽分支额外要求nextAction ∈ {offer_provisional_range, complete_with_range, ask_fact_collection}。区分卡 / holdout 照常持久化。inspectNonTerminalTurnExit与 idle 共用穷尽态判定。 - 验证:同测试文件:关闭天花板但留一条可渲染探针 → 持久化区分卡焦点,无门槛句;holdout 仍开 → 持久化 holdout,无门槛句。
- 防复发:不得把穷尽门槛排在
ask_candidate_discriminator/ask_holdout_validation之前。不得重新引入USER_COLLECT_QUESTION.other兜底。 - 相关记录:BUG-558、BUG-565
- 复发自:BUG-558(穷尽交付分支未白名单 nextAction)
- 修复版本:待合入
origin/staging(codex/rectification-convergence-exit-fix-20260906)
BUG-567 | 范围小字伪装成停止按钮,文案仍是状态句
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
RectificationReadonlyRange、口述采集停止按钮、frontend/DESIGN.md - 用户现象:范围一行写着「目前范围 …–…,还在收窄」,看起来不可点,点了却等于「先这样」。同一屏下方已有明确停止按钮。
- 触发条件:口述采集或只读范围可见时。
- 根因:BUG-558 A3 把只读范围改成
<button>并挂onStop,文案未改成动作句。 - 修复:范围行还原为
<p role="status">,删除onStop。口述采集输入框上方的「先这样,先看当前范围」按钮保留。 - 验证:源扫描断言范围函数没有
onStop/<button,口述停止按钮仍用CHOICE_STOP_LABEL。 - 防复发:
RectificationReadonlyRange不得再渲染成按钮或接受停止回调。 - 相关记录:BUG-558
- 复发自:BUG-558(A3 把范围小字做成第二个停止入口)
- 修复版本:待合入
origin/staging(codex/rectification-convergence-exit-fix-20260906)
BUG-568 | 报告与聊天按开工搜索窗口读盘,没用采用时的可信区间
- 状态:resolved
- 首次发现:2026-09-06
- 最近更新:2026-09-06
- 影响面:
read_report_candidate_range、采用 RPC、consultation-route-serviceverified_chart、jyotish_engine._candidate_minutes、采用旁白 - 用户现象:采用一段 10~30 分钟可信区间后,个人报告的出生时间敏感度仍按开工搜索窗口分层;聊天把代表分钟当成精确出生分钟排盘。
- 触发条件:校正采用成功后打开报告或新建
verified_chart咨询。 - 根因:采用 RPC 只写代表分钟,不落库可信区间。报告 RPC 读开工
candidate_range。引擎窗口超过 15 分钟只取 3 个样本。聊天accepted不传区间。 - 修复:采用写入
adopted_credible_range,不改开工candidate_range。报告 RPC 优先读采用区间。≤31 分钟逐分钟采样,更宽等距 31 点。采用旁白追加服务端「稳定 / 随分钟变」两句。accepted咨询按provisional+ 区间读盘;confirmed不变。声明时段咨询复用同一分层并标明粗看。 - 验证:
frontend/tests/adopted-credible-range-migration.test.ts、frontend/tests/rectification-range-reading-20260906.test.ts、采用旁白范围读盘用例、tests/test_flexible_birth_time_engine.py27/60 分钟采样。Dockertest:db见测试说明;无 Docker 时不得标通过。 - 防复发:采用不得改写
candidate_range。报告窗口必须先读adopted_credible_range。flexible_birth_time_profile上限 31。稳定/敏感句不得由模型补写。 - 相关记录:BUG-560
- 复发自:无
- 修复版本:待合入
origin/staging(codex/rectification-convergence-exit-fix-20260906)
BUG-569 | 答后旁白把搜索窗口当成可信区间,每题都说「范围没变」
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
composeChoiceNarration、applyRectificationChoice、probe-explain.explainRangeChange - 用户现象:顶部只读范围已经随答题收窄,助手旁白仍说「范围没变」。
- 触发条件:点选区分卡 A/B/C,搜索窗口
range_start/range_end不变,而credible_range变了。 - 根因:
applyRectificationChoice把InferenceState.range_start/range_end(开工搜索窗口)传给旁白。答题改的是credible_range。直喂composeChoiceNarration的单测没走这条路径。 - 修复:旁白改比
previous.credible_range与applied.state.credible_range。入参改名credibleBefore/credibleAfter。任一为空则不说范围句。 - 验证:
frontend/tests/rectification-answer-choice.test.ts:搜索窗口固定、credible_range收窄时旁白含「范围从 X 收到 Y」;credible_range不变时仍说「范围没变」。源扫描锁定不再传range_start。 - 防复发:不得把
range_start/range_end再传给答后范围句。新增端到端用例必须走applyRectificationChoice,不能只直喂纯函数。 - 相关记录:BUG-568
- 复发自:过程解释层(
814c924e)把搜索窗口误当可信区间 - 修复版本:
517df002
BUG-570 | 时段支持度按段内原始分求和,长时段先天占优
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
scripts/rectification/api_service.py::block_scan - 用户现象:完全不清楚出生时间时,第一张时段卡下午段支持度偏高,即使各分钟分数相同。
- 触发条件:
stage=block_scan、10 分钟步长扫 24 小时。五段候选数 24/24/36/30/30。 - 根因:
relative_support用段内sum(score)。下午 36 个候选,清晨 24 个;引擎原始分底座远大于事件差,于是在没有证据差异时也是在比时段长度。 - 修复:
raw_support = max(mean(score in block) − min(all scores), 0),再归一。全部为零时五段各 20%。不进采用门,不改分钟级_relative_support。 - 验证:
tests/test_rectification_v5_services.py:144 行同分 → 五段各 20.0;只抬高清晨 → 清晨最高,下午不再因长度领先。minute_step=1指纹用例未改。 - 防复发:
block_scan不得再对段内原始分求和。分钟级 prior 仍走原来的正比例归一(BUG-560)。 - 相关记录:BUG-560
- 复发自:unknown-time
block_scan(814c924e)按时段长度偏置 - 修复版本:
517df002
BUG-571 | 档案里声明的不确定度被忽略,有钟点一律 ±15
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
frontend/src/lib/birth-time-intake-model.ts、birth-time-intake.tsx、account-profile-patch.ts、case-service.ts::deriveRectificationOpenPlan - 用户现象:家人记得大概时间时,intake 选不到前后半小时 / 一小时 / 两小时;校正开工窗口仍是填报钟点 ±15,档案里的
uncertainty_before/after不参与搜索。 - 触发条件:资料有填报钟点;来源本应是
approximate或医院记录。 - 根因:intake 单选只露出
family_exact/period_only(含 unknown)。开 Case 用固定FRESH_CASE_SEARCH_RADIUS_MINUTES = 15,注释写明那是引擎边界、不是用户声明。 - 修复:intake 改三档——医院记录(固定 ±2 无感检查)、家人大概时间(±15/30/60/120)、时段或完全不知道。
family_exact从新选项移除,读档映射成「差不多准」。有钟点时:hospital_record/family_exact→ ±15;approximate→ ±max(15, 声明值),上限 120,前后独立。窗口 exclusive span >120 先进block_scan(BUG-573)。 - 验证:
birth-time-intake.test.ts三档与 120 校验;account-api.test.ts120 合法、90 非法;rectification-v9-case-service.test.ts既有三处family_exact04:45–05:15 保留,新增 approximate ±60 → 04:00–06:00、±120 → 03:00–07:00。 - 防复发:有钟点开 Case 不得再写死 ±15;
approximate校验集合必须与 intake 按钮同为 {15,30,60,120}。 - 相关记录:BUG-572、BUG-573
- 复发自:无
- 修复版本:
8e31680b
BUG-572 | 事件吻合率低且贴窗口边缘时不提议放宽搜索窗
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
decision-from-dossier.ts、window-widen.ts、method-followup.ts、widen_agentic_rectification_case_window、set_agentic_rectification_widen_declined - 用户现象:经历已经明显对不上家人记的时间,校正仍只在原来的 ±15/±30 里出区分卡,没有一键放宽。
- 触发条件:训练门已开、
event_fit_rate.band=low且total≥3、代表分钟距窗口边缘 ≤3 分钟、stage=minute、未采用、有填报钟点、当前证据指纹未拒过、当前半径 <120。 - 根因:
event_fit_rate只进采用后八法报告。分钟阶段没有改candidate_range的 RPC;唯一改窗 RPC 要求stage=block_scan。 - 修复:服务端四选卡
choice_kind: widen_window(不进 Python 探针契约),排在区分卡之前。A/B 扩大窗口(必须包含旧窗、inclusive 宽 ≤241)、重算,并把档案写成approximate+ 新半径;hospital_record只改窗口不改档案来源。C/D 记widen_declined_at_fingerprint。档位 15→±30/±60,30→±60/±120,60→±120 / 「先补一件带月份的经历再说」。D 文案「说不好 / 先这样」(clippedCopy最短 4 字)。不动采用/确认门、MIN_SEPARATION_LEAD、分钟级_relative_support、adopted_credible_range。 - 验证:
rectification-window-widen-20260907.test.ts六条;迁移合同锁 widen RPC 与不得改adopted_credible_range。Dockertest:db在database-rectification-block-scan.test.ts加 widen 扩大/缩小与权限。 - 防复发:放宽卡必须服务端生成;不得让模型问「偏移 / 误差」;widen RPC 只许扩大且包含旧窗。
- 相关记录:BUG-571、BUG-573
- 复发自:无
- 修复版本:
8e31680b
BUG-573 | 选定时段或 ±120 窗口后直接进 4~6 小时分钟网格
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
scripts/rectification/api_service.py::block_scan、contracts.py可选blocks、advance_agentic_rectification_case_from_block_scan、search-window.ts、block-scan.ts - 用户现象:只知道傍晚或家人说前后两小时时,选定后马上在 4~6 小时上按分钟比,网格过密、题也问不清。
- 触发条件:exclusive span >120(例如 18:00–22:59、03:00–07:00、下午 12:00–17:59)。
- 根因:
block_scan只服务全日五时段;advance_*要求原窗 00:00–23:59,选定后一律stage=minute。set_stage也曾要求全日窗。 - 修复:请求可带 1~5 段
blocks(须落在窗口内、除共享端点外不重叠)。无blocks时仍用五声明时段;全日默认步长 10。子窗三等分、无共享分钟;步长 >360→10、>180→5、其余 2。advance_*改为新窗须为当前窗子区间;span>120 且 rounds<3 仍block_scan,否则minute。每 Case 最多 3 张子段卡,超过旁白「时段分不开,直接按分钟比」。unknown 仍先五时段,赢段再切三段。全零相对支持按 100/n 均分(三段 33.3/33.3/33.4),不再写死五段各 20。不改分钟级_relative_support。 - 验证:
test_rectification_v5_services.py自定义三段和为 100、等分 33.3/33.3/33.4、越界/重叠报错;rectification-block-scan-20260906.test.ts±120 开 Case 为 block_scan、傍晚三子段、选下午后仍 block_scan;Docker 上午 08:00–11:59 选定后仍 block_scan,08:00–09:00 进 minute。 - 防复发:exclusive span >120 不得直接
stage=minute。block_scan全零均分必须按段数,不得写死 20%。 - 相关记录:BUG-570、BUG-571
- 复发自:unknown-time 选段后一律进分钟(
814c924e) - 修复版本:
8e31680b
BUG-574 | 报告列表 JSON 路径 select 在 staging PostgREST 上 500
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
GET /api/reports、personal_reports列表查询、报告中心卡片 - 用户现象:登录后报告中心无法打开列表,接口稳定返回 500
{"error":"报告列表暂时无法读取","code":"report_generation_failed"}。同一会话读取已有报告详情 200。 - 触发条件:部署含
cfcd369d的应用后打开报告中心。自托管 PostgREST 解析card_summary:report_document->executiveSummary->>summary失败。 - 根因:列表
select增加了 PostgREST JSON 路径别名。该语法未被test:db或真实 PostgREST 覆盖;supabase-js 返回的 PostgrestError 不是Error实例,列表日志只记UnknownError。 - 修复:列表改为普通列
card_summary;迁移增加可空text列(≤500 字符);complete_personal_report_job从封面文档executiveSummary.summary(MD「摘要」截取)写入。旧行不回填。列表 catch 记录code/hint,不记行数据。 - 验证:
frontend/tests/database-personal-report-card-summary.test.ts(可空、authenticated 只读、service_role 可写、超长拒绝、complete RPC 写入);列表/观测单测;staging72dd5e9d已部署,health 账本为20260907010000_personal_report_card_summary.sql;未登录列表 401。登录态列表 200 与新报告卡片摘要仍待真实会话。 - 防复发:新增 PostgREST JSON 路径、嵌套 embed 或别名必须有
test:db真查询或部署后 smoke;禁止再用未经验证的 JSON 路径变体赌列表查询。 - 相关记录:无
- 复发自:无
- 修复版本:
b466a6fc8cbb17d48c5aa242dbafd44b9a7c651b
BUG-575 | 生时校正选项悬停时间解释闪动,输入框上方步骤提示是套话
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
RectificationChoiceCard、RectificationAgenticChat输入框上方步骤条 - 用户现象:鼠标在 A/B/C/D 选项间移动时,选项下方出现「会让 … 这段领先/落后」并随悬停切换不断闪动;输入框上方还有「第 N 步·… — … — 下一步:…」套话。
- 触发条件:生时校正出现选择题后,鼠标在选项间来回移动;或进入校正对话看到输入框。
- 根因:选择题把
answer_impact绑到onMouseEnter/onFocus,悬停切换会改写同一行文案并撑开布局。步骤条把服务端step_state拼成输入框上方提示。 - 修复:选项不再监听悬停、不再渲染
answer_impact。输入框上方不再展示步骤条;采集题的「先这样」按钮仍保留在输入框上方。服务端仍可生成answer_impact和step_state,只是用户界面不再读出来。 - 验证:
frontend/tests/rectification-surface-contract.test.ts、frontend/tests/rectification-step-state-20260906.test.ts、frontend/tests/rectification-v9-contracts.test.ts。 - 防复发:选择题源码不得再出现
onMouseEnter或rectification-choice-impact;聊天源码不得再出现rectification-step-state。 - 相关记录:无
- 复发自:无
- 修复版本:
1ded6bb0
BUG-576 | MD-only 长报告引擎 200 后附录 upsert 未带冲突键,staging 三次重试仍 failed
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
generatePersonalReportLongform、personal_report_longform_appendices、自托管createLocalPostgresDataClientupsert、报告详情失败态 - 用户现象:personal_full MD-only 首次真实运行在引擎调用阶段失败(
calculation_unavailable,progressPercent=30)。直连引擎同形虚构盘 HTTP 200。 - 触发条件:staging 自托管 web worker 调用
/api/professional_report_reference成功后写入 longform 附录。 - 根因:本地 postgres 客户端的
upsert必填onConflict;附录写入只调用.upsert(row)。请求在写库前抛错,附录表 0 行。Job 三次尝试均打到引擎(三次 HTTP 200,约 26–32s,低于 180s 超时)。不是 api 容器滞后,也不是引擎超时。web 容器 docker logs 几乎只有启动行,附录错误码此前也不在详情 API 中。 - 修复:附录 upsert 显式
onConflict: "report_id";本地客户端缺省冲突键回落到主键;详情失败态透出appendixLastErrorCode与可读原因;引擎调用记录http_status/duration_ms;api 容器与/api/health暴露构建 SHA。 - 验证:定向 frontend 单测、附录
test:dbupsert 无onConflict、health/compose 源合同。未改.gitea/workflows/**,不提升 main。 - 防复发:自托管 upsert 必须能在缺
onConflict时按主键执行;附录写入必须带 PK 冲突键;失败详情必须带附录错误码。 - 相关记录:BUG-574
- 复发自:无
- 修复版本:
e87c58d6e48850749ea9e0a92e532133db03e292
BUG-577 | 性格卡答过之后,后续每次候选比较都被 400 拒绝
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
asked_probe_keys、normalize_rectification_request、rectification-compare-candidates、inference-adapter.ts - 用户现象:性格对照卡或月宿边界卡答完后,再说一件事时候选比较立刻失败(约 30ms,不是引擎超时)。顶部范围不再随新经历变化。
- 触发条件:答过带
candidate_split_hash的性格/边界卡后,再走候选比较。 - 根因:BUG-559 把
probe_id、semantic_key、candidate_split_hash都塞进引擎asked_probe_keys。性格卡 hash 约 128 字符,引擎原上限 120,整单请求 400。去重仍应靠semantic_key,不该把 hash 传给引擎。 - 修复:引擎请求只传
semantic_key与账本年份键;TS 再丢掉>200或含:varga.的键。引擎上限改为 200,超长键跳过并在 receipt 计数dropped_asked_probe_keys,不再 400。128 字符键在新上限下会保留(决策是提上限,不是跳过 128)。 - 验证:
tests/test_rectification_input_contract.py;frontend/tests/rectification-v9-engine-contract.test.ts;frontend/tests/rectification-probe-year-dedupe-20260906.test.ts。 - 防复发:比较请求体不得含
:varga.hash;引擎契约须覆盖真实长度的 split hash,不能只用短键。 - 相关记录:BUG-559、BUG-578、BUG-579、BUG-580
- 复发自:BUG-559
- 修复版本:
4e0db55f
BUG-578 | 候选比较失败被吞掉,过期快照也不自动重算
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
rectification-compare-candidatesreceipt、agent-run.ts、persistNextInterviewIfIdle - 用户现象:比较已经失败,助手仍写「这条记下了、很有帮助」。用户看不到失败,下一轮也不重试。
- 触发条件:比较工具抛错(含引擎 400)后 Agent 继续作答。
- 根因:失败 receipt 只有笼统
tool_failed;Agent 不追加可见说明;闲置下一问不因快照过期重算。 - 修复:失败 detail 带
safe_error_code与引擎信息前 120 字;正文追加「候选比较这次没跑成,下一句话时会自动再试。」;闲置持久化若快照过期且账本有可评分事件,同一证据指纹只重算一次。 - 验证:
frontend/tests/rectification-stale-compare-fix-20260907.test.ts。 - 防复发:比较失败必须有用户可见固定句;过期快照的闲置路径必须先重算。
- 相关记录:BUG-577
- 复发自:无
- 修复版本:
4e0db55f
BUG-579 | 用户停止被过期快照压过,点「先这样」后没有候选卡
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
decideRectification、applyRectificationChoiceSTOP、stop_and_review - 用户现象:点「先这样,先看当前范围」后出现「再问下去也分不开」一类旁白,但没有候选卡,界面写「没有拿到下一个问题」。
- 触发条件:账本已比最新比较结果多出可评分事件(快照过期)时停止。
- 根因:
snapshotCurrent === false排在userStopped之前,停止后仍判采集;停止路径也不先重算。 - 修复:有排序候选时,用户停止优先于快照过期,进入
complete_with_range且session_outcome属于可出采用卡的集合。停止路径先重算;重算失败则按上一次成功比较交付,旁白加「这是按上一次成功比较给出的范围。」 - 验证:
frontend/tests/rectification-decision-authority.test.ts;frontend/tests/rectification-stale-compare-fix-20260907.test.ts;frontend/tests/rectification-answer-choice.test.ts。 - 防复发:
snapshotCurrent=false && userStopped && ranked>0必须交付,不得回到采集。 - 相关记录:BUG-577、BUG-578
- 复发自:无
- 修复版本:
4e0db55f
BUG-580 | Holdout 题用过期盘外领域,被改写成「再说一件事」
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
holdoutFollowupFor、采集题干 - 用户现象:账本里已经有该领域带年月的事,助手仍问「也再说一件你能记得大致时间的事」。
- 触发条件:过期
oos_blind_prompts里的领域其实已在账本;焦点按盘外核对给模型改写。 - 根因:holdout 只排除拒答领域,不排除账本已有带年月事件的领域;题干不是服务端固定采集句。
- 修复:候选领域须既未拒答、账本也没有该领域带年月事件。没有可问领域则不再出 holdout 题。题干用
USER_COLLECT_QUESTION[domain],焦点为口述采集,不给选择题框。决策层不因此改成unavailable,以免走成无卡的 offer。 - 验证:
frontend/tests/rectification-collect-direction-20260904.test.ts。 - 防复发:账本已有财务等带年月事件时不得再出该领域 holdout;可问时题干必须等于对应采集固定句。
- 相关记录:BUG-577
- 复发自:无
- 修复版本:
4e0db55f
BUG-581 | 停止后重算丢掉全部答题,采用报 candidate_state_inconsistent
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
rescoreMinuteAfterWindowChange、scoreAndPersistCurrentEvidence、STOP / 闲置重算、accept_agentic_rectification_candidate_for_case_v2 - 用户现象:答完多张卡后点「先这样,先看当前范围」,范围比答题后更宽,卡片数字变成引擎裸相对支持度;点采用报「候选状态已更新,请刷新后重试」。
- 触发条件:账本已比最新比较结果多出可评分事件(快照过期)时停止;时段选定或放宽窗口后的重算也走同一条裸路径。
- 根因:BUG-579 让停止先重算,但
rescoreMinuteAfterWindowChange只跑引擎分数并persistV9Candidate,不重建inference_state、不带previous、不重放已答探针。采用 RPC 要求decision_receipt.inference_state存在且候选非空。 - 修复:把比较路径的
scoreAndPersistCurrentEvidence抽到v9/score-persist.ts。停止 / 闲置keepAnswers=true重放上一份推断;时段选定 / 放宽keepAnswers=false仍写出空答案的inference_state。采用 RPC 校验不放宽。 - 验证:
frontend/tests/rectification-stale-compare-fix-20260907.test.ts(5 条已答探针 STOP 后仍在 receipt 里,posterior 不是裸支持度;窗口变更后answered_probes为空)。任务书指定 TS 切片 1202/1202。Dockerdatabase-rectification-*.test.ts本机未跑。 - 防复发:STOP / 闲置 / 窗口变更不得再走只写引擎分数、不写
inference_state的裸 persist。 - 相关记录:BUG-577、BUG-578、BUG-579
- 复发自:BUG-579
- 修复版本:待发布(
codex/rectification-stop-rescore-fix-20260907)
BUG-582 | 无框区分题退化成开场采集句
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
persistServerOwnedFocus、spokenCollectFallbackFollowup、spokenFollowupForUser、persistPlanFocus、buildMethodFollowupPlan - 用户现象:健康拒答后助手再问开场那句「从你最容易想起来的一件事开始就好——比如哪年上的大学…」。
- 触发条件:
precision_stage=lagna_frame或观察层给出没有choice_frame的distinguish_candidates;计划仍把它当下一问。 - 根因:无框区分题被
persistSpokenChoiceFallback改写成采集,domain=other、source=method_coverage、method_id=dasha_events,spokenFollowupForUser判成开场 GENERIC。工具层?? GENERIC_COLLECT_QUESTION与persistPlanFocus在 persist 失败后再转一次口述采集是同一条路。与 BUG-558 同一「死问题」现象、不同入口。 - 修复:无框 / 文案无效的区分题一律
skipped,不得改写成采集。计划层记dropped_probes(reason=frameless_distinguish)后继续带年月采集 → 职业 → holdout。GENERIC 只留给真正的collect:other开场。holdout 排到 dated 与职业之后。 - 验证:
frontend/tests/rectification-server-focus.test.ts、frontend/tests/rectification-collect-direction-20260904.test.ts、frontend/tests/agent-voice-copy-contract.test.ts、frontend/tests/rectification-walkthrough-polish.test.ts。 - 防复发:区分题 persist 失败不得
spokenCollectFallbackFollowup;工具层不得?? GENERIC_COLLECT_QUESTION。 - 相关记录:BUG-558、BUG-580
- 复发自:BUG-558
- 修复版本:待发布(
codex/rectification-stop-rescore-fix-20260907)
BUG-583 | 区间交付卡判空顺序使 tsc 失败,staging 无法部署
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-07
- 影响面:
rectification-agentic-chat.tsx区间卡渲染、采用后首轮旁白、RectificationRangeDelivery「再补一件经历」 - 用户现象:staging 门禁与
next build因 TypeScript 失败,BUG-581~582 的修复无法部署。采用后看不到承诺过的预测窗口。点「再补一件经历」后卡片消失、输入框上方没有下一句。 - 触发条件:
ab57d03f合入后跑tsc --noEmit;或交付轮点「再补一件经历」;或采用后进入核对。 - 根因:渲染条件先读
candidateResult.resultId再写candidateResult &&,收窄发生在属性读取之后。prospectiveWindowsNarration从交付旁白撤掉后没有接到采用后首轮。onContinue只改本地 dismiss,不给采集提示。步骤条类名rectification-step-state已由 BUG-575 禁止,不能靠加回步骤条过关。 - 修复:判空提到
dismissedRangeResultId之前。persistNextInterviewIfIdle在已采用时把prospective_probes接到首轮旁白末尾;deferredCareerWindow仍留在交付旁白。收起卡片后用RangeDeliveryContinueHint显示「第 1 步·收集经历」加continueCollectFallback,不用被禁的步骤条类名。 - 验证:
frontend/tests/rectification-range-delivery-20260907.test.ts;tsc --noEmit原文见PROGRESS-rectification-range-delivery-fix-20260907.md。 - 防复发:区间卡渲染源码必须先出现
candidateResult &&再读resultId。采用后首轮 idle persist 必须调用appendPostAdoptProspectiveWindows。聊天源码仍不得出现rectification-step-state。 - 相关记录:BUG-575、BUG-581、BUG-582
- 复发自:无(区间卡
ab57d03f引入;任务书 4.2 验收未过) - 修复版本:待发布(
codex/rectification-range-delivery-fix-20260907)
BUG-584 | 同一轮采集旁白说两遍
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-08
- 影响面:
applyStepAnswerChunk、KEEP_LIVE_SPOKEN_TOOLS、publishSpokenStep、POST /api/rectification/agent - 用户现象:每一轮采集助手气泡里有两段近似旁白。第一段已经承接用户刚说的事,第二段换个说法再讲一遍。
- 触发条件:模型在
rectification-set-focus前写出正文,拿到工具结果后再写一段。 - 根因:BUG-533 让 set-focus 不撤回已直播正文。set-focus 之后下一个 step 的正文仍按
shouldPublishStepText发布;publishSpokenStep把两段无分隔拼进assistant_message。 - 修复:set-focus 结果后置
stemAttached。本轮已经发布过正文则丢掉后一段;尚未发布则仍放行开场问候。set-focus 调用时把未直播的中文半句publish出去,英文规划稿仍 discard。 - 验证:
frontend/tests/rectification-step-answer.test.ts;frontend/tests/rectification-v9-agent.test.ts文本→set-focus→文本只保留第一段。 - 防复发:set-focus 之后不得再拼接第二段正文。开场仍允许「先问候、后 set-focus」或「只在 set-focus 后说一句」。不得把这个例外扩到 record/compare/diagnostics。
- 相关记录:BUG-533
- 复发自:无(533 的放行例外把第二段也留下了)
- 修复版本:待发布(
codex/rectification-duplicate-narration-20260907)
BUG-585 | 题干在正文和问题块各出现一次
- 状态:resolved
- 首次发现:2026-09-07
- 最近更新:2026-09-08
- 影响面:
stripQuestionSentences、attachQuestionsToTurns、runV9AgentTurn、RECTIFICATION_USER_COPY.collectHandoff - 用户现象:旁白末尾已经用自然语言问了下一题,问题块再显示同一句服务端题干。
- 触发条件:本轮 set-focus 成功,模型把问句写进正文;
detachCollectSpokenAssistantText只剥逐字后缀。 - 根因:不让模型提问的约束只在提示词。BUG-488 防复发写「不得同时让模型提问又拼接同一句服务端题干」,
d9404976把题干改成消息内问题块后没有代码守卫。collectSpokenEmitted是从未使用的常量。 - 修复:有本轮 focus 时,
answer.composed前按句删掉与题干相同、题干前 12 字开头、或以问号结尾的句子;剪空则用collectHandoff。变化时answer.delta replace:true。历史回看同样走stripQuestionSentences。删除collectSpokenEmitted。 - 验证:
frontend/tests/rectification-collect-prompt.test.ts;frontend/tests/rectification-v9-agent.test.ts;frontend/tests/agent-voice-copy-contract.test.ts。 - 防复发:有 focus 的已结算助手正文不得再出现以问号结尾的句子。不得靠提示词单独保证模型不问。不得恢复把题干拼进
assistant_message。 - 相关记录:BUG-488、BUG-461、BUG-471、
d9404976 - 复发自:BUG-488(防线只在提示词)
- 修复版本:待发布(
codex/rectification-duplicate-narration-20260907)
BUG-586 | 七领域问完落到补问兜底句,职业题被覆盖、区间卡出不来
- 状态:mitigated
- 首次发现:2026-09-07
- 最近更新:2026-09-08
- 影响面:
USER_COLLECT_QUESTION、spokenFollowupForUser、active-focus 承接 followup、rectification-set-focus兜底、persistNextInterviewIfIdle - 用户现象:学业、感情、事业、家人、财务、迁居、健康都问过或拒过后,助手不问「你平时主要做什么工作?」,也不出区间交付卡,只剩一句「也可以再说一件你记得大概时间的事」。
- 触发条件:七个带年月领域已覆盖或拒答,职业尚未问完或已有
collect:other:*焦点挂着;同轮可能两次 set-focus。 - 根因:三条活路已由代码确认。职业焦点落库时
persistableFocusDomain("occupation")仍写other(表约束没有 occupation);承接 followup 用focus.targetDomain重建成domain=other,稳定 id 变成collect:other:*并 supersede 职业题。set-focus 两次invalid_spoken_prompt后查USER_COLLECT_QUESTION[domain],domain=other把那句兜底写进焦点。collectQuestionDomain把空领域归为 other。挂着的collect:other:*让persistNextInterviewIfIdle见 active focus 提前返回,出卡路径跑不到。同轮第二次 set-focus 覆盖职业题的具体 receipt 链(任务书 2.2)未从事故 Case 证实,不得写成已闭环。 - 修复:删除
USER_COLLECT_QUESTION.other与 retry。非开场domain=other的口述题返回null。承接 followup 从collect:<domain>:解析领域。set-focus 兜底只允许表内真实领域,否则不落地。同轮已成功的 set-focus 第二次返回idempotent: true。账本已有带年月事件时,闲置路径把存量collect:other:*标skipped。采用跳过持久化时写terminalNote,让 agent-run 能写 exhaustion 闸门轮。 - 验证:
frontend/tests/rectification-other-collect-fallback-20260908.test.ts;frontend/tests/rectification-exhaustion-exit-20260906.test.ts覆盖事故形状出卡;frontendtsc --noEmit0;指定 TS 切片 983/0;grep -rn "也可以再说一件" src tests为空。未拿事故 Case receipt,2.2 仍 investigating。 - 防复发:源码与测试不得再出现「也可以再说一件」整句。
USER_COLLECT_QUESTION不得再有other键。职业承接 id 必须仍是collect:occupation:*。set-focus 不得在domain=other时查表落地。闲置路径必须跳过孤儿collect:other:*。 - 相关记录:BUG-558、BUG-580、BUG-582
- 复发自:BUG-558(同现象、不同来路;558 只堵了穷尽采集
otherCollectFollowup) - 修复版本:待发布(
codex/rectification-other-collect-fallback-20260908)
BUG-587 | 新证据重算后已答题无法重放,范围弹回搜索窗口
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
buildInferenceState、buildCaseInferenceState、分盘对照semantic_key、证据轮 compare 后的inference_state - 用户现象:答完多张对照卡后范围已经收到大约 8 分钟,再补一件带年月经历,交付时范围又回到大约 29 分钟,候选分数跟引擎 prior 一样。
- 触发条件:已有 5 条点选答案;新证据触发 compare,引擎 9 个分钟换掉其中 2 个;本轮
probes按 asked keys 排除已答题。 - 根因:BUG-559 让引擎和对照包不再生成已答题。第一次重算还能从
previous.probes找到定义,但新状态的probes不携带这些题。第二次重算时previous.probes也没有了,if (!probe) continue把 5 条答案变成死账本,rounds: []、posterior === prior。分盘风格题的semantic_key还把分钟列表编进去,候选集一变 key 也对不上。BUG-581 的keepAnswers修的是停止路径同一根因的另一半。 - 修复:已答题的定义随状态携带(
carried: true),重放按候选分钟,新分钟落中性。分盘semantic_key改为星座分区,例如varga.d9.天秤座|天蝎座|射手座,不再内嵌HH:MM。candidate_split_hash仍按当前候选集计算。 - 验证:
frontend/tests/rectification-probe-replay-loss-20260908.test.ts(连续两次重算仍 5 轮、未变分钟 delta 不变、新分钟 delta 0);指定 TS 切片 1000/0;tsc --noEmit0。 - 防复发:候选集变化后
answered_probes对应的题目定义必须仍在state.probes。新生成的分盘semantic_key不得匹配\d\d:\d\d。nextProbe不得把携带题再问一遍。 - 相关记录:BUG-559、BUG-581、BUG-588、BUG-589、BUG-594
- 复发自:BUG-559(去重删生成侧定义);BUG-581(停止路径已修,证据路径未修)
- 修复版本:待提交(
codex/rectification-probe-replay-loss-20260908)
BUG-588 | 证据轮旁白不报范围变化
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
runV9AgentTurn证据/比较轮结算、withRangeChangedAfterEvidence - 用户现象:范围从 8 分钟变回 29 分钟时,三轮证据旁白只说「记下了」,用户要到交付卡才看见变宽。后续复发:补经历走
rectification-record-evidence-batch内部重算,旁白仍只说「接下来我们继续」,没有范围句。 - 触发条件:本轮
credible_range变了。最初只在toolsUsed含rectification-compare-candidates时接句;batch 内 compare 不进该集合。 - 根因:点选题由服务端写「范围从 X 收到 Y」;证据轮旁白是模型写的。第一次修复按工具名触发,batch 路径没有 compare 工具名。
- 修复:结算时先按 BUG-585 剪问句,再比较本轮开始时与结算时的
credible_range。变了就把「范围从 A–B 变为 C–D。」接到正文末尾;没变不写。不再看toolsUsed。 - 验证:
frontend/tests/rectification-probe-replay-loss-20260908.test.ts锁结算段不按 compare 工具名触发,batch 形状 04:47–04:59 → 04:47–05:14 仍接范围句;frontend/tests/agent-voice-copy-contract.test.ts收录新句并锁先剪后接。 - 防复发:范围句看
credible_range事实,不看本轮点了哪个工具。变了必须写进落库assistant_message。不得靠提示词让模型自己报。 - 相关记录:BUG-585、BUG-587、BUG-569、BUG-594
- 复发自:无;后续复发见本条 batch 路径(随 BUG-594 同修)
- 修复版本:
df182c16;batch 路径随 BUG-594 同修
BUG-589 | 交付/选择卡在最后一题出现前可能闪现
- 状态:investigating
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
showSelectionCards、loadCaseSnapshot - 用户现象:最后一题的选择卡出现前一瞬,时间选择卡片弹出又消失。
- 触发条件:用户报告发生在证据轮之后、下一张对照卡出现之前。按导出最终状态
can_adopt=false,正常结算后不应出卡。 - 根因:未证实。可能是
loadCaseSnapshot先写入candidateResult,问题合并进消息是另一次渲染,中间interviewQuestionBlocksAdoptOffer看不到新问题。 - 修复:源码加守卫:
showSelectionCards要求!busy && caseSnapshotLoaded;快照与问题合并放进同一次startTransition。不得声称已复现闪现。 - 验证:
frontend/tests/rectification-probe-replay-loss-20260908.test.ts、frontend/tests/rectification-agentic-entry.test.ts源码锁。无复现实验。 - 防复发:选择卡不得在
busy或快照未加载时出现。 - 相关记录:BUG-587
- 复发自:无
- 修复版本:待提交(守卫已加,现象仍 investigating)
BUG-590 | 范围已收到 2 分钟、决策已是采用,却再问答过的财务采集题
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
method-followup.ts职业覆盖后的 yearless→采集分支、shouldSkipFollowupPersist、holdoutFollowupFor - 用户现象:范围已经收到大约 2 分钟,决策已是采用,输入框上方却再问一遍已经答过的财务采集题,区间交付卡出不来。
- 触发条件:七个带年月领域和职业都覆盖或拒答;yearless 目录里还有无年份分盘对照(本例 D11 财务);闲置路径带着
contrastPacket持久化下一问;财务账本已有带年月确认事件且该领域采集焦点已resolved。 - 根因:职业覆盖后的 yearless→采集分支只取
yearlessDiscriminators[0],只跳过拒答,不看该领域是否已有带年月事件、是否已经问过collect:<domain>:*。财务已覆盖仍被改写成 dated collect。shouldSkipFollowupPersist只要isRemainingEvidenceCollect为真就不跳过,采用被这道题挡住。holdout 的occupied不认health/health_pressure同义,答过健康后仍可能再问健康线。 - 修复:遍历全部 yearless 对照,跳过拒答、已确认(含健康同义)和已问过采集的领域,全部跳过则不再派采集。采用且
probe_pool_exhausted时,剩余采集只计「该领域没有确认事件且未拒答」的题,否则跳过持久化并写terminalNote。holdoutoccupied把health与health_pressure归并。不动采用门、确认门、Skill 版本(仍 10.0.15)。 - 验证:
frontend/tests/rectification-occupation-coverage-exit.test.ts事故形状:yearless→采集next_followup === null;迁居未覆盖仍问迁居;idle persistterminalNote: true且 0 次 set-focus;agent-run 闸门轮与canShowRectificationSelectionCards;holdout 健康同义。frontend/tests/rectification-server-focus.test.ts见 BUG-591。frontendtsc --noEmit0;指定 TS 切片 1006/0。 - 防复发:yearless→采集不得只取第一道分盘题。已覆盖或已问过的领域不得再派 dated collect。采用穷尽时不得把已覆盖领域的采集当剩余采集挡住出卡。holdout occupied 必须与
hasConfirmedHealth同口径。USER_COLLECT_QUESTION.other不得回潮。 - 相关记录:BUG-586、BUG-587、BUG-591
- 复发自:BUG-586(同一「职业覆盖后多问一题」现象,不同分支);BUG-587(收敛成功后才暴露)
- 修复版本:待提交(
codex/rectification-covered-domain-recollect-20260908)
BUG-591 | 非 retry 采集焦点撞 id 时无条件加 :next 再插一次
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
server-focus.ts::persistCollectFocus、persistFocusAfterChoice - 用户现象:财务采集题已经问过并关闭,下一轮仍以
collect:finance:collect_method_evidence:next落库,题干与已答财务题相同。 - 触发条件:计划层再次派出同一领域的非
collect_retry采集题;set_agentic_rectification_conversation_focus报focus_idempotency_conflict。 - 根因:
75fc456d(BUG-462 附带)在撞 id 时一律改${questionId}:next再插,原本给穷尽采集撞开场题号用。BUG-586 删掉 other 穷尽采集后,这条路径只剩副作用:已覆盖领域只要被计划层重复派出,就能以:next落地。 - 修复:撞 id 时仅
followup.collect_retry === true才用:next;否则返回duplicate_focus、不插入。persistFocusAfterChoice把duplicate_focus当已处理、不再读 dossier 重试。 - 验证:
frontend/tests/rectification-server-focus.test.ts:非 retry 冲突status: "duplicate_focus"且 RPC 一次;collect_retry: true仍以:next落地。 - 防复发:非 retry 采集撞 id 不得再加
:next。retry 采集仍须能以:next落地。 - 相关记录:BUG-462、BUG-586、BUG-590
- 复发自:BUG-462(
:next重试本为穷尽采集撞开场题号) - 修复版本:待提交(
codex/rectification-covered-domain-recollect-20260908)
BUG-592 | 答完「2023 年 5 月前后」又问「2023 年前后」(同域同年点选题)
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
inspectDiscriminatorProbes、rankRenderableDiscriminators、existenceProbeAsked/sameYearProbeAsked、点选后decideAfterInferenceChange - 用户现象:已经答过某年某月「有没有入职/换工作」,下一张点选卡又问同一领域同一年的整年题,题干几乎同一句。
- 触发条件:同一次候选比较生成的
inference_state.probes里同时有domain.YYYY.MM.*和domain.YYYY.*;点选不重跑引擎,按信息量从高到低问。 - 根因:BUG-559 引擎侧只在重新生成时按
asked_probe_keys屏蔽同年与相邻年;点选下一题走 TS 对照包。inspectDiscriminatorProbes只认精确semantic_key/ hash /probe_id,没有领域+年份规则。rankRenderableDiscriminators虽调existenceProbeAsked,结果只是把asked=true交给rankDiscriminatorScore降权,不排除。旧验证只锁了引擎重生成和existenceProbeAsked返回值。 - 修复:同领域同年份硬排除(
dropped_probes(reason="same_year_asked")),year=0的分盘风格题不动。相邻年(±1)继续降权、不硬排除。existenceProbeAsked拆出sameYearProbeAsked。引擎asked_probe_keys、SCORE_DELTA、四选项合同、Skill 版本(仍 10.0.15)不动。 - 验证:
frontend/tests/rectification-probe-year-dedupe-20260906.test.ts:答完 5 月后不得再问同年 activation、相邻年 2024 仍可问、buildMethodFollowupPlan同口径、事故五题回放第五题不再是同年整年题。frontend/tests/rectification-inference-machine.test.ts锁核心 identity 不去重、inspect 硬排除。tests/test_rectification_event_probes.py不改、须仍绿。 - 防复发:同一份 probes 列表里,同领域同年份答过一道就不得再问第二道。不得只靠降权。相邻年不得被硬排除。点选路径必须走
inspectDiscriminatorProbes/rankRenderableDiscriminators的同年硬排除,不能只依赖引擎重生成。 - 相关记录:BUG-559
- 复发自:BUG-559(引擎侧只管重生成;TS 侧只降权)
- 修复版本:
8ae17a26
BUG-593 | 交付轮验证报告引用推断前的引擎事实
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-10
- 影响面:
latestResultToolProjection、buildSkillVerificationPacket、jyotish-birth-time-rectification@10.0.16 - 用户现象:区间卡已是约 3 分钟可信窗,交付轮正文却写宽度约半小时、双轨偏向已淘汰分钟、某候选 D10 升成下一座。
- 触发条件:推断层已把候选收到短区间并出交付卡;引擎
tied_minute_count/dasha_agreement仍按全部候选计算;window_scan换升在代表分钟之后。 - 根因:结果投影把
candidates换成推断层,但报告仍喂引擎indistinguishable_width_minutes和dasha_agreement。分盘上升没有按候选分钟给出星座,模型按换升时刻自己算。 - 修复:有推断层时报告宽度用
credible_range含两端分钟数;双轨在 active 候选内重算,引擎原值只留dasha_agreement_pre_inference供审计。顶层宽度改名engine_indistinguishable_width_minutes。报告给出sign_by_candidate,与分歧面板共用换升函数。确认门不改。Skill 10.0.16 要求只抄该表。 补正(2026-09-10):代码里已有dasha_agreement_pre_inference。BUG-638 修的是引擎原值仍按网格分钟判冲突、与报告口径不一致,不是这个字段缺失。 - 验证:
frontend/tests/rectification-delivery-report-facts.test.ts:推断窗 04:51–04:53、引擎宽 29、双轨 top 为已淘汰分钟时,报告width_minutes === 3且正文不含 29 / 已淘汰分钟;D10 05:00 换升时sign_by_candidate["04:53"].d10 === "巨蟹座"。agent-voice-copy-contract/skill-registry锁 10.0.16 新句。 - 防复发:交付轮宽度、双轨、分盘星座必须来自推断层报告字段,不得再把引擎原跨度或换升时刻交给模型自算。
- 相关记录:BUG-545、BUG-568、BUG-290、BUG-638
- 复发自:BUG-290(宽度字段进投影后未随推断层更新);BUG-568(区间读盘,交付正文仍用开工跨度)
- 修复版本:
06e44104
BUG-594 | 重算后新分钟不继承已答题结论,范围从 13 分钟回弹到 28 分钟
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
apply-probe-outcome.ts::directionFor、buildInferenceState携带题重放、window_scan.transitions、证据轮范围句 - 用户现象:5 道点选题把范围收到 04:47–04:59 后,再补迁居经历,交付卡变成 04:47–05:14。新进候选的 05:14 在已答题上全是中性、零冲突,把右端拉宽。
- 触发条件:引擎重算换入不在旧
supports/conflicts里的分钟;已答题按 BUG-587 携带重放。 - 根因:BUG-587 任务书决策 2 规定新分钟一律
neutral、不做邻近插值。directionFor按候选 id 查成员,查不到就记 0。分盘题本可按换升星座判定,大运/激活题的 outcome 本是连续分钟段,结论可以确定推出。 - 修复:推翻该中性决策。分盘风格题用
signFromTransitions按已答星座判 support/conflict,缺 transitions 则仍中性。其余题:新分钟落在某 outcome 集合最小与最大分钟之间、且两侧最近旧分钟同属该集合时继承该集合,否则中性。携带题写入outcome_by_minute。buildCaseInferenceState把window_scan.transitions传进核心层。范围句改看credible_range事实(见 BUG-588 batch 路径)。淘汰阈值、SCORE_DELTA、四选项合同、Skill 版本不动。 - 验证:
frontend/tests/rectification-probe-replay-loss-20260908.test.tsBUG-594 (a)–(d):05:14 重放后strong_conflict_count ≥ 3且eliminated,范围仍 04:47–04:59;两集合之间的分钟保持中性;无 transitions 时分盘题保持中性;连续换入被覆盖的新分钟不把范围拉宽。 - 防复发:新分钟不得只因不在旧名单里就零冲突存活。分盘题必须能用换升星座继承;大运/激活题必须能用连续分钟段继承。不得再用「不做插值」挡住可确定推出的结论。
- 相关记录:BUG-587、BUG-588
- 复发自:BUG-587(任务书决策 2 定错;重放定义已在,结论层把新分钟放生)
- 修复版本:待发布(
codex/rectification-new-minute-inherit-20260908)
BUG-595 | 交付面四个按钮、输入框「先这样」和采用状态条把界面撑乱
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
rectification-range-delivery.tsx、rectification-agentic-chat.tsx、user-copy.ts、divergence-panel.ts、Skill 10.0.17 - 用户现象:区间卡下面四个动作(分歧两列、「都不像」「说不好」、「按这个范围用」「再补一件经历」);输入框上方一直挂「先这样,先看当前范围」;采用后另有一条状态条。交付正文是长篇八法 markdown,表格在气泡里摊成纯文本。
- 触发条件:访谈收口出区间交付卡;采用代表性分钟。
- 根因:
TASK-rectification-range-delivery-card-20260907.md把交付对象做成「两个动作 + 分歧面板」,DESIGN 又在口述采集上方放先这样。产品决定推翻该设计。 - 修复:卡片改为按候选分钟逐行点选采用。删分歧列、四个按钮、composer「先这样」、composer-meta、采用状态条。交付正文三句;八法报告进默认收起的「查看验证报告」。没有核对题时
postAdoptVerifyDone改成「已采用 HH:MM,新建对话即按这个时间排盘。」Skill 10.0.17。 - 验证:
frontend/tests/rectification-range-delivery-20260907.test.ts行选 DOM;frontend/tests/rectification-delivery-ui-simplify-20260908.test.ts三句旁白、折叠块、composer-wrap 无先这样/状态条。任务书 TS 切片 1039/1039。 - 防复发:composer-wrap 不得再出现
CHOICE_STOP_LABEL或rectification-adopt-status。逐行点选卡已被 BUG-597 的三列对照取代,不得把「一行一个分钟」或「排盘用」标签加回去。 - 相关记录:BUG-544、BUG-545、BUG-558、BUG-583、BUG-597
- 复发自:无
- 修复版本:
7e3b6cdc
BUG-596 | 交付轮连续写出两条助手消息
- 状态:mitigated(触发链仍
investigating) - 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
persistNextInterviewIfIdle、persistExhaustionGateTurn、runV9AgentTurn、finalizeSuccessfulTurnExit、客户端send("opening")、regenerate - 用户现象:职业答完后出现两条助手消息。第一条已写摘要;第二条又把完整八法报告写一遍。中间没有用户输入。
- 触发条件:出牌/穷尽路径
persistNextInterviewIfIdle返回terminalNote后,同一 Case 在无用户消息时再跑一轮 agent。 - 根因:未知。已在四条路上打
rectification_delivery_turn日志。runV9AgentTurn在latestResult.resultId未变的 60 秒窗口内,对无用户消息的第二次运行返回already_delivered,不计费、不写 turn。agent-run 与 turn-exit 对齐:已有askedTurnId时不再persistExhaustionGateTurn。 - 修复:日志 +
already_delivered守卫。 - 验证:
frontend/tests/rectification-delivery-ui-simplify-20260908.test.tsmock 两次连续无消息运行,第二次already_delivered。任务书 TS 切片 1039/1039。 - 防复发:交付后无用户消息不得再开一轮计费 turn。
- 相关记录:BUG-462、BUG-565
- 复发自:无
- 修复版本:
7e3b6cdc
BUG-597 | 交付卡逐行点选看不出候选差别,又像直接给了一个时间
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
rectification-range-delivery.tsx、divergence-panel.ts、refinement_packet.py、event_probes.py、user-copy.ts、Skill 10.0.18 - 用户现象:区间卡每个候选分钟一行。与代表分钟 D9/D10 相同的行右侧是空的;代表分钟预先标成「排盘用」,用户觉得是直接出一个时间,而不是在几个分钟里对照后选择。
- 触发条件:访谈收口出区间交付卡;范围内有两个以上候选分钟。
- 根因:BUG-595 把交付做成「点一行即采用」,右侧只写与代表分钟的分盘差异。产品决定改成并排对照:每列写该分钟自己的相对可能性、性格处事、经历吻合和未来一年窗口。
- 修复:卡片改成一行至多三列,按后验概率排序、按精确 HH:MM 去重。每列由服务端写相对可能性、D9/D10/月宿性格处事、经历强/中/弱计数与最不吻合一件(年-月+领域)、往后 12 个月 1–2 个窗或固定句「未来一年没有明显的窗口」。按钮「更像这个」走现有 accept RPC,选择不计分。同一次 compare 增加
event_dasha_ledger_by_time与prospective_windows_by_time。不预标「排盘用」。Skill 只改references/candidate-comparison.md,bump 10.0.18。 - 验证:
frontend/tests/rectification-range-delivery-20260907.test.ts三列 DOM、「更像这个」、无「排盘用」;frontend/tests/rectification-delivery-ui-simplify-20260908.test.ts三句旁白仍不含「排盘用」;frontend/tests/agent-voice-copy-contract.test.ts锁新文案;tests/test_rectification_v5_services.py::test_compare_returns_ledger_and_windows_by_candidate_time三分钟各一份 ledger/窗口。 - 防复发:交付卡必须是列对照而不是逐行空差异;列文案必须来自服务端类型表与 ledger/窗口映射,不得让模型写。超过三列时必须出现「还有 N 个分钟可能性更低,已并入范围」。没有窗口时必须写固定句,不得空白。
- 相关记录:BUG-595、BUG-583、BUG-568
- 复发自:BUG-595(逐行卡上线后用户看不出为何要选另一分钟)
- 修复版本:待发布
BUG-598 | 点选题成年下限只在 followup 生效,inspect 回退会漏掉
- 状态:resolved
- 首次发现:2026-09-08
- 最近更新:2026-09-08
- 影响面:
inspectDiscriminatorProbes、adult-floor.ts、decideAfterInferenceChange、answer-choice.ts - 用户现象:已经按年龄下限滤掉的迁居等点选题,在答完上一题后的 inspect 回退路径里又出现。
- 触发条件:
decideAfterInferenceChange走matched ?? inspected.selected;method-followup已按DOMAIN_AGE_LO过滤,但 inspect 没有出生日期。 - 根因:
probeBelowAdultFloor只在method-followup.ts三处生效。inspectDiscriminatorProbes不接收birthDate,回退选中的探针可以低于成年下限。 - 修复:抽出共享
adult-floor.ts。inspectDiscriminatorProbes接收birthDate,低于下限写入dropped_probes(reason="below_adult_floor")。decideAfterInferenceChange与答题持久化从出生快照传入该日期。采集题「没有」按钮本单不做,等产品决定。 - 验证:
frontend/tests/rectification-candidate-contrast-packet.test.ts:出生日期 2000-01-01 时 2015 迁居题被丢掉;1996-01-01 时保留。按年份计算:2015 低于下限当且仅当出生年 > 1997;1997 年生在 2015 年满 18 岁,不越线。 - 防复发:inspect 与 followup 必须共用同一套成年下限。点选回退不得在没有
birthDate时把已过滤的领域年再选出来。 - 相关记录:BUG-597
- 复发自:无
- 修复版本:待发布
BUG-599 | 回访登录自动打开生时校正并挤歪首页
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:登录后首页、
resolveBootstrapSessionSelection、生时校正 Case open、.welcome布局 - 用户现象:以前登录过的账户一进首页,主栏内容挤到左边;用户未点任何入口就出现「校正服务暂时不可用」。
- 触发条件:登录落到
/(没有?c=)。会话列表第一项是历史生时校正;该会话列表不带消息,校正会话又跳过消息水合。 - 根因:bootstrap 把
sessions[0]当成当前会话。校正会话会在 prepare 阶段自动POST /api/rectification/cases/open。打开失败时 catch-all 文案是「校正服务暂时不可用」,并画在首页卡片下方。同时.conversation.is-empty要求messagesHydrated,校正会话保持 false,首页 1040px 容器没有水平居中。 - 修复:仅当地址栏
?c=指向该校正会话时才自动打开。默认/落地改到空咨询会话(没有则新建)。.welcome/.starter-list增加margin-inline: auto;空校正会话在未打开 surface 时也使用is-empty。 - 验证:
frontend/tests/home-bootstrap-reveal.test.ts、frontend/tests/rectification-surface-contract.test.ts、frontend/tests/session-conversation-layout.test.ts。 - 防复发:裸
/不得因最近一条历史是生时校正就调用 Case open;首页居中不得依赖校正会话的messagesHydrated。 - 相关记录:BUG-027、BUG-034
- 复发自:无
- 修复版本:待发布
BUG-600 | 新用户保存账户资料返回 PATCH /api/account 500
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
PATCH /api/account、staging 自托管 PostgreSQL、首次称呼/出生资料保存 - 用户现象:新账号登录后保存称呼或出生资料,接口返回
500 {"error":"暂时无法保存账户资料"}。 - 触发条件:self-hosted 新用户已有 trigger 创建的
profiles行,前端persistProfile提交默认ayanamsa。 - 根因:
20260903010000_profile_ayanamsa.sql只把ayanamsa的UPDATE授给authenticated。账户 PATCH 走service_role;PostgreSQL 对未授权列返回42501 permission denied for table profiles。该错误不含column,现有缺列回退不会去掉ayanamsa,因此首次保存直接 500。无档案 INSERT 不受影响,所以已有 trigger 建档的新用户会踩中。 - 修复:新增迁移为
service_role补齐ayanamsa的列级SELECT / INSERT / UPDATE。 - 验证:本地 PostgreSQL 在补授权前,含
ayanamsa的service_roleUPDATE 返回42501,去掉该列后 UPDATE 成功;补授权后含ayanamsa的 UPDATE 返回一行。frontend/tests/profile-persistence.test.ts锁定新旧迁移的授权差。 - 防复发:账户 PATCH 新增列必须同时授予
authenticated自助保存和service_role并发读写;缺列回退不得把 table 级42501当成可忽略的缺列。 - 相关记录:BUG-039、BUG-005
- 复发自:BUG-039(全球地点列漏授 service_role 的同类权限缺口)
- 修复版本:迁移
20260909010000,待发布
BUG-601 | 报告生成进度后端已产出,前端解析后一字未渲染,8 分钟只见转圈
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
/reports/[reportId]生成等待屏、personal-report-page.tsx、personal-report-route-core.ts - 用户现象:生成个人报告后停在等待页,屏幕上只有一个
InlineSpinner、一句固定文案「正在生成报告,大约 10–30 秒」和「已等待 X 分 Y 秒」。实际耗时以分钟计,轮询预算 8 分钟。用户无从判断是在推进还是已经卡死,普遍在三分钟左右刷新或离开。 - 触发条件:任何一次个人报告生成,必现。
- 根因:两段断链。其一,
personal-report-page.tsx的classifyReportEnvelope已经把progressPercent/progressPhase解析进generating状态对象(原第 97–102 行),但该分支的渲染(原第 231–246 行)完全没有引用这两个字段,等待屏因此与后端进度无关。其二,resolveReportRead只在row.status === "failed"时读取分章行,reportView也不含分章字段,所以生成中根本没有任何章节状态离开服务端——即便前端想渲染也无数据可用。worker 侧的阶梯(loading_context10 →generating_report30 →section:<id>30–85 →persisting_report90)一直在正常写库,只是没有读者。 - 修复:分章进度成为等待屏的主体。服务端在
generating与failed两种状态下读取分章行(ready路径不变,不增加查询);新增personal-report-progress.ts把行状态映射为只读的四态done / failed / writing / waiting并随reportView下发,只带id与state。等待屏按 phase 分三段:准备阶段保留 spinner,写作阶段换成"已完成 N / M 章"加按章分格的进度条与章节清单,收尾阶段回到 spinner。停滞满 90 秒(REPORT_PROGRESS_STALL_MS)时当前章文案改为「用时较长,仍在写」,该计时复用既有的一秒 tick,未新增定时器或 effect。 - 验证:
frontend/tests/personal-report-progress.test.ts16 项全绿,覆盖:认领态判定为「正在写」而非按完成数顺推(字典序返回时顺推会指向timing而正确答案是marriage)、phase 命名的章已完成、blocked 计入完成数且hasFailure为真、全 ready 与部分 blocked 两种收尾、90 秒停滞文案切换且不出现「第 N 次尝试」、准备/写作/收尾三分支、分章行缺失时回落准备态而非渲染「已完成 0 / 0 章」、面板无 percent/定时器/预计剩余、attemptCount等后台字段不出服务端。全量npm test2929→2945(pass 2887→2903),失败 28 条与基线逐条一致(全部为无 Docker 的数据库/部署套件)。tsc --noEmit0 错,npm run lint0 error。 - 防复发:三条写进
frontend/DESIGN.md§9「报告生成等待态」并由上述测试锁死。其一,进度条按章分格,不得改画百分比条——job 的 percent 在 0→30 与 90→100 是瞬间跳变,线性条会演出后端没做的动作。其二,"正在写哪一章"只能由行状态(pending且已认领)判定,不得由section:<id>的 phase 名或"已完成数 + 1"推导:前者命名的是刚写完的那一章,后者在字典序列表上会指错。其三,界面上不得出现按定时器推进的插值动画或预计剩余时间。 - 相关记录:BUG-043(禁止把后台评分状态渲染成用户需要管理的面板。本处边界不同且不冲突:等待屏是只读的,展示的是用户交付物自身的章节结构,
attemptCount、原始错误码、lease、job id、payload 一律不出服务端)、BUG-576、BUG-574 - 复发自:无
- 修复版本:待发布
BUG-602 | 时间轴宽度按差值算,卡片与报告按含两端算
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
rectification-timeline-scale.ts区间读数widthLabel、frontend/DESIGN.md§10 - 用户现象:同一屏上时间轴写的宽度比三列卡和验证报告少 1 分钟。例如区间 04:51–04:59 条上写「8 分钟」、卡上写「9 分钟」;05:07–05:09 条上写「2 分钟」;整天窗口写「23 小时 59 分」。
- 触发条件:分钟阶段已有
credible_range,或时段阶段轴铺满整天窗口。必现。 - 根因:
widthLabel用timelineDurationLabel(bandEnd - bandStart),少算两端都计入的那一分钟。BUG-593 已把交付报告与三列卡定为含两端分钟数;任务书示例「05:07–05:09 · 2 分钟」是差值口径,首版时间轴照抄了。 - 修复:改为
timelineDurationLabel(bandEnd - bandStart + 1)。整天 00:00–23:59 显示「24 小时」,单分钟显示「1 分钟」。DESIGN.md §10 示例改为「05:07–05:09 · 3 分钟」。 - 验证:
frontend/tests/rectification-timeline-20260909.test.ts(含两端宽度表:04:51–04:59 → 9 分钟、05:07–05:09 → 3 分钟、04:51–04:53 → 3 分钟、单分钟 → 1 分钟、整天 → 24 小时);与frontend/tests/rectification-delivery-report-facts.test.ts的width_minutes === 3同一形状。 - 防复发:时间轴读数必须与报告/卡片同一套含两端分钟数;禁止再把
bandEnd - bandStart当宽度。 - 相关记录:BUG-593
- 复发自:无
- 修复版本:
92e3d5e7
BUG-603 | 被排除的分钟到不了客户端,时间轴空心点画不出来
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
rectification-candidate-result.ts、rectification-timeline-scale.ts、rectification-agentic-chat.tsx的timelineView候选来源 - 用户现象:DESIGN.md §10 写「被排除的点留在原地变空心、不消失」;真实会话里答完一题后那些分钟从条上消失,剩下的全是实心。看不到刚才那一答排掉了哪几分钟。
- 触发条件:分钟阶段已有推断层、至少淘汰过一个候选。必现。
- 根因:
inference-adapter.ts投影只保留status !== "eliminated"的候选;时间轴把candidateResult.candidates.map(time)当点,再按是否落在区间带内判 in/out。到达客户端的点都在带内,全是实心;被淘汰的分钟根本不在列表里。测试里的["out","in","in","in","out"]是合成输入,不是这条数据路径。 - 修复:
RectificationCandidateResult增加只读inferenceMarks: [{time, eliminated}],从decisionReceipt.inference_state.candidates的time/status解析(组件不读原始 receipt)。state = status === "eliminated" ? "out" : "in",不再按点是否落在带内判断。没有inference_state时退回引擎候选全部实心。 - 验证:
frontend/tests/rectification-timeline-20260909.test.ts:推断层 9 个候选、7 个 eliminated、可信区间 04:51–04:53 → 9 个点、7 空心 2 实心;再淘汰 1 个 → 8 空心 1 实心,key 不变。frontend/tests/rectification-candidate-result.test.ts:有inference_state时解析出全集,无则[]。聊天源码合同:timelineView读inferenceMarks,不读decisionReceipt。 - 防复发:时间轴标记必须来自推断层候选全集;禁止再用引擎 active 投影或带内位置当 in/out。空心/实心只看
status === "eliminated"。 - 相关记录:BUG-560、DESIGN.md §10
- 复发自:无
- 修复版本:
92e3d5e7
BUG-604 | 开场不讲做法、一次只收一件,同一批经历被拆成多轮计费
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
buildOpeningBrief、GENERIC_COLLECT_QUESTION、jyotish-birth-time-rectification@10.0.19OpeningPolicy、口述开场正文 - 用户现象:开场不说当前窗口和最后会给什么,也不点出可以讲哪些方面;题干只请人说一件,一条消息里的多件经历被拆成多轮,每轮都要重算。
- 触发条件:新建生时校正、进入开场采集。
- 根因:opening brief 和 Skill OpeningPolicy 禁止领域清单、禁止一次说完;题干带年份例子。产品决定推翻该口径:列领域不列年份,一条消息可以报多件。
- 修复:brief 写入当前搜索窗口与 intake 来源、做法三句要点、六类领域。开场正文不合格时服务端换成模板。首题仍是
collect:other:*,题干改为「先说你最容易想起的一两件,年月大概就行」。批量写入路径仍是rectification-record-evidence-batch。Skill 10.0.19。只报一件走既有逐领域采集,不插入「还有吗」追问轮。 - 验证:
frontend/tests/rectification-v9-agent.test.ts(opening brief、落库正文替换)、frontend/tests/agent-voice-copy-contract.test.ts(开场模板与六类)、frontend/tests/rectification-spoken-collect.test.ts(三件已确认领域不再被逐个追问;单事件开场下一问为逐领域采集且正文不含「还有吗 / 别的吗」)、frontend/tests/rectification-server-focus.test.ts(GENERIC 题干)。 - 防复发:开场落库正文必须 ≤4 句、至少五类领域、不含四位年份。不得要求先准备材料。不得把具体年份写进开场。不得因只报一件就插入追问轮或重复开场邀请。
- 相关记录:BUG-488、BUG-558
- 复发自:无
- 修复版本:
aa7ccb30、1453fb16
BUG-605 | 口述采集只能打字「没有」,没有「记不清」,也不知道可以跳过
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
rectification-agentic-chat.tsxcomposer-wrap、applyCollectFocusDenial、isCollectSkipUtterance、采集回执文案 - 用户现象:采集题只有输入框。想说这方面没有或这题记不清时,不知道可以跳过;打字「没有」才能走拒答。
- 触发条件:
collect_spoken焦点激活且未 busy。 - 根因:BUG-595 删掉输入框上方「先这样」后没有替代采集快捷回答。产品不要示例骨架条,只要「没有 / 记不清」。
- 修复:composer-wrap 内两个 44px 次要按钮,类名
rectification-collect-reply,不复用rectification-step-state。「没有」与打字「没有」同一 POST,走 declined,回执「记下了,这方面先跳过。」「记不清」走 skipped,回执「记下了,这题先放着。」精确这两句不调模型。skipped 领域本会话不再采集;采用后核对仍可碰到 skipped,不碰 declined。 - 验证:
frontend/tests/rectification-spoken-collect.test.ts(按钮只在 collect_spoken、精确「没有 / 记不清」短路分类器、skipped 不再采集、reverse-verify 仍可碰)。 - 防复发:采集快捷回答不得画成「先这样」,不得用
rectification-step-state。不得在回答条预填年份。后续产品否决该芯片,见 BUG-628。 - 相关记录:BUG-546、BUG-618、BUG-628
- 复发自:无
- 修复版本:
aa7ccb30
BUG-606 | 每轮气泡太长:方法句、领先落后、价值评价叠在正文里
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
chat-message-content.tsx、consultation-run-timeline.tsx、composeChoiceNarration、trimEvidenceTurnBody、agent-run开场/证据守卫 - 用户现象:助手气泡里除了记下经历,还写「本轮对照了…」、点选后「这段领先那段落后」,以及「对校正特别有用」之类评价。
- 触发条件:证据轮或点选题答完后看助手正文。
- 根因:方法句渲在气泡;旁白拼接
explainScoreMovement;证据轮只靠提示词,模型仍写三句加评价。 - 修复:气泡不再渲染
vargaSentence,展开的活动时间线保留。点选旁白只有「已记录,范围没变。」或「已记录,范围收到 / 变为 A–B。」证据轮超过两句只留首句,并去掉「很有帮助 / 很有价值 / 很有分量 / 特别有用」。 - 验证:
frontend/tests/rectification-varga-style-copy.test.ts、frontend/tests/rectification-answer-choice.test.ts、frontend/tests/rectification-v9-agent.test.ts、frontend/tests/rectification-collect-prompt.test.ts、frontend/tests/agent-voice-copy-contract.test.ts(机器词表含「领先」「落后」)。 - 防复发:用户可见旁白不得出现「领先」「落后」。证据轮落库不得含价值评价四词。方法句不得回到气泡正文。
- 相关记录:BUG-545、BUG-585
- 复发自:无
- 修复版本:
aa7ccb30
BUG-607 | 长报告页分盘标题下没有图
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
/reports/<id>Markdown 阅读页、personal-report-markdown-view.tsx、scripts/report_chart_block.py、vedic-chart-svg.tsx - 用户现象:长报告里
#### D1 — Rashi Chart(本命盘)、#### D9 — Navamsa(婚盘)、#### Moon Chart(月亮参考盘)以及 Vargas I 下 19 个分盘标题后面全是空白,只有标题没有星盘图。 - 触发条件:打开任意一份 2026-09-06 之后生成的个人长报告详情页。
- 根因:引擎在 Markdown 里内联了南印度 SVG HTML。09-06 把阅读页改成
react-markdown+skipHtml后,内联 HTML 被静默丢弃。skipHtml本身是红线(出生地等用户字符串会进正文),不能靠放开rehype-raw修。 - 修复:引擎在每张 SVG 旁追加
jyotish-chart围栏 JSON;阅读页只认围栏,zod 校验后用北印式菱形组件自绘。导出.md前剥掉围栏,保留 SVG 给外部阅读器。 - 验证:
tests/test_report_chart_block.py7 项(22 块围栏 == 22 个<svg>,度数/星座/逆行与 core_chart 一致,且该文件在CORE_PYTEST_TARGETS);frontend/tests/report-chart-block.test.ts、frontend/tests/personal-report-markdown-view.test.tsx(组件 SVG 有role="img",不含引擎viewBox="0 0 420 480",坏块显示「图盘数据无效」且无<script)。全量npm test2975 tests / 2971 pass / 4 fail,失败 4 条均为 Docker Postgres fixture,与图盘无关。tsc --noEmit0 错;next build --webpack后/仍为 Static。 - 防复发:Markdown 阅读页继续
skipHtml/ 禁止rehype-raw;图盘文本只来自白名单映射。上述测试锁住围栏块数与渲染通路。 - 相关记录:
TASK-report-md-page-20260906.md、TASK-report-chart-render-20260909.md、BUG-616、BUG-617 - 复发自:无(09-06 直渲回归,不是旧 BUG 编号复发)
- 修复版本:待发布
BUG-608 | Venus / Jupiter / Moon 大运被压成一句婚恋机会
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
/api/relationship、/api/consultation_workflow婚恋主题证据、scripts/relationship_analysis.py、mcp_server.py婚恋裁决 - 用户现象:问「我什么时候会结婚」时,只要当前是金星、木星或月亮大运,回答就容易写成笼统的婚恋机会,把心动、成对、领证混成一件事。
- 触发条件:婚恋主题且请求带了大运信息;本仓旧路径原先没把大运传进感情分析,所以上游 09-04 审计的那句假阳性在本仓既不会出现、也不会被拆开。
- 根因:感情分析把 5 宫主 / 7 宫月亮 / 金星大运收成同一条机会话。法律婚、成对、心动没有分层;更细一层的命中还会被升格。
- 修复:大运信息传入感情分析后输出三层
event_class_split。心动层只作观察;法律婚只认大运/小运命中。Punarphoo 只进观察上下文,不改写legal_marriage标签。证据项由新模块组装,不改咨询响应已有键。 - 验证:
tests/test_punarphoo_observation.py、tests/test_relationship_event_class_evidence.py、tests/test_mcp_strict_workflow_relationship.py新增「Punarphoo 不改法律婚标签」;grep 婚姻机会在感情分析与增强大运文件为 0。 - 防复发:咨询契约只增不改。Punarphoo / 心动不得写成会结婚或领证窗口。条件大运不进多系统交叉投票、不进校正打分。
- 相关记录:
TASK-upstream-sync2-20260909.md;上游 09-04 婚恋触发审计 - 复发自:无(上游审计结论在本仓补登记)
- 修复版本:待发布
BUG-609 | 网页咨询婚恋路径喂的是出生大运,事件类 hits 恒空
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
/api/consultation_workflow婚恋主题、_thematic_dasha_info、report_orchestrator婚恋timing - 用户现象:问结婚时机时,心动 / 成对 / 领证拆分没有大运命中;时间标签可能出现
Unknown,并仍写出「缔结或调整伴侣关系的关键期」。 - 触发条件:网页咨询走复用 chart 模块的婚恋主题,chart 模块 dasha 没有
vimshottari_analysis。 - 根因:
_thematic_dasha_info只从 full-reading 的vimshottari_analysis取当前 MD/AD。咨询路径 09-06 起复用 chart 模块后这个字段不存在,于是只剩出生时current_md。BUG-608 把dasha_info接上后,断链第一次变成空 hits。激活句也不看event_class_split。 - 修复:当前 MD/AD/PD 改从
chart.modules.dasha_sub_periods.current读取,缺了再回退vimshottari_analysis;婚恋activation_description按事件类出句;AD 缺失时dasha_period只写 MD。 - 验证:
tests/test_thematic_dasha_info_source.py、tests/test_report_orchestrator_marriage_timing.py、tests/test_relationship_event_class_evidence.py咨询形黄金件。 - 防复发:上述测试列入
CORE_PYTEST_TARGETS。咨询契约只增不改。感情分析模块逻辑未改,只修喂数。 - 相关记录:BUG-608、BUG-555、
TASK-upstream-sync2-fix-20260909.md - 复发自:无(BUG-608 接入后的喂数断链)
- 修复版本:
7d3bb0c5
BUG-610 | 分盘围栏构造失败会让整份长报告 500
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
render_pl9_markdown、/api/reports/<id>/professional-reference、pl9-export - 用户现象:某一张分盘上升星座名不在英文十二星座内时,专业参考 Markdown 与导出一起失败。
- 触发条件:分盘
Ascendant.sign缺失或为中文星座名,围栏build_chart_block/_sign_name抛ValueError。 - 根因:南印度 SVG 在
try内,失败只写「图盘生成失败」;紧接着的围栏构造在try外,一张分盘出错就中断整份报告。 - 修复:围栏失败只丢掉该张围栏、保留 SVG,并打一条不含用户串的 warning。
- 验证:
tests/test_report_chart_block.py中文上升名用例:Markdown 仍含<svg,该处无jyotish-chart围栏。 - 防复发:该文件已在
CORE_PYTEST_TARGETS。失败路径不加围栏,也不再打断报告。 - 相关记录:BUG-607、
TASK-upstream-sync2-fix-20260909.md - 复发自:无(BUG-607 围栏成功路径未覆盖失败分支)
- 修复版本:
7d3bb0c5
BUG-611 | /api/health 的 version 写死旧 Skill 号
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
GET /api/health、chart 成功/回退响应里的version - 用户现象:健康检查报的 Skill 版本与
jyotish_vedic.__version__不一致。 - 触发条件:访问
/api/health或读取 chart 响应version。 - 根因:版本字符串散落三处硬编码,没有从
jyotish_vedic.__version__读。 - 修复:三处改为读取
__version__;回退路径保留-fallback后缀。 - 验证:
tests/test_api_server_security.py::test_health_endpoint_exposes_runtime_accuracy_metadata。 - 防复发:健康检查断言
version == jyotish_api_server.__version__。 - 相关记录:
TASK-upstream-sync2-fix-20260909.md - 复发自:无
- 修复版本:
7d3bb0c5
BUG-612 | 首页「深入看今日」计算完成却没有回答(empty_answer)
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:首页「深入看今日」、
POST /api/consultagentic 路径、canonicalDomainPlan、分段写作composeSection/composeByHeadings - 用户现象:活动区走完「读取分析方法 → 计算本命盘 → 先整理本盘的统一参数 → 接下来分析你的综合 → 用审计表收口后再落到生活」后,正文只有「计算已完成,但这次没有生成回答,本次不会扣点。请再发送一次。」(
empty_answer)。不扣点是对的,但用户拿不到回答。 - 触发条件:已校验星盘账号从首页点「深入看今日」(
theme: timing+entrypoint: daily_starlanguage);计费配置已有chat.standard。staging 观测到该 runretryCount=5、modelFinishReason=tripwire、墙钟约 110s。 - 根因:两层叠在一起。其一,
canonicalDomainPlan在模型显式传了domains时以模型为准,路由选的timing被改成general,分段标题变成本命「综合 / 技法审计表 / 现代生活」,与入口扩展的「今日趋势 / 适合推进 / 避开 / 行动」错位。其二,每个分段maxSteps = 1且工具仍可调,模型在分段里再发起工具调用(想换回timing),一步用完、零正文;四个分段全空后再走 BUG-280 的retryForAnswer,面对的还是同一套错位标题,救不回来。 - 修复:
daily_starlanguage/birth_time_rectification入口钉死路由theme,模型改写只记planOverrideIgnored,不报错不扣步。今日入口改用三节写作计划(今日趋势/适合推进 / 需要避开/一个行动,审计表和边界句折进最后一节)。分段toolChoice: "none";某一节没有新正文时同节再写一次(section-empty-retry),仍空才进入既有answer-retry。 - 验证:
frontend/tests/consultation-agentic-runtime.test.ts新增入口钉死、分段空写重试、全空才answer-retry;consultation-thinking-plan.test.ts锁三节标题与applyThinkingSectionProgress;consultation-workflow-contract.test.ts锁toolChoice: "none"与 entrypoint 接线。定向tsx --test上述文件 +public-thinking+consultation-entrypoint共 119 项通过;tsc --noEmit清洁。staging[agent-observability]已见活动区「综合」、tripwire、110s、retryCount=5。tool.input.domains未出现在该次日志里,记investigating,不挡住决策 1–3(模型改写领域是活动区文案已证明的事实)。 - 防复发:入口请求不得执行模型自选领域。分段写作禁用工具。空切片必须先就地重试,不能把「标题错了所以写不出」交给 BUG-280 的整轮回答重试。
- 相关记录:BUG-280(同一「计算成功、模型没写」出口;本条是新来路)、BUG-613(同一次事故的思考区词渣)、
TASK-consultation-daily-empty-answer-20260909.md - 复发自:BUG-280(现象同类,防线只覆盖「整轮没写」;没有挡住入口领域被改写,也没有挡住分段再调工具)
- 修复版本:待发布
BUG-613 | 咨询思考流按 chunk 过滤,英文被削成词渣
- 状态:resolved(本地修复,待提交与发布)
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
frontend/src/lib/public-thinking.ts、咨询thinking.delta、校正思考映射复用同一 sanitizer - 用户现象:与 BUG-612 同一次「深入看今日」里,思考区出现缺词英文(
The a for 2026-09-09 (, per). The is a - an, not a. … Let me run-jyotish-.),夹杂工具名前缀。 - 触发条件:模型思考以 1–2 个英文词为一个
reasoning-deltachunk 流出;其中部分 chunk 含 ≥4 字母英文且无中文。 - 根因:
sanitizePublicThinkingText按单个 chunk 判定「含 ≥4 字母英文且无中文就丢」。长词所在 chunk 被丢,短词(The / a / for / is)和带中文的 chunk 被放行,拼出来就是词渣。工具名正则只剥了带后缀的完整 id,留下run-jyotish-。 - 修复:改为有状态的按句缓冲(
。!?、换行、英文.!?)。无中文句整句丢;中文句里再删 ≥4 字母英文词和完整工具名/UUID;未闭合的尾巴等下一个 chunk,一次模型循环结束时 flush。咨询流式通道共用同一个 sanitizer 实例。sanitizePublicThinkingText(text)仍是 push+flush,校正映射不用改调用点。 - 验证:
frontend/tests/public-thinking.test.ts把事故英文按 3–6 字符切开喂入,输出不含 ≥4 字母英文词、不含run-jyotish,中文句完整;另锁「未闭合英文不得粘到后一句中文」。既有consultation-agentic-runtime/rectification-v9-stream思考通道回归仍绿。 - 防复发:思考过滤不得按流式 chunk 单独判定英文。工具名要从
run-jyotish前缀整段删,不能只剥后缀。 - 相关记录:BUG-612(同一次事故的空回答)、
TASK-consultation-daily-empty-answer-20260909.md - 复发自:无
- 修复版本:待发布
BUG-614 | 三列卡把「还没对照」显示成 0 和没有窗口
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
scripts/rectification/refinement_packet.py(column_times_for_compare)、frontend/src/lib/rectification-agentic/v9/divergence-panel.ts、rectification-range-delivery.tsx、user-copy.ts - 用户现象:区间卡第 2、3 列写「强相关 0 · 有关联 0 · 弱关联 0」,三列都写「未来一年没有明显的窗口」;相同性格句每列重复一遍。
- 触发条件:推断层后验前三与引擎分数前三不是同一组分钟;客户端按列时间查
event_dasha_ledger_by_time/prospective_windows_by_time找不到键。 - 根因:
column_times_for_compare只取引擎分数前三,卡片列按后验前三投影。缺键时投影把空表当成 0 次吻合、把缺窗口当成「没有窗」。 - 修复:
columns改为引擎candidate_times去重全集(上限 9)。客户端缺键写fit: null/windows: null,灰字「这一分钟还没对照」,不得回退成 0。经历对照改成「N 件里 M 件对得上」;无窗写「未来一年没有明显的时段」。三列共有的性格句只出现在卡顶一次。 - 验证:
tests/test_rectification_v5_services.py::test_compare_ledgers_cover_all_engine_candidate_times;frontend/tests/rectification-range-delivery-20260907.test.ts后验前三与引擎前三不同时fit非 null,缺键 DOM 出现「这一分钟还没对照」、不出现0 · 0 · 0。 - 防复发:
set(ledgers) == set(candidate_times);用户可见文案禁用「强相关 / 有关联 / 弱关联」;列内不得再出现<h3>「经历对照 / 往后 12 个月」。对照盘高亮一旦给.is-changed写规则,该类名必须离开knownUnstyled(见 BUG-622)。 - 相关记录:BUG-597、BUG-615、BUG-622、
TASK-rectification-compare-card-polish-20260909.md - 复发自:BUG-597(三列对照的 by_time 只覆盖引擎前三)
- 修复版本:待发布
BUG-615 | 交付旁白被证据轮裁句裁成一句
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
frontend/src/lib/rectification-agentic/v9/collect-prompt.ts、agent-run.ts - 用户现象:交付轮助手正文只剩「校正到这里可以收口了。」一类单句,BUG-595 定的三句(范围、经历与吻合率、边界句)被裁掉。
- 触发条件:访谈收口出区间卡;该轮
action === "evidence",模型写了三句以上。 - 根因:BUG-606 的
trimEvidenceTurnBody(超过两句只留首句)对所有 evidence 轮生效。交付也走 evidence 轮。 - 修复:先跑
persistNextInterviewIfIdle。仍有下一问时继续裁到一句;该函数返回terminalNote时保留 ≤3 句。裁剪不得发生在 idle 之前,否则交付四句会先被裁掉。裁完与流式正文不同时发answer.delta replace,气泡跟落库同一段。 - 验证:
frontend/tests/rectification-v9-agent.test.ts交付轮模型写四句 → 落库三句;普通证据轮三句 → 一句。frontend/tests/rectification-collect-prompt.test.ts锁trimSpokenTurnForInterview。 - 防复发:
agent-run必须在 idle 之后调用trimSpokenTurnForInterview(answerText, interviewIdle?.terminalNote === true)。不得把交付轮重新裁成一句。 - 相关记录:BUG-606、BUG-595、BUG-614、
TASK-rectification-compare-card-polish-20260909.md - 复发自:BUG-606(证据轮裁句没有把交付轮排除)
- 修复版本:待发布
BUG-616 | 报告页 22 张北印盘叠在同一位置
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
/personal-reportMarkdown 阅读页、personal-report-markdown-view.tsx、globals.css、report-chart-grid-rehype.ts - 用户现象:「Birth Chart / Vargas」里 D1 / D9 / Moon 三张盘叠在同一块;Vargas I 的 19 个
####标题挤在左上角,19 张盘全部叠在一起,「Shodashvarga 星座总表」和后面的表也压在盘上。 - 触发条件:打开含
jyotish-chart围栏的个人长报告详情页,宽于 720px 的桌面视口。 - 根因:BUG-607 用浮动 +
margin-left: -50%在扁平 Markdown 兄弟节点上凑两栏。负 margin 让每个 figure 的 margin box 宽度为 0,浮动算法认为它不占横向空间,连续多对标题+盘必然重叠。renderToStaticMarkup只看 DOM,看不见布局,所以当时的 golden 全绿。 - 修复:删掉
.personal-report-chart-*上的 float / 负 margin。rehype 插件把「h4(D… / Moon Chart)+ 可选一段说明 +pre > code.language-jyotish-chart」包进.personal-report-chart-card,相邻卡片再包进既有的.personal-report-chart-grid(单张加is-single)。不放开skipHtml,不引入rehype-raw。 - 验证:
frontend/tests/personal-report-markdown-view.test.tsx:3 对连续标题+围栏(第 3 对夹一段p)得到 1 个 grid / 3 张卡,卡内顺序为h4→(p)→figure,后面的普通h4不在卡内;单对带is-single;golden 22 张figure全在卡内;globals.css中同时含personal-report-chart与float:/margin-left: -50%的规则为 0。 - 防复发:布局合同走 DOM 结构(grid/card 包裹)和 CSS 禁令,不只看「有没有
<svg>」。 - 相关记录:BUG-607、
TASK-report-chart-layout-fix-20260909.md - 修复版本:
87daffe2
BUG-617 | 滚动报告时整篇 Markdown 卸载重建,页面卡顿
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
personal-report-markdown-view.tsx目录高亮与文章树 - 用户现象:长报告页(尤其 Vargas I 19 张盘揭开后)一滚动就明显卡,像整页在闪。
- 触发条件:打开含星盘围栏的长报告,滚动使目录高亮从一个
h2/h3切到下一个。 - 根因:
activeId放在PersonalReportMarkdownView顶层,IntersectionObserver 每次切换都setActiveId。markdownComponents()每渲染新建一套h2/h3/h4/pre函数;React 按元素 type 引用比较,type 变了就卸载重建整棵子树。09-06 Markdown 直渲时已是这个结构,当时没有 SVG,代价没被感知;22 张盘进来后每次滚过一个标题就销毁再创建全部多边形和<text>。 - 修复:目录高亮下沉到
ReportToc;PersonalReportMarkdownView不再持有滚动态。lead 与LazyMarkdownSection的renderMarkdown结果整树useMemo,LazyMarkdownSection本身memo()。 - 验证:源码合同:
renderMarkdown(只在useMemo内调用;PersonalReportMarkdownView函数体没有useState。本仓测试是renderToStaticMarkup,数不了重渲染次数;真机条目写在docs/testing/report-chart-render-20260909.md第 7 节。 - 防复发:文章树不得订阅目录高亮 state。
components映射可以每次renderMarkdown新建(id 去重 cursor 需要),但调用点必须被useMemo包住。 - 相关记录:BUG-616、
TASK-report-md-page-20260906.md、TASK-report-chart-layout-fix-20260909.md - 修复版本:
87daffe2
BUG-618 | 采集题「没有 / 记不清」浮在输入框上方,离问题太远
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
rectification-agentic-chat.tsx口述采集快捷回答、globals.css.rectification-collect-replies - 用户现象:口述采集时,「没有」「记不清」两个按钮贴在底部输入框上方,和问题气泡分开,看起来像多余的条。
- 触发条件:
collect_spoken焦点激活且未 busy。 - 根因:BUG-605 把快捷回答挂在
composer-wrap输入框上方,没有跟选择题卡一样嵌进同一条助手气泡。 - 修复:两个按钮改到问题
afterAnswer里,紧挨题干;composer 不再渲染它们。点选仍走原来的「没有 / 记不清」短路。 - 验证:
frontend/tests/rectification-spoken-collect.test.ts(按钮在 afterAnswer,不在 composer-wrap)。 - 防复发:采集快捷回答不得回到 composer。后续产品否决该芯片,见 BUG-628;不得按本条把「没有 / 记不清」按钮加回。
- 相关记录:BUG-605、BUG-628
- 复发自:无
- 修复版本:待发布
BUG-619 | 生时校正时间轴比对话内容更贴边
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
globals.css.rectification-timeline水平内边距 - 用户现象:校正对话上方的时间轴比下面的候选卡、助手气泡更靠屏幕外边,左右对不齐。
- 触发条件:打开生时校正,尤其是已出现三列候选卡时。
- 根因:条用
space-5/ 窄屏space-4,对话列是space-8/space-4再加头像让位--assistant-content-inset;条又不在.message-list里,继承不到该变量。 - 修复:条的内边距改为对话沟槽加头像让位;背景和底边发丝线仍拉满。高度不变。
- 验证:
frontend/tests/rectification-timeline-20260909.test.ts。 - 防复发:时间轴水平内边距必须含
--assistant-content-inset,不得写回贴边的space-5。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-620 | 点「更像这个」后三列卡消失,收尾句贴左不对齐
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
rectification-agentic-chat.tsx区间交付卡、verified_idle收尾句、globals.css.rectification-pending-note - 用户现象:点「更像这个」后三列时间卡忽然消失,底下出现未对齐的「已采用 HH:MM,新建对话即按这个时间排盘。」
- 触发条件:交付卡出现后点其中一列「更像这个」;尤其是没有后续核对题、
next_user_action为start_consultation时。 - 根因:
showSelectionCards用!busy整张卸卡;采用请求把对话标成 busy。收尾句用未定义样式的.rectification-pending-note直接挂在消息列表里,没有助手列让位。采用后若 live offer 锚点被清掉,卡也不再回到原消息。 - 修复:采用中或已采用时 busy 不再卸卡;用 resultId 锁住首次出示卡片的那条消息。收尾句改到卡片下方,样式与卡片同一条左边线。已采用列按钮保持按下并禁用。不恢复输入框上方状态条。
- 验证:
frontend/tests/rectification-adopt-cards-stay-20260909.test.ts、frontend/tests/rectification-candidate-offer-anchor.test.ts。rectification-surface-contract.test.ts锁verified_idle为verifiedIdleCopy && !showSelectionCards(BUG-622)。 - 防复发:交付卡不得因采用 busy 卸载;
verified_idle不得使用无 inset 的裸段落;不得把采用状态写回 composer。改verified_idleJSX 条件时必须同步表面合同正则。卡锚锁必须走渲染期useState,不得在 render 里读写 ref(react-hooks/refs)。 - 相关记录:BUG-595、BUG-619、BUG-622
- 复发自:无
- 修复版本:待发布
BUG-621 | 升 Skill 版本后历史生时校正从列表点不开
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
openRectificationCase、open_agentic_rectification_case_v2、历史会话列表、KNOWN_RPC_ERROR_CODES - 用户现象:历史对话里的生时校正(含当天较早创建的)点了没反应,主栏也不换会话。
- 触发条件:生时校正 Skill 已从 10.0.15 升到 10.0.19;点开绑定旧版本的历史校正。
- 根因:08-14 不可变注册表让 Case 钉住自己的
skill_name/version/sha256/source_commit,agent 也按绑定版本跑。打开 RPCopen_agentic_rectification_case_v2却把当前注册版本与 Case 做相等比较,任一不同就skill_identity_mismatch。该错误码未映射,变成 500「校正服务暂时不可用」;客户端再兜成「暂时无法打开生时校正」。错误只画在首页起始卡下,而 BUG-599 之后selectSession等 Case 打开成功才切会话,历史列表点击看起来像没反应。 - 修复:
intent === "session"时先走 v1 按会话取 Case,再读绑定身份,用resolveExactSkillPackage核验快照存在且 sha 一致,核验通过后把绑定身份交给 v2。homepage/new仍用当前注册版本。映射skill_identity_mismatch→ 409。历史打开失败把错误画在被点的那一行下面,起始卡不重复同一条。不改迁移、不改采用门。 - 验证:
frontend/tests/rectification-v9-case-service.test.ts、frontend/tests/rectification-history-open-20260909.test.ts、frontend/tests/skill-registry.test.ts。Docker DB 套件本机跑不了(环境缺口);本轮无新迁移。 - 防复发:按会话打开必须用 Case 绑定身份,不得把当前注册版本交给 v2 做相等比较。升 Skill 版本后,历史校正必须仍能从历史对话打开,且以其绑定版本运行。废弃版本快照须仍可
resolveExactSkillPackage。 - 相关记录:BUG-599、BUG-583、BUG-593、BUG-595、BUG-597、BUG-604
- 复发自:无
- 修复版本:待发布
BUG-622 | staging 质量门因两条过期前端合同红掉,Deploy staging 未派发
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:Gitea Independent Staging Quality Gate(
npm test --prefix frontend)、class-name-definition-contract.test.ts、rectification-surface-contract.test.ts - 用户现象:staging 推送后质量门失败,镜像不发布。产品 UI 无新故障。
- 触发条件:
ca921c3f(BUG-618–620)改了verified_idleJSX;5c476ba7(BUG-614/615)给.personal-report-chart-cell.is-changed写了填充。随后af70ae77再推 staging 仍是同一对失败(runs 2514 / 2516 / 2517)。 - 根因:产品代码已改,合同测试仍锁旧字面量。
knownUnstyled还列着已有 CSS 规则的is-changed;表面合同仍要求questionGap === "verified_idle" && (,实际已是verifiedIdleCopy && !showSelectionCards。测试转绿后 lint 才跑到:BUG-620 在渲染期读写selectionOfferLockRef,react-hooks/refs4 error。 - 修复:
is-changed移出 allowlist;verified_idle断言改跟现行 JSX。采用卡锁改为useState,在渲染期按seededTurns同一模式更新(nextSelectionCardLock同值返回同一对象,避免循环 setState)。 - 验证:
frontend/tests/class-name-definition-contract.test.ts、frontend/tests/rectification-surface-contract.test.ts、frontend/tests/rectification-adopt-cards-stay-20260909.test.ts、frontend/tests/rectification-candidate-offer-anchor.test.ts;npm run lint0 error。 - 防复发:给已在 allowlist 的 class 加规则必须同时删 allowlist 行;改
verified_idle条件必须改表面合同正则,并写原值/新值/原因。跨渲染保留的卡锚不得写进 ref 再在 render 里读。 - 相关记录:BUG-614、BUG-615、BUG-620
- 复发自:无
- 修复版本:待发布
BUG-623 | 一小时窗口按时间取前 12 簇,后段真实出生时间从未进入候选
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
scripts/rectification/candidate_contrast.pyselect_signature_representatives、MAX_PUBLIC_CLUSTERS、公开候选集 - 用户现象:intake 填一小时窗后,第一次比较给出的范围停在窗口前段(例如 14:00–15:00 只收到约 14:04–14:43)。用户知道的更晚时段从头到尾不在候选里。
- 触发条件:分钟网格上签名簇超过 12 个。半小时窗(±15)通常 ≤12,所以以前测不到;「前后半小时」的 60 分钟窗必现。
- 根因:
cluster_contexts_by_signature按代表分钟时间排序后,循环里len(representatives) >= MAX_PUBLIC_CLUSTERS直接break。多出的整簇被丢掉,而且永远是窗口尾部。这发生在证据评分之前。 - 修复:不再按时间截断。安全上限改为 64;超过 64 时合并相邻低分簇,不丢尾部。公开候选仍是每簇一个代表分钟。
- 验证:
tests/test_rectification_v5_services.py的 17 簇夹具(含 14:46–14:51)与 65 簇相邻合并保尾。 - 防复发:禁止再对时间排序后的簇做
break截断;超上限只能按分数合并相邻簇。 - 相关记录:BUG-560(以前「分钟级≈随机」的校准是在 ±15 窗口上得出,一小时窗还叠加了本截断)
- 复发自:无
- 修复版本:待发布
BUG-624 | 可信区间按代表分钟跨度算,簇内其余分钟被视觉淘汰
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
build_candidate_decisions、推断层cluster_range、credible-range.ts - 用户现象:即便某簇还活着,区间右端只写到代表分钟。例如 14:40–14:45 这一簇的代表是 14:40 时,读数停在 14:40。
- 触发条件:签名簇覆盖连续多分钟,代表分钟不是簇的末端。
- 根因:引擎候选只带
time。推断层用换升时刻在代表分钟之间重新聚类,多数变成单分钟;unionStillValidRange再取这些点的跨度。 - 修复:每个公开候选带
cluster_times/cluster_start/cluster_end。推断层优先用引擎簇覆盖;换升聚类只作缺字段时的兜底。可信区间是仍有效簇覆盖的并集。时间轴实心/空心点仍画代表分钟。 - 验证:Python 簇 14:40–14:45、代表 14:40 →
cluster_end=14:45;frontend/tests/rectification-window-cluster-cap-20260909.test.ts区间右端 14:45。 - 防复发:可信区间不得只用代表分钟;缺
cluster_times才允许换升兜底。 - 相关记录:BUG-623
- 复发自:无
- 修复版本:待发布
BUG-625 | 中途说出出生时段时助手口头答应改窗口,但什么都没改
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
frontend/src/app/api/rectification/agent/route.ts自由文本、declared-window-utterance.ts、Skill 10.0.20、agent-run正文守卫 - 用户现象:校正做到一半,用户说「我的出生时间是 14 点 45 到 14 点 50」。助手回答「明白了,以你说的为准」,搜索窗口和候选集不变。下一句「继续吧」被当成没听懂的点选题回答。
- 触发条件:点选或采集焦点下,用户用自由文本申报钟点范围或「HH:MM 左右」。
- 根因:15 个工具里没有改搜索窗口的入口;opening brief 还禁止改窗口。自由文本进意图分类器,枚举没有「申报时间段」,落
unclear。模型在采集焦点下会自己答应。产品否决了中途改窗口。 - 修复:自由文本在分类器和选模型之前做确定性解析。命中则落库固定回复,不调模型,不改
candidate_range,当前焦点保持。正文守卫删除「以你说的…为准」。Skill 写明不得口头改窗口。intake 自定义更小范围等产品答复,本单不做。 - 验证:
frontend/tests/rectification-window-cluster-cap-20260909.test.ts(解析、路由在选模型前拦截、簇覆盖区间、禁用口头承认)。 - 防复发:中途申报时段不得进模型、不得改窗口;助手不得写「以你说的为准」。
- 相关记录:BUG-623、BUG-572
- 复发自:无
- 修复版本:待发布
BUG-626 | 跳过的健康线在 holdout 里被当成没用过,撞同 id 焦点后静默断流
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
holdoutFollowupFor、persistNextInterviewAfterChoice、persistExhaustionCollect - 用户现象:健康采集点「记不清」后再答完职业,界面只剩「没有拿到下一个问题。」
- 触发条件:七领域采集接近穷尽,健康焦点
target_domain=health且skipped,盘外提示仍是health_pressure。 - 根因:BUG-590 只给 occupied 归并 health / health_pressure,holdout 的 declined 也曾漏过这一层;即便归并后,
duplicate_focus仍可能落到空旁白。DB 约束只有health,计划层用health_pressure。 - 修复:holdout 的 declined 与 occupied 一样归并 health / health_pressure。
duplicate_focus记rectification_focus_duplicate并走persistExhaustionCollect;穷尽旁白为空时用noCandidatesGate兜底。 - 验证:
frontend/tests/rectification-collect-direction-20260904.test.ts、frontend/tests/rectification-skipped-health-deadend-20260909.test.ts。 - 防复发:跳过的健康线本会话不得再作 holdout;
duplicate_focus必须留下可见载体。 - 相关记录:BUG-558、BUG-590、BUG-591、BUG-605
- 复发自:BUG-590 决策 4(occupied 已归并,declined 曾漏)
- 修复版本:待发布
BUG-627 | 出口闸门把穷尽当成已交付,断流后不修复
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
inspectNonTerminalTurnExit、ensureNonTerminalTurnExit、agent-runidle catch、POST /api/rectification/cases/[caseId]/repair-exit - 用户现象:穷尽后没有任何问题、卡或句子落库,闸门仍放行;「重新加载」只重取空快照。
- 触发条件:
exhausted为真,无活动焦点、未采用、无 terminalNote host turn。 - 根因:
inspectNonTerminalTurnExit.satisfied含exhausted。穷尽只是状态,不是用户能看见的出口。idle 异常被agent-run吞掉后也到不了闸门。 - 修复:satisfied 只认活动问题 / 已采用 / 已确认 / 已落库的 terminalNote host turn /
publicCanAdopt。无载体时ensureNonTerminalTurnExit写出门槛 host turn。idle 失败后仍调用闸门。缺口按钮改调repair-exit,文案「接着问」;连续两次仍无载体才提示新建。 - 验证:
frontend/tests/rectification-exhaustion-exit-20260906.test.ts、frontend/tests/rectification-skipped-health-deadend-20260909.test.ts。 - 防复发:穷尽不得单独满足出口闸门;无可见载体必须写出 host turn。
- 相关记录:BUG-558、BUG-626
- 复发自:BUG-558 防复发条款被闸门绕过
- 修复版本:待发布
BUG-628 | 口述采集「没有 / 记不清」按钮被用户差评
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:口述采集快捷回答、
rectification-agentic-chat.tsx、globals.css - 用户现象:生时校正口述采集题下面挂着「没有」「记不清」,刚进入开场和后续分领域追问都会出现;用户一致觉得像在鼓励关掉采集。
- 触发条件:任意 live
collect_spoken题。 - 根因:BUG-605 / BUG-618 把拒答芯片做成默认采集 UI。开场是开放叙述,后续口述题也不是点选题;芯片把「没有」做成最显眼动作。
- 修复:删除
CollectSpokenReplies及其样式。打字「没有」仍走 declined,「记不清」仍走 skipped。选择题卡选项不动。 - 验证:
frontend/tests/rectification-spoken-collect.test.ts(组件、样式、composer 均无芯片;打字短路仍在)。 - 防复发:口述采集不得再渲染「没有」「记不清」按钮,也不得挂回 composer。
- 相关记录:BUG-605、BUG-618
- 复发自:无
- 修复版本:待发布
BUG-629 | 无年月性格题与带年月题同权,参与了淘汰
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:
applyProbeOutcome、buildMethodFollowupPlan、技法审计表、Skill 10.0.21 - 用户现象:真实校正里头两道题就是性格自评(相处方式 / 做事风格)。这些题没有年月锚点以外的分辨力验证,却按 ±2 计分并计入三次淘汰。
- 触发条件:账本已有感情或事业锚点(BUG-559),候选尚未分开,计划层把
varga_style/nakshatra_boundary与带年月区分题排在同一池。 - 根因:
varga_style与带年月dasha_boundary共用SCORE_DELTA和strong_conflict_count。分盘星座是确定计算,「星座 ↔ 性格」「用户自评 ↔ 类型」两层从未校准。 - 修复:性格题只在可问的带年月区分题为空、且仍有 ≥2 个未分开候选时才成为下一问,否则
dropped_probes(reason="yearless_deferred")。分值 ×0.5(±1),不递增冲突次数,rounds.kind=tie_break。报告D9 / D10 类型对照与月宿边界标reference。离线脚本只对医院记录且不确定度 ≤2 分钟的匿名导出算命中率。 - 验证:
frontend/tests/rectification-yearless-probe-downgrade-20260909.test.ts;tests/test_varga_style_calibration_report.py;既有rectification-probe-replay-loss-20260908.test.ts断言未改。 - 防复发:不得把
varga_style/nakshatra_boundary加回带年月区分池;不得让性格冲突计入STRONG_CONFLICT_ELIMINATION_COUNT;不得改SCORE_DELTA数值来代替权重。asInferenceState的ROUND_KINDS必须含tie_break,否则答过性格题的inference_state整份被丢掉。审计列 CHECK 仍只认informative/low_information;tie_break只活在 JSON,不得为迁列把 kind 改回informative。 - 相关记录:BUG-559、BUG-560、BUG-623
- 复发自:无
- 修复版本:待发布
BUG-630 | 初始化后点首页「家庭」报运行合同未完成(runtime_contract_incomplete)
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-09
- 影响面:首页主题卡、「家庭」等 onboarding 建议、
POST /api/consult本命 agentic 路径、run-jyotish-consultation合同门禁 - 用户现象:填完生辰和地址后点「家庭」,对话里出现用户问题(例如「请帮我看看我家庭关系的整体模式和特点」),随后红字「Agent 未完成必要的方法与计算步骤,本次不会扣点。」不扣点是对的,但没有回答。
- 触发条件:已有可用出生分钟的账号完成初始化,从首页「从一个主题开始」点家庭(或其它主题卡);模型为 thinking 模式(事故为
deepseek-v3-flash)。 - 根因:本命合同要求本轮成功调用一次
run-jyotish-consultation。Skill 已由服务端写入系统提示,用户回合仍写「先加载 Jyotish Skill」,thinking 模型会把第一步花在skill_read或直接按 Level 2 骨架空写。家庭路由没有 named strict checklist,模型更容易不调计算工具。主题卡也不带入口身份,领域钉死只覆盖「深入看今日」和生时校正,家庭卡的theme: family可被改写成别的合法领域。Python 家庭 workflow 本身能算完,不是计算 400/500。 - 修复:本命 Agent 第一步
prepareStep只开放run-jyotish-consultation,toolChoice: "auto"(thinking 提供商拒绝 named/required)。合同补跑与空回答补跑用同一钩子。分段写作和续写仍用原来的streamOptions,禁止把该钩子接到无分钟 / 声明窗口 Agent。首页主题卡带entrypoint: guided_topic,钉死路由主题且不改写可见问题。钉死入口的用户回合去掉「先加载」,并要求调用时不要填domains。普通手输问题仍可自选领域。 - 验证:
frontend/tests/consultation-agentic-runtime.test.ts家庭guided_topic忽略domains: ["general"]、第一步工具 id;consultation-workflow-contract.test.ts锁 natalprepareStep不进共享streamOptions、分段仍toolChoice: "none";consultation-entrypoint.test.ts/starter-questions.test.ts锁入口枚举与主题卡接线。 - 防复发:本命
requireTool: true的第一步必须能调到计算工具。prepareStep不得套到 general/window Agent,也不得套到composeSection。主题卡必须带guided_topic。不得把domains改成自由字符串来绕过 BUG-255 的枚举拒绝。 - 相关记录:BUG-205、BUG-214、BUG-255、BUG-257、BUG-612
- 复发自:BUG-214(同一句用户文案和合同门;本条是模型根本没成功调计算工具,不是失败计数把成功次数算超)
- 修复版本:待发布
BUG-631 | 申报时段拦截把带钟点的经历当成改窗口
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-10
- 影响面:
parseDeclaredBirthWindow、POST /api/rectification/agent自由文本、采集/点选题下的经历落库 - 用户现象:用户报经历时带了钟点(例如「2024 年 8 月 8 日 20:00 左右分手」),助手立刻回复「搜索范围开始时按资料定、过程中不改」,这句话不进模型,证据也不落库。
- 触发条件:校正做到一半,自由文本里出现
HH:MM/X 点 Y 分加「到 / 至 / -」或「左右 / 前后」,同时这句话其实是经历而不是申报出生时段。 - 根因:BUG-625 只按钟点样式拦截。经历里的钟点和申报出生时段共用同一套样式,没有出生语境或年月日门。
- 修复:命中钟点后再判语境。含出生语境词才算申报;含年月日且无出生语境一律当经历;只有裸钟点范围(可带「大概 / 下午」这类虚词)仍拦截。
- 验证:
frontend/tests/rectification-declared-window-20260909.test.ts;既有rectification-window-cluster-cap-20260909.test.ts补了三句经历不拦截。 - 防复发:不得只靠钟点正则拦截自由文本;带年月日的经历即使含「20:00 左右」也必须进模型。走查见
docs/testing/rectification-window-cluster-cap-20260909.md第 4 节。 - 相关记录:BUG-625
- 复发自:BUG-625(拦截范围过宽)
- 修复版本:待发布
BUG-632 | 经历对照只算引擎前 9 个候选,推断前三会显示还没对照
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-10
- 影响面:
column_times_for_compare、三列对照卡event_dasha_ledger_by_time/prospective_windows_by_time - 用户现象:一小时窗有 17 个引擎候选时,对照卡某一列写「这一分钟还没对照」。
- 触发条件:BUG-623 之后公开候选可以超过 9 个;推断层前三不在引擎候选列表的前 9 个里。
- 根因:BUG-614 把对照从「引擎分数最高的三个」改成「引擎候选列表」,但仍保留
limit=9。卡片按推断前三查表,键不在这 9 个里就显示没对照。 - 修复:默认上限改为公开候选全集(
MAX_PUBLIC_CLUSTERS,64)。column_times仍可传入更小的 active 集合;column_compare_ms继续记录耗时,预算 3 秒。 - 验证:
tests/test_rectification_v5_services.py:17 个候选 → by_time 17 键,65 个截到 64,column_compare_ms低于 3000;column_times子集 3 键。前端columnTimesForSlowCompare在上一轮超过 3 秒时把 active 分钟写入引擎请求。 - 防复发:不得把对照列截回前 9;推断层查表的分钟必须落在 by_time 键集合里。超 3 秒时只允许改传入的
column_times,不能再静默丢掉后段候选。 - 相关记录:BUG-614、BUG-623
- 复发自:BUG-614(对照集合仍被 9 截断)
- 修复版本:待发布
BUG-633 | 证据轮模型无正文被判整轮失败,下一问已落库却只剩「没有拿到下一个问题」
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
agent-run.tsstreamAttempt收尾、host-fallback.ts、rectification-agentic-chat.tsx失败刷新、rectificationQuestionGapState - 用户现象:补完带年月经历后,助手气泡下面直接是「没有拿到下一个问题。」和「本轮没有生成可展示的回复,状态已记录。」输入框写着「请回答上面的问题…」,上面没有问题。刷新无效。
- 触发条件:证据轮
rectification-record-evidence-batch已 completed,下一问焦点已落库,模型以stop/length/max_steps结束且没有可展示正文。 - 根因:
empty_stream不在可重试集合(fe87a9ec为避免重放证据而移出)。失败路径不结算、不写主持人正文、客户端run.failed后不刷新快照。问题只挂在 settled 助手消息上,失败轮没有助手行,缺口走preparing再unavailable。 - 修复:batch 已完成且无正文时用工具返回的事件复述写成「记下了:…。」,
finalizeTurn("completed"),phases 含answer.host_fallback,公开 receiptanswer_origin=host_fallback。empty_stream仍不进RETRYABLE_ERROR_CODES;仅当本 attempt 没有任何公开写工具(batch / set-focus / compare)完成时才 retryable 一次。失败也loadCaseSnapshot;快照有current_question且question_source=focus时渲染主持人问题行。 - 验证:
frontend/tests/rectification-v9-stream.test.ts、rectification-host-fallback.test.ts、rectification-agentic-entry.test.ts、rectification-surface-state.test.ts、rectification-spoken-collect.test.ts。 - 防复发:有写工具完成不得重试 empty_stream(BUG-186);兜底正文只能来自 batch 返回;问题行
focus_id只能来自current_question。 - 相关记录:BUG-186、BUG-359、BUG-558、BUG-627
- 复发自:无
- 修复版本:待发布
BUG-634 | 答题旁白只会说「范围没变」,时间线写死「还在收窄」
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
composeChoiceNarration、explainScoreMovement、RectificationReadonlyRange - 用户现象:多道选择题答完,每次都是「已记录,范围没变。」顶部一直「目前范围 X–Y,还在收窄」,看不出分数有没有动、选择题是不是已经问完。
- 触发条件:答题后可信区间宽度不变(落后峰值不足淘汰阈值),或带年月区分题已经问完。
- 根因:旁白只用
explainRangeChange,explainScoreMovement算得出领先/落后却没说出来。时间线「还在收窄」是写死的,不看快照里还有没有带年月探针。 - 修复:范围不变且 deltas 非零时旁白补「04:52–04:53 领先,05:08–05:12 落后」;全零只说「已记录,范围没变。」范围变了仍不讲领先落后(BUG-606)。时间线在
discriminate_candidates且没有带年月探针时写「选择题已问完,再补带年月的经历才会变」,否则「还在核对」,不写「收窄」。 - 验证:
frontend/tests/rectification-answer-choice.test.ts、rectification-surface-state.test.ts、agent-voice-copy-contract.test.ts。 - 防复发:静态用户文案词表禁止「还在收窄」;聊天源码合同禁止写死该句;范围变了的旁白不得带领先/落后。
- 相关记录:BUG-593、BUG-606、BUG-629
- 复发自:无
- 修复版本:待发布
BUG-635 | 证据轮只说「记下了」却没写入,财务经历静默丢失、流程停在原题
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
runV9AgentTurnexpectedWrite、streamAttempt收尾、RECTIFICATION_USER_COPY.evidenceNotRecorded、liveQuestionOnMessages - 用户现象:财务采集题下回一句带年月的欠债/收入变化后,助手只回「记下了:…。」下面没有下一问也没有卡;时间线仍是原范围;证据数不变;该轮照常扣点。
- 触发条件:collect 焦点下用户提供带年月经历,模型只调
rectification-read-case后按格式写「记下了」,不调rectification-record-evidence-batch/rectification-set-focus。 - 根因:路由分类结果没传给运行器,收尾只要正文非空就
completeAttempt,不看有没有公开写工具。客户端把旧消息上的同focus_id当成问题仍在显示,缺口不补主持人问题行。 - 修复:路由把
expectedWrite设为"evidence" | "none" | "unknown"传进运行器;provide_new_evidence或answer_current_focus+has_new_dated_event→"evidence"。分类器 null/出错重试一次,仍失败则"unknown"且守卫 fail-open,不用年份正则或关键词兜底。无焦点的message路径也跑同一分类器。无写入的「记下了」第一次evidence_not_written可重试(零写工具,不进RETRYABLE_ERROR_CODES);第二次主持人正文「这件我还没记上。请再说一次大概年月和发生的事。」,answer.host_fallback,不结算计费。liveQuestionOnMessages只认最后一条 settled 助手消息。 - 验证:
frontend/tests/rectification-unwritten-evidence.test.ts,以及 host-fallback / spoken-collect / agentic-entry / turn-intent-classifier / agent-voice-copy-contract。rectification-v9-agent.test.ts的 persist 三句夹具必须带 completed batch,否则 635 会把它收成未写入(Gitea run 2545)。 - 防复发:有公开写工具 completed 不得重试(BUG-186);兜底正文不得由宿主复述用户原话冒充「记下了」;旧消息同
focus_id不得挡住主持人问题行。 - 相关记录:BUG-186、BUG-278、BUG-359、BUG-449、BUG-633
- 复发自:BUG-633(只补了 batch 已完成且无正文,没补正文声称记下了但 batch 没跑)
- 修复版本:待发布
BUG-636 | 校正流把 Mastra inputSchema 拒绝信封报成 tool completed
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
mapStreamChunkToActivity/mapStreamChunkToPhase/batchResultFromToolChunk - 用户现象:本事故未触发。若模型给 batch 传了不在
EVIDENCE_KINDS的 kind,回执会显示evidence.proposedcompleted,写工具守卫会被假 completed 绕过。 - 触发条件:
createTool的inputSchema校验失败时 resolve{ error: true, message, validationErrors },流层当普通tool-result。 - 根因:咨询流 BUG-278 已按信封改发
tool.failed;校正流tool-result没有同样判定。 - 修复:信封结构
error === true且validationErrors为对象时发tool.activity failed code=tool_call_rejected,不发 completed phase;batchResultFromToolChunk与composeHostFallbackNarration对信封返回 null。公开回执不含validationErrors。 - 验证:
frontend/tests/rectification-unwritten-evidence.test.ts、rectification-host-fallback.test.ts。 - 防复发:不得把 Mastra 拒绝信封当 batch 返回值;不得把
validationErrors写进公开事件。 - 相关记录:BUG-278、BUG-635
- 复发自:BUG-278(咨询流已修,校正流未跟)
- 修复版本:待发布
BUG-637 | 账本同年键把已记学业的发挥质量题一并丢掉
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
askedKeysForSameYearDedup、inspectDiscriminatorProbes、renderableEventProbe、remainingReverseVerifyProbes - 用户现象:账本已有某年学业经历,
dropped_probes里education.YYYY.known_event_quality被标same_year_asked,界面再也问不到「那次发挥怎么样」。 - 触发条件:账本派生
domain.year键进入askedDiscriminatorKeys;发挥质量语义键必然同域同年。 - 根因:BUG-592 的同年硬排除对账本键和已答键一视同仁。发挥质量题存在的前提就是账本已有那年事件,于是被误杀。BUG-389 的测试没覆盖「账本已有同年事件」。
- 修复:账本派生的精确
domain.year键只拦存在性探针。known_event_quality/event_quality仍受回执已答键约束,同一道不问两遍。不改SCORE_DELTA、四选项、确认门。 - 验证:
frontend/tests/rectification-probe-year-dedupe-20260906.test.ts、rectification-engine-convergence.test.ts。 - 防复发:账本
domain.year不得单独让发挥质量题变成same_year_asked。已答过该质量键后仍须丢掉。 - 相关记录:BUG-389、BUG-592
- 复发自:BUG-389(被 BUG-592 覆盖)
- 修复版本:待发布
BUG-638 | 双轨一致性按网格分钟判冲突,同簇两个峰值被降置信度
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
dasha_agreement、dashaAgreementAmongActive、decision_policy - 用户现象:主限和分盘大运峰值落在同一段候选里,回执仍写冲突并降低把握;报告另算一套说只作观察。
- 触发条件:两轨 argmax 分钟不同,但同属一个
cluster_start–cluster_end。 - 根因:Python 对整窗网格分钟逐分取峰值,分钟不等即
conflict。决策把该冲突写进 reasons 并降置信度。TS 报告只在推断层 active 候选上重算。 - 修复:按簇比较:两轨 argmax 映射到所在簇,同簇即
agree。没有簇时仍按分钟比较。推断层 active 簇一致也视为 agree,即使某一轨峰值不是代表分钟。引擎原值保留为dasha_agreement_pre_inference。确认门不因本次agree从关到开。 - 验证:
tests/test_rectification_refinement_packet.py、frontend/tests/rectification-delivery-report-facts.test.ts。 - 防复发:同簇不同分钟不得再写
vimshottari_narayana_conflict。跨簇仍须conflict。不得把confirmation_allowed因本次 agree 打开。 - 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布
BUG-639 | 不可分宽度按簇代表分钟少算两端延伸
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
indistinguishable_width_minutes、indistinguishableWidthMinutes、确认门adjacent_passed - 用户现象:报告宽度跟区间两端对得上,引擎「不可分宽度」却按各簇代表分钟算,少算两端延伸;极端时可把 ≤5 分钟的确认门误开。
- 触发条件:各簇
cluster_start/cluster_end超出代表分钟。 - 根因:宽度公式用代表分钟的
max-min+1,不是簇覆盖。 - 修复:改为
max(cluster_end)-min(cluster_start)+1;缺簇字段时回退到time。tied_minute_count不动。决策层与客户端解析把clusterStart/clusterEnd传进确认门。 - 验证:
tests/test_rectification_confirmation_and.py、frontend/tests/rectification-delivery-report-facts.test.ts、rectification-confirmation-gate.test.ts、rectification-candidate-result.test.ts。 - 防复发:两簇 04:50–04:55 与 04:56–05:02 必须得到 13,不得算成 7。确认门不得因代表分钟看起来相邻而打开。
- 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布
BUG-640 | 宫位表和本命重算跟的不是卡片上的代表分钟
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
latestResultToolProjection、publicChartForMinute、natalRecastMeaning - 用户现象:卡片代表分钟是推断层的一分钟,回执宫位表和「已按该分钟重算」写的是引擎另一分钟;两者若跨分盘换升,正文会写错盘。
- 触发条件:推断代表分钟 ≠ 引擎代表分钟,且
house_tables_by_time两分钟都有表。 - 根因:投影已能按推断分钟取表,但引擎原表没有改名留下;Agent 看见的事实仍可能混用。
- 修复:有推断层且两分钟不同时,公开
house_table/natal_recast跟推断代表分钟;引擎原值改名representative_time_pre_inference/house_table_pre_inference/natal_recast_pre_inference。Agent 可见投影剥掉这三个字段。不改引擎。 - 验证:
frontend/tests/rectification-delivery-report-facts.test.ts。 - 防复发:公开
house_table.time必须等于投影representative_time。Agent 可见 JSON 不得含*_pre_inference。 - 相关记录:BUG-593
- 复发自:无
- 修复版本:待发布
BUG-641 | 财务与健康被写成「只有主动说才问」,服务器却会主动采集
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
skills/jyotish-birth-time-rectification/SKILL.md、conversation-strategy.md、scripts/rectification/event_probes.py、Skill 10.0.22 - 用户现象:Skill 写财务/健康只有用户主动说才问,实际对话里服务器仍会问钱和身体;Python 采集探针又不给这两域年份线索。
- 触发条件:新校正走到方法覆盖采集;账本只有学业/感情时看
evidence_collection_probes。 - 根因:三处口径各自为政。Skill L84 与 conversation-strategy 写 volunteer-only;TS
DATED_COLLECT_ORDER自 BUG-462 起含 finance;PythonVOLUNTEER_ONLY把 finance / health_pressure 挡在missing_collection_domains和evidence_collection_probes外。 - 修复:Skill 10.0.22 改为财务、健康与其他领域同权,服务器按
method_followup_plan.next_followup主动问。删除VOLUNTEER_ONLY。MAX_COLLECTION_PROBES3→7,避免学业+感情账本被前三个领域占满后发不出财务/健康。历史 10.0.21 包冻结,resolveExactSkillPackage仍能打开。 - 验证:
tests/test_rectification_event_probes.py学业+感情账本含 finance 与 health_pressure;frontend/tests/skill-registry.test.ts10.0.21 精确解析;live SKILL.md 与versions/10.0.22逐字节相等。 - 防复发:现行 Skill / 10.0.22 不得再写「主动说才问」。不得把冻结历史包改写成新口径。不得把
.workbuddy镜像当运行主仓。 - 相关记录:BUG-462、BUG-621、BUG-642
- 复发自:无
- 修复版本:
52db714b
BUG-642 | 带年份线索的采集题被整句去前缀,无年份性格卡还能抢在采集前面
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
user-copy.ts、spokenFollowupForUser、nextDatedCollectFollowup、方法覆盖链 - 用户现象:家人采集题号带 2021,题干却没有年份;同领域无年份性格卡可以抢在带线索采集题前面。
- 触发条件:
evidence_collection_probes给出age_band年份;同域还有 D12 等无年份对照。 - 根因:BUG-539 因「某年前后…记得大概哪年就行」自相矛盾而把年份前缀整句删掉。
sameDomainYearlessCard在感情/事业/家人未覆盖时优先于采集。 - 修复:采集题拆成无线索模板与带线索模板。有
probe_year时写成「家里在 2021 年前后有没有…有就说个大概年月,别的年份也行。」不得同时出现「记得大概哪年就行」。未覆盖的感情/事业/家人先采集;nextDatedCollectFollowup在DATED_COLLECT_ORDER内先排有年份线索的领域。带年月区分点选题仍在一切采集之前。 - 验证:
rectification-spoken-collect/rectification-eight-method/rectification-collect-stall:家人 2021 采集口语含「2021 年前后」、不含「记得大概哪年就行」;感情/事业/家人未覆盖时next_followup是采集而不是无年份性格卡;existence 类 D12 仍记yearless_ungrounded_contrast,没有单独的「拒答后出 D12 风格卡」用例。无probe_year用无线索模板。 - 防复发:有线索的采集口语必须带「某年前后」且允许别的年份。无线索模板不得带年份。不得让同域无年份性格卡抢在该域采集之前。
- 相关记录:BUG-539、BUG-418、BUG-629、BUG-641
- 复发自:BUG-539(整句去前缀把回忆线索一并删掉)
- 修复版本:
52db714b
BUG-643 | 「没有 / 记不清」靠原字匹配,分类器分不出「没有」和「先放着」
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
turn-intent-classifier.ts、POST /api/rectification/agent、expectedWriteFromCollectIntent - 用户现象:只有整句等于「没有」或「记不清」才走跳过;「不记得了」「忘了」落到 Agent。采集题分类器只教了
no。用户用带年月的事回答采集题时,expectedWrite仍是 none,BUG-635 守卫只剩正文「记下了」。 - 触发条件:采集焦点下打「没有」「不记得了」「2018 年 3 月开始欠债」。
- 根因:
isCollectDeclineUtterance/isCollectSkipUtterance去掉句末标点后要求整句相等。shouldDeclineCollectFocus只认no。expectedWriteFromCollectIntent不把 collect 焦点上的 yes/weak_yes 当写入。 - 修复:删除两个原字匹配函数和路由短路。分类器:没有→
no(declined),记不清→unsure(skipped),带年月直接回答采集题→yes且expectedWrite=evidence。collectFocusCloseStatus走既有applyCollectFocusDenial。分类器失败记collectIntent=unclassified,不做关键词兜底。 - 验证:
rectification-turn-intent-classifier.test.ts、rectification-spoken-collect.test.ts、rectification-unwritten-evidence.test.ts(collect + yes + 无写入 → 重试)。源码合同:route 与 classifier 不再引用那两个函数。 - 防复发:采集跳过/没有不得用正则或词表。collect 焦点 yes/weak_yes 必须
expectedWrite=evidence。分类器 null 不得回退到关键词。 - 相关记录:BUG-605、BUG-626、BUG-635
- 复发自:无
- 修复版本:
52db714b
BUG-644 | 被顶替的采集题号永久占位,落库撞号后当成已问过并断流
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
persistCollectFocus、persistExhaustionCollect、set_v10_conversation_focus - 用户现象:同一轮里分两次记下两件经历后,再答完各领域采集题,最后一条线回「没有」,助手改口说「这次给出的范围…」,下面是「没有拿到下一个问题。」感情采集题从未出现过。
- 触发条件:一轮里
record-evidence-batch被调两次,第一次落下的采集焦点被第二次顶替成superseded;之后计划层仍要问那个领域。 - 根因:RPC 把旧焦点改成
superseded而不删行,unique (case_id, question_id)仍占着原题号。计划层看不到 superseded,collect_retry为 false,persistCollectFocus撞号直接duplicate_focus。persistExhaustionCollect把duplicate_focus当成「这题已经问过并关掉了」,跳过口语采集走进交付旁白。 - 修复:采集题撞号一律试
:next/:next2/:next3;collect_retry只换重问文案。duplicate_focus与其它落库失败一样返回采集口语或中间态范围句,并在rectification_exhaustion_collect日志写下persist_status与题号。点选探针题号不加后缀。 - 验证:
frontend/tests/rectification-superseded-focus.test.ts、frontend/tests/rectification-server-focus.test.ts。 - 防复发:采集题撞号不得因
collect_retry为 false 就放弃换号;duplicate_focus不得落到 adopt 分支。 - 相关记录:BUG-462、BUG-520、BUG-525、BUG-559、BUG-626、BUG-627、BUG-633、BUG-645
- 复发自:BUG-462、BUG-626(题号唯一约束叠上顶替不删行)
- 修复版本:
a3a51c32
BUG-645 | 出口旁白说「这次给出的范围」却没有卡、公开 can_adopt 为 false
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
persistExhaustionCollect、persistNextInterviewAfterChoice、POST /api/rectification/agent非收敛区间分支、deliveryNarrationAllowed - 用户现象:助手正文是交付三句(「这次给出的范围…代表性候选」),公开
can_adopt=false、没有对照卡,下面是「没有拿到下一个问题。」 - 触发条件:内部
decision.canAdopt为真,但session_outcome=collect_evidence,公开can_adopt仍为 false;穷尽采集落库失败后走进 adopt 旁白。 - 根因:交付旁白只看内部
canAdopt。公开can_adopt只在采用结果态为真,采集期即使内部可采纳也不出卡。 - 修复:新增
deliveryNarrationAllowed:必须publicCanAdopt为真,或selection_allowed且下一动作为offer_provisional_range。三处调用在不允许时改用中间态范围句加当前采集题。 - 验证:
frontend/tests/rectification-superseded-focus.test.ts、frontend/tests/agent-voice-copy-contract.test.ts、frontend/tests/rectification-delivery-report-facts.test.ts、frontend/tests/rectification-exhaustion-exit-20260906.test.ts。 - 防复发:
can_adopt=false时源码合同禁止交付句路径;公开不能采用时不得输出「这次给出的范围」。 - 相关记录:BUG-644、BUG-626、BUG-627
- 复发自:无
- 修复版本:
a3a51c32
BUG-646 | 采集穷尽后无下一问,却显示「没有拿到下一个问题」并说做不了
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
persistExhaustionCollect、收集问题池、rectificationQuestionGapState、Skill 10.0.23 - 用户现象:各领域采集都答完或只有一两件带年月经历时,助手停在门槛句或交付口吻,下面是「没有拿到下一个问题。」,没有结果卡,也没有可继续说的入口。
- 触发条件:训练门仍关且收集池已空;或训练门已开、选择题增益见底。
- 根因:穷尽出口只写「还差 N 件,领域不限」且不落焦点。客户端把「无焦点 + 列表成功」当成问题加载失败。产品不允许「做不了」,也不允许固定题数。
- 修复:训练门关且池空时只写精确缺口句,Case 保持开放,占位「再说一件带年月的事」,客户端进入
collect_waiting,不显示「没有拿到下一个问题」。训练门开且增益见底时交付区间卡加一句具体「再补什么」。Skill 升 10.0.23。 - 验证:
rectification-exhaustion-exit-20260906.test.ts、rectification-surface-state.test.ts、rectification-collection-question-pool.test.ts。 - 防复发:训练门关不得出卡、不得出现禁词;无焦点且训练门关不得走
unavailable。 - 相关记录:BUG-558、BUG-627、BUG-645、BUG-647、BUG-648
- 复发自:BUG-558(关顶不落焦点)
- 修复版本:
dd8f35f7
BUG-647 | 恰好三件带月经历时一件被留作 holdout,训练门永远差一件
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
split-holdout.ts、scripts/rectification/case_holdout.py、trainingScoreableGate、验证报告 Real Case Calibration 行 - 用户现象:按要求给了三件带年月经历,仍到不了选择题,看起来像还差一件。
- 触发条件:带月事件恰好 3 件、2 个种类;旧规则从第 2 件起就留 holdout。
- 根因:
MIN_EVENTS_TO_RESERVE_HOLDOUT=2,3 件里留 1 件后训练只剩 2,达不到MIN_ACCEPTANCE_EVENTS=3。 - 修复:≥4 件才留 1 件 holdout;恰好 3 件全部训练。回执
holdout.status="not_reserved_min_events",报告 Real Case Calibration 写not reserved。确认门与引擎计分不变。 - 验证:
rectification-split-holdout.test.ts、tests/test_candidate_discriminator_contract.py。 - 防复发:3 件必须全进训练;4 件才允许 holdout。
- 相关记录:BUG-646、BUG-648
- 复发自:无
- 修复版本:
dd8f35f7
BUG-648 | 开场后按领域轮转盘问,题干年份来自生日而不是用户说过的事
- 状态:resolved
- 首次发现:2026-09-10
- 最近更新:2026-09-10
- 影响面:
collection-question-pool.ts、method-followup.ts、user-copy.ts、Skill 10.0.23 - 用户现象:刚说完一两件,系统立刻按家人/学业/财务轮着问,题干还带着「2020 年前后」这类不是用户说的年份。
- 触发条件:开场写入第一件带年月经历后,
DATED_COLLECT_ORDER与COLLECT_QUESTION_YEAR_CUE(年龄段中点)生效。 - 根因:采集顺序按方法覆盖轮转,年份线索来自出生年+典型年龄段。这是上午写进 BUG-642 的口径,产品已撤回。
- 修复:收集池按信息价值排序:邀请「还有吗」→ 用户年份锚定追问 → 无年份通用补问。题干不得出现年龄段年份。邀请拒答不把整个
other列入禁问。 - 验证:
rectification-spoken-collect.test.ts、rectification-collection-question-pool.test.ts、agent-voice-copy-contract.test.ts。 - 防复发:
user-copy.ts不得再出现无用户年份的「年前后」;邀请在产出期间必须排第一。 - 相关记录:BUG-642、BUG-546、BUG-641、BUG-646
- 复发自:BUG-642(年份线索口径错误)
- 修复版本:
dd8f35f7
BUG-649 | 职业采集题答出的年月被抹成不计分备注
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
applyOccupationCollectLedgerNorm、rectification-v9-toolsbatch/propose、职业采集焦点 - 用户现象:职业题被问到开始年份并答出年月后,助手只回「记下了」,训练门仍关,账本只有不计分的职业备注。
- 触发条件:职业采集焦点下回答带年月的工作开始(例如某年某月开始做某一行)。
- 根因:BUG-442 P0 把职业焦点下任何 career/occupation 条目一律改成
occupation_note且精度unknown、日期置空。occupation_note不计训练门。batch/propose 又用??把归一后的 null 日期回落成原日期,出现「unknown 却带日期」的账本行。 - 修复:无年月仍只写
occupation_note(覆盖判定仍不读正文、不用关键词、不依赖模型 domain)。带年月时另记一条domain=career主评分事件,kind 沿用模型给出的 career kind,否则career_entry。归一后的 null 不再用??回落。职业通用题不问年份;开始年份走 career 锚定题。 - 验证:
frontend/tests/rectification-occupation-dated-answer-20260911.test.ts、frontend/tests/rectification-replay-20260911.test.ts、frontend/tests/rectification-collection-question-pool.test.ts。 - 防复发:职业焦点下无日期仍不得把
occupation_note算进 3 件;带日期必须另有 scoreable career 行;源码合同禁止?? item.occurredFrom。 - 相关记录:BUG-442、BUG-356、BUG-389、BUG-646、BUG-647、BUG-648、BUG-650
- 复发自:BUG-442(职业覆盖归一过宽)
- 修复版本:待发布
BUG-650 | 工具轮结束时精确缺口句未并入本轮正文
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
persistNextInterviewIfIdle、composeIdleGapIntoSpoken、agent-run证据轮出口 - 用户现象:训练门仍关、收集池已空时,工具轮结束只见「记下了」,没有精确缺口句,客户端像停住。
- 触发条件:证据轮有
turnId(因此不会走persistExhaustionGateTurn),且 idle 路径写出了精确缺口。 - 根因:BUG-596 禁止再开第二条门槛 turn。缺口只停在 idle 的
hostNarration,没有并入本轮p_assistant_message。 - 修复:证据轮在
trimSpokenTurnForInterview之后,若 idle 缺口含「现在记下的是 / 就能开始筛」,把缺口接到同一条复述后面。交付旁白不会被误接。 - 验证:
frontend/tests/rectification-replay-20260911.test.ts无日期职业变体;rectification-v9-agent.test.ts交付轮仍裁成三句。 - 防复发:工具轮出口的缺口必须出现在本轮正文;不得为缺口再开一条 turn。
- 相关记录:BUG-646、BUG-596、BUG-649
- 复发自:BUG-596(禁止第二条门槛 turn 后缺口没并入正文)
- 修复版本:待发布
BUG-651 | 带年月区分题池空必须交付区间,不得改问性格题
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
decideRectification、discriminatorProbeIfFollowupCanAsk、buildMethodFollowupPlan - 用户现象:训练门已开、带年月选择题问完后,决策仍是
discriminate_candidates,界面没有结果卡。 - 触发条件:训练门开、
acceptance_allowed为真、带年月探针无可问,只剩 D9/D10varga_style或月宿边界题。 - 根因:
!separation.sufficient把无年月性格题当成下一道区分题;buildMethodFollowupPlan在带年月池空时走firstRenderableYearlessFollowup/ 性格卡。重设计单 S3「探针池空 → 交付」以带年月题为准,性格题不得挡在结果前面。 - 修复:probe 只认带年月探针;性格题 / 月宿一律
yearless_deferred,不再作为ask_candidate_discriminator。池空时按canAdopt落到offer_provisional_range/adopt_representative。带年月精筛卡(probe_year或 period 含年份)仍问。本单按让步顺序先不问性格题,可选「再答两道参考题」入口另开一单。 - 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.ts;rectification-yearless-probe-downgrade-20260909.test.ts带年月池空不再出 D9。 - 防复发:不得把
varga_style/nakshatra_boundary加回带年月区分池(BUG-629);不得让性格题参与淘汰。 - 相关记录:BUG-629、BUG-442、BUG-627、BUG-652
- 复发自:BUG-629(性格题降权后仍被当成必答区分题)
- 修复版本:
66f63c76;门禁跟进f870d3d7
BUG-653 | 带年月题池空必须按剩余候选刷新探针,不得直接出卡
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
refreshDiscriminatorProbes、persistNextInterviewIfIdle/AfterChoice、event_probes.py - 用户现象:六道带年月选择题答完后,范围仍是约 20 分钟、头名约 29%,系统却直接出三列区间卡。
- 触发条件:训练门已开、引擎一次性生成的带年月探针问完、TypeScript 侧只更新分数、未再拿剩余活跃候选去引擎生成探针。
- 根因:区分探针只在引擎跑账本时按初始簇生成一次。选择题答完后不刷新。
MAX_PROBES=8、每域每年一道、家人存在题被 0.85 先验整域丢弃,把还能切开剩余簇的题掐掉。 - 修复:池空且未收敛时
refreshDiscriminatorProbes用活跃候选column_times、全部已答键asked_probe_keys、refresh_probes=true调引擎,探针并入inference_state.probes,不改candidate_set_id/ 已答题。每个候选集最多刷新 2 次。剩余候选 ≤5 时刷新上限 12/4。带月份的家人dasha_boundary不再因先验丢弃,只用于排序。 - 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.tsT0/T1;tests/test_candidate_discriminator_contract.py刷新上限与家人月级边界。 - 防复发:刷新必须走现有
keepAnswers回放路径(BUG-587 / BUG-594);asked_probe_keys带全部已答键,同域同年去重(BUG-559)。性格题不得进刷新池(BUG-651)。 - 相关记录:BUG-651、BUG-654、BUG-655、BUG-629、BUG-559、BUG-587、BUG-594
- 复发自:BUG-651(池空即交付,未先按剩余候选再出题)
- 修复版本:待发布
BUG-654 | 刷新后仍无带年月题须定向补事,卡片标题为目前范围
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
targetedCollectPool、decideRectification、区间卡RectificationRangeDelivery - 用户现象:带年月题问完后直接出卡,标题写成「这次给出的范围」,没有「还能再收窄」;家人 / 财务 / 迁居等能切开 05:00 换升的线从未被问。
- 触发条件:刷新后带年月池仍空,或用户尚未说「没有了」;交付文案把当前范围写成结束。
- 根因:S1 锚定追问在训练门开后停;S2 池空直接 S3。交付条件把「池空」当成结束。卡片标题用「这次给出的范围」,BUG-651 的「再补什么」句只在 persist 旁白、不在卡上。
- 修复:刷新后仍无带年月题则出定向补事口述题(剩余换升层映射到领域,≥2 个具体例子,不带推算年份)。答新事则重算回 S2;答「没有了」才交付。交付条件改为收敛,或刷新与定向补事都用尽,或用户主动停。卡片标题「目前范围」,卡下「还能再收窄」取定向补事首条。禁用「这次给出」「最终」。
- 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.tsT3/T4;rectification-collection-question-pool.test.ts定向补事;agent-voice-copy-contract.test.ts禁词。 - 防复发:
!separation.sufficient && !probe在refreshExhausted && targetedCollectExhausted之前不得completeWithRange/finish。不得静默空载体(BUG-652)。 - 相关记录:BUG-653、BUG-651、BUG-652、BUG-655、BUG-629
- 复发自:BUG-651(池空即交付,未做定向补事)
- 修复版本:待发布
BUG-652 | 答题事务有决策无载体时必须回落到交付或缺口句
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-11
- 影响面:
persistNextInterviewIfIdle、persistNextInterviewAfterChoice、persistServerOwnedFocus、时间线rectificationReadonlyRangeCopy - 用户现象:六道带年月选择题答完后,助手只回「已记录,范围没变…」,时间线写「选择题已问完,再补带年月的经历才会变」,界面「没有拿到下一个问题。」没有结果卡、没有采用入口、没有缺口句。
- 触发条件:训练门已开、引擎已允许交付,决策仍是
ask_candidate_discriminator,但下一张区分卡没落下(persist 返回 skipped / invalid_choice_schema / 空正文)。 - 根因:
persistNextInterviewIfIdle对ask_candidate_discriminator/ask_holdout_validation豁免了persistExhaustionCollect。persistNextInterviewAfterChoice在焦点不可渲染时用spokenFollowupForUser(followup) ?? fallback,空串不触发兜底。时间线在没有结果卡时仍写「才会变」。 - 修复:撤回 IfIdle 豁免;答题事务在无可渲染焦点且无正文时同一事务回落到
persistExhaustionCollect;persist 非 created/already_open 时console.warnrectification_discriminator_persist_skipped;时间线有卡写「选择题已问完,下面是当前范围」,门关写「再说一件带年月的事就能继续」,其余「还在核对」。 - 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.tsT3;rectification-surface-state.test.ts时间线合同;agent-voice-copy-contract.test.ts禁词「才会变」。 - 防复发:决策有动作就必须有载体;不得靠客户端「接着问」补救。
- 相关记录:BUG-651、BUG-627、BUG-442
- 复发自:BUG-627(
ensureNonTerminalTurnExit只在点「接着问」时触发,不在答题事务里) - 修复版本:
66f63c76
BUG-655 | 刷新无新题不得写推理账本;并入探针须与已答同名
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-12
- 影响面:
refreshDatedDiscriminatorPoolIfNeeded、persistV9InferenceState、GETdecideFromDossier与persistServerOwnedFocus - 用户现象:带年月题问完后刷新,GET 已选出下一道探针,但持久化层拒绝,界面再次停在没有下一问。
- 触发条件:训练门已开、带年月池空、刷新调用引擎后没有可并入的新题,或并入探针 id 与已答 id 命名不一致,或写入时候选被清空 / 候选集被改掉。
- 根因:刷新在引擎没有新题时仍写
reason=supersede的推理记录;GET 侧可能选中未映射进inference_state.probes的引擎探针,expectedAnswerSchemaFor盖不上probe_id后 persist 返回invalid_choice_schema/skipped。 - 修复:只在引擎映射出可问新题时写库;写入前断言候选非空且
candidate_set_id不变,候选沿用刷新前集合;并入探针 id 与已答同一规则(probe:<semantic_key>或带 split hash);刷新只把可问探针写进inference_state.probes和 receipt,GET 选中的 key 必须能 stamp 上probe_id。 - 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.tsT0 打印最后一行推理记录与 GET 探针 key / persist 状态;有新题时supersede,空刷新改由 BUG-658 写尝试记录。 - 防复发:新探针写库必须同时满足新题、候选非空、候选集不变、探针 id 与已答同名。空刷新不得写
supersede。不得靠客户端「接着问」补救(BUG-652)。 - 相关记录:BUG-653、BUG-654、BUG-652、BUG-587、BUG-594、BUG-656、BUG-657、BUG-658
- 复发自:BUG-653(刷新总会写库,未校验新题与候选集)
- 修复版本:
512be9b7
BUG-656 | 采集焦点 kind 必须映射到表约束枚举
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-12
- 影响面:
persistCollectFocus、persistSpokenChoiceFallback、agentic_rectification_conversation_focuses.target_kind - 用户现象:带年月题问完后应出现定向补事口述题,但焦点写不进库,界面停在没有下一问。
- 触发条件:下一问是定向 / 锚定 / 通用采集,
kind_hint为targeted:<domain>、anchor:…或generic:…。 - 根因:
targetKind直接写入kind_hint,违反target_kindCHECK;插入失败后 persist 返回skipped,定向题从未成为活动焦点。 - 修复:写库前经
collectFocusTargetKind映射到 CHECK 枚举(产品别名relationship_change→relationship_start、education_milestone→education_start);原kind_hint写入expected_answer_schema.collect_kind;读取侧先读collect_kind再回退旧字段。不放宽 CHECK、不加迁移。 - 验证:
frontend/tests/rectification-server-focus.test.ts用真实 CHECK 列表断言三类 followup 的targetKind合法且status=created,invite_more写target_kind=null+collect_kind;rectification-probe-pool-exhausted-20260911.test.ts第六题后定向题成为活动焦点。门禁跟进:家人拒答后的邀请 persist 与 idledecideFromDossier源码锁仍通过。 - 防复发:采集焦点
target_kind必须落在 CHECK 列表内或为 null;kind_hint原值只进 schema,不进列。invite_more等非枚举 hint 不得写进target_kind。 - 相关记录:BUG-648、BUG-442、BUG-658
- 复发自:BUG-648(采集题号可重试,但 kind 仍未映射到表约束)
- 修复版本:待发布
BUG-657 | 引擎刷新必须按剩余候选生成探针
- 状态:resolved
- 首次发现:2026-09-11
- 最近更新:2026-09-12
- 影响面:
scripts/rectification/refinement_packet.py、event_probes.pyrefresh and remaining分支 - 用户现象:第六题后刷新仍按整段约 31 分钟网格出题,剩余几分钟上没有新边界时永远
no_new_probes。 - 触发条件:
refresh_probes=true且请求带非空column_times(剩余活跃候选)。 - 根因:
build_refinement_packet把grid_times交给探针生成,column_times只喂三列对照;refresh and remaining看不到剩余分钟。 - 修复:刷新且
column_times非空时,用它们作为_discriminating_event_probe_lists的candidate_times;回执写refresh_result=new_probes|no_new_probes。 - 验证:
tests/test_candidate_discriminator_contract.py「31 格 + column_times=5」:探针candidate_ids只含这 5 个;无边界则refresh_result=no_new_probes。 - 防复发:刷新探针生成不得默认整网格;
column_times非空时必须作为剩余候选。 - 相关记录:BUG-653、BUG-658
- 复发自:BUG-653(刷新调用了引擎,但探针仍按全网格算)
- 修复版本:待发布
BUG-658 | 刷新尝试必须落库;等待收窄按已问过判定;定向题落库失败同一事务出卡
- 状态:resolved
- 首次发现:2026-09-12
- 最近更新:2026-09-12
- 影响面:
refreshDatedDiscriminatorPoolIfNeeded、narrowingExhaustion、persistExhaustionCollect、interviewCollectWaiting - 用户现象:引擎没有新题时界面永久「等待收窄」,每次答题再调一次引擎(约 20s),并出现「没有拿到下一个问题」。
- 触发条件:训练门已开、带年月池空、刷新无新题;或定向补事焦点因 CHECK/落库失败。
- 根因:512be9b7 空刷新什么都不写,
refresh_count恒 0;GET 不调刷新,refreshExhausted永假,waitToNarrowCapability无题无卡。shouldRefreshDatedPool只看refresh_count,同一已答数会反复打引擎。定向题 persistskipped时仍停留在无焦点的口述。 - 修复:空刷新把
inference_state.refresh_attempts[]以reason=already_answered写入(幂等键refresh_attempt:<set>:<count>);有新题仍走supersede并附带尝试记录。refreshExhausted由同一候选集、同一已答数下已有尝试记录(或剩余换升层为空)判定;shouldRefreshDatedPool同源,至多一次引擎刷新。定向题 persistskipped时同一事务把collect:targeted:<domain>记为 skipped 并出「目前范围」。客户端collect_evidence且无 stopReason、无焦点无卡时走collect_waiting。 - 验证:
frontend/tests/rectification-probe-pool-exhausted-20260911.test.ts空刷新写already_answered、第二次不调引擎、GET 进定向补事;定向 persist 失败同一事务出「目前范围」、不得出现「没有拿到下一个问题」;rectification-surface-state.test.tscollect_waiting无insufficient_*也可成立。 - 防复发:空刷新不得只打日志;GET 决策不得依赖只存在于内存的刷新结果。新探针仍禁止空
supersede(BUG-655)。 - 相关记录:BUG-655、BUG-651、BUG-652、BUG-656、BUG-657
- 复发自:BUG-655(堵住空
supersede后未留下「已刷新」事实) - 修复版本:待发布
BUG-659 | 记完经历后 set-focus 连续失败且原始错误无处可查
- 状态:resolved
- 首次发现:2026-09-12
- 最近更新:2026-09-12
- 影响面:
rectification-set-focus、safeToolErrorCode、工具失败回执、mapStreamChunkToActivity - 用户现象:记下两件学业后,「设置对话焦点」连续失败约六次,回执只有
tool_failed,本轮多等约 20 秒后走宿主兜底。用户没有被卡住;GET 快照里下一问已经是「还有吗」。 - 触发条件:batch 工具记完证据后已把同一 questionId 写成活动焦点,模型再调 set-focus;或把
kind_hint(如invite_more)抄进targetKind;或工具内抛出未列入已知列表的码。 - 根因:失败回执只写
safeErrorCode,没有脱敏原文、没有rectification_tool_failed日志,无法从库或容器判断六次失败的真实原因。invalid_case_id/invalid_domain/invalid_event_kind/invalid_focus不在已知列表,一律显示为tool_failed。同一 questionId 已由 batch 落下时,set-focus 仍走 RPC 身份比较,schema 键不同会冲突;模型传入的kind_hint还会被 Zod 在 execute 前拒绝。 - 修复:失败回执写入脱敏
engine_message(≤120 字,UUID/邮箱打码),并console.warnrectification_tool_failed。上述码进入已知列表与活动码白名单。同一 questionId 已是活动焦点时幂等返回已有焦点(可更新 spoken_prompt;RPC 冲突也不得抛给模型重试)。targetKind/targetDomainschema 放宽为字符串;kind_hint忽略,新焦点仍写target_kind=null。非法 domain 仍在 execute 抛invalid_domain。 - 验证:
frontend/tests/rectification-set-focus-tool-failed-20260912.test.ts(同 id 即使 RPC 冲突也idempotent=true、无失败回执;invite_more不拒绝;三类工具内抛错回执可辨,未知仍tool_failed);rectification-v9-status-security.test.ts;rectification-v9-stream.test.ts失败活动带码、英文重试旁白不得进正文。 - 防复发:工具自己抛的码必须进
safeToolErrorCode已知列表;失败回执必须带脱敏原文。已落下的同一 questionId 不得再让模型重试。不得把kind_hint写进target_kind(BUG-656)。 - 相关记录:BUG-442、BUG-650、BUG-656、BUG-660
- 复发自:无
- 修复版本:待发布
BUG-660 | 宿主兜底「记下了」把日期写两遍
- 状态:resolved
- 首次发现:2026-09-12
- 最近更新:2026-09-12
- 影响面:
host-fallback.tsrecapLine、证据轮answer_origin=host_fallback - 用户现象:工具轮失败后宿主兜底正文写成「记下了:2016-09 2016年9月上大学…」,日期重复。
- 触发条件:batch 回执同时有
display_date_label(如2016-09)和已含年月的event_phrase(如「2016年9月上大学」),且模型没有写出正文。 - 根因:
recapLine把标签和短语直接空格拼接,不检查短语是否已含年份或以标签开头。 - 修复:短语匹配
\d{4}年或以display_date_label开头时只用短语;短语为空时仍用标签。 - 验证:
frontend/tests/rectification-host-fallback.test.ts夹具「2016-09 / 2016年9月上大学」→「记下了:2016年9月上大学(本科入学)、2020年6月毕业。」;短语已以标签开头时不重复前缀。 - 防复发:host fallback recap 不得把已含年月的短语再拼一份标签。
- 相关记录:BUG-633、BUG-650、BUG-659
- 复发自:无
- 修复版本:待发布
BUG-661 | 定向补事必须逐条点选,不得一道口述堆所有剩余线
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
targetedCollectPool、targetedCollectFollowup、answer-choice.ts存在题分支、persistExhaustionCollect - 用户现象:六道带年月题答完、引擎刷新无新题后,落下一条口述题把结婚、收入、搬家、添丁全堆在一句里,没有可点的选项。
- 触发条件:训练门已开、带年月池空、刷新
no_new_probes、仍有未覆盖的剩余换升层。 - 根因:
targetedCollectFollowup把剩余层合成一道口述题;「没有」被当成整池结束,而不是只关掉当前那条线。 - 修复:按剩余层一次出一张存在题卡(有过这件事 / 没有发生过 / 记不太清楚 / 这条先跳过,
scoring:false)。A 再问「大概哪年几月?」;B 该线 declined;C/D skipped。年月答完走既有 batch 写证据并重算。所有剩余线问完才出「目前范围」。 - 验证:
frontend/tests/rectification-targeted-collect-cards-20260913.test.ts;rectification-probe-pool-exhausted-20260911.test.ts第六题后choiceReady且题干为「结过婚或订过婚吗?」;四条都 B 才出范围卡。 - 防复发:定向补事存在题必须是
choice_frame且scoring:false;不得把多条剩余线合成一道口述题。性格题不得因此回到计分池(BUG-629)。 - 相关记录:BUG-654、BUG-656、BUG-662、BUG-663
- 复发自:BUG-654(交付前定向补事做成了一道开放口述)
- 修复版本:待发布
BUG-662 | 定向补事与范围提示不得写范围两端钟点
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
remainingCandidatesLine、rangeNarrowHint、agent-voice-copy-contract - 用户现象:提示写成「能把 04:51 和 05:06 分开」,读起来像在比较范围两端,而不是还剩几个候选。
- 触发条件:定向补事或「目前范围」卡下提示需要说明还能切开剩余候选的线。
- 根因:
rangeNarrowHint用 split 两端钟点当主语。 - 修复:改写为「现在还剩 HH:MM–HH:MM 里 N 个候选,能把它们分开的是这几条线:…」;保留「还能再收窄:如果记得…」。合同加禁词「能把 HH:MM 和 HH:MM 分开」。
- 验证:
frontend/tests/agent-voice-copy-contract.test.ts;rectification-targeted-collect-cards-20260913.test.ts剩余候选句。 - 防复发:用户可见文案不得出现「能把两端钟点分开」句式;剩余候选句不得当作存在题题干。
- 相关记录:BUG-654、BUG-661
- 复发自:BUG-654(定向补事举例时带上了范围端点)
- 修复版本:待发布
BUG-663 | 「目前范围」卡下可点「再答两道参考题微调排序」
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
rectification-range-delivery.tsx、tieBreakPersonalityFollowup、POST /api/rectification/cases/[caseId]/tie-break - 用户现象:带年月题和定向补事问完后,用户没有入口去答 D9/D10 参考题微调排序;若自动出性格题又会挡住结果。
- 触发条件:已交付「目前范围」卡,候选仍分不开,用户想微调头名排序。
- 根因:BUG-629 把性格题移出计分池后,没有产品入口可选择再答;自动出牌会挡住结果卡。
- 修复:卡下可选按钮「再答两道参考题微调排序」。点了才走
tie_break落 D9/D10varga_style(±1、不淘汰、不改can_adopt)。不点不出。 - 验证:
frontend/tests/rectification-targeted-collect-cards-20260913.test.ts不点不出;入口文案进listUserVisibleCopy()。 - 防复发:性格题不得在定向补事之前或未点入口时成为下一问;
SCORE_DELTA与淘汰阈值不得被这条路径改掉。 - 相关记录:BUG-629、BUG-651、BUG-661、BUG-666、BUG-667
- 复发自:BUG-629(性格题降权后没有可选入口)
- 修复版本:待发布
BUG-664 | 刷新阶段同年换运间隔可收到 30 天,首轮仍是 45 天
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
scripts/rectification/event_probes.py_boundary_windows/_union_boundary_dates/discriminating_event_probes - 用户现象:六道带年月题答完后,刷新经常立刻
no_new_probes,即使剩余候选的同年换运只差三十多天。 - 触发条件:训练门已开、带年月池空、
refresh_probes=true、剩余候选上存在 30–44 天的同年边界。 - 根因:生产
MIN_BOUNDARY_DAYS=45对首轮和刷新共用。离线 20 例测量里,只把刷新阈值降到 30 平均多 1.0 个独立划分、头名不降;首轮保持 45。 - 修复:新增
REFRESH_MIN_BOUNDARY_DAYS=30。仅request.refresh_probes is True时传给_union_boundary_dates;首轮调用签名与阈值不变。不改EXISTENCE_NEARBY_YEARS、R1/R2、SCORE_DELTA。 - 验证:
tests/test_rectification_refresh_r3_r4.py:40 天同年窗首轮不出、刷新出dasha_boundary。既有tests/test_rectification_event_probes.py、tests/test_candidate_discriminator_contract.py首轮断言不放宽。 - 防复发:默认
MIN_BOUNDARY_DAYS必须仍是 45;不得把研究脚本接到 API;刷新仍走 distinguish 合同。 - 相关记录:BUG-653、BUG-657、BUG-665
- 复发自:BUG-653(刷新已按剩余候选出题,但边界阈值与首轮相同)
- 修复版本:待发布
BUG-665 | 刷新阶段并入 pratyantar 与 D9/D10 上升 Narayana 边界
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
scripts/rectification/event_probes.py_vim_start_dates/_union_boundary_dates - 用户现象:六道带年月题答完后,本命上升相同、D9/D10 或第三级换运已不同时,刷新仍无新的带年月题。
- 触发条件:
refresh_probes=true,剩余候选 Vimshottari pratyantar 或 D9/D10 上升 Narayana 有可分辨边界。 - 根因:首轮并集只用 Vimshottari MD/AD 与本命上升 Narayana。
_vim_start_dates(..., include_pratyantar=)已存在但刷新默认仍为 false。离线测量 R4 单独 +1.084 独立划分,头名不降。 - 修复:仅刷新阶段
include_pratyantar=True,并对剩余候选 D9/D10 上升再算 Narayana 边界并入并集。首轮不传这些参数,_vim_start_dates调用保持原签名以免旧 mock 失败。本命上升 Narayana 与 MD/AD 不变。 - 验证:
tests/test_rectification_refresh_r3_r4.py:pratyantar 与 D9 上升不同时刷新出新年月、首轮不出。研究脚本scripts/research/probe_supply_after_six.py仍离线。 - 防复发:不得对首轮打开 pratyantar 或分盘 Narayana;不得全局改
MIN_BOUNDARY_DAYS。刷新 0 题时仍走定向补事(BUG-654 / BUG-661),不得报「没有拿到下一个问题」。 - 相关记录:BUG-653、BUG-654、BUG-661、BUG-664
- 复发自:BUG-653(刷新只复用首轮边界层)
- 修复版本:待发布
BUG-666 | 参考题入口在两道答完后仍显示,再点报没有可答题
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
rangeDeliveryForSnapshot、GETrange_delivery.tie_break_available、POST /api/rectification/cases/[caseId]/tie-break - 用户现象:「目前范围」卡下「再答两道参考题微调排序」两道都答完后按钮还在;再点出现红字「现在没有可答的参考题」。
- 触发条件:候选 D9/D10 仍不同,但 D9/D10
varga_style都已问过;或从未有可渲染的参考题。 - 根因:入口只看
window_scan.d9_candidates_differ / d10_candidates_differ。路由用tieBreakPersonalityFollowup跳过已问键,两道答完返回空 → 409tie_break_unavailable。前端把 409 写进setError。 - 修复:GET 与点击路径共用「还有未问的 D9/D10
varga_style且未采用」。没有剩余题时不画按钮;若仍点到空池,只重拉快照,不把 409 文案写到界面。 - 验证:
frontend/tests/rectification-tie-break-entry-20260913.test.ts。 - 防复发:不得只用 window_scan 决定入口。planner 不得打进客户端包。性格题仍不得自动成为下一问(BUG-663)。
- 相关记录:BUG-663、BUG-667、BUG-629
- 复发自:BUG-663
- 修复版本:待发布
BUG-667 | 点一次参考题入口只出一道,不会自动接第二道
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
requestTieBreakPersonality、persistNextInterviewAfterChoice、expectedAnswerSchemaFor - 用户现象:按钮写「再答两道」,点一下只出 D9;答完回到范围卡,要再点一次才出 D10。
- 触发条件:用户从范围卡点入口,答完第一道
varga_style。 - 根因:入口只持久化当前下一问。答完后
buildMethodFollowupPlan无tieBreakRequested,且varga_style不算剩余区分题,can_adopt仍为真时跳过下一问持久化。 - 修复:入口落下的焦点 schema 带
tie_break_round: true。答完该轮焦点时继续tieBreakPersonalityFollowup落第二道;没有第二道或两道都问完则结束该轮,回到范围卡。自动接第二道只在这一轮内生效。 - 验证:
frontend/tests/rectification-tie-break-entry-20260913.test.ts:未问时 D9、问过 D9 后 D10、两道都问过则空;schema 带 round 标记;计分仍tie_break、不淘汰。 - 防复发:不得把任意性格题答完都当成参考题轮次。
can_adopt与淘汰阈值不得因参考题改变。 - 相关记录:BUG-666、BUG-663、BUG-629
- 复发自:BUG-663
- 修复版本:待发布
BUG-668 | 定向补事死辅助函数、Skill 三选项与引擎 mock 签名分叉
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
collection-question-pool.ts、skills/jyotish-birth-time-rectification、scripts/rectification/event_probes.py - 用户现象:卡上是四个选项(含「这条先跳过」),Skill 仍写三个。生产代码为迁就测试替身把
_vim_start_dates/_boundary_windows拆成两套调用。 - 触发条件:读 Skill 或跑刷新边界;
isTargetedCollectDeclined/isTargetedCollectYearFollowup无src/调用方。 - 根因:BUG-661 曾用「整池关闭」脚枪,辅助函数留下;Skill 10.0.25 未写入第四选项;刷新参数用空 kwargs 避开旧 mock。
- 修复:删除无调用方的两个导出。Skill 10.0.25 冻结,现行 10.0.26 写「有 / 没有 / 记不清 / 这条先跳过」。
_union_boundary_dates一律把include_pratyantar与min_days传给生产函数,不再为 mock 分叉。 - 验证:源切片确认两个导出已删;Skill 合同锁 10.0.26 文案;
tests/test_rectification_event_probes.py与tests/test_rectification_refresh_r3_r4.py。 - 防复发:Skill 选项数必须与卡一致;生产调用不得为测试替身分叉。10.0.25 仍可 exact-resolve。
- 相关记录:BUG-661、BUG-621、BUG-665
- 复发自:—
- 修复版本:待发布
BUG-669 | 定向补事快照投影拿不到 choice_card,刷新后卡点不动
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
projectRectificationChoiceCard、GET/api/rectification/cases/[caseId]choice_card、内嵌RectificationChoiceCard - 用户现象:六道带年月题后出现定向补事四点选;第一次能答,刷新或再拉快照后卡看得见、选项是灰的。
- 触发条件:活动焦点为
collect:targeted:<domain>(含 persist 撞号后的:next),schema 为服务端四点选且targeted_collect: true。 - 根因:
buildMethodFollowupPlan承接collect_method_evidence时不重建choice_frame。projectRectificationChoiceCard只把persistedVerifyCard()留给reverse_verify/out_of_sample_check,无 frame 就返回 null。projectCurrentQuestion仍给出kind: "choice",前端liveQuestion要求 GET 卡focus_id一致,于是disabled={!liveQuestion}。:next还会被「questionId 与 frame 全等」挡掉。 - 修复:活动焦点 schema 满足
targeted_collect === true且能解析出四点选时,直接按持久化文案投影choice_card(question_id/focus_id用焦点的、scoring: false、stop_label「这题跳过」),不依赖计划层 frame,也不做 questionId 全等比较。 - 验证:
frontend/tests/rectification-targeted-card-live-20260913.test.ts;:next同样出卡。 - 防复发:定向存在题的 GET 卡必须能从已持久化 schema 投影;不得只用计划层重建 frame 当唯一身份。区分题
semantic_key/candidate_split_hash全等判定不得放宽(BUG-559 / BUG-581)。 - 相关记录:BUG-661、BUG-654、BUG-591、BUG-510、BUG-498
- 复发自:BUG-661(采集焦点 + 点选卡是新组合,落入 BUG-510 只修了核对题的洞)
- 修复版本:待发布
BUG-670 | 承接焦点分支让模型把定向题改写成口述题
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
buildMethodFollowupPlan承接当前焦点分支、user_prompt_hint - 用户现象:定向卡出现前先冒出一道 UI 不一样的口述题,回答后才变成点选卡。
- 触发条件:活动焦点是定向存在题,计划层
method_id: "active_focus"且choice_frame为空,提示词要求模型用spokenPrompt自己写题干。 - 根因:承接分支只为
reverse_verify/out_of_sample_check/distinguish_candidates重建点选。定向补事是史上第一个「采集焦点 + 点选卡」。 - 修复:识别
targeted_collectschema 时用buildTargetedCollectExistenceFrame加持久化文案重建choice_frame;user_prompt_hint改为「照发服务端卡片,不要自己写题干。」 - 验证:
frontend/tests/rectification-targeted-card-live-20260913.test.ts活动焦点为定向存在题时next_followup.choice_frame非空且question_id等于焦点题号。 - 防复发:定向存在题的题干与选项仍是服务端自有;模型不得改写。不得因此放宽区分题身份校验。
- 相关记录:BUG-669、BUG-661、BUG-559、BUG-581
- 复发自:BUG-661
- 修复版本:待发布
BUG-671 | 选择题无卡时不得停在采集等待态
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
rectification-agentic-chat问题缺口、interviewChoiceCardUnavailable、repair-exit - 用户现象:卡点不动时,时间轴写「再说一件带年月的事就能继续」,没有修复入口。刷新页面后选项依旧是灰的。
- 触发条件:
current_question.kind === "choice"且 GETchoice_card为空。 - 根因:有持久化选择题时走
persisted_question/ 采集等待,不进unavailable。刷新只重取同一份缺卡快照,救不回来。未答的死卡还会用消息里的选项画成 disabled。 - 修复:选择题缺卡时不记为采集等待、不记为已持久化可见题,直接进现有
unavailable+repair-exit(「接着问」);未答的死卡不再画成 disabled 点选。 - 验证:
frontend/tests/rectification-targeted-card-live-20260913.test.ts;表面合同锁deadChoice、unansweredDeadChoice与 repair-exit 路径。 - 防复发:
kind=choice且无choice_card不得静默停在「再说一件带年月的事」,也不得画看不见的可点卡。BUG-669 修好后这条是兜底。 - 相关记录:BUG-669、BUG-627、BUG-652、BUG-510
- 复发自:BUG-669
- 修复版本:待发布
BUG-672 | 健康与职业别名在十处比较里各走各的,拒答或已报过仍再问
- 状态:resolved
- 首次发现:2026-09-09
- 最近更新:2026-09-13
- 影响面:
canonicalCollectDomain/canonical_domain、holdoutFollowupFor、remainingReverseVerifyProbes、remainingConflictProbes、probeYearAlreadyCovered、oos_blind_prompts、_event_years、volunteeredDomainsFromEvidence、declinedDomains - 用户现象:跳过或报过健康后,核对题、区分题或盘外提示仍问同一条健康线;职业题落库成「其他」后,事业还没覆盖时会再问职业。
- 触发条件:账本或焦点是
health,计划/引擎探针是health_pressure;或职业焦点target_domain=other且题号collect:occupation:。 - 根因:三套词汇并存。BUG-590 只归并 holdout occupied,BUG-626 只补 holdout declined。其余比较仍用字面相等。任务书写 BUG-628,该号已被口述采集按钮占用。
- 修复:TS/Python 各一个归并函数,比较两侧都走它。DB
target_domain与persistableFocusDomain不改。源码合同禁止method-followup.ts再写"health"比较。 - 验证:
frontend/tests/rectification-domain-alias-audit-20260913.test.ts;tests/test_rectification_event_probes.pyDomainAliasTests。 - 防复发:健康/职业比较必须经归并函数。不得在
method-followup.ts新增"health"字面量比较。不得改 DB 约束来迁就别名。 - 相关记录:BUG-590、BUG-626、BUG-586、BUG-641
- 复发自:BUG-590 决策 4(occupied 已归并,其余站点未归并)
- 修复版本:待发布
BUG-673 | 定向补事存在题被存成口述焦点,裸题顶掉卡片
- 状态:resolved
- 首次发现:2026-09-13
- 最近更新:2026-09-13
- 影响面:
serverOwnedExpectedAnswerSchema、persistServerOwnedFocusCore、persistSkippedCollectFocus、rectification-set-focus、buildMethodFollowupPlan承接焦点、projectCurrentQuestion、interviewChoiceCardUnavailable - 用户现象:答完带年月题后出现一行没有头像、没有 A/B/C/D 的粗体裸题(如「家里添过丁或长辈住过院吗?」),无法点选;本轮计划的 D9 关系题消失。
- 触发条件:定向存在题以采集型 schema(
collect: true、collect_kind: targeted:*)落进活动焦点;choice.applied的nextInterviewPersisted=true让前端不再追一轮。 - 根因:写入侧在
expectedAnswerSchemaFor没产出.choice时把采集意图降成口述 schema;复原侧只认targeted_collect === true章,拦不住采集型 schema。BUG-670 的识别条件覆盖不到这条不经模型的路径。 - 修复:定向存在题禁止降级成采集型 schema,重建不出 frame 就不写焦点。承接焦点与 GET 投影按题号前缀识别口述定向存在题并重建四点选。焦点落库失败时
persistNextInterviewAfterChoice显式persisted: false,题干进本轮旁白。persistSkippedCollectFocusresolve 失败重试一次,不得留下status=active。前端把口述形态的定向存在题当缺卡,走 repair-exit。年份阶段collect:targeted:<domain>:year仍是口述。 - 验证:
frontend/tests/rectification-targeted-spoken-focus-recovery-20260913.test.ts;rectification-targeted-card-live-20260913.test.ts、rectification-surface-state.test.ts补口述定向存在题。 - 防复发:定向补事存在题的焦点 schema 只允许点选形态;识别定向存在题以题号前缀为准,不得只认
targeted_collect字段。不得把persisted_question块画成卡片来绕过数据层。 - 相关记录:BUG-670、BUG-669、BUG-671、BUG-661
- 复发自:BUG-670
- 修复版本:待发布
BUG-674 | 探针耗尽后判别题被念进正文,同一题反复问且答了不算数
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
persistNextInterviewAfterChoice、expectedAnswerSchemaFor/stampChoiceSchemaWithProbe、persistExhaustionCollect - 用户现象:答完带年月题后,正文反复出现同一句判别题(无 A/B/C/D)。用户打字回答「没有」只得到「记下了,这方面先跳过」,同一句再出现一次。
- 触发条件:
prospective_probes为空,精度阶段仍给出带choice_frame的判别题;stampChoiceSchemaWithProbe盖不上probe_id,persist 返回invalid_choice_schema。 - 根因:上一单把「焦点没写成功」当成「题还在、只是没进消息」,把卡片题干拼进旁白。探针耗尽时这道题根本不该再问,也没有焦点可答。
- 修复:判别题(
intent === distinguish_candidates,或带choice_frame的非采集题)落不了库时改走persistExhaustionCollect,不得把题干写进正文。采集题仍可进正文,但与上一条助手消息相同的题干会去重并转耗尽分支。 - 验证:
frontend/tests/rectification-unstampable-probe-20260914.test.ts。 - 防复发:判别题落不了库时不得把题干写进正文;探针耗尽必须转定向补事卡。
- 相关记录:BUG-673、BUG-652、BUG-661
- 复发自:BUG-673(D3 把未落库题干拼进旁白)
- 修复版本:待发布
BUG-675 | 快照已有 choice_card,界面仍画一行裸题
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
attachQuestionsToTurns、persisted_question渲染、persistedQuestionSurface - 用户现象:
目前范围下面一行裸题(如「结过婚或订过婚吗?」),无头像、无选项、也没有「接着问」。 - 触发条件:
current_question.kind === "choice"且 GETchoice_card非空,但焦点没有asked_turn_id,卡片无法挂到消息上;persisted_question只画prompt。 - 根因:
persisted_question从设计起只服务口述题。BUG-673 把定向题修成点选后,这个纯文本块成了新的裸题来源。 - 修复:无
asked_turn_id的活动选择题挂到最后一条助手消息,走既有消息内卡片。persisted_question在有卡时渲染同一套RectificationChoiceCard,不再只画一行字。 - 验证:
frontend/tests/rectification-unstampable-probe-20260914.test.ts;rectification-surface-state.test.ts。 - 防复发:
current_question.kind === "choice"且有choice_card时必须渲染可点卡,不得只画题干。不得新增第二套卡片组件。 - 相关记录:BUG-673、BUG-669、BUG-671
- 复发自:BUG-673(数据形态修好后渲染仍走口述兜底)
- 修复版本:待发布
BUG-676 | 代表时间取到被淘汰候选
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
evaluateCandidateSeparation、rankActive、decision receiptrepresentative_time/last_inference_round、slimDecisionReceipt - 用户现象:同一轮 receipt 里引擎
representative_time落在eliminated_ids中(例如 05:14),界面范围是 04:48–05:07,也不含该分钟。 - 触发条件:本轮有淘汰名单,引擎代表时间与前端推理第一名 / 范围不一致。
- 根因:引擎与前端是两套代表时间,未对账。Python 引擎按自身事件拟合排整张网格,不吃前端累积问答,把 05:14 写进结果行;
overlayPublicDecision在fromInference时用推理值覆盖,界面从未显示过 05:14。真实副作用是case-receipt-projection把latestResult.representativeTime(引擎值)算进活跃时间集合,已被淘汰分钟的house_tables_by_time仍送给模型。 - 修复:活跃时间集合去掉引擎
representativeTime,只保留推理活跃候选、inference.representative_time、用户已采用的selectedTime。不改排序。上一轮的不变量告警rectification_representative_time_inconsistent保留。 - 验证:
frontend/tests/rectification-spoken-orphan-20260914.test.ts(淘汰分钟不进house_tables_by_time;selectedTime仍保留);告警路径仍由rectification-unstampable-probe-20260914.test.ts覆盖。 - 防复发:引擎代表时间不得进入模型上下文的活跃宫位表,也不得显示给用户,不得反过来覆盖前端推理值。
- 相关记录:BUG-442、BUG-678
- 复发自:无
- 修复版本:待发布
BUG-677 | staging quality gate 在残留 .venv 上被 Python 3.14 mkdir 打断
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:Gitea
Independent Staging Quality Gatevalidate「Install dependencies」、publish(因needs: validate被 skip) - 用户现象:推送
69ffa07d后门禁 run2594约 20 秒失败;测试、lint、build 未执行,publish/deploy skipped。 - 触发条件:
runs-on: xiaoxin的 act_runner hostexecutor 复用 job 目录且已有 gitignored.venv/;runner 报告Python 3.14.4。 - 根因:
python3 -m venv .venv对已存在目录执行mkdir,Python 3.14 抛出FileExistsError: [Errno 17] File exists: '.../hostexecutor/.venv'。git status --porcelain --untracked-files=all仍为空,因为忽略目录不出现在 porcelain 里。不是 pip、npm 或校正测试失败。 - 修复:安装步骤先
rm -rf -- .venv,再python3 -m venv --clear .venv。不改测试、lint、build、exact-SHA 与镜像发布语义。 - 验证:
frontend/tests/staging-backend-workflows.test.ts锁定清除后再--clear;Gitea gate 对修复 SHA 复跑。 - 防复发:质量门创建
.venv必须允许 hostexecutor 残留目录。不得把 Python 3.14FileExistsError当成业务测试失败。 - 相关记录:BUG-149、BUG-364
- 复发自:无
- 修复版本:待发布
BUG-678 | 口述题没有 askedTurnId 时仍是无头像裸行
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
attachQuestionsToTurns、focusSpokenPrompt、rectificationQuestionGapState/persisted_question - 用户现象:定向补事答「有过这件事」之后,助手气泡只有「记下了。」,时间轴「目前范围」下面一行没有头像的裸题「大概哪年几月?」。
- 触发条件:年份追问焦点
collect:targeted:<domain>:year、kind = collect_spoken、askedTurnId = null、status = active;上一轮挂回规则只覆盖选择题。 - 根因:BUG-675 把
attachQuestionsToTurns的 hanging 限定成turnQuestionKind(focus) === "choice"。口述题没有卡可画,但同样需要头像与消息位置;短题干「大概哪年几月?」只有 7 字,focusSpokenPrompt的 8 字下限会让挂回后的turnQuestionFromFocus仍返回空。 - 修复:无
askedTurnId的活动焦点一律可挂到最后一条尚无题的助手消息;采集 schema 允许短于 8 字的题干。persisted_question纯文本块只留给一条助手消息都没有的兜底。年份追问仍是口述,不做点选卡。 - 验证:
frontend/tests/rectification-spoken-orphan-20260914.test.ts;rectification-collect-prompt.test.ts补口述挂回。 - 防复发:挂回规则不得再按
choice排除口述题。不得把年份追问改成点选卡来绕过渲染。不得新增第二套问题组件。 - 相关记录:BUG-675、BUG-673、BUG-671、BUG-679
- 复发自:BUG-675(同一块兜底只修了选择题)
- 修复版本:待发布
BUG-679 | staging quality gate 因 DESIGN 合同仍锁旧 persisted-question 文案失败
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:Gitea
Independent Staging Quality Gatevalidate「Validate backend, package, frontend, and database contracts」、frontend/tests/rectification-unwritten-evidence.test.ts、frontend/DESIGN.md - 用户现象:推送
2dbcf266后门禁 run2598在校验步骤失败;publish/deploy skipped。依赖安装已通过,不是 BUG-677 的.venvmkdir。 - 触发条件:
npm test --prefix frontend在 Linux runner 上跑全量套件。 - 根因:BUG-678 把 DESIGN 的 persisted-question 从「completed turn that claimed to record evidence」改成「only when no assistant message can carry the prompt」,合同测试仍按旧句匹配,3179 通过、1 失败。产品行为测试未红。
- 修复:合同断言改跟新 DESIGN:question-live 含无
asked_turn_id的挂回,persisted-question 只留给没有助手消息可挂;旧句不得再出现。不改产品代码。 - 验证:
frontend/tests/rectification-unwritten-evidence.test.ts该条;Gitea gate 对修复 SHA 复跑。 - 防复发:改
frontend/DESIGN.md校正状态表必须同步源码合同测试。不得为了过门禁把 DESIGN 改回旧裸行口径。 - 相关记录:BUG-678、BUG-635、BUG-677
- 复发自:BUG-678(DESIGN 与合同测试分叉)
- 修复版本:待发布
BUG-680 | 刷新尝试落库失败仍按已尝试推进,POST 交付 GET 回采集
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
refreshDatedDiscriminatorPoolIfNeeded、persistRefreshAttempt、decideRectification、shouldSkipFollowupPersist - 用户现象:跳过最后一道定向补事题后,助手播出完整交付结论,刷新后没有交付卡、没有下一题,时间轴写「再说一件带年月的事就能继续」,并印出「没有拿到下一个问题。」
- 触发条件:前两名 posterior 并列(如 16/16),
stop_reason=tied_first;本轮选择题刚推进 revision,刷新尝试用旧 revision 写库撞版本冲突。 - 根因:
persistRefreshAttempt失败时返回值被丢弃,attemptRecorded仍为 true,POST 第二次decideFromDossier把refreshExhausted当成真并交付;GET 读库没有这条 attempt,stillNeedNarrowing为真,把tied_first压回collect_evidence。并列到顶本是终局,被「还有收窄手段」无条件压过。 - 修复:落库失败时
attemptRecorded=false,不把内存态写进 dossier,并打rectification_refresh_attempt_persist_failed。tied_first在stillNeedNarrowing/ coverageBlocks 之前直接completeWithRange。idle persist 在tied_first且 outcome 属于交付集合时跳过下一问。不放宽can_adopt。 - 验证:
frontend/tests/rectification-delivery-vs-collect-20260914.test.ts:persist 抛错后 attemptRecorded=false;同一份并列 dossier 上persistNextInterviewIfIdle与decideFromDossier都交付。 - 防复发:任何「是否已尝试/已耗尽」的标志,落库失败时一律按未完成处理。POST 与 GET 对同一状态的决策必须一致,并由对拍测试守着。不得靠内存 overlay 播交付。
- 相关记录:BUG-674、BUG-681、BUG-682
- 复发自:BUG-674(POST 与 GET 对同一状态判定不一致)
- 修复版本:待发布
BUG-681 | 交付话术能说、交付卡不能画
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
DELIVERY_OUTCOMES、deliveryNarrationAllowed、canShowRectificationSelectionCards、canShowRectificationReadonlyRange - 用户现象:正文已经念完「现在给的范围是…代表分钟为…」,界面没有区间交付卡。
- 触发条件:
session_outcome为completed_with_range或provisional_range;can_adopt为假。 - 根因:卡片闸门用
ADOPT_OUTCOMES且要求canAdopt。这两种正常收尾不在采用集合里。话术闸门认offer_provisional_range/ 公开采用,于是能说不能画。 - 修复:新增
DELIVERY_OUTCOMES= 采用集合 ∪{completed_with_range, provisional_range}。卡片闸门用它,不把can_adopt提权。交付 outcome 且selection_allowed时出卡、不出「再说一件」范围行。 - 验证:
rectification-delivery-vs-collect-20260914.test.ts、rectification-candidate-result.test.ts表驱动:话术为真则卡为真。 - 防复发:不得新增第二套交付卡。话术与卡必须由同一判据驱动。
- 相关记录:BUG-680、BUG-590
- 复发自:无
- 修复版本:待发布
BUG-682 | 已交付无题时缺口状态机兜底成「没有拿到下一个问题」
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
rectificationQuestionGapState、interviewDeliveredGap、rectification-agentic-chat.tsx - 用户现象:没有题、没有卡时印出「没有拿到下一个问题。」并出现「接着问」。
- 触发条件:
current_question === null且session_outcome=collect_evidence、stop_reason=tied_first(或已交付 outcome);不是 collect_waiting 的那几个不足理由。 - 根因:缺口状态机没有「已交付 / 没有更多可问」终态,重试耗尽后落到
unavailable。 - 修复:新增
delivered:无题且 outcome 属于交付集合,或stop_reason为tied_first/user_uncertainty_too_high。该状态显示出口说明,禁止「没有拿到下一个问题」。普通题没取到仍是unavailable。 - 验证:
rectification-delivery-vs-collect-20260914.test.ts、rectification-surface-state.test.ts。 - 防复发:已交付无题不得再走修复入口文案。不得把
delivered误伤真正缺题的unavailable。 - 相关记录:BUG-680、BUG-681
- 复发自:无
- 修复版本:待发布
BUG-683 | 并列早退在还有探针可问时就把第二题判成可采用
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
decideRectification的tied_first早退、completeWithRange未收窄窗口的采用闸门 - 用户现象:新建校正只答两轮采集,助手已经在问第三道带年月区分题,界面却宣布可采用、代表分钟,可信区间仍是开局窗口(如 04:45–05:15)。
- 触发条件:前两名(或前三名)分数完全并列;
stop_reason=tied_first;仍有带年月探针或定向补事未问完。 - 根因:复发自 BUG-680 的修复。上一单 D2 写的是把
tied_first提到!separation.sufficient分支里、stillNeedNarrowing之前;实现把它放到了coverageBlocks之前、整个不足分支之外,连「还有探针」和「方法覆盖未达标」都绕过。配套测试传入discriminatorProbe: null,实现却没有这个守卫,测试与实现语义不一致,所以回归没被拦住。并列在采集早期只是分数还没拉开,不是「无论如何都问不下去」。 - 修复:
tied_first早退必须同时probe === null且targetedCollectExhausted !== false。refreshExhausted不参与该判断。交付时若credible_range等于开局case.candidateRange,can_adopt为假,session_outcome只允许completed_with_range/provisional_range,不得宣布代表分钟可采用。 - 验证:
frontend/tests/rectification-tied-first-fix-20260914.test.ts;rectification-delivery-vs-collect-20260914.test.ts成对用例(有探针不交付 / 无探针且定向补事问完才交付)。 - 防复发:
tied_first交付测试必须成对——有带年月探针时不交付,无探针且定向补事问完时才交付。不得把开局窗口原样宣布为可采用。不得为了让并列局面收尾而放宽can_adopt。 - 相关记录:BUG-680、BUG-681、BUG-682、BUG-674
- 复发自:BUG-680(修复实现比任务书更宽,测试用空探针锁的是「没有探针才交付」,实现无此守卫)
- 修复版本:待发布
BUG-684 | 交付态下死卡仍把缺口打成「没有拿到下一个问题」
- 状态:investigating
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
interviewDeliveredGap、rectificationQuestionGapState、rectification-agentic-chat.tsx - 用户现象:决策已是
complete_with_range/tied_first,屏幕上仍有一道没有可点选项的题,并印「没有拿到下一个问题。」+「接着问」。 - 触发条件:交付 outcome 或
stop_reason=tied_first,同时current_question非空且interviewChoiceCardUnavailable为真。 - 根因:待取证。已确认
interviewDeliveredGap原先要求questionMissing === true,有题时delivered不触发,死卡走questionLoadFailed→unavailable。写入路径尚未钉死。 - 修复:本轮只做兜底与取证。
questionMissing || deadChoice时允许进入delivered;死卡且处于交付态打rectification_delivered_state_dead_choice(session_outcome、stop_reason、question_id、has_choice_card)。非交付态死卡仍unavailable,不误伤 BUG-671。 - 验证:
frontend/tests/rectification-tied-first-fix-20260914.test.ts锁定交付+死卡 →delivered,采集+死卡 →unavailable。 - 防复发:交付态不得再出现「没有拿到下一个问题」。根因确认前不得把本条标为 resolved。
- 相关记录:BUG-682、BUG-674、BUG-671、BUG-683
- 复发自:BUG-682(
delivered终态要求无题,死卡把会话钉在问题态) - 修复版本:待发布
- 证据缺口:同一会话 GET 的
current_question、choice_card、question_source,以及最后一条助手 turn 的offer_result_id。缺这四项之前不得臆断写入路径。
BUG-685 | 点「再答两道参考题」服务端成功,对话区没有任何变化
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
requestTieBreak、loadCaseSnapshot、applySnapshotTurnsToMessages、rectificationQuestionGapState - 用户现象:点「再答两道参考题微调排序」后 POST 返回
ok: true、choiceReady: true,界面仍是原来的范围卡,新参考题看不见。 - 触发条件:范围卡在场时点参考题入口;服务端已写入助手 turn 和活动焦点。
- 根因:
requestTieBreak调用不带合并回调的loadCaseSnapshot(),新 turn 不进messages。交付卡在场使缺口为idle,persisted_question也不会兜底画这道题。 - 修复:
requestTieBreak用与submitChoice相同的mergeTurnQuestions/appendUnseenAssistantTurns。未答焦点优先于「等读者点卡」的 idle。 - 验证:
frontend/tests/rectification-tiebreak-before-card-20260914.test.ts。 - 防复发:服务端返回 ok 而界面无变化,一律视为缺陷。任何新写 turn 的前端动作必须把 turn 并进对话区。
- 相关记录:BUG-663、BUG-666、BUG-667
- 复发自:BUG-663(入口只写焦点,前端刷新不合并 turn)
- 修复版本:待发布
BUG-686 | 平局直接出卡,参考题留成卡下可选按钮
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
decideRectificationheldForTieBreak、persistNextInterviewAfterChoice/persistNextInterviewIfIdle、范围卡按钮 - 用户现象:带年月题和定向补事问完后直接出范围卡;「你平时做事」「感情里你倾向」没被问。答「没结过婚」后连感情风格题也不再出现。
- 触发条件:带年月池空、即将交付;参考题仍可用且未用过。或该领域没有带年月事件。
- 根因:性格题被设计成出卡后的可选入口。另:
varga_style被错误要求该领域账本有带年月事件作锚,答「没有」会连带关掉风格题。 - 修复:产品拍板放宽版 A(2026-09-14)。出卡前只要参考题可用且没用过就先连问最多两道
varga_style(不看分差;仍只 ±1、不淘汰)。删除风格题的事件锚点丢弃;分盘上升星座少于两种时该题仍不出现。仍平时卡上写「这两分钟按现有信息分不开,参考题已经用过。」用过之后不再显示入口按钮。 - 验证:
frontend/tests/rectification-tiebreak-before-card-20260914.test.ts:16/16 与 lead=2 都先问参考题;无事件锚点时风格题仍出现;两道用完才交付。 - 防复发:不得把性格题改成淘汰候选。不得为凑满两道而放宽「该分盘至少两种上升星座」。自动进入参考题时不得再写第二条交付消息。卡上入口已于 BUG-706 删除,前置收集是唯一路径。
- 相关记录:BUG-663、BUG-667、BUG-629、BUG-597、BUG-706、BUG-708
- 复发自:BUG-663(入口做成出卡后可选)
- 修复版本:待发布
BUG-687 | 交付文案把已经拒答的线又列一遍
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
rangeNarrowHint - 用户现象:结婚、收入、家人都答过「没有/跳过」,范围卡仍写「能把它们分开的是这几条线:哪年结婚或订婚、哪年收入明显变过、家里哪年添丁」。
- 触发条件:定向补事已拒答或已关闭,随后交付区间。
- 根因:
rangeNarrowHint调targetedCollectPool时传入空的declinedTopics,有意忽略拒答。 - 修复:传入真实
declinedTopics。一条都不剩时写「能问的都问完了」,不再暗示还有没问的线。 - 验证:
frontend/tests/rectification-tiebreak-before-card-20260914.test.ts。 - 防复发:交付文案不得列出已答「没有发生过」的线。跳过的线按服务器计划最多再问一次,不在交付文案里当还开着的线列出。2026-09-16 产品修改防复发口径,见
TASK-rectification-precision-gate-guided-collect-20260916.mdD4。 - 相关记录:BUG-661、BUG-662、BUG-741
- 复发自:无
- 修复版本:待发布
BUG-688 | 风格题前置把耗尽与收口路径的交付也压住了
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
decideRectificationshouldHoldForTieBreak、耗尽 / closed-ceiling 出口 - 用户现象:带年月题和定向补事都问完、本应出范围卡时,若风格参考题拿不出来,会话停在「还在区分」且没有题。门禁上 6 条「该交付却不交付」变红。
- 触发条件:
pendingTieBreak为真,但风格题因分盘只有一种上升星座、焦点落库失败或已 hold 过一次而无法渲染;路径是 exhausted / closed-ceiling / 无载体收口。 - 根因:BUG-686 把「还有没问的风格题」做成无条件延迟交付,插在所有交付分支之前,没有出口、没有次数上限。
6fd7925a只把断言改绿,行为未修。 - 修复:只有真正拿得到可渲染风格题时才 hold;exhausted、closed-ceiling、holdout 不可用的收口不 hold;同一 Case 最多 hold 一轮。恢复「恰好一条交付闸」和「参考题确认语不算出口」两条断言。
- 验证:
frontend/tests/rectification-tiebreak-before-card-20260914.test.ts、rectification-exhaustion-exit-20260906.test.ts、rectification-probe-pool-exhausted-20260911.test.ts。 - 防复发:任何「延迟交付」的守卫都必须带出口,且不得作用于耗尽与收口路径。验收必须比对
npm test失败数与基线。不得把参考题确认语算作出载体。 - 相关记录:BUG-686、BUG-656、BUG-674、BUG-680、BUG-597、BUG-689
- 复发自:BUG-686(风格题前置扩大到所有交付分支)
- 修复版本:待发布
BUG-689 | 七条采集线问完后只说「能问的都问完了」,用户不知道还能补经历
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
rangeDeliveryCollectClosed、rangeNarrowHint、范围交付卡、交付后补证据 - 用户现象:结婚 / 收入 / 家人等固定线答过「没有/跳过」后,范围卡只写「能问的都问完了」。用户不知道还可以再说一件带年月的经历,也不知道补了会重算。真机并列约 26% / 26% / 20%。
- 触发条件:定向补事七条线全部关闭或覆盖,随后交付区间;前两名相对可能性差 ≤ 3 个百分点。
- 根因:BUG-687 把空池收口写成终局句。采集线关闭不等于校正结束:交付后 Case 仍是
candidate_ready,输入框不禁用,新的带年月证据会改账本指纹并让快照过期。缺的是邀请;过期快照若仍带着tied_first停止原因,缺口状态机会继续停在delivered。 - 修复:空池文案改为不限领域的补充邀请:先请记得确切哪一天的事,再退年月;举七条线之外的例子;不承诺定到分钟。并列且线已关闭时,邀请出现在交付卡正文,不躺在浅色说明里。快照因新证据过期时,决策不再把
tied_first等耗尽停止原因带进采集,缺口才不会停在交付态。 - 验证:
frontend/tests/rectification-open-collect-invite-20260914.test.ts。 - 防复发:采集线关闭不等于校正结束。2026-09-16 起自由文本邀请删除,改为系统点名的引导题 + 年/月选择器(BUG-740)。不得把已经答「没有发生过」的线再列一遍(BUG-687)。
- 相关记录:BUG-687、BUG-646、BUG-651、BUG-653、BUG-688、BUG-590、BUG-740
- 复发自:BUG-687(空池收口写成终局句)
- 修复版本:待发布
BUG-690 | 家人推算的出生时间与医院记录等同对待,输出直接称「你的出生时间」
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-14
- 影响面:
birth_time_source投影、交付卡、宫位板、采用旁白 - 用户现象:录入时已选「医院记录 / 家人记得 / 只知道时段」,校正链里决策和文案不看这个字段。家人事后推算的时间和出生证上的分钟被同样称作出生时间。
- 触发条件:
approximate或period_onlyCase 进入交付或采用;或hospital_record的分钟落在目前范围外。 - 根因:
birth_time_source只在放宽窗口时改写过一次,公开投影和用户文案没有来源标签。 - 修复:GET /
decideFromDossier投影带birth_time_source(缺失按approximate)。文案分档:医院记录称「出生记录时间」并可并列与目前范围的差值(不站队);家人记得称「你填的大概时间」;只知道时段称「你给的时间段」。不改打分与搜索窗中心。医院记录与范围冲突时如何取舍挂给产品负责人。 - 验证:
frontend/tests/rectification-birth-time-provenance-20260914.test.ts、agent-voice-copy-contract.test.ts。 - 防复发:
birth_time_source不是录入时的一次性字段,任何指代出生时间的输出都必须按它分档;推算值不得表述为已证实的出生时间。 - 相关记录:BUG-587、BUG-621、BUG-689
- 复发自:无
- 修复版本:待发布
BUG-691 | 医院记录与校正区间冲突时只并列差值,不说默认按哪个排盘,用户不知道点「更像这个」会换掉什么
- 状态:resolved
- 首次发现:2026-09-14
- 最近更新:2026-09-16
- 影响面:
frontend/src/lib/rectification-agentic/birth-time-provenance.ts、v9/divergence-panel.ts、components/rectification-range-delivery.tsx、frontend/docs/VOICE.md、frontend/DESIGN.md、Skillreferences/candidate-comparison.md - 用户现象:录入选了「医院记录」且记录那一分钟落在目前范围外时,交付卡只有一句「出生记录时间 04:40。目前范围不含这一分钟,相差 8 分钟」。没说默认按哪个时间排盘,也没说还能继续用记录;采用按钮仍写中性的「更像这个」,点下去实际上把之后的排盘从出生记录时间换成了另一分钟,用户看不出自己在换什么。
- 触发条件:
birth_time_source = hospital_record,且reported_birth_time落在credible_range之外,进入区间交付卡。 - 根因:BUG-690 定了三档来源标签,但把「记录与范围冲突时站哪边」显式挂给产品负责人未决,所以冲突文案只实现了并列差值的前半句;采用入口本就是为非冲突态设计的「更像这个」,没有随来源分档。
- 修复:产品负责人 2026-09-14 拍板「记录优先,分歧如实呈现」(依据:
docs/research/cluster_width_2026_09_14.md,封存 20 例六题后头名簇命中率 0.80 / 0.55 / 0.35 对 ±10 / ±30 / ±60 分钟窗,证据强度撑不起推翻书面记录;但医院记录确实会因事后补记、五分钟整数舍入、家属转述而错,故不得反过来宣布校正结果无效)。reportedTimeOffsetCopy的冲突分支补满三层:默认仍按记录排盘 / 经历指向另一段时间差 N 分钟 / 两条路都可以走。新增recordRangeConflict作为唯一的冲突判据,经RangeDeliveryProjection.record_conflict投给交付卡:冲突态按钮写「改用校正结果」,按钮下补一行「选它之后,排盘会从出生记录时间 hh:mm 换成 hh:mm」。只改文案与措辞,不改打分、判据、采用 RPC、置信度与确认门控,不动数据库,不新增入口。 - 验证:
frontend/tests/rectification-record-conflict-copy-20260916.test.ts(冲突判据只认落在范围外的医院记录;冲突文案含三层且不触FORBIDDEN_RECORD_VERDICT_PHRASE;记录落在范围内的文案逐字不变;冲突态按钮与逐列确认句;approximate/period_only/ 记录在范围内三态仍是「更像这个」且无确认句;投影往返后候选与代表分钟不变)。rectification-birth-time-provenance-20260914.test.ts一条断言按「原值 / 新值 / 原因」三栏改写(该用例的 05:12 对 04:48–05:07 本就是冲突态)。tsc --noEmit0 错,npm run lint0 error,npm test失败数与基线同为 31 条且逐条同名(全部是无 Docker / 无外网的既有环境缺口)。 - 防复发:记录与校正冲突时默认站在记录一边,但两者必须并列;系统不得宣布任何一方无效——不得写「记录不准 / 记录错了 / 以证据为准」,也不得写「校正结果无效 / 不作数」。该禁令由
FORBIDDEN_RECORD_VERDICT_PHRASE在文案层锁住,并写进frontend/docs/VOICE.md与 Skillreferences/candidate-comparison.md§6.1。冲突态改的只能是措辞,采用仍是校正采用时间,不是已确认的唯一出生分钟(BUG-587 红线维持)。 - 相关记录:BUG-690、BUG-587、BUG-621
- 复发自:无
- 修复版本:待发布
BUG-692 | 交付邀请语写「这两分钟」却与同段「4 个候选」矛盾;5 条契约断言未跟上
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
rangeDeliveryCollectClosed、rangeNarrowHint、exitAppendCalls、publicDecisionFields、精度研究 M1b 判定 - 用户现象:范围卡旁白先写「还剩 04:47–04:53 里 4 个候选」,紧接着写「这两分钟按现有信息分不开」。同时
npm test从基线 27 条涨到 32 条:4 条 exhaustion-exit 因短语表不含新交付文案判成 0 条出口载体,1 条 decision-authority 深比较缺birth_time_source。 - 触发条件:固定七条采集线关闭后交付;剩余候选数 ≠ 2。或在含 BUG-689/690 的树上跑全量
npm test。 - 根因:BUG-689 把 tie-break 的「这两分钟」硬编码进任意候选数的邀请语;BUG-689/690 改了用户可见文案和公开字段,但没同步两处锁契约的测试。staging 当时部署仍是
cf972f40,未影响真实用户。 - 修复:邀请语按候选数出话(2 → 「这两分钟」;>2 → 「这几个候选」;未知 → 不说这半句)。
exitAppendCalls()加入「收到 … 这 N 分钟 / 按现有信息分不开 / 说出来我接着算」,保持assert.equal(gates.length, 1),不加「只微调排序」。公开字段深比较补birth_time_source。M1b 的 V1/V2 从no_benefit勘误为not_measured。 - 验证:
frontend/tests/rectification-open-collect-invite-20260914.test.ts、rectification-exhaustion-exit-20260906.test.ts、rectification-decision-authority.test.ts。 - 防复发:文案里出现的数字必须来自同一份数据,不得硬编码;改用户可见文案或公开字段时,必须同步跑
npm test并把失败数与基线比对。 - 相关记录:BUG-689、BUG-690、BUG-688、BUG-687、BUG-587
- 复发自:BUG-689(邀请语复用两候选句)、BUG-690(公开字段未进深比较)
- 修复版本:待发布
BUG-693 | 专业报告 result_hash 只绑中间态,交付包一半键不在覆盖里
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
build_professional_report_reference_packet、bind_result_to_profile、full_report_quality_gateprovenance:result_binding - 用户现象:专业参考包回执自称绑定完整,质量门
provenance:result_binding恒 passed。实测第一次哈希 18 个顶层键 / 6.24 MB,交付对象 36 个顶层键 / 10.04 MB;full_report_pack、chart_identity、timing_precision_contract、birth_provenance、rectification_evidence_contract等 18 个键未进result_hash。 - 触发条件:
pl9-export --pack full。虚构盘1990-05-17 09:26 +08 / 31.23,121.47。 - 根因:
attach_calculation_profile在已有 profile 时早退。专业参考链末尾那次调用被当成重绑,实际什么都没做。质量门只比result_binding与input_hash/result_hash自洽。 - 修复:对
sanitize_professional_report_reference之后的交付对象显式bind_result_to_profile。不改通用早退语义。质量门新增provenance:result_binding_scope:按回执排除集重算哈希,缺 scope / 意外排除 / 对不上都blocked。 - 验证:
tests/test_calculation_profile_contract.py、tests/test_full_report_quality_gate.py;虚构盘三次 CLIresult_hash一致,覆盖 33 个顶层键且含上述五个必覆盖键。 - 防复发:专业参考链的绑定必须发生在返回对象上;排除集必须显式落在回执里并被质量门校验。
- 相关记录:BUG-694、BUG-576
- 复发自:无
- 修复版本:待发布
BUG-694 | 同一份输入两次专业报告 result_hash 不同
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
bind_result_to_profile哈希载荷、result_binding.binding_scope - 用户现象:
input_hash相同,两次 CLI 的result_hash不同(f4f65216…/d841a4b3…)。回执只能证明自洽,不能证明同输入同结果。 - 触发条件:同一虚构盘连续跑两次
pl9-export --pack full --format json。 - 根因:被哈希的载荷含墙钟字段(
elapsed_seconds、分阶段耗时、VedAstrocalled_at/ cache 时间)。kendra_lords由list(set(...))生成,跨进程顺序不稳定。 - 修复:
binding_scope显式排除自引用(report_quality_gate、shared_full_report_authority)、生成时刻(generated_at以及**.called_at/**.cache_created_at/**.cache_expires_at)、墙钟耗时(**.elapsed_seconds)。业务字段仍输出。Kendra / trikona lords 改为排序后的稳定列表。 - 验证:同输入两次构造
result_hash相同;只改elapsed_seconds不变;改coverage必变。虚构盘三次 CLI 哈希2f4739a4…全同,内部elapsed_seconds仍为 2.74 / 2.86 / 2.83。 - 防复发:排除集只能是自引用 / 生成时刻 / 墙钟三类,必须写进回执并由质量门重算。加第四类要改任务书。允许集有字面量合同测试守门,改动必须先过测试再改任务书。
- 相关记录:BUG-693、BUG-576
- 复发自:无
- 修复版本:待发布
BUG-695 | 回答操作触屏热区 27×34,点赞容易点成点踩
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:聊天回答下方复制 / 点赞 / 点踩 / 重新生成,以及代码块复制按钮
- 用户现象:四个图标并排,手机上点赞很容易点成点踩。
- 触发条件:触屏设备上点助手回答下方的操作行。
- 根因:复发自 BUG-446 的有意让步。当时中心距只有 27px、下方 follow-up 只有 8px,44×44 会互相吃点击,热区停在 27×34。触屏推荐仍是 44×44。
- 修复:视觉图标仍是 26×26。
(pointer: coarse)下 gap 和底边距收到 20px,伪元素放大到 44×44。细指针几何不变。代码块复制按钮在粗指针下min-height: 44px。 - 验证:代码级契约测试锁细指针 27×34、粗指针 44×44 且 gap/底边 20px。浏览器级验收见
docs/testing/mobile-touch-and-breakpoints-20260915.md。 - 防复发:
frontend/tests/touch-target-contract.test.ts同时锁两套几何。粗指针热区不得与相邻按钮或 follow-up 药丸重叠。 - 相关记录:BUG-446
- 复发自:BUG-446(当时为避免重叠把热区停在 27×34)
- 修复版本:待发布
BUG-696 | CSS 平板上限 900px 与侧栏 1024 不一致,901–1023 是混合态
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:兑换码表单、模型选择器、首页产品卡在平板宽度下的栅格
- 用户现象:窗口拖到大约 iPad 横屏附近时,侧栏已经是窄版平板,内容区却按桌面排。
- 触发条件:视口宽度 901–1023px。
- 根因:
sidebarViewportForWidth在 1024 切桌面,CSS 另有max-width: 900px当平板上限。 - 修复:这些 CSS 切点改为
max-width: 1023px。主题卡留下的.starter-list > button孤儿规则删除,不改断点再留着。 - 验证:
viewport-breakpoint-contract.test.ts断言@media里不再出现max-width: 900px,且 JS 阈值仍是 768 / 1024。浏览器级验收见docs/testing/mobile-touch-and-breakpoints-20260915.md。 - 防复发:宽度断点白名单契约测试;新切点必须先改白名单。
- 相关记录:BUG-697
- 复发自:无
- 修复版本:待发布
BUG-697 | 报告域 720 / 760 / 860 三个断点互不对齐
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:报告中心、专业报告阅读页、报告目录
- 用户现象:761–860px 目录已经塌成抽屉,正文还是桌面内边距;721–760 阅读区已窄、报告中心还是桌面。
- 触发条件:视口落在那两个混合区间。
- 根因:报告中心用 720、阅读区用 760、目录用 860,彼此和全局 767 都不对齐。
- 修复:720 与 760 并入 767。860 保留并就地注释:目录是 1120 阅读页的第三栏,必须比正文更早塌。会员 639/640 与校正 640/641 统一为 640/641。录入卡 620 并入 640。toast / 校正面 430 并入 480。
- 验证:白名单契约测试抽出的
@media宽度集合等于 480 / 640 / 641 / 767 / 768 / 860 / 1023 / 1024。浏览器级验收见docs/testing/mobile-touch-and-breakpoints-20260915.md。 - 防复发:
viewport-breakpoint-contract.test.ts;globals.css顶部白名单注释。 - 相关记录:BUG-696
- 复发自:无
- 修复版本:待发布
BUG-699 | 打开历史校正会话会改成今天的标题并顶到列表最前
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:侧栏历史列表、
openRectificationCase、persistSession的 update 标题字段 - 用户现象:点开一条以前的生时校正,标题变成今天的日期,并出现在列表最上面。刷新后位置会回去,标题不会。
- 触发条件:点侧栏里已有的校正会话,或从首页校正卡打开一条已存在的可续校正。
- 根因:复发自 BUG-553。
openRectificationCase构造merged时pinned/archivedAt从existing继承,title和updatedAt却无条件用墙钟重算,再persistSession把标题写回服务端。persistSession的 update 不写updated_at(BUG-553 的修复仍在),所以排序只是客户端暂时错位。BUG-553 的防复发当时只是一句话,测试只锁了元数据 PATCH 和改名/收藏/归档/换模型/换资料,没有覆盖校正 open。 - 修复:已有会话的
title/updatedAt继承existing,只有新建才resolveSessionTitle+timestamp()。错日期标题按created_at的 Asia/Shanghai 月日修回;对不上正则的手改标题不动。存量错标题的修补已改为一次性迁移20260916020000_rectification_session_title_repair.sql,随Migrate Staging Database按钮应用(原先只写在frontend/scripts/repair-rectification-session-titles.mjs里,要SCHEMA_DATABASE_URL才能跑,产品没有执行通道,所以一次都没跑过);该脚本降级为只读核对工具。 - 验证:
frontend/tests/session-open-preserves-identity.test.ts、rectification-session-title-repair.test.ts、rectification-session-title-repair-migration.test.ts(比对迁移与核对脚本的 SQL 是否仍逐字一致)。浏览器级验收见docs/testing/rectification-open-identity-20260915.md。欠账:staging 标题修回行数待产品点一次Migrate Staging Database后,从运行日志的notice ... repaired_titles=回填。无 Docker 环境跑不了npm run test:db,迁移未实跑过。 - 防复发:构造可能作用于已有会话的
ChatSession时,不得无条件写updatedAt: timestamp()。resolveSessionTitle的at默认墙钟,只能用于新建。 - 相关记录:BUG-553、BUG-704、BUG-705
- 复发自:BUG-553
- 修复版本:待发布
BUG-700 | vendored 七政引擎四柱时柱按 UTC 墙钟计算
- 状态:blocked
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:vendored
@4n6h4x0r/stem-branch0.8.0 CLI 的--pillars;本仓/api/qizheng适配器 - 用户现象:把带时区偏移的 ISO 时刻交给
--pillars时,时柱按 UTC 墙钟而不是出生地本地民用时。中国用户会得到错 8 小时的时柱;午夜前后日柱也会错。 - 触发条件:CLI
--pillars --json且--date带+HH:MM/-HH:MM偏移。 - 根因:同一时间入口同时服务「天文时刻」(要 UTC)和「历法干支」(要本地民用时)。本轮不修引擎。
- 修复:本轮不调用
--pillars/--luck/--polaris/--qimen/--liuren。/api/qizheng只走七政四余库函数。四柱不在本单产品范围。 - 验证:适配器源码与
_NODE_EVAL不含上述子命令;tests/test_qizheng_chart_engine.py锁死不得调用。三组对照(同一本地墙上 13:24):带+08:00偏移 → 时柱为卯;去掉偏移 → 时柱为未;写+00:00→ 时柱为未。本地民用时应为未。 - 防复发:绝不把带时区偏移的 ISO 字符串喂给需要本地民用时的子命令。若将来做四柱,必须先解决本条,并继续遵守 BUG-245 / BUG-246「所有星盘计算入口必须统一补算历史 offset」。任务书所称 BUG-905 与此同族;本仓当前无独立 BUG-905 条目。
- 相关记录:BUG-245、BUG-246
- 复发自:无
- 修复版本:未修(blocked)
BUG-701 | 计都派别 ketuMode: apogee 未暴露为参数
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:七政四余本命、
scripts/qizheng_chart_engine.py、POST /api/qizheng - 用户现象:引擎默认
ketuMode=apogee,计都落在月孛附近而不是罗睺对点。请求侧看不到、也改不了这一派。 - 触发条件:调用七政四余且未传
ketu_mode/sidereal_mode。 - 根因:vendored CLI 不把
ketuMode/siderealMode做成旗标;适配器若只透传 CLI 默认值,派别会静默生效。 - 修复:
ketu_mode与sidereal_mode从请求体读取,缺省apogee/{type: modern},回写进calculation。实现走dist/index.cjs的getSevenGovernorsChart(..., options),因为 CLI 没有对应旗标。 - 验证:
tests/test_qizheng_chart_engine.py覆盖默认回写与descending-node覆盖(计都与罗睺约 180°)。 - 防复发:计都派别与宿度起算口径必须是显式参数,不得只靠引擎默认。
- 相关记录:BUG-702
- 复发自:无
- 修复版本:待发布
BUG-702 | 适配器 boundary 把空神煞与未闭合庙旺说成已生成
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
scripts/qizheng_chart_engine.py的boundary/dignities - 用户现象:文案写已生成神煞和当前庙旺;实测神煞为空,11 曜庙旺除太阳「陷」外全是「平」。
- 触发条件:读取七政四余响应的 boundary / dignities。
- 根因:boundary 按引擎能力清单写,不按本次实际输出写。庙旺表 132 格只闭合 9 格,
runtime_promotable_count = 0,不能当判定用。 - 修复:boundary 不再出现「神煞」二字;庙旺带
unclosed且may_enter_conclusions=false,并引用 132/9/runtime_promotable_count=0口径。解读层、报告层、咨询层不得引用庙旺。 - 验证:
tests/test_qizheng_chart_engine.py、tests/test_qizheng_api_productization.py断言 boundary 不含「神煞」,dignities.status 为 unclosed。 - 防复发:boundary 必须按本次实际输出写;庙旺在未闭合前不得进入结论。
- 相关记录:BUG-701
- 复发自:无
- 修复版本:待发布
BUG-703 | /api/panchanga_range 岁差写死 Lahiri,与账户默认 Raman 不一致
- 状态:investigating
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
POST /api/panchanga_range、星历/今日路径的五要素 - 用户现象:账户默认岁差是 Raman,本命盘按 Raman 算;同一用户在 panchanga 范围接口看到的五要素口径却是 Lahiri。
- 触发条件:
POST /api/panchanga_range即使请求带ayanamsa=raman,响应report.calculation_policy.panchanga仍写SwissEph Lahiri at sunrise-relative reference time。 - 根因:本轮只记录,不猜测。是统一到 Raman 还是声明 panchanga 永远用 Lahiri,属产品决策。
- 修复:未修。
- 验证:未修,无回归测试。
- 防复发:待产品决策后另出单。岁差必须按请求参数走,不得写死。
- 相关记录:BUG-707(星历页只标注、不修)
- 复发自:无
- 修复版本:未修(investigating)
BUG-704 | 校正答题不推进 chat_sessions.updated_at,会话边用边下沉
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
agentic_rectification_cases.last_activity_at、chat_sessions.updated_at、侧栏历史排序 - 用户现象:一条校正连着答几小时,侧栏位置仍停在创建那天。叠加上被改成今天的标题后,列表变成「名字是今天、排序是旧日期」。
- 触发条件:在生时校正里写 turn、点选、采用或停止。打开、刷新、改名、收藏不触发。
- 根因:复发自 BUG-553 的另一半。全仓只有咨询 RPC
append_consultation_question写chat_sessions.updated_at。校正 turn 走append_agentic_rectification_turn等,只推agentic_rectification_cases.last_activity_at,不碰会话表。persistSession的 update 按 BUG-553 故意不写updated_at。 - 修复:
last_activity_at推进时触发器同步把对应校正会话的updated_at推到同一时刻,且不得回拨。历史冻结行按最后一条 turn /last_activity_at回填,该回填同样已改为一次性迁移20260916020000_rectification_session_title_repair.sql(与 BUG-699 的标题修补同一条),随Migrate Staging Database应用;updated_at <的单调守卫保证只前进不回拨,重复应用影响 0 行。客户端persistSession仍不写updated_at。 - 验证:
frontend/tests/rectification-v9-migration.test.ts的 touch-session 迁移合同、rectification-session-title-repair-migration.test.ts的回填 SQL 合同。浏览器级见docs/testing/rectification-open-identity-20260915.md。欠账:staging 回填行数待产品点一次Migrate Staging Database后,从运行日志的notice ... refreshed_activity=回填。无 Docker 环境跑不了npm run test:db,迁移未实跑过。 - 防复发:校正对话活动必须推进
chat_sessions.updated_at。元数据 PATCH、打开、刷新不得推进。不得为了修这个让persistSession重新写updated_at。 - 相关记录:BUG-553、BUG-699、BUG-705
- 复发自:BUG-553
- 修复版本:待发布
BUG-705 | ?c= 不在已加载页就被当成已删除
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
resolveBootstrapSessionSelection、GET /api/sessions/{id}、首页 bootstrap、popstate - 用户现象:刷新或从链接进入一条校正,提示「该对话不存在或已被删除」,地址栏
?c=被清掉,侧栏也看不见。没有删除动作发生。 - 触发条件:目标会话不在当前已加载的 40 条里(常见于 BUG-704 把它压到旧日期),URL 仍带着它的 id。
- 根因:
listedIds只覆盖当前页。未命中就missing: true并replace-clear。服务端单条 GET 存在却从未被问。 - 修复:合法 UUID 未在已加载页时改为
lookup。GET /api/sessions/{id}200 则并进列表并选中;404 才宣告删除并清 URL;5xx / 网络失败保留?c=,提示「这条对话暂时读不到,请稍后重试。」 - 验证:
frontend/tests/session-lookup-unlisted.test.ts、chat-session-url.test.ts。 - 防复发:不在已加载页不得等于已删除。
SESSION_MISSING_NOTICE只允许在服务端 404 之后出现。 - 相关记录:BUG-553、BUG-699、BUG-704
- 复发自:无
- 修复版本:待发布
BUG-706 | 点卡上「再答两道参考题」后交付卡整张消失
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
applyLiveCandidateOffer、showSelectionCards、区间交付卡 - 用户现象:范围卡在场时点「再答两道参考题微调排序」,卡消失,只剩一句确认语和一道没有选项的题,上一轮「范围在上面」也对不上了。
- 触发条件:交付卡上的参考题按钮;
requestTieBreak写入一道未答choice/collect_spoken。 - 根因:
applyLiveCandidateOffer和showSelectionCards只要有未答题就把每一条candidateOffer剥掉 / 整卡不画。守卫本意是有活题时不要同时诱导采用,但按钮就长在卡上,点一下必然丢卡。产品原意(BUG-686)是出卡前收集参考题,卡上这条路不该存在。 - 修复:删除卡上入口。有活题时卡留下,「更像这个」置灰并写「先答完上面这道,再选时间」。前置收集仍走
requestTieBreakPersonality。 - 验证:
frontend/tests/rectification-tiebreak-card-loss-20260915.test.ts、rectification-candidate-offer-anchor.test.ts、rectification-range-delivery-20260907.test.ts。 - 防复发:任何抑制交付卡的守卫都必须有出口。不得再在交付卡上放参考题按钮。新写 turn 仍须并进对话区(BUG-685)。
- 相关记录:BUG-685、BUG-686、BUG-688
- 复发自:BUG-686(卡上保留了第二条路)
- 修复版本:待发布
BUG-707 | 星历页五要素按 Lahiri,星盘页按账户岁差(默认 Raman)
- 状态:investigating
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
/api/panchanga_range、星历页五要素段、星盘页 //api/chart的账户岁差 - 用户现象:同一账户在星盘页看到的是 Raman 盘(账户默认自
80102459起已是 Raman),在星历页五要素看到的却是 Lahiri 口径。 - 触发条件:已登录并打开
/ephemeris;五要素来自/api/panchanga_range。请求里即使带ayanamsa: "raman",report.calculation_policy.panchanga仍是"SwissEph Lahiri at sunrise-relative reference time"。 - 根因:未判定。同一现象以 BUG-703 为准,本条不另开根因。
- 修复:本轮不修。星历页五要素段标注「这一段按 Lahiri 岁差算,和星盘页用的岁差不是同一套。」行运段走
/api/chart、用账户岁差,不加这句。 - 验证:
frontend/tests/ephemeris-page.test.tsx锁定标注只出现在五要素段。真人清单见docs/testing/ephemeris-page-20260915.md。 - 防复发:在产品未拍板前,不得把 panchanga 默认岁差改成与
/api/chart静默对齐,也不得删掉星历页这行标注。 - 相关记录:BUG-703
- 复发自:无
- 修复版本:未修
BUG-708 | 参考题按钮能亮,点开却是没有选项的裸题
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
tieBreakPersonalityAvailable、buildChoiceCard/canRenderYearlessChoice、interviewChoiceCardUnavailable - 用户现象:点参考题后出现「亲密关系里,你更接近哪一种相处方式?」,没有 A/B/C/D。交付卡已被 BUG-706 抹掉,两条路都断。
- 触发条件:
tieBreakPersonalityFollowup拿得到 followup,但styleOptions建不出或分盘上升星座少于两种。 - 根因:复发自 BUG-686。按钮只看 followup 非空;卡片渲染另走
completeStyleOptions+canRenderYearlessChoice。两个判据不一致。也落在 BUG-674 / BUG-675 的裸题家族上。 - 修复:followup 存在且同一套可渲染性判断通过,才算 available,才允许 persist。焦点已落库但没有
choice_card时走既有 repair-exit,不画裸题。 - 验证:
frontend/tests/rectification-tiebreak-card-loss-20260915.test.ts。rectification-tiebreak-before-card-20260914.test.ts既有断言未减弱。 - 防复发:
tieBreakPersonalityAvailable必须复用vargaStyleFollowupRenderable,不得再复制第三套条件。建不出选项的题不得提出,也不得渲染成裸题。 - 相关记录:BUG-686、BUG-674、BUG-675、BUG-706
- 复发自:BUG-686
- 修复版本:待发布
BUG-709 | 交付旁白写「相对支持度」,并与卡上入口自相矛盾
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:采用/交付旁白、
MACHINE_VOICE_LEXICON、validateAdoptNarration、VOICE.md - 用户现象:旁白写「没有年份的分盘题也不再问」「相对支持度都是 16」,同一屏卡上却摆着「再答两道参考题」。
- 触发条件:走到区间交付;Agent 旁白引用了内部计分词。
- 根因:采用旁白指令允许使用「相对支持度数字」。校验只拦未知数字,不拦禁用词。卡上仍保留参考题按钮,与「不再问」打脸。
- 修复:禁用词表与校验都拦「相对支持度」。指令改为「这两个时间按现有信息分不开」。卡上入口删除后,「不再问」与控件不再打架。卡上「相对可能性 %」保持不变。
- 验证:
frontend/tests/agent-voice-copy-contract.test.ts、rectification-tiebreak-card-loss-20260915.test.ts。 - 防复发:用户可见文案不得出现「相对支持度」。
MACHINE_VOICE_LEXICON与validateAdoptNarration双拦。 - 相关记录:BUG-706、BUG-686
- 复发自:无
- 修复版本:待发布
BUG-710 | 七政 ketu_mode / sidereal_mode 收了请求却不传给引擎
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
scripts/qizheng_chart_engine.py、POST /api/qizheng、星盘页七政 Tab 的计都派别文案 - 用户现象:请求
ketu_mode: "descending-node"仍得到月孛派计都位置,响应calculation.ketu_mode却写成 descending-node,没有 warning。 - 触发条件:
POST /api/qizheng带非默认ketu_mode或sidereal_mode。 - 根因:vendored CLI 的
getSevenGovernorsChart(date, location)不传 options。适配器只调 CLI 旗标,旗标里没有派别。_normalize把请求值写进calculation.ketu_mode,把 CLI 实际值另放engine_ketu_mode。 - 修复:走任务书方案 A。
scripts/qizheng_seven_governors.js在内存里取出库函数getSevenGovernorsChart并传入ketuMode/siderealMode。calculation.ketu_mode与engine_ketu_mode都只表示实际生效派别。前端chart-view-mapper读calculation.engine_ketu_mode。 - 验证:
tests/test_qizheng_chart_engine.py新增非默认派别 / 岁差用例;frontend/tests/chart-view-route.test.ts锁 mapper 用引擎字段。 - 防复发:成功响应里
calculation.ketu_mode必须等于engine_ketu_mode。不得再把请求值回写成已生效派别。 - 相关记录:BUG-701、TASK-qizheng-native-chart-20260915、TASK-readonly-pages-fix-20260916
- 复发自:BUG-701(当时只把默认值暴露成参数,没有把参数送进引擎)
- 修复版本:待发布
BUG-711 | 星历页合同断言禁止 sidebar 出现 ephemeris,与星盘页入口冲突
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
frontend/tests/ephemeris-page.test.tsx、frontend/src/components/app-sidebar.tsx - 用户现象:两份只读页单合入后
npx tsx --test tests/ephemeris-page.test.tsx1 红,staging 门禁红。 - 触发条件:chart-page 单按任务书在 sidebar 同时加「星盘」「星历」入口;星历单合同断言
doesNotMatch(app-sidebar.tsx, /ephemeris/)。 - 根因:星历单把「本单不改 sidebar」写成了「sidebar 里不得出现 ephemeris」。前者是文件归属,后者不是产品事实。
- 修复:删掉对 sidebar 的禁止断言,改为只约束本单文件归属(独立
/ephemerisroute、不碰page.tsx)。不删 sidebar 星历入口。 - 验证:
npx tsx --test tests/ephemeris-page.test.tsx全绿。 - 防复发:文件归属断言不得写成产品入口禁止。改既有断言必须写原值 / 新值 / 原因。
- 相关记录:TASK-chart-page-20260915、TASK-ephemeris-page-20260915、TASK-readonly-pages-fix-20260916
- 复发自:无
- 修复版本:待发布
BUG-712 | ephemeris_events golden 存全精度浮点且缺失时自动重建
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
tests/test_ephemeris_events.py、tests/golden/ephemeris_events_raman_20260915_90d.json - 用户现象:同一窗口、同一岁差,事件种类 / 日期 / 星体 / 星座一致,只有
speed_longitude尾数差一位,验收机红。 - 触发条件:golden 在 Windows / Anaconda 3.11.7 生成,Linux / Python 3.13 / 仓库
.venv上assert result["events"] == golden。 - 根因:全精度浮点整体相等跨 pyswisseph / 星历数据会抖。golden 不存在时测试自己写出文件,等于没有 golden。
- 修复:
longitude/speed_longitude量化到 6 位再比;kind/date/body/ 星座字段保持严格相等。缺失 golden 失败,并提示用scripts/generate_ephemeris_events_golden.py重建。 - 验证:
pytest tests/test_ephemeris_events.py;缺失 golden 的定向用例确认不会写文件。 - 防复发:不得对全精度浮点做整体
==。测试不得写 golden。 - 相关记录:TASK-ephemeris-page-20260915、TASK-readonly-pages-fix-20260916
- 复发自:无
- 修复版本:待发布
BUG-713 | 新增 /ephemeris 未同步 capability audit 路由契约,staging 门禁红
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:Gitea
backend-quality-gaterun2635、tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources - 用户现象:
a38a9415门禁 Python 段1 failed, 765 passed。失败断言app_routes在chart之后多了ephemeris。 - 触发条件:星历页独立 route
frontend/src/app/ephemeris/page.tsx合入后跑 capability audit 精确集合。 - 根因:BUG-143 / BUG-454 同类。
_scan_app_routes动态扫描frontend/src/app/**/page.tsx;精确期望列表未列入ephemeris。星历单被要求不碰后端测试,chart-page 只把chart写进了该列表。 - 修复:期望路由集合加入
ephemeris,不隐藏真实入口。 - 验证:
pytest tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources;推 staging 后核对门禁 run。 - 防复发:新增或删除
frontend/src/app/**/page.tsx必须同一提交更新该精确集合,并跑 capability audit。前端矩阵不能替代这条 Python 契约。 - 相关记录:BUG-014、BUG-126、BUG-143、BUG-454
- 复发自:BUG-454
- 修复版本:待发布
BUG-714 | gated-paths.txt 有 vendor/**,quality-gate workflow 的 paths: 没有
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:Gitea
backend-quality-gaterun2636、frontend/tests/staging-backend-workflows.test.ts - 用户现象:run
2636前端门禁# fail 1:gated paths are one list shared by both quality-gate triggers。期望含vendor/**,workflow 实际没有。 - 触发条件:qizheng 单把
vendor/**写入deploy/gated-paths.txt后,未同步.gitea/workflows/backend-quality-gate.yml的pull_request/pushpaths:。 - 根因:清单是单一真相,workflow 过滤器靠合同测试对齐。只改了 txt、没改 yml,合同必然红;同时 vendor 纯改动也不会触发门禁。
- 修复:两个 trigger 的
paths:都补上vendor/**,顺序与gated-paths.txt一致。不改 job 逻辑、不改 deploy-production。 - 验证:
npx tsx --test tests/staging-backend-workflows.test.ts该条通过;推 staging 后门禁 run。 - 防复发:改
deploy/gated-paths.txt必须同一提交改backend-quality-gate.yml的两份paths:。该合同测试就是为这件事存在的。 - 相关记录:TASK-qizheng-native-chart-20260915、TASK-readonly-pages-fix-20260916
- 复发自:无
- 修复版本:待发布
BUG-715 | 星盘页引擎失败被碾成 null,忙和坏盘共用一句「过一会儿再打开」
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
frontend/src/lib/chart-view-service.ts、frontend/src/lib/chart-view-load.ts、frontend/src/lib/chart-view-engine.ts、/chart - 用户现象:星盘打开失败只看到「这张盘这会儿算不出来。资料还在,过一会儿再打开。」服务端日志里看不出是 429、500、超时还是坏 JSON。
- 触发条件:
/api/chart返回非 2xx、超时、JSON 解析失败,或 mapper 抛错。 - 根因:
postEngine把所有失败return null,catch {}吞掉异常且不打日志。用户文案把「等一下会好」和「这张盘有 bug」写成同一句。 - 修复:引擎调用返回
ok / busy / http_error / timeout / bad_payload。每一种非 ok 打console.warn(只含路径、状态码或错误名、耗时,不含出生资料)。429 文案「算盘的服务正忙,稍等几秒再打开就好。」其它「这张盘算不出来,我们已经记录下来了。」mapper 失败同样记日志。 - 验证:
npx tsx --test tests/chart-view-engine.test.ts tests/chart-view-route.test.ts tests/chart-page-view.test.tsx;四种桩各有可区分日志,429 与其它走不同文案。 - 防复发:引擎调用不得用
catch {}吞掉原因,失败必须留服务端日志且用户文案按原因分档。 - 相关记录:BUG-716、BUG-717、TASK-chart-page-blocking-open-20260915
- 复发自:无
- 修复版本:待发布
BUG-716 | /chart 动态 SSR 等完五个引擎调用才开始画,白屏最长 45 秒
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
frontend/src/app/chart/page.tsx、frontend/src/components/chart-page/chart-page-view.tsx、frontend/src/hooks/use-chart-page.ts、/api/chart-view - 用户现象:点侧栏「星盘」后整页空白很久,然后才出盘或失败句。侧栏已改成硬文档跳转,动态路由在服务端等完才吐 HTML。
- 触发条件:从对话进
/chart。/api/chart若慢或挂起,用户盯空白,最长 45 秒(engineTimeoutMs)。 - 根因:
chart/page.tsxforce-dynamic且await loadChartView(),而loadChartView先串行/api/chart再并行四个后续调用。文档卸载重载期间浏览器里什么都没有。 - 修复:
/chart改为静态壳,客户端先画标题和返回,再取/api/chart-view。进页只算主盘;分盘 / Chara / 西洋 / 七政按 tab 或非 D1 chip 按需取。超时改为 10 秒(实测主盘 0.60 秒,10 秒约 16 倍余量,避免再盯 45 秒空白)。等待句用「还没拿到」,不转圈。 - 验证:无
view时外壳可见;挂起主盘不再依赖四个后续调用;源码合同禁止force-dynamic/loadChartView出现在chart/page.tsx。 - 防复发:
/chart首字节不得等待四个后续引擎调用。揭幕后填 tab 不得再上 spinner / 骨架 / 「正在加载」。 - 相关记录:BUG-715、BUG-717、TASK-chart-page-blocking-open-20260915
- 复发自:无
- 修复版本:待发布
BUG-717 | 开一次星盘页占满重计算配额,且「打开即有」印在失败页上
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-17
- 影响面:
assembleChartView的 layer 参数、chart-page-view.tsx眉标、服务端引擎结果缓存 - 用户现象:打开星盘常失败;失败页上还写着「直接计算 · 打开即有 · 不消耗点数」。生产与 staging 重计算并发默认 2,开页并行打
/api/western与/api/qizheng就会占满。 - 触发条件:打开
/chart。同时有第二个人开星盘,或校正 / 咨询在跑,更容易 429。 - 根因:进页无条件并行打两个
HEAVY_COMPUTE_PATHS端点;cache: "no-store"且无记忆;失败分支也渲染同一句速度承诺眉标。 - 修复:开页只请求
/api/chart。西洋 / 七政点到对应 tab 才发,不再占开页配额。同一账户 + 出生资料指纹 + ayanamsa +node_mode的引擎结果内存缓存 5 分钟,资料或岁差一变即换键。失败页删除眉标。不放宽限流。2026-09-17 产品判定成功页「主盘直接算 · 分盘按需 · 不消耗点数」仍是过度提示,连同基础信息「下面是词条式释义,不是对你个人的判断。」一并去掉;按需加载与不扣点的行为不变,只是不再写在页上。 - 验证:无 layers 时
assembleChartView的引擎路径只有/api/chart;成功页与失败页都无 eyebrow、无「不消耗点数」、无「词条式释义」;缓存键随 ayanamsa / node_mode 变化。 - 防复发:开页不得并行打
/api/western与/api/qizheng。不得靠放宽HEAVY_COMPUTE_PATHS或提高JYOTISH_HEAVY_COMPUTE_CONCURRENCY来「解决」429。承诺速度、计费、词条边界的说明不写在星盘页上。chart-page-view.test.tsx成功页与失败页都断言不得出现这些句子。 - 相关记录:BUG-707、BUG-715、BUG-716、TASK-chart-page-blocking-open-20260915
- 复发自:无
- 修复版本:待发布
BUG-718 | 星盘页首屏 /api/chart 同步挂上 VedAstro 外部证据,页面却不读它
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
frontend/src/lib/chart-view-load.ts、GET /api/chart-view、/chart - 用户现象:打开星盘要等零点几秒到数秒;生产上缓存未命中时会同步等外部占星服务。盘面本身用的是本站 Swiss Ephemeris。
- 触发条件:登录后打开
/chart,出生资料齐全,引擎缓存未命中。 - 根因:
/api/chart默认会_attach_vedastro_main_entry_overview(完整 snapshot + 三领域扫描)。跳过要显式传skip_vedastro_main_entry_overview。BUG-161 只给/api/consult装了这个标志。星历页传了,2026-09-15 新建的星盘页没传。chart-view-mapper/chart-view-contract不读这份证据。 - 修复:
birthPayload()带上skip_vedastro_main_entry_overview: true,与星历页同一写法。不改引擎默认行为,解读链仍取外部证据。 - 验证:合同测试锁住星盘 BFF 与星历两处 natal/transit 请求都带该标志;删掉
birthPayload那一行测试必须红。 - 防复发:任何前台页面路由调
/api/chart必须显式声明外部证据策略,并有合同测试覆盖;新增页面路由时按此检查。 - 相关记录:BUG-161、BUG-065、BUG-715、BUG-716、BUG-717、ERR-107、ERR-108、TASK-chart-page-blocking-open-20260915
- 复发自:BUG-161
- 修复版本:待发布
BUG-719 | 官方 vedastro SDK 在 import 时联网自升级,运行期版本与 pin 不一致
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
vedastroPython 包、deploy/railway-api.Dockerfile、bridge 子进程 - 用户现象:镜像按
requirements.txt固定版本安装,进程一启动却去访问 pypi,并可能把包升到更新版本。 - 触发条件:
import vedastro(每次 spawnvedastro_python_bridge子进程都会发生)。 - 根因:
vedastro/update_check.py在导出符号前请求 pypi 并pip install --upgrade。包本身是 REST 客户端,不是本地计算库。 - 修复:镜像在
pip install之后运行deploy/patch_vedastro_update_check.py,把check_for_update改成 no-op;hook 不存在则构建失败。 - 验证:补丁单测(无网络)、Dockerfile 合同(install 之后必须跑 patch)、运行期版本等于 pin(本机若未装该包则 skip)。
- 防复发:第三方 SDK 若在 import 期联网或改写环境,必须在镜像层中和,并用测试锁住 pin。
- 相关记录:ERR-107、BUG-089
- 复发自:无
- 修复版本:待发布
BUG-720 | 无 key 时 VedAstro 免费层同步排队可堵住前台线程数分钟
- 状态:investigating
- 首次发现:2026-09-15
- 最近更新:2026-09-15
- 影响面:
scripts/vedastro_service_adapter.py的_acquire_free_tier_slot - 用户现象:外部证据路径可能卡住很久。是否在生产发生取决于
VEDASTRO_API_KEY是否真的有值,产品负责人尚未回填docs/testing/vedastro-runtime-20260915.md。 - 触发条件:命中官方公共 endpoint 且没有 API key;一次 full snapshot 约 24 个请求,免费层默认 5 个/分钟。
- 根因:名额耗尽时持进程级锁
time.sleep(),没有前台等待预算。 - 修复:增加
VEDASTRO_FREE_TIER_WAIT_BUDGET_SECONDS,默认不超过VEDASTRO_TIMEOUT_SECONDS。超出预算立即返回free_tier_rate_limited降级,不再无限等。生产是否仍会走到这条路径,等清单回填后更新本条。 - 验证:预算为 0 时第二次请求不 sleep、返回体可区分限流降级;既有「窗口内等待一次」回归仍绿。
- 防复发:前台同步路径不得无上限排队;不得靠调大免费层配额绕过第三方额度。
- 相关记录:ERR-108、BUG-065、BUG-161、BUG-718
- 复发自:无
- 修复版本:待发布
BUG-721 | 生时校正把候选分钟不变量放在事件循环里重复计算
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
scripts/active_rectification_event_engine.py、scripts/rectification/scoring_service.py、scripts/rectification/refinement_packet.py - 用户现象:每记一条证据或答一道题都要重算
POST /api/rectification/v5/score,等待明显偏长。不是回归,也不是结果算错。 - 触发条件:一次请求里有多个候选分钟,并且事件带 year/month 采样(采样日把候选×事件再放大)。
- 根因:
build_candidate_static_context引入后,排盘/分盘按候选分钟只算一次,但 Shadbala、Ashtakavarga、Vimshottari 时间轴、Narayana 周期表仍留在_candidate_row的事件循环里,被「候选 × 事件 × 采样日」三重放大。过境盘只依赖事件日期,却按候选分钟在最内层重算。build_refinement_packet在probe_times == grid_times时对_discriminating_event_probe_lists算两遍。scoring_service._cached_rows是加错层的死代码,生产入口从不走。 - 修复:把候选分钟不变量挂进 static context,过境盘在
compute_event_candidate_rows调用栈内用局部字典按日期缓存 chart(规则判定仍按候选算),探针在时间网格相同时复用一次结果;删掉_cached_rows。不改算法、权重、阈值、采样规则,也不修 static context 里 Shadbala 的birth_minute双算。 - 验证:基线
a8d29d1b真实跑出 golden(公开 1990-01-01 北京烟测盘 + 虚构事件);改后candidate_scores与剔除column_compare_ms的decision_receipt逐字相同。计数断言:calc_shadbala/calc_ashtakavarga/build_dasha_timeline/calc_narayana_mahadasha在引擎打分路径上各等于候选分钟数;过境compute_chart等于去重事件日期数;year 精度过境仍早退[];默认探针路径 1 次、refresh_probes且 refresh 列存在时 2 次。 - 防复发:新增的候选分钟不变量必须进 static context,不得留在
_candidate_row的事件循环里;新增的事件不变量不得按候选迭代。记忆化只允许请求内显式传递的 context / 局部字典,禁止模块级lru_cache跨请求持有出生资料派生数据。 - 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-722 | 意图分类器两次异常被说成用户没说清,经历被丢掉
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
classifyTurnIntentWithRetry、POST /api/rectification/agent点选题与ask_candidate_discriminator分支 - 用户现象:点选题下回一句带年月的经历,助手说「我不太确定这句是不是在回答上面的问题」。焦点仍在,但这句话没有当成经历记下,用户只能自己再说一遍。
- 触发条件:
action=message且当前焦点是点选题;意图分类器两次尝试都抛异常(超时、供应商 5xx、网络抖动),或两次都解析不出结构化结果。 - 根因:
classified === null(模型没答上来)与classified.intent === "unclear"(用户确实说不清)被合并进同一条分支,回unclearFocusReply并落库。采集题分支在 BUG-643 已把 null 记成collectIntent=unclassified并 fail-open;点选题没有跟上。 - 修复:
classifyTurnIntentWithRetry增加outcome: classified | unclear | classifier_unavailable。点选题与 discriminator 分支:unclear维持原回复;classifier_unavailable回「这边没接上,把刚才那句再发一次就行。」,不写证据、不推进焦点、不扣点。服务端打rectification_classifier_unavailable日志(只含耗时与次数,不含原文或案例 ID)。采集题collectIntent=unclassified语义不变。不得用关键词/正则兜底。 - 验证:
frontend/tests/rectification-turn-intent-classifier.test.ts:两次抛异常 →outcome=classifier_unavailable;模型返回intent: unclear→outcome=unclear;两条路由文案不同。源码合同禁止!classified || classified.intent === "unclear"。 - 防复发:路由不得把分类器 null 与用户
unclear合并。分类器失败不得回退到关键词、正则或词表。 - 相关记录:BUG-643、BUG-635、BUG-522
- 复发自:无(BUG-522 记录末尾写明「意图分类器超时仍是既有缺口,本单不修」,本单关闭该缺口)
- 修复版本:待发布
BUG-723 | 校正引擎 429 被当成引擎坏了,重算静默失败、范围不动
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
engine-client.tsreadEngineJson、runV9CandidateScore/runV9BlockScan、autoRescoreAfterEvidenceChange、证据轮主持人正文 - 用户现象:记下经历后助手写「记下了:…」,范围一动不动。没有「这次没有重新比较」之类的说明。只在两个人同时校正、重计算闸门饱和时出现。
- 触发条件:
record-evidence-batch已 accepted 后自动重算;Python 返回 429 +Retry-After+ERR_COMPUTE_BUSY。 - 根因:所有非 2xx 压成
engine_request_failed。autoRescoreAfterEvidenceChange整段try/catch返回status=failed且不打日志;系统提示词要求工具静默,模型继续写「记下了」。 - 修复:引擎调用按
busy / http_error / timeout / bad_payload分档,读Retry-After,每种非 ok 打console.warn(路径、状态码或错误名、耗时;不含出生资料)。score / block_scan 对busy按Retry-After退避,上限RECTIFICATION_ENGINE_BUSY_RETRY_LIMIT=2、总退避RECTIFICATION_ENGINE_BUSY_BACKOFF_BUDGET_MS=6_000。仍失败时 projection 带回error_kind,主持人正文写「这次没有重新比较」,并去掉「收窄」类进度句。不改并发闸门。 - 验证:
frontend/tests/rectification-v9-engine-contract.test.ts桩 429 / 500 / 超时 / 坏 JSON;只有 429 触发退避;退避成功后 score 与从未失败路径相同。rectification-v9-stream.test.ts重算失败正文含「这次没有重新比较」、不含「收窄」。 - 防复发:任何调用 Python 引擎的客户端都不得把非 2xx 压平成单一错误码;429 必须单独成档。引擎调用不得用
catch {}吞掉原因,失败必须留服务端日志且用户文案按原因分档。 - 相关记录:BUG-715
- 复发自:BUG-715(那一单的范围写死在星盘页三个文件,防复发没有扫到
engine-client.ts) - 修复版本:待发布
BUG-724 | 两次模型尝试总预算大于路由 maxDuration,重试时无错误码断流
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
runV9AgentTurn、RECTIFICATION_RUN_BUDGET_MS、agent / regenerate 路由maxDuration - 用户现象:发生一次可重试失败(
empty_stream、evidence_not_written)后,用户等三五分钟拿到一个连错误码都没有的断流。 - 触发条件:单次 attempt 210s × 2 = 420s,路由
maxDuration240s。 - 根因:BUG-388 只约束「单次 attempt < maxDuration」,没约束两次尝试总预算。
RECTIFICATION_AGENT_ATTEMPT_TIMEOUT_MS = 210_000还在agent-run.ts与rectification-activity-labels.ts各写一遍。 - 修复:引入整轮
RECTIFICATION_RUN_BUDGET_MS=225_000(小于 240s)。单次超时取min(210s, 剩余预算)。重试还要剩余预算 ≥ RECTIFICATION_MIN_RETRY_ATTEMPT_MS(20s),否则走 host fallback 给出可见正文,不启动注定被砍的第二次尝试。210_000 只在rectification-run-budget.ts定义一处。不把 attempt 砍到 110s,不把maxDuration提到 430s。已盖戳open_question的超时轮保持现状 fail-closed(见既有 stream 测试),不把题干贴进超时回复。 - 验证:
RECTIFICATION_RUN_BUDGET_MS < maxDuration,maxDuration从 agent 与 regenerate 路由源码读取。第一次尝试耗尽预算后返回 retryable,不得发起第二次尝试,必须 host fallback。源码合同:src/里只有一处210_000。 - 防复发:两次模型尝试的总预算必须由测试断言钉死为小于路由
maxDuration,不能只约束单次 attempt。attempt 超时常量只能有一处定义。 - 相关记录:BUG-059、BUG-388
- 复发自:BUG-059(总预算约束);BUG-388 的防复发只写了单次尝试,因此没拦住
- 修复版本:待发布
BUG-725 | 校正会话流式时整条对话每帧重建,已结算消息每帧重跑 Markdown
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
rectification-agentic-chat.tsx消息列表、chat-message-content.tsx结算态 Markdown、chat-message-row.tsx - 用户现象:生时校正会话越长越卡,尤其在正在写出结果的那一轮。历史气泡跟着直播行一起抖。
- 触发条件:校正会话已有十余轮结算消息,再发一条会触发长流式回答。
- 根因:BUG-473 在本文件落地了
stream-frame-buffer(每帧最多提交一次),但校正面没有咨询面那套「已结算历史 / 正在流的那一条」记忆化边界。messages.map内联派生时间轴、varga 句、选择题卡和afterAnswer;ChatMessageRow无memo;结算态走renderProse全量 parse。每帧setMessages只替换直播行对象,但整张列表仍重渲,已结算行连同 Markdown 全部重算。同类前史:BUG-249(巨型容器每次击键全量重渲)。 - 修复:结算态 Markdown 复用
StableMarkdownPrefix(memo+ 按文本的解析缓存);抽出RectificationMessageEntry(memo),把逐消息派生搬进组件,回调经actionsRef更新.current;ChatMessageRow同样memo。不把流式文本搬出messages,不启用 React Compiler,不用React.lazy+Suspense承载 Markdown。 - 验证:
frontend/tests/rectification-settled-render-split.test.ts(源码合同 + 按帧渲染计数:已结算行 4 次不随 5 个流式帧增长,未拆分对照 25 次);chat-markdown-split.test.ts新增「同一段文本连续渲染两次,解析器只调用一次」(改前 2 次,改后 1 次)。改前该计数测试因模块不存在而红。咨询面既有home-streaming-render-split/chat-markdown-split/stream-frame-buffer无断言改动且全绿。 - 防复发:任何流式会话面都必须为已结算消息保留记忆化边界,并由一条按帧驱动的渲染计数断言钉死;新增会话面必须同时补这条断言。流式状态仍须经
stream-frame-buffer提交;流式期间 Markdown 仍走前缀/尾块切分;结算态不得再走无缓存的整篇 parse。 - 相关记录:BUG-473、BUG-249、BUG-250、BUG-260、BUG-617
- 复发自:BUG-473(影响面含本文件,但两条防复发只约束正在流的那一行)
- 修复版本:待发布
BUG-726 | 一轮校正把同一份 Case 档案从数据库取多次
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
loadV9CaseDossier/loadV9CaseCompute、POST /api/rectification/agent、regenerate、Case GET/POST - 用户现象:同一轮对话里档案被重复取回。用户看不到这条,但 2 vCPU 主机上这份 jsonb 投影与 Next.js、Python 引擎抢同一批核。
- 触发条件:一次 HTTP 请求内多处调用
get_agentic_rectification_case_dossier或get_agentic_rectification_case_compute,中间没有写操作。 - 根因:只读投影没有任何请求作用域缓存。每个调用点独立打 Postgres RPC。这不是回归,是 V9 运行时引入以来的分层遗漏。
- 修复:新增
withRectificationRequestCache,挂在客户端对象上。两个只读投影按(fn, p_user_id, p_case_id)缓存 in-flight promise;其它 RPC 与.from(...)一律先清空再转发。生命周期等于一次请求。路由入口各包一处,零调用点改动,不跨请求缓存。 - 验证:
frontend/tests/rectification-request-dossier-cache.test.ts:连续两次读只打一次底层;中间其它 RPC 后必须重读;不同 caseId 互不命中;并发读合并;.from透传后缓存清空;五条路由源码合同。既有 stream 50 / answer-choice 34 一条不改仍全绿。路由预取 + 证据轮case_dossier5→4。 - 防复发:Case 只读投影必须经请求作用域缓存读取;新增只读投影要么进缓存白名单,要么在记录里写明为什么不能缓存。不得把档案挂在模块作用域或
globalThis。 - 相关记录:BUG-176
- 复发自:无
- 修复版本:待发布
BUG-727 | 普通聊天每轮每域同步等外网,超时不取消导致线程池饱和
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
POST /api/consultation_workflow前台路径、execute_consultation_workflow、vedastro_gateway、前台 VedAstro 线程池 - 用户现象:普通聊天每发一条、每个问题域都要等外网;本地排盘只占约 3%。超时后技法表 VedAstro 云状态仍是 blocked,之后每一轮继续白等约 1.5 秒。
- 触发条件:网页咨询
defer_optional_external_evidence: true;同一张盘一天内多轮提问;前台线程池默认 2 个 worker。 - 根因:外网证据本就按「盘 + UTC 日期」组织,却没有按这个键缓存,每轮重新 TLS 握手打
api.vedastro.org。_join_foreground_vedastro超时返回official_blocked但不cancel()后台任务,任务继续跑满预算;join 1.5 s、budget 8 s、workers 2 三个数不在同一处约束,平均每 4 秒一轮就把池子占死。 - 修复:新增独立模块
scripts/vedastro_snapshot_cache.py(目录与 key 函数都与_api_chart_cache分开)。同日命中直接用、零等待、不 submit;1–7 天前先用旧的并后台刷新;entrypoint=daily_starlanguage只接受当天。超时必须future.cancel();join / budget / workers 成组声明,且budget ≤ 2 × join。本单不是推翻 BUG-301:前台仍取官方层,只是用缓存去掉重复等待。 - 验证:
tests/test_vedastro_snapshot_cache.py(同日第二次零网关调用、跨日 stale+刷新不等待、daily_starlanguage 不吃昨天、缓存目录≠chart 缓存、N+1 等待不超过 join、三常数同块且 budget 比例、超时取消排队任务);既有前台赶上/超时降级回归。 - 防复发:前台外部证据必须有「盘 + 日期」级缓存;任何有界等待都必须同时取消它等待的后台任务。
- 相关记录:BUG-161、BUG-301、BUG-718
- 复发自:无(BUG-301 是故意把 VedAstro 放回前台,本单用缓存同时满足 161 与 301)
- 修复版本:待发布
BUG-728 | western_evidence_packet 无人读却每轮每域传约 122 KB
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
POST /api/consultation_workflow响应体、consumer_context.western_spectrum - 用户现象:一轮咨询响应约 52 万字符,其中西洋证据整包约 122 KB;前端解析后丢掉。三个域就是约 366 KB 无效 JSON。
- 触发条件:普通聊天每个问题域调用
/api/consultation_workflow。 - 根因:西洋盘计算是 must-use 层,但整包被无条件放进前台响应。
frontend/src零读取点;被读的是consumer_context.western_spectrum压缩投影。 - 修复:默认不把
western_evidence_packet放进咨询工作流响应,计算与western_spectrum仍保留。MCP、高严谨、显式include_western_evidence_packet/western_oracle_payload仍返回整包。consultationWorkflowResponseSchema为.passthrough(),去掉该键仍能解析。 - 验证:
tests/test_vedastro_snapshot_cache.py默认省略、显式请求保留、oracle payload 仍返回;frontend/tests/consultation-workflow-contract.test.ts无该键仍safeParse成功。 - 防复发:工作流响应新增大字段前必须有读取点;没有读取点的字段不得进入前台响应。
- 相关记录:BUG-727
- 复发自:无
- 修复版本:待发布
BUG-729 | 咨询历史丢掉整轮时模型看不见任何痕迹
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
consultationHistoryWindow、consultationUserTurnContent、POST /api/consult - 用户现象:历史超过模型预算后,追问「刚才你说的那个时间」时模型当成从没说过。单条超长会写「省略 N 字」,整轮被丢掉时什么都不留。
- 触发条件:普通咨询多轮之后,尾巴字符数超过当前模型的历史预算,窗口从最旧整条丢弃。
- 根因:
droppedCount算出来了,route.ts只取.tail。BUG-555 的防复发只写了「不得再按固定 12 条 × 头部截断静默砍结论」,整轮丢弃不在字面里,所以没拦住。 - 修复:
droppedCount > 0时在摘要槽追加与omissionMarker同风格的说明。有摘要时写「更早的 N 轮问答已并入上面的会话摘要」;没有摘要时诚实写结论尚未并入。droppedCount === 0不加这句话。 - 验证:超预算历史的模型可见文本含丢弃说明且轮数等于
droppedCount;零丢弃不加这句话;源码合同断言route.ts读取historyWindow.droppedCount。 - 防复发:咨询历史任何形式的丢弃(截断单条、丢整轮)都必须在模型可见文本里留痕。
- 相关记录:BUG-555
- 复发自:BUG-555(防复发只覆盖头部截断)
- 修复版本:待发布
BUG-730 | 写摘要的阈值写死 16,000,追不上按窗口算出的历史预算
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
historyBudgetChars、consultationHistoryCheckpointChars、shouldCheckpoint、checkpointSessionContextSummary - 用户现象:后台上架中等上下文窗口的模型后,每轮静默丢掉最老的几轮问答,摘要却还没开始写。
- 触发条件:模型
context_window低于约 70,667(例如 64k / 32k)。历史预算已经小于写死的 16,000 字阈值。 - 根因:
shouldCheckpoint用常量 16,000,consultationHistoryWindow用historyBudgetChars()。两个数各写各的,没有「阈值必须低于预算」的断言。 - 修复:阈值改为预算的 0.4(默认 128k 窗口仍是 16,000)。运行时断言阈值 < 预算。检查点把会话模型的
contextWindow传进去。 - 验证:表驱动覆盖 200k / 128k / 64k / 32k / null,逐条
checkpoint < budget;128k 仍为 16,000。 - 防复发:触发摘要的阈值必须由历史预算派生,并由一条断言钉死「阈值 < 预算」在所有合法上下文窗口下成立。
- 相关记录:BUG-555
- 复发自:BUG-555(检查点阈值与窗口预算未绑在一起)
- 修复版本:待发布
BUG-731 | 对话写满后开新对话不继承服务端已有的会话摘要
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
chatSessionCreateSchema、POST /api/sessions、continueInNewChat、startNewChat - 用户现象:这段对话已写满、点「开新对话」之后,模型对刚才的结论一无所知,用户被要求从零开始。
- 触发条件:
append_consultation_question返回session_full,客户端走「开新对话」。 - 根因:新会话是空的。
context_summary不在列表 GET 列里,创建合同也不接收它。即便前端想带,也没有合法入口。 - 修复:创建合同增加可选
continued_from_session_id(保持.strict(),不加context_summary)。服务端按当前用户读源会话摘要,读到才写入新行;读不到、不属于该用户、或为空都静默跳过,仍返回 201。写满出口把当前会话 id 带进创建请求。界面不加「接着上次聊」之类提示。 - 验证:带来源 id 时新行摘要等于源会话;源会话属于别人时新行无摘要且 201;源码合同断言创建 schema 没有
context_summary字段;写满后「开新对话」没有新增提示文案。 - 防复发:会话满员后的「开新对话」出口必须由服务端继承
context_summary;摘要文本任何时候都不得由客户端提供。不得把messages加回列表 GET。 - 相关记录:BUG-464、BUG-555
- 复发自:无
- 修复版本:待发布
BUG-732 | 对话额度把思考文本算进去,十几轮就「已写满」
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
append_consultation_question、POST /api/consult的session_full、咨询会话详情GET /api/sessions/[id] - 用户现象:普通咨询问大约十几轮就提示「这段对话已写满,开个新对话继续吧」。200 条消息那档永远碰不到。
- 触发条件:继续往同一段咨询会话里发问;助手消息带有
thinkingText/thinkingSections。 - 根因:BUG-464 立下的 200,000 字符上限身兼二职却两职都没做好。求和把用户读不到的
thinkingText、thinkingSections算进去,却不算同样入库的techniqueTruth/workflowReceipt/agentExecutionReceipt。这不是回归,是那条上限从一开始就混用了「对话有多长」和「这一行有多大」。 - 修复:新迁移
CREATE OR REPLACE该函数。对话额度仍是 200,000,只累加elem->>'text'。另加物理上限sum(length(elem::text)),算式 50 轮 ×(正文约 4,000 + 思考 4,000 + 分节 3,000 + 三个 receipt 约 3,000)≈ 700,000,取 1,000,000。两档都返回既有session_full。签名、返回列、error_code、advisory lock、request_id幂等、200 条上限、单条 16,000 字校验均未改。 - 验证:
frontend/tests/consultation-session-capacity.test.ts锁定额度求和不含thinkingText/thinkingSections、物理上限算式、签名与session_full。frontend/tests/database-consultation-session-capacity.test.ts用真实 Postgres 覆盖思考不占额度、短正文+大 receipt 撞物理上限、额度边界不先撞物理上限;本机无 Docker,该文件 skip,不得写成通过。既有幂等 / 满员用例未改。 - 防复发:会话上限必须分成两条各司其职的口径:面向用户的对话额度只数用户读得到的正文;面向存储的物理上限必须把整条消息 JSON 算全。新增会存进
messages的字段时,必须明确它进哪一条,不得默认落进对话额度。 - 相关记录:BUG-464
- 复发自:无
- 修复版本:待发布
BUG-733 | 校正记忆化 golden 对全精度浮点做整体 ==,跨机门禁靠运气绿
- 状态:resolved
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
tests/test_rectification_engine_memoization.py、tests/golden/rectification_engine_memoization_v1.json(文件未改) - 用户现象:用户看不见。门禁 glob
tests/test_rectification_*.py在CORE_PYTEST_TARGETS里,换一台机器或基础镜像会因score/margin_percent尾数差 1.0e-4~1.1e-3 而红,CI 绿只是碰巧和生成 golden 的那台舍入一致。 - 触发条件:
test_score_candidates_matches_baseline_golden把 live payload 与 golden 做整体==。 - 根因:golden 存了全精度浮点并整体相等。这是 BUG-712 的同一形状。BUG-712 的防复发只落在
ephemeris_events那一处(量化到 6 位再比),没有仓库级守卫,BUG-721 新写的 rectification golden 重蹈覆辙。 - 修复:只改测试,不动
scripts/。主证据改成同进程差分:同一批 static contexts,A 带四层缓存、B 把ashtakavarga_result/shadbala_result/vimshottari_timeline/narayana_periods置None,compute_event_candidate_rows输出严格相等。golden 改成分档比较:离散字段严格相等;浮点用实测最大漂移 1.1e-3 推出的容差(绝对 2e-3 与相对 5e-4 取更宽者)。禁止调用write_golden()更新那份 JSON。 - 验证:同进程 A/B 逐字相等,且 B 的
calc_shadbala次数显著高于 A(本机 6 vs 0)。反向:把某一层缓存换成全零 shadbala 对象后不再相等。golden 分档比较通过;把 golden 浮点改 1e-2 必须红;把离散字段time改掉必须红。pytest tests/test_rectification_engine_memoization.py与pytest tests/test_rectification_*.py0 failed。golden JSONgit diff无改动。 - 2026-09-16:
decision_receipt新增guided_collect_windows后重生成 golden,diff 仅此一键(62 行),未放宽_assert_tiered_equal、未调用write_golden();同时把_golden_payload()的date.today()冻结,见 BUG-747。 - 防复发:任何 golden 比较都不得对浮点做整体
==;浮点必须量化或带容差,容差数值要有实测依据并写在注释里;能在同进程内做差分证明的命题,不得用跨机 golden 代替。 - 相关记录:BUG-712、BUG-721
- 复发自:BUG-712
- 修复版本:待发布
BUG-734 | 每轮聊天用一次完整业务请求做健康探测,吃光对方限流额度还不复用连接
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/vedastro_gateway.py的probe_official_rest_health/gateway_status/run_gateway_packet;经run_gateway_packet的全部前台路径(professional_reading、rectification_gate、/vedastro/gateway/run) - 用户现象:BUG-727 的快照缓存每轮都命中、响应体从 52 万字符降到 40 万,耗时却几乎没变。逐轮 trace 显示快照命中的轮次仍然多打一次外网,每轮多等约 1 秒。连续提问到第 6 轮时技法表的 VedAstro 云状态会毫无道理地变成 blocked。
- 触发条件:任何经
run_gateway_packet的前台请求;同一分钟内连续 6 轮即可复现假official_blocked。 - 根因:三件事叠在一起。(1)
probe_official_rest_health不是 ping——它 POST 一份虚构 smoke 排盘到官方HoroscopePredictions,拿返回的 Status 当健康信号,本机实测单次 1,034 ms;(2)它经call()且没传skip_rate_limit,每轮都吃掉对方每分钟 5 个限流令牌之一,于是第 6 轮探测被本地限流器挡下、反过来报告服务不可用,真正的业务调用还要和它抢令牌;(3)探测结论没有任何 TTL,而「这个外部服务此刻可用吗」不会每 500 毫秒变一次。这是 BUG-721 同一形状的又一个实例:只依赖静态输入的昂贵结果被放在每请求都走的路径上。BUG-727 只覆盖了快照,没覆盖探测。 - 修复:探测主体逻辑一字未改地挪进
_probe_official_rest_health_uncached();probe_official_rest_health/gateway_status增加force_refresh形参并加进程级 TTL 缓存,run_gateway_packet显式走force_refresh=False。缓存键是影响探测结论的有效配置(mode、official endpoint、self-host endpoint、network enabled、是否配了 API key、JYOTISH_SKIP_LOCAL_ENV),配置一变立刻失效;只记 key 配没配的布尔,绝不存 key 的值。成功 TTL 60 秒、失败 TTL 10 秒(失败必须显著更短,否则对方恢复了还要再瞎等一分钟)。探测在锁外执行,不在等外网时按住进程级锁。force_refresh默认True:诊断端点_compute_vedastro_gateway_status调的是裸gateway_status()且该文件本轮不得改(AGENTS §6),默认实时才能保证运维不会看到一个 60 秒前的假象。连接复用这一半未做——实测证明urllib.request没有连接池,模块级共享 opener 在 6 次请求下仍开 6 条 TCP 连接(AbstractHTTPHandler.do_open每次新建并强制发Connection: close),按任务书设想实现只能通过「是同一个 opener 实例」的断言而省不掉任何握手;真正的连接池需要改所有业务调用共用的call()或新增依赖,另立单。 - 验证:
tests/test_vedastro_health_probe_cache.py14 条——连调 3 次只探测 1 次、推过 TTL 后变 2 次、失败 TTL 过后成功 TTL 之内服务恢复能被看见、失败 TTL < 成功 TTL、配 API key 或换 endpoint 立刻失效、缓存键不含 key 值、network disabled 不碰外网、force_refresh=True每次真探测且默认值被inspect.signature钉死、run_gateway_packet必须传force_refresh=False、返回值被调用方改写不污染缓存、探测期间锁可获取。同口径实测(连续 6 轮前台请求):探测 6 → 1 次,TLS 握手 5 → 1 次,消耗对方限流令牌 6 → 1 个;改前第 6 轮健康结论为假official_blocked,改后 6 轮全部official_verified。既有tests/test_vedastro_gateway.py、tests/test_vedastro_runtime_ops.py断言一条未改仍绿。 - 防复发:前台路径上的外部服务探测必须有 TTL,且不得消耗业务限流额度;失败 TTL 必须显著短于成功 TTL;诊断端点必须保持实时,缓存只能加在前台那条路上。健康探测不得用一次完整业务调用充当。探测与业务调用共用连接这一条仍未落地——在引入带连接池的客户端之前,每次外呼仍各付一次 TLS 握手(本机实测 207 ms)。
- 相关记录:BUG-727(快照缓存,只修了快照没覆盖探测)、BUG-161、BUG-301(前台外部证据的两条既有红线)、BUG-721(同一形状)、BUG-718(不得持锁等外网)
- 复发自:无
- 修复版本:待发布
BUG-735 | 静态规则表达式每个请求重新编译 386 次
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/yoga_engine.py的_eval_custom;所有走 yoga 判定的路径(普通咨询、报告、校正打分) - 用户现象:用户看不见错误结果,只感到慢。单请求 cProfile 里运行时 AST 编译占 9.4%,而真正的占星计算
swisseph.calc_ut只占 2.4%。 - 触发条件:任何一次 yoga 检测。
- 根因:
_eval_custom拿到的是源码字符串cond["expr"],每次调用都重新编译:先eval(expr)(CPython 在内部编译字符串),多语句表达式在这一步抛SyntaxError、被捕获后再ast.parse→ 改写末尾表达式 →compile→exec。表达式全部来自仓库内的静态规则表references/yoga_rules.json(198 条expr、去重 192 条、最长 2,614 字符、其中 103 条是多语句),在进程生命周期内不会变。本机实测每次检测 386 次源码编译(182 次eval字符串 + 204 次builtins.compile)。这是 BUG-721 / BUG-734 的同一形状:只依赖静态输入的昂贵结果被放在每请求路径上。 - 修复:
_eval_custom拆成「造求值命名空间」与「求值」两段。_build_custom_exec_globals每次调用都新建exec_globals(它绑定当前这张盘的ctx)。新增模块级_compiled_eval_code/_compiled_exec_code,各带functools.lru_cache(maxsize=512),缓存的是 code object,不是求值结果;_run_custom_expr不带任何缓存装饰器。语义与改前逐条对齐:先表达式模式、编译期SyntaxError退到改写后的 exec 模式、两条路异常仍全吞结果为None、ast.parse仍对expr.strip()、运行期SyntaxError也仍退到 exec 分支。不改任何 yoga 规则内容、判定口径或置信度边界。 - 验证:
tests/test_yoga_expr_compile_cache.py9 条。等价用同进程差分(BUG-733 立下的做法,不写跨机 golden):参照实现逐字复刻改前代码,规则表全部 192 条去重表达式 × 3 张公开虚构示例盘 = 576 次逐条同值,并有反向验证证明该差分不是恒真。红线断言:缓存里是types.CodeType;同一条house_of('Sun')在两张盘上必须得到 10 与 5 两个不同答案(表达式与多语句两条路都验);_run_custom_expr无cache_info;maxsize有界且 ≥ 去重表达式数。来源合同:规则表在仓库内、全仓cond.get("expr"仅 1 个读取点、生产入口jyotish_engine.py/jyotish_api_server.py的detect_yogas(不传rules_path——未发现任何用户输入能进入expr。同口径实测:每次检测源码编译 386 → 第二次起 0,稳态单次检测 27.4 ms → 3.3 ms,命中 yoga 数三轮均为 70 不变。tests/test_yoga_rules_integrity.py、tests/test_yoga_benchmark_cases.py、tests/test_lakshmi_yoga.py仍绿。 - 防复发:
eval/exec的入参若来自静态规则表,必须缓存编译产物;缓存 code object,永远不缓存求值结果——缓存结果会让所有人拿到同一张盘的 yoga 判定,后果比性能问题严重得多。编译缓存必须有界,且必须有断言证明expr只来自仓库内的静态规则表;若哪天有用户输入能进入expr,立刻停手按安全问题处理。 - 相关记录:BUG-721(同一形状的第三个实例)、BUG-734(同一轮的另一处)、BUG-733(同进程差分怎么写)
- 复发自:无
- 修复版本:待发布
BUG-736 | 纯前端改动打红 Python 源码合同,快速门连续两轮带红合入
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
tests/test_supabase_user_data_contract.py、scripts/run_quality_gate.py --profile quick、frontend/src/app/api/sessions/route.ts、frontend/src/hooks/use-synastry.ts - 用户现象:无(门禁问题,用户不可感知)。
- 触发条件:纯前端轮次移动了
page.tsx/ 路由里的字面量或把逻辑搬进新 hook。 - 根因:
test_supabase_user_data_contract.py用正则扫前端源码,_home_surface()只拼OPTIONAL_HOME_HOOKS白名单里的文件。两轮改动各打红一批断言:149e1ec4把创建路由改走chatSessionCreateInsertRow(...),user_id: user.id字面量消失;状态下沉第二批把合盘搬进新建的use-synastry.ts,它不在白名单里,于是fetch("/api/synastry"与未能存入历史在并集里找不到。三条都是断言过期,归属校验与云端持久化保证本身没丢。 - 为什么没被拦住:这几条是 Python 测试,但断言对象是前端源码。前端轮次的验收口径(tsc / lint /
npm test/next build)不含 Python 快速门,而 AGENTS §10 只要求「任何 Python 改动」跑快速门——纯前端改动落在两张网中间。验收方(Claude)两轮都只跑了前端套件,未跑快速门,因此带红合入。 - 修复:
OPTIONAL_HOME_HOOKS补use-synastry.ts并加注释说明新增 Home 级 hook 必须同步登记;user_id: user.id断言按「原值 / 新值 / 原因」三栏改成userId: user.id(路由)+user_id: input.userId(chat-session-write-contract.ts),两处都断,不放宽。 - 验证:
.venv/bin/python -m pytest tests/test_supabase_user_data_contract.py8 条全绿;run_quality_gate.py --profile quick的 pytest 段 792 passed / 1 skipped / 0 failed(此前 3 failed)。快速门整体仍退出 1,剩余唯一原因是本机无rsync与无 Docker 的既有环境缺口。 - 防复发:改动
page.tsx、Home 级 hook 或frontend/src/app/api/**的轮次,验收必须另跑run_quality_gate.py --profile quick,不能只跑前端套件——有 Python 测试把前端源码当断言对象。新增 Home 级 hook 必须同步登记进OPTIONAL_HOME_HOOKS。取门禁退出码不得隔着管道(| tail会把退出码换成tail的)。 - 相关记录:BUG-729~731(创建路由改造)、BUG-464(会话持久化归属)
- 复发自:无
- 修复版本:待发布
BUG-737 | --font-display 里没有一个字体能加载,中文标题全站落到宋体
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/app/globals.css(--font-display/--font-body)、frontend/src/app/admin/admin.css、frontend/src/components/admin/admin-app.tsx;波及 20 条使用var(--font-display)的规则 - 用户现象:品牌字、顶栏会话标题、首页大标题、入口卡标题、助手回答正文里的
h2/h3、所有弹窗标题、模型选择器标题、登录页大标题,在真实设备上以宋体渲染——笔画细、小字号发虚,与 Inter 正文的字重对不上。 - 触发条件:任何用户、任何页面,一直如此。macOS / iOS 命中
Songti SC;Windows 拉丁走Georgia、中文掉到浏览器默认 serif(SimSun);多数 Android 无Noto Serif CJK SC,同样落到系统默认 serif。 - 根因:
--font-display的值是"Tiempos Headline", "Songti SC", STSong, "Noto Serif CJK SC", Georgia, serif,--font-body以StyreneB开头。这两个是 Anthropic 授权字体,本仓从未加载过它们:git grep "@font-face" frontend/src无命中,frontend/public/下没有字体文件,layout.tsx只用next/font/localvendor 了InterVariable-latin.woff2。栈首两个名字永远不命中,实际生效的是它们后面的中文衬线回退。 更上一层的原因:frontend/CLAUDE_DESIGN.md扒的是 claude.com 营销官网,该文件自己在## Known Gaps写明 claude.ai 产品界面不在范围内。它的「display 用 Copernicus 衬线 400」是拉丁 display face 的规则,被DESIGN.md当成实现契约照抄到中文界面,就成了宋体。 - 为什么没被拦住:没有任何测试或构建检查会验证「声明的 family 是否真的能加载」。相反,
DESIGN.md§3 把这个栈记录成既定事实,personal-report-theme-contract与consultation-entrypoint两个合同测试还把「--font-display+font-weight: 400」锁成了设计契约——等于把 bug 写进了断言。 - 修复:display 与 body 合并为同一条无衬线栈(
var(--font-inter, Inter)→ 系统 →PingFang SC/Microsoft YaHei);display 层级改由字重承担,33 条 display 规则的font-weight: 400抬到500。StyreneB从--font-body、admin.css、admin-app.tsx一并删除。 拉丁衬线方案被实测否决:Newsreader 拉丁可变子集 132 KB(是整个 Inter 文件的 2.7 倍),只为给一个品牌字上衬线;且会把「D10 事业盘怎么读」这类混排标题劈成两种字形,读起来像字体加载失败。CJK 不用衬线是本条的结论。 - 验证:
git grep -nE "Tiempos|StyreneB|Songti|STSong" frontend/src除解释性注释外无命中;tsc --noEmit0 错;npm run lint0 error / 118 warning(与基线同);npm test3346 条、pass 3300 / fail 31,失败清单与基线逐条 diff 一致(均为无 Docker);next build后/仍○ Static,产物 CSS gzip 41,095 → 41,096 字节(+0.002%)。 - 防复发:新增
frontend/tests/font-stack-loadable-contract.test.ts——断言--font-display/--font-body里每一个带引号的 family 都必须在layout.tsx里有对应的next/font/local声明,否则必须属于系统字体白名单。声明一个加载不到的 family 会直接打红。 另:frontend/CLAUDE_DESIGN.md是营销官网的设计系统,不是产品界面的实现契约;引用它之前先读它自己的## Known Gaps。 - 相关记录:BUG-738(同轮的强调色分阶)
- 复发自:无
- 修复版本:待发布
BUG-738 | 亮暗强调色不同源,且 --color-ring 在 @theme inline 里硬编码不跟随
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/app/globals.css的四处亮色声明(@theme inline的--color-ring、:root、@media print里的:root)、frontend/DESIGN.md - 用户现象:亮色下强调色是深棕
#85432f,暗色下已经是 coral#d78064,同一个产品两套观感。 - 触发条件:切换明暗主题。
- 根因:暗色块早就带着注释「
#85432fis too dark to read as an action on」自行改用了 coral,亮色没跟。另有一处更隐蔽的:--color-ring写在@theme inline块里,是硬编码十六进制而不是var(--color-focus),所以它不跟随:root——任何改强调色的轮次只要没单独改它,就会出现「按钮变了、Tailwindring-*的焦点环没变」。 - 修复:产品拍板亮色换 Claude coral
#cc785c。实测发现它不能直接当文字色:对#fbfaf7画布只有 3.14:1,而 62 个调用点里 53 个是color:(含回答里的 markdown 链接)。因此按角色拆成两阶——--color-action: #a9583e(文字与图标,4.85:1)与--color-action-strong: #cc785c(填充、导轨、焦点环、描边,适用 3:1 非文字阈值)。--color-ring改为var(--color-focus);@media print里的第四处:root同步;暗色两块补上--color-action-strong(与--color-action同值#d78064,暗底上一阶就够)。 - 已知缺口(产品已知悉并接受):白字落在
#cc785c填充上是 3.28:1,低于正文 AA。涉及.button-primary与两个头像圆。claude.ai 本身就是这个值,而本轮的目标正是「像 Claude」。正文不受影响——那正是--color-action这一阶要保护的。 - 验证:
git grep -n "#85432f" frontend/src/app/globals.css只剩--report-accent两处(刻意保留,见下);对比度按 WCAG 相对亮度公式手算并记入进度记录;套件与基线逐条一致。 - 防复发:
design-token-contract.test.ts新增断言--color-ring: var(--color-focus),并把两阶 token 的顺序锁进:root正则。改强调色时漏掉@theme inline那处会直接打红。 - 相关记录:BUG-737(同轮);
--report-accent保持#85432f是刻意的——报告是打印纸面,不跟应用强调色走,见DESIGN.md§2 末尾的说明,不要「修」这个不一致。 - 复发自:无
- 修复版本:待发布
BUG-698 | 设置弹窗尺寸随分区变化:dvh 无回退,且仓库唯一那处回退被压缩器吃掉
- 状态:investigating
- 首次发现:2026-09-15
- 最近更新:2026-09-16
- 影响面:
frontend/src/app/globals.css(.settings-modal/.account-modal及全文件 8 处height: …dvh)、设置弹窗四分区、登录页、后台外壳、移动端侧栏 - 用户现象:「设置面板的弹窗大小不一样,选别的就立马缩小了。」宽度没动,高度在跳。
- 触发条件:登录后打开设置弹窗,在个人资料 / 星盘资料 / 账户与点数 / 通用设置之间切换。
- 根因(机制已证实,用户环境未定位):
.settings-modal的height与max-height只写了dvh,没有vh回退。不认识该单位的引擎会整条声明作废,height回到auto,高度改由内容决定;max-height同时作废后连上限都没有,于是内容多的分区高、内容少的分区矮。宽度走vw不受影响,所以看上去只有高度在跳。- 实测(Chrome 151.0.7922.169,1440×900,真实
next build产物 CSS + 逐节点复刻的弹窗 DOM):dvh 正常时四个分区全是 866.80 × 630.40、computed height 640px —— 事故在当前浏览器上不复现;把 dvh 摘掉后四个分区变成 313.23 / 313.23 / 378.58 / 1130.16,宽度不变 —— 与用户描述的形状完全一致。 dvh自 Chrome/Edge 108、Safari 15.4、Firefox 101、Android WebView 108 起支持,因此该机制只在旧 iOS 15.0–15.3、未升级的 Android System WebView、或钉死旧 WebView 的 App 内置浏览器上成立。用户当时用的是哪个浏览器尚未确认,这是本条停在investigating而不是resolved的唯一原因。
- 实测(Chrome 151.0.7922.169,1440×900,真实
- 为什么 BUG-554 的防复发没拦住:BUG-554 的条款是「设置弹窗必须同时声明 width 与 height」,守着它的断言是
assert.match(styles, /\.settings-modal \{[^}]*width:[^}]*height:/)—— 它读 CSS 源文本,只检查声明存不存在,不检查声明是否生效。本次 width 与 height 都老实写着,条款全绿,现象照出。BUG-554 的四个分区共用一个类这一条本轮未被推翻,且新增了直接断言常量的同尺寸契约把它钉死。 - 附带发现(比原判更严重):任务书要求照抄
globals.css:637的重复声明式回退height: 100vh; height: 100dvh;。实测 Lightning CSS(Tailwind v4 的压缩器)会合并同一规则内同名属性的重复声明,只保留最后一条:源码写了两条,产物里.group\/sidebar-provider[data-viewport]只剩height:100dvh。全仓唯一那处 vh 回退在线上早就是死的,而且birth-time-mobile-scroll-contract.test.ts还有一条断言专门守着这个从未发布过的写法。照任务书交付等于交一个 no-op。 - 修复:改用特性查询 ——
vh作基线,@supports (height: 1dvh)内升级为dvh,压缩器无法证伪条件因而整块保留。覆盖凡height(不含max-height/min-height)用 dvh 的 8 处:.standalone-page、.app-loading、.group\/sidebar-provider[data-viewport]、.admin-app-shell、.auth-page、[data-sidebar="sidebar"]、.settings-modal(桌面与移动端);另把.account-modal/.settings-modal的max-height/min-height一并纳入,因为实测里max-height塌成none也是尺寸失控的一环。同轮去掉设置分区菜单的强调色条并拆开选中/悬停两态(产品决策,不占编号)。 - 验证:修复后同 harness 重测 —— dvh 正常的引擎四个分区仍是 866.80 × 630.40 / 640px(与修复前逐像素一致,无回归);模拟不支持 dvh 的引擎从 313/378/1130 收敛到恒定 640px。新增
frontend/tests/viewport-unit-fallback-contract.test.ts(3 条,全文件范围),三次破坏性验证各自打红对应那条、还原后全绿。account-dialog-overlay.test.ts新增同尺寸契约与分区菜单契约,破坏性验证同样打红。tsc --noEmit0 错,npm run lint0 error / 118 warning(持平基线),next build后/仍○ Static,快速门 pytest 段 792 passed / 1 skipped / 0 failed。 - 防复发:
height用dvh必须写成vh基线 +@supports (height: 1dvh)升级,不得用height: 100vh; height: 100dvh;重复声明式回退——后者会被 Lightning CSS 吃掉,从来没发布过。由frontend/tests/viewport-unit-fallback-contract.test.ts全文件强制,三条断言分别管「dvh 必须在特性查询内」「禁止重复声明式回退」「升级过的选择器必须有 vh 基线」。设置弹窗四分区必须映射到同一个类名,由account-dialog-overlay.test.ts直接断言常量。只检查「声明存不存在」的 CSS 文本断言不算防线(这就是 BUG-554 的教训),新写 CSS 契约要么断到实际产物、要么断到可被破坏性验证证伪的结构。 - 相关记录:BUG-554(同一现象的第一次,本条为其复发)、BUG-695~697(触控与断点轮次,同一文件相邻区段)
- 复发自:BUG-554
- 修复版本:待发布
BUG-739 | 咨询会话容量的真实 Postgres 合同把 false: 写成期望值,staging 门禁从 BUG-732 起一直红
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/tests/database-consultation-session-capacity.test.ts、frontend/tests/helpers/postgres-fixture.ts的psql()、Independent Staging Quality Gate(backend-quality-gate.yml) - 用户现象:用户看不见产品行为变化。staging 从
dcfc2f15(BUG-732)起每次含门禁路径的推送都红,镜像不发布、环境不更新。 - 触发条件:质量门跑
npm run test:db。Gitea run2683/ job6103,SHA1e3570b4。 - 根因:
fixture.psql()把boolean::text的false规范成 psql-A的f,好让true:f:f这类冒号拼接在不同 Postgres 表示之间稳定。BUG-732 的真实库合同用了success::text,却把期望值写成"false:session_missing"。夹具一规范化,实际是f:session_missing。本机当时无 Docker,该文件 skip,带红合入。 - 修复:五处失败分支的期望值改成
f:session_missing/f:invalid_question_message/f:session_full,与既有database-billing-admin等合同同一套字母表。夹具改写保留,加一行说明。源码合同锁住「期望值必须是f:、夹具必须保留 false→f 规范化」,不依赖 Docker。 - 验证:
consultation-session-capacity.test.ts新增无 Docker 源码合同;定向套件全绿。本机仍无 Docker,真实test:db留给门禁。 - 防复发:对
fixture.psql()的冒号拼接结果不得断言"false:"。新增真实 Postgres 合同前先对照postgres-fixture.ts的false→f规范化,并用一条不需要 Docker 的源码合同锁期望值。 - 相关记录:BUG-732(引入这条真实库合同但未在有 Docker 的机器上跑)
- 复发自:无
- 修复版本:待发布
BUG-740 | 精度未达仍出交付卡,卡下只有自由文本邀请
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
decideRectification、guided_collect_windows、引导采集池、范围交付卡 - 用户现象:30 分钟窗给了 5 件带年月经历后直接出卡,区间 20 分钟、5 候选并列约 26%/26%/20%。卡下是「你要是还记得确切哪一天的事,不限领域,说出来我接着算」。
- 触发条件:七条定向线关闭、自动题池空、头名并列。
- 根因:
tied_first耗尽分支没有任何宽度或领先度条件。自动题池空之后没有有方向的题源。 - 修复:出卡须宽度 ≤10 分钟且前两名相对可能性差 >3 个百分点且不并列;未达时按引擎边界窗口逐条点名,用类型芯片 + 年/月选择器录入。用户说「没有了」仍按现行规则出卡。自由文本邀请删除。
- 验证:
frontend/tests/rectification-precision-gate-20260916.test.ts、frontend/tests/rectification-guided-collect-20260916.test.ts、tests/test_event_probes_guided_windows.py。 - 防复发:耗尽/offer 出卡必须过
precisionGateMet,除非用户停止或引导题源已空。不得再把自由文本邀请当作精度未达时的下一步。 - 相关记录:BUG-689、BUG-653、BUG-654、BUG-687
- 复发自:BUG-689(空池邀请没有方向,且没有精度门槛)
- 修复版本:待发布
BUG-741 | 「跳过」和「没有发生过」同样永久关闭整条线
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
targetedDomainClosed、定向存在性题、时间点题 - 用户现象:点「这条先跳过」或「记不太清楚」后,那条线再也不会出现。时间点题答「那个时候没发生」后,整条感情线也被当成没有。
- 触发条件:定向存在性题选 C/D;或
source=event_probe的年月是非题选「明确没有发生」。 - 根因:
declined与skipped同样写入关闭集合。存在性题干只覆盖一两个例子却按整个领域记「没有」。时间点负答案没有与领域关闭隔离。 - 修复:
declined永久关闭;skipped换一种更具体的问法再问一次,再次跳过才关闭。存在性题问整个领域、任何时间,B 写成「这类事都没有过」。时间点题的负答案不关领域。 - 验证:
frontend/tests/rectification-guided-collect-20260916.test.ts。 - 防复发:关闭集合不得把一次跳过当成拒绝。时间点题不得写入会让
targetedDomainClosed判定领域关闭的状态。七条线一张表,不得按单个领域写死。 - 相关记录:BUG-687、BUG-643、BUG-740
- 复发自:无
- 修复版本:待发布
BUG-742 | 定向存在性题 B/C/D 回执文案与状态对调
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
targetedCollectExistenceAck、RECTIFICATION_USER_COPY.collectDeclinedAck/collectSkippedAck - 用户现象:答「没有发生过」听到「记下了,这方面先跳过」。
- 触发条件:定向存在性题选 B。
- 根因:B 落
declined、C/D 落skipped是对的;回执把 declined 写成「先跳过」。不是 D 落成 declined。 - 修复:B 回「记下了,这条按没有发生过记」;C/D 回「记下了,这题先放着,后面换个问法再问一次」。
- 验证:
frontend/tests/rectification-guided-collect-20260916.test.ts断言两句回执。 - 防复发:declined 回执不得写「跳过」;skipped 回执不得写「没有发生过」。
- 相关记录:BUG-741
- 复发自:无
- 修复版本:待发布
BUG-743 | 性格题 ±1 写入 posterior_score,可能把卡在 8 分边界的簇推过线
- 状态:investigating
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
applyProbeOutcome、unionStillValidRange - 用户现象:性格题答完后范围从 20 分钟弹到 28 分钟,下一题又缩回。
- 触发条件:未淘汰簇落后头名刚好 8 分,性格题给其中一侧 ±1。
- 根因(代码路径已确认,真机未复现):
varga_style以PROBE_WEIGHT.yearless = 0.5把SCORE_DELTA写进同一份posterior_score。unionStillValidRange用peak - posterior_score < MIN_SEPARATION_LEAD。风格题只该改排序。本轮不改打分,未改并集规则。 - 修复:无。已排除:性格题不会淘汰(
yearless不增加strong_conflict_count)。未排除:并集是否因此扩/缩。 - 验证:未附回归。不得为收口编根因。
- 防复发:风格微调若并入范围计算,必须用一份不含风格分的分数做并集。
- 相关记录:BUG-651、BUG-629、BUG-740
- 复发自:无
- 修复版本:无
BUG-744 | 侧栏写成两套标记,次级页会话行与页脚跟首页长得不一样
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/components/app-sidebar.tsx、已删除的app-nav-rail.tsx、frontend/src/components/sidebar-session-row.tsx、frontend/src/app/globals.css - 用户现象:staging 真机上,
/的侧栏和/chart、/ephemeris、/reports的侧栏不是同一个东西——会话行的高度、内边距、当前项标记都对不上,页脚一个 56px 一个 44px。 - 触发条件:从
/走到任一次级页,对比左侧同一列。 - 根因:次级页侧栏当初为了不把
Home()的会话管理层上提,另写了第二个组件。它的会话行是<Link className="session-row nav-rail-row">直接套.session-title,没有.session-main这一层,因此丢掉了padding: var(--space-2)、min-height: 44px和.session-main[data-active="true"]::before的 2px 当前项标记;而.session-row的grid-template-columns: minmax(0, 1fr) 44px仍给一个从不渲染的菜单按钮留着 44px 空列。页脚另起.nav-rail-identity(44px flex,带「N 点」),与/的.profile-trigger(56px grid、带 chevron)并列存在。产品要的是「不带写操作」,从来不是「长得不一样」;分叉是实现手段的副作用。 - 修复:
AppSidebar收编第二个组件——会话操作与账户菜单收进可选的controls,不传就渲染只读模式:会话行仍是同一个SidebarSessionRow、同一套.session-row > .session-main标记,只是.session-main是<Link>、不渲染菜单按钮、并用data-readonly="true"去掉那一列 44px 空位;页脚是同一个.profile-trigger,渲染成去/的链接,不带 chevron、不带菜单。app-nav-rail.tsx与.nav-rail-*两段 CSS 删除。只读模式只少菜单按钮、chevron、账户菜单三样。 - 验证:
frontend/tests/sidebar-contract.test.ts新增「the same component renders read-only when/is not the one mounting it」,锁controls?可选、只读行带.session-main、只读行不含session-menu-trigger、.session-row[data-readonly="true"]不留 44px 列、页脚是.profile-trigger链接;grep -rn "AppNavRail\|nav-rail" frontend/src= 0。改过的既有断言逐条写在docs/tasks/PROGRESS-sidebar-unify-20260916.md。 - 防复发:产品里只有一个侧栏组件。再要一个「只读侧栏」必须走
controls缺省,不得新建第二个组件或第二套类名;合同测试锁住「只读模式只少三样」。 - 相关记录:BUG-711、BUG-438
- 复发自:无
- 修复版本:待发布
BUG-745 | 首页进次级页整页刷新,次级页外壳逐页重挂,每跳一次重拉会话列表
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/components/app-sidebar.tsx、frontend/src/app/(secondary)/layout.tsx、frontend/src/hooks/use-sidebar-data.ts、frontend/src/lib/sidebar-data-cache.ts、frontend/src/hooks/use-session-management.ts、frontend/src/app/page.tsx - 用户现象:每进一次次级页,侧栏的会话列表都要重新读一遍;从次级页点「新建对话」回首页要等很久。
- 触发条件:
/→/chart;/chart→/ephemeris→/reports。 - 根因:三件事叠加。① 侧栏的
leaveChat()用window.location.assign(path),首页出去是整页刷新,React 树与内存全部清零。②app/下没有把四个次级路由包起来的 layout,三个页面各自在组件内部渲染SecondaryShell,每份都挂一套SidebarProvider + 侧栏,因此即便是客户端跳转外壳也整个重挂,数据 hook 的 effect 重新GET /api/sessions?limit=40+GET /api/account。③ 这两个读没有任何缓存。 - 修复:① 三个页面项改
<SidebarMenuLink href=…>客户端跳转,persistLoginSessionReturn()保留在onClick里,/login仍是硬跳转;顺带删掉从来没被调用过的死 proponOpenReports(侧栏里解构成_onOpenReports),它删掉后page.tsx的router再无消费者,useConsultationRun那个同样解构成_router的死参数一并删。② 四个路由移进app/(secondary)/路由组,layout.tsx承载SidebarProvider + AppSidebar(只读) + SidebarInset,URL 与四个渲染标记均未变。③ 模块级按账户 id 键的 60 s 缓存,同步读、后台刷新;/上新建 / 重命名 / 删除 / 归档 / 收藏成功后调invalidateSidebarCache(),401 清空。不落 localStorage。 - 验证:
frontend/tests/sidebar-data-cache.test.ts4 条(命中不重拉、过期重拉一次、写操作后拿到新标题、双账户不串、只存内存、401 清空);frontend/tests/chart-page-view.test.tsx新增「the (secondary) layout mounts one read-only sidebar for all four routes」;next build四个路由标记与基线逐个一致(/chart○、/ephemeris○、/reportsƒ、/reports/[reportId]ƒ),/仍○。浏览器级证据(Network 面板没有 document 请求、跨页没有/api/sessions)无 Chrome 无登录态做不了,写成docs/testing/sidebar-unify-20260916.md的真机清单。 - 防复发:次级页不得再在组件内部挂第二份
SidebarProvider;合同测试锁 layout 里<SidebarProvider>恰好一次、且不传controls。侧栏源码里不得再出现window.location(/login的硬跳转在page.tsx与use-billing-panel.ts,由chat-navigation-a11y-contract守)。 - 相关记录:BUG-744、BUG-746
- 复发自:无
- 修复版本:待发布
BUG-746 | 侧栏折叠状态不跨页保留,每换一页又展开
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/components/ui/sidebar.tsx、frontend/src/lib/sidebar-state.ts - 用户现象:在
/把侧栏收起来,进/chart又是展开的;反过来也一样。 - 触发条件:桌面或平板宽度下收起侧栏,然后换一页。
- 根因:
SidebarProvider的defaultOpen只活在内存里,ready之后一律回到defaultSidebarOpen(viewport)的断点默认值,没有任何持久化。以前四个次级页各挂一份 provider,跳一次就重置一次。 - 修复:
readStoredSidebarOpen/writeStoredSidebarOpen落在localStorage的sidebar_state键;commitOpen每次显式切换时写入,ready的那个 effect 用readStoredSidebarOpen(viewport) ?? defaultSidebarOpen(viewport)取值。移动端抽屉不读不写。 - 验证:
frontend/tests/sidebar-state.test.ts新增两条——收起后重挂仍收起(含无存储 / 存了脏值 / 存储不可用三种降级)、移动端不读不写且null存储不抛。 - 防复发:不得改用需要服务端读取的 cookie:
/、/chart、/ephemeris都是○ Static,服务端读 cookie 会让三条路由一起掉出静态渲染。 - 已知缺口(不得写成已解决):读取发生在挂载后的同一个 effect 里,也就是断点默认值原本就生效的那一帧。对一个把侧栏收起来的桌面用户,整页加载时首帧仍是展开、随后收起,这是让步顺序第 1 条,任务书允许。客户端跳转(
/↔ 次级页)不受影响,因为 provider 不重挂或重挂时localStorage已可读。 - 相关记录:BUG-745
- 复发自:无
- 修复版本:待发布
BUG-747 | 校正 receipt 新增 guided_collect_windows 键,记忆化 golden 门禁红
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/rectification/decision_policy.py、tests/test_rectification_engine_memoization.py、tests/golden/rectification_engine_memoization_v1.json - 用户现象:用户看不见。
run_quality_gate.py的CORE_PYTEST_TARGETS含tests/test_rectification_*.py,test_score_candidates_matches_baseline_golden红,门禁不放行、镜像不发布,staging 停在上一个 SHA。 - 触发条件:
build_decision_receipt往 receipt 里加了guided_collect_windows,golden 里没有这个键;_assert_tiered_equal对 receipt 做键集合严格相等。 - 根因:形状变更没有同步 golden。BUG-733 的分档比较与「禁止
write_golden()」是对浮点漂移的防线,它按设计拦住了这次的键集合差异——拦截生效了,是上一轮执行方没有跑这个文件(进度记录里 Python 只跑了四个定向文件,没跑tests/test_rectification_*.py全量)。 - 修复:golden 只追加
guided_collect_windows一个键(git diff只有这一处、62 行),不放宽_assert_tiered_equal、不调用write_golden()。另外把_golden_payload()里的date.today()冻结到FROZEN_TODAY:窗口年份取自墙钟,不冻结的话这份 golden 会在跨年时自己变红(BUG-733 同一族的隐患)。 - 验证:
.venv/bin/python -m pytest tests/test_rectification_*.py tests/test_event_probes_guided_windows.py -q→ 193 passed;golden 的git diff --stat为1 file changed, 62 insertions(+)。 - 防复发:改
decision_receipt形状的轮次必须跑tests/test_rectification_*.py全量,不能只跑定向文件;golden 的任何字段都不得依赖墙钟。 - 相关记录:BUG-733、BUG-712、BUG-721、BUG-740
- 复发自:BUG-733(同一份 golden;旧防线拦住了,未被执行)
- 修复版本:待发布
BUG-748 | 16 条既有校正断言变红却被写成「已按三栏改」
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/tests/rectification-*.test.ts(11 个文件)、docs/tasks/PROGRESS-rectification-precision-gate-guided-collect-20260916.md - 用户现象:用户看不见。
npm test从基线 3372 条 / 31 红变成 3391 条 / 47 红,新增的 16 红全在校正套件,没有一个文件被改过。 - 触发条件:上一轮只跑了本单新增与相关的定向套件,全量
npm test因本机无 Docker / 并行 OOM 被判定为「不能当回归清单」,于是没有与基线逐条比对。 - 根因:AGENTS §7.3「测试总数不得低于开工时实测、改断言必须写三栏」被当成只约束主动修改的断言;被代码变更打红的既有断言没有走同一条流程。无 Docker 只影响 31 条固定清单,其余部分照样可比。
- 修复:本轮把 16 条逐条处置(外加 D5/D6 连锁打红的另外 25 条,见 BUG-751),每条在测试里写「原值 / 新值 / 原因」三栏;不删测试、不 skip。
- 验证:
npm test→ 3396 条 / 31 红,红清单与基线 31 条逐条同名(无 Docker 与[eval]路径别名那一组)。 - 防复发:无 Docker 时的正确做法是「与基线逐条比对失败清单」,不是放弃全量;进度记录里不得把没跑过的套件写成已处置。
- 相关记录:BUG-751、BUG-740
- 复发自:无
- 修复版本:待发布
BUG-749 | 引导窗口的领域是轮询贴上去的,一个窗口只问一个随机领域
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/rectification/event_probes.pyguided_collect_windows、collection-question-pool.tsguidedWindowPrompt/guidedWindowPool/windowAlreadyAsked、event-date-entry-card.tsx - 用户现象:「2018 年 3 到 5 月之间,有没有入职、换工作或职责变重?」答「这段没有」之后,这个时间窗再也不会出现——用户那段时间的搬家或恋爱永远问不到。
- 触发条件:边界来自 Vimshottari 或那罗延本命轨道(与领域无关),引擎按
fallback[unlayered_index % len(fallback)]轮流贴一个领域;前端windowAlreadyAsked以(年, 月区间, 领域)为键,而同一窗口只会以那一个领域出现一次。 - 根因:把「哪一段时间能切开候选」和「这段时间发生了哪一类事」混成一个字段。只有
nara:d9/nara:d10两条轨道真的带领域。 - 修复:无领域轨道的窗口
domain = "any",题干写「YYYY 年 M 到 M 月之间,有没有什么事,比如<开放领域口语 2~3 个>?」;口语从KIND_ORAL表取,剔除已拒答与已覆盖的领域,决策层不按领域写if。windowAlreadyAsked的键去掉领域,只看年 + 月区间。A 之后录入卡的芯片默认第一个仍开放的领域。nara:d9/nara:d10保持固定领域;该领域被拒答时也退回any。 - 验证:
tests/test_event_probes_guided_windows.py(真实引擎 20 分钟窗 golden,7 passed)断言无领域轨道窗口是any、d9/d10 保留固定领域、拒答领域退回any;frontend/tests/rectification-guided-collect-20260916.test.ts断言开放题干的口语列表 ≤3 条、同一窗口换领域标签也不重问、芯片默认第一开放领域。 - 防复发:窗口的
domain只能来自真正带领域的轨道;一个边界窗口是一个问题,已问判定不得把领域并进键里。 - 相关记录:BUG-740、BUG-741、BUG-751
- 复发自:无
- 修复版本:待发布
BUG-750 | 录入卡提交的是选项列表拼成的句子,账本 summary 变成三选一
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
frontend/src/components/event-date-entry-card.tsxformatEventDateEntryMessage - 用户现象:用类型芯片 + 年/月选择器提交一件事后,账本里记的是「2019 年 3 月,开始认真关系、分手或结婚」——一句三选一列表,而不是用户选的那一类。
- 触发条件:引导窗口题答「有,我来填时间」后从录入卡提交。
- 根因:提交文本直接拼
KIND_ORAL[domain],而KIND_ORAL是题干用的例子列表,不是事件描述。 - 修复:改成「YYYY 年 M 月(D 日),<领域标签>方面有一件事」,标签取
RANGE_DELIVERY_DOMAIN_LABEL;文本里不再出现「或」。 - 验证:
frontend/tests/rectification-guided-collect-20260916.test.ts断言提交文本与「不含『或』」。 - 防复发:任何写进账本的用户文本都不得复用题干的例子列表。2026-09-16 录入卡按产品决策删除,本条防复发仍适用于任何用户可见文本。
- 相关记录:BUG-740、BUG-749、BUG-908
- 复发自:无
- 修复版本:待发布
BUG-751 | 精度门槛被当成充分条件,把「线没问完不出卡」一起短路
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
core/rectification-decision.tsmayDeliverOnPrecision/decideRectification、v9/decision-from-dossier.tsnarrowingExhaustion、Skill §流程 - 用户现象:门槛达标时,刷新与定向补事都还没问完就直接出交付卡;反过来,任何没有传这两个 flag 的调用(
decideConversationalSession、decideAfterInferenceChange的无 state 分支)因为「缺省即放行」而完全绕开了 BUG-654 / BUG-656 的「刷新与定向线未穷尽不得交付」。另外guidedCollectExhausted()不含refreshExhausted:引导池空、刷新还没试过,也会出卡。 - 触发条件:
precisionGateMet或guidedCollectExhausted任一为真(或两者都缺省)。 - 根因:
mayDeliverOnPrecision写成了「或」,并且被塞进stillNeedNarrowing与datedMethodCollectOpen两条采集分支做短路,等于用门槛去撤销采集条件。 - 修复(产品 2026-09-16 决策 D2 + D6):门槛只在还有题可问时挡住出卡。flag 全部缺省时门槛不参与,采集分支回到
317e9f18的判断;flag 传入时交付条件 =userStopped || (guidedCollectExhausted && refreshExhausted !== false && targetedCollectExhausted !== false)——即userStopped || 所有题源都问完。题源都空了就是 D2 说的「用户确实补不出来」,卡片、采用按钮、三句正文照现行规则给,不加「未达门槛」标注。撤回两处!mayDeliverOnPrecision短路,门槛只加在最终交付分支。narrowingExhaustion计算的guidedCollectExhausted现在含refreshExhausted。precisionGateMet保留计算,并新挂到RectificationDecision.precisionGateMet与publicNextAction().precision_gate_met上报,只是不再单独决定时机。Skill §流程改成「所有线(含引导窗口题、跳过线重问、未覆盖领域题)问完后,或用户说没有了后,才交付目前范围」,bump 到 10.0.28。 - 口径提醒:按 D2 + D6,10 分钟门槛不改变出卡时机,本轮实际生效的是题源变多(引导窗口题 + 跳过线重问 + 七条线全部轮到)、线问完才出。
- 验证:
frontend/tests/rectification-precision-gate-20260916.test.ts新增五条——引导池空但refreshExhausted=false不交付、门槛达标但定向线未问完继续问、所有线问完但门槛未达照现行规则出卡(含采用)、门槛值在采集出口也照常上报、helper 路径上报 null;rectification-decision-authority.test.ts的公开字段深比较跟上precision_gate_met。撤回短路后连锁打红的 41 条既有断言(16 条来自 BUG-748 的基线红 + 25 条本轮新红)全部按「原值 / 新值 / 原因」三栏处置,均为 D5「七条线全部轮到」与 D6「问完再出」的预期变化。 - 防复发:门槛不得出现在任何采集分支的条件里,也不得成为「题源已空仍不出卡」的理由(那会造出没有题也没有卡的死角,违反 BUG-652);缺省 flag 只能表示「门槛不参与」,不能表示「放行」。
- 相关记录:BUG-654、BUG-656、BUG-740、BUG-748、BUG-749
- 复发自:BUG-654(刷新与定向线未穷尽不得交付,被门槛短路绕开)
- 修复版本:待发布
BUG-752 | 离线回放注入的事件与真值边界无关,「0/20」不能当结论
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/research/guided_collect_holdout_replay.py、docs/research/guided_collect_holdout_2026_09_16.json - 用户现象:用户看不见。研究结论写成「没有一例靠引导件达标」,被当作引导题源无效的证据。
- 触发条件:
synthetic_event一律取window["month_lo"]作为事件月,领域取轮询值,与真值候选在该窗口上的边界日期无关。 - 根因:任务书要求注入「真值方向」的带年月事件,脚本注入的是窗口左端点。落在窗口里但不在真值候选的边界上,对打分几乎没有方向性,所以量到的是合成事件的噪声,不是题源的信息量。
- 修复:按窗口的(轨道, index)取回每个候选自己的边界日期,
truth组注入真值候选的那一天所在月,另跑一组opposite(离真值最远的候选的同一边界)做对照;三档半径都跑。汇总新增met_after_six_only/met_via_guided/median_events_via_guided,把「六题后本来就达标」和「靠引导件补上」分开数。 - 验证:见
docs/tasks/PROGRESS-rectification-precision-gate-guided-collect-fix-20260916.md的数字表与docs/research/guided_collect_holdout_2026_09_16.json。这不是合入门槛。 - 防复发:离线回放的合成证据必须能说清「注入在谁的边界上」;只报总达标数、不分「本来就达标 / 靠注入达标」的数字不得写进结论。
- 相关记录:BUG-740、BUG-749
- 复发自:无
- 修复版本:待发布
BUG-753 | 次级页移进 (secondary) 路由组后 capability audit 把文件夹名当成公开路由
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:Gitea
backend-quality-gaterun2696/2697、tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources、scripts/jyotish_api_server.py_scan_app_routes - 用户现象:用户看不见。
dc732825把校正门禁红修绿后,staging 推送 run2697仍1 failed, 797 passed:app_routes出现(secondary)/chart等,期望仍是chart/ephemeris/reports。 - 触发条件:侧栏统一把四个次级页移进
app/(secondary)/后跑 capability audit 精确集合。 - 根因:BUG-713 同类。
_scan_app_routes把文件系统相对路径当公开路由,Next.js 路由组括号不进 URL。侧栏单和精度门槛修复单都只跑了前端矩阵,没跑这条 Python 契约。 - 修复:扫描时丢掉
(…)路由组段。公开 URL 集合不变,不把(secondary)/chart写进期望列表。 - 验证:
pytest tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources。 - 防复发:新增或移动
frontend/src/app/**/page.tsx必须同一提交跑 capability audit。路由组不是新入口。 - 相关记录:BUG-014、BUG-126、BUG-143、BUG-454、BUG-713
- 复发自:BUG-713
- 修复版本:待发布
BUG-908 | 年月阶段焦点题干被模型追问覆盖,下面还挂着录入卡
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
spoken-prompt.tswithSpokenPrompt、turn-decision.tsprojectCurrentQuestion、录入卡组件(已删)、Skill 10.0.29 - 用户现象:定向健康题答 A 后打字给了年月,助手追问「是你本人做的吗」,追问句下面还挂着「哪一类事 + 发生年月」录入卡。
- 触发条件:活动焦点题号是
collect:targeted:<domain>:year或collect:guided:window:…:year,模型用同号set-focus覆盖prompt。 - 根因:BUG-673 只保护了
targeted_collect存在题;年月阶段 schema 会被withSpokenPrompt整段换成模型的话。产品同时拍板:录入卡整个删掉,年月阶段改打字。 - 修复:年月阶段以题号
stage === "year"识别,只写spoken_prompt、不动prompt。投影只用服务器题干。删除EventDateEntryCard/EventDatePicker。选项 A 改「有,我来说」。Skill 10.0.29。 - 验证:
frontend/tests/rectification-year-focus-overlay-20260916.test.ts。 - 防复发:年月阶段识别以题号前缀为准,与 BUG-673 同口径。不得再引入第二套录入入口。
- 相关记录:BUG-673、BUG-750
- 复发自:BUG-673
- 修复版本:待发布
BUG-909 | 事件入账后年月焦点没有关闭
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
resolveActiveCollectFocusAfterEvidence、pendingTargetedYearDomain、rectification-record-evidence-batch、rectification-propose-evidence - 用户现象:时间轴已「记下了」年月事件,画面仍停在该领域的年月题。
- 触发条件:存在题答 A 落后年月焦点,用户打字给带年月事件。
- 根因:回放显示两条都要修。(a) batch 原先只在
isCollectFocusSchema时 resolve,年月题号要按前缀识别;(b)pendingTargetedYearDomain在存在题 resolved 后只要 year 题未关就会再出,即使账本已有该领域带年月事件。单条propose-evidence原先不 resolve。 - 修复:抽
resolveActiveCollectFocusAfterEvidence,batch 与 propose 共用;isCollectFocusSchema或:year题号都 resolve。pendingTargetedYearDomain把coveredCollectKinds也算 yearClosed(health别名走canonicalCollectDomain)。 - 验证:overlay 测试「dated evidence closes the year stage」与 persist collect schema。
- 防复发:两条写入路径必须走同一段 resolve;该领域已有带年月事件不得再出
:year。 - 相关记录:BUG-908
- 复发自:无
- 修复版本:待发布
BUG-910 | 年月阶段收到无年月短句会烧完步骤上限
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
agent-run.tshost 前置、year-entry-host.ts - 用户现象:对着追问回「是/不是」一类短句,整轮报「本轮达到步骤上限」。
- 触发条件:活动焦点是年月阶段,用户消息没有
19xx/20xx或「x 年」。 - 根因:模型拿到「有年份走 batch,否则再问」的 hint,短句写不进、set-focus 同号幂等,循环到 8 步没正文。完整 agent 循环未在单测里复现(记防御性前置)。
- 修复:日期可靠性分类器之后、计费之前,host 以服务器题干再问一次,不进模型、不扣点。焦点保持原样,失败轮不会只关掉年月题。
- 验证:
shouldHostReaskYearEntry与 agent-run 源码顺序断言。 - 防复发:年月阶段无年份短句不得进模型。
applyHostFallback条件未改。 - 相关记录:BUG-908
- 复发自:无
- 修复版本:待发布
BUG-911 | 独立问题块与助手正文不在同一条左边线
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
globals.css.rectification-message-question.is-standalone、rectification-agentic-chat.tsx - 用户现象:消息外的 persisted 问题块题干顶到左边。
- 触发条件:
questionGap === "persisted_question"的独立块。 - 根因:独立块没有
--assistant-content-inset。 - 修复:独立块加
is-standalone,margin-inline-start: var(--assistant-content-inset);块内 embedded 点选卡 inset 归零。 - 验证:overlay CSS 声明断言。
- 防复发:独立问题块必须与助手正文共用同一 inset token。
- 相关记录:BUG-908
- 复发自:无
- 修复版本:待发布
BUG-912 | D9 感情判别题借 D24 对照探针:错分 + 无卡
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-17
- 影响面:
stampChoiceSchemaWithProbe、varga-distinguish-probe.ts、projectRectificationChoiceCard、persistNextInterviewIfIdle - 用户现象:正文念了感情判别题,下面没有 A/B/C/D;打字答「没有」后区间被收窄。
- 触发条件:D9 风格 followup 没有自己的
semantic_key,inference_state 里另有高增益 D24 对照探针。 - 根因:
stampChoiceSchemaWithProbe在无 preferred key 时退到selectHighestGainProbe。BUG-375 只堵住了对比探针被已答教育题盖掉,没堵住「无探针的分盘风格题借最高增益探针」。 - 修复:不再退到最高增益探针。D9/D10 等风格题按剩余候选的分盘上升自建探针。GET 从落库副本投影;过期焦点
superseded后落下一问。首版只在盖戳那一刻把自建探针注入内存 state,答题 / GET / idle 三处读持久化 receipt 都找不到它(BUG-915);2026-09-17 由该单补齐重建,并按产品决策不再出风格计分题。 - 验证:
frontend/tests/rectification-dead-d9-choice-20260916.test.ts(2026-09-17 改写为落库形状回放);BUG-375 原断言仍绿。 - 防复发:无
semantic_key的计分题不得盖上别的分盘探针。宁可不落库(走 BUG-674)也不错分。只在内存里存在的探针不算落库,新增自建探针必须同时给出「每个读 state 的地方都重建」的路径(BUG-915)。 - 相关记录:BUG-375、BUG-578、BUG-582、BUG-674、BUG-915、BUG-917
- 复发自:BUG-375
- 修复版本:待发布
BUG-913 | 答「没有」后平局直接出交付卡
- 状态:closed_by_design
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
yearlessPersonalityCanAsk、shouldHoldForTieBreak、tieBreakPersonalityFollowup - 用户现象:感情判别题打字答「没有」后出现三列交付卡。
- 触发条件:已有多件带年月经历、候选并列、采集进度
missing:0。 - 根因:回放。
yearlessPersonalityCanAsk在datedCount > 0时为假;平局参考题只认varga.d9/varga.d10性格探针。线问完后的并列出卡是 BUG-689 设计出口,不是 BUG-751 / BUG-688 复发。 - 修复:不改出卡门槛。
- 验证:
yearlessPersonalityCanAsk({ datedCount: 5 }) === false。 - 防复发:有带年月经历时不得把平局交付改成强制性格题。
- 相关记录:BUG-689、BUG-688、BUG-751、BUG-912
- 复发自:无
- 修复版本:待发布
BUG-914 | 交付卡把一对相反性格都列成共同点
- 状态:resolved
- 首次发现:2026-09-16
- 最近更新:2026-09-16
- 影响面:
scripts/rectification/refinement_packet.pyNAKSHATRA_TRAITS选项、buildRangeDeliveryshared_traits - 用户现象:三列卡头同时出现「希望两边都能说得过去」和「必要时会直接选边」。
- 触发条件:升点靠近月宿交界,代表分钟是最晚一列,三列都拿 earlier 那一对。
- 根因:每个 option 把相反两极整对塞进
traits;splitSharedTraits把两句都判成共同点。 - 修复:每个 option 只取第一极;
user_meaning写成「A:{earlier[0]}。B:{later[0]}。」 - 验证:Python
test_rectification_refinement_packet断言traits长度为 1 且两 option 不同句;前端断言两极不得同时出现在shared_traits。 - 防复发:一列只写一句,不得自相矛盾。
- 相关记录:BUG-912
- 复发自:无
- 修复版本:待发布
BUG-915 | 自建分盘探针只活在内存里:点选报 stale_probe、GET 无卡、idle 立刻 superseded
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
varga-distinguish-probe.ts、expectedAnswerSchemaFor、applyRectificationChoice、rectification-resolve-focus、choiceCardFromCaseDossier、persistNextInterviewIfIdle、buildMethodFollowupPlan - 用户现象:正文出现「亲密关系里,你更接近哪一种相处方式?」,下面四个选项灰显点不了;同屏范围行与输入框占位却写「再说一件带年月的事」。点选或打字回答会报错。
- 触发条件:BUG-912 首版给分盘风格题自建了探针;该探针只在盖戳时注入内存 state,
decision_receipt.inference_state里没有。 - 根因:自建探针没有按月宿边界探针的既有模式在每个读 state 的地方重建。答题路径、GET 投影、idle 过期判定都读持久化 receipt:
matchProbeForChoice找不到 →stale_probe;liveInferenceDistinguishProbe找不到 → 无卡;liveDistinguishProbe找不到 → 刚落库的焦点每次 idle persist 都被superseded。 - 修复:新增
withOwnedDistinguishProbes(state, receipt),按window_scan.transitions+ state 候选确定性重建全部自建探针,接入盖戳、点选答题、打字答题、GET 投影、idle 过期五处(答题两处共用inferenceForPersistedAnswer);重建探针的source为专用owned_varga_style,并像nakshatra_boundary一样从 contrast packet 与 event 探针池排除,避免答题写回 state 后它们在下一轮变成可问的题。产品 2026-09-17 同时拍板(选项 b):分盘风格题不再作为distinguish_candidates计分题——分层判别题只在引擎给出该领域带年份事件探针时以「某年前后有没有…」出题并按该探针计分,否则该题不出,计划直接走下一条线。删除attachVargaDistinguishIdentity/withFollowupOwnedProbe。 - 验证:
frontend/tests/rectification-dead-d9-choice-20260916.test.ts10 条:落库形状回放(未重建 =stale_probe,重建后applied且只按 D9 分组计分)、choiceCardFromCaseDossier出卡、persistedDistinguishFocusStale不误伤且候选集变化仍过期、五处接入点源码契约、(b) 决策两半。npm test3421 条 / 31 红(与基线c32f81e7逐条一致)。 - 防复发:任何不在引擎
inference_state里的探针,必须有一个从 receipt 确定性重建的函数,并在每个读 state 的地方接入;只在盖戳时注入内存的写法一律视为未落库。分盘风格题不得作为计分判别题。 - 相关记录:BUG-912、BUG-375、BUG-559、BUG-674、BUG-917
- 复发自:BUG-912(同一根因的第二层:探针身份有了,持久化没有)
- 修复版本:待发布
BUG-916 | 年月阶段 host 前置排在会话校验之前
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
runV9AgentTurn、PUBLIC_RECTIFICATION_PHASES - 用户现象:无直接用户现象;跨会话(旧标签页)发来的无年月短句也会被写成一条确定性轮。
- 触发条件:BUG-910 的 host 前置写在
dossier.case.sessionId !== sessionId校验之前;返回的phases: ["answer.host_year_entry"]不在公开 phase 白名单里(类型是 string,tsc 不报)。 - 根因:前置插入位置只考虑了「不要进模型」,没有沿用既有的会话校验顺序;phase 名新造而未登记。
- 修复:host 前置挪到会话校验之后(计费位置不变);phase 复用既有
answer.host_fallback,公开快照的answer_origin: "host_fallback"语义一致。 - 验证:
frontend/tests/rectification-v9-stream.test.ts等既有 host 前置断言仍绿;npm test失败清单与基线逐条一致。 - 防复发:任何早退分支都必须排在会话 / Skill 身份校验之后;返回的 phase 必须是
PUBLIC_RECTIFICATION_PHASES里的值。 - 相关记录:BUG-910、BUG-915
- 复发自:无
- 修复版本:待发布
BUG-917 | 死掉的点选卡与「再说一件带年月的事」同屏
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
rectificationQuestionGapState、rectification-agentic-chat.tsx、rectification-message-entry.tsx - 用户现象:最后一条助手消息挂着一张灰色不可点的 A/B/C/D,同屏的范围行与输入框占位却说「再说一件带年月的事就能继续」,两条指令互相矛盾。
- 触发条件:焦点被 idle persist 判成
superseded(BUG-915),GET 没有choice_card;current_question为 null 于是interviewCollectWaiting为真。 - 根因:缺口态只看
current_question,看不到消息级的未答点选;消息内卡片只把status === "active"的当成未答,superseded且没有作答的焦点仍然渲染成disabled的选项。 - 修复:
rectificationQuestionGapState新增unansweredDeadChoice输入,在delivered之后、collect_waiting之前返回unavailable(「没有拿到下一个问题」+「接着问」);chat 按最新一条 settled 助手消息计算该标志;message-entry 对「superseded且无answer_option」不再画卡。 - 验证:
frontend/tests/rectification-dead-d9-choice-20260916.test.ts「a dead tap card never shares the screen with the collect-wait placeholder」:collect_waiting+ 死卡 →unavailable,并有 chat / message-entry 源码契约。 - 防复发:死卡与
collect_waiting不得同屏;未作答却已被关闭的点选题不得渲染成灰色选项。 - 相关记录:BUG-915、BUG-675、BUG-674
- 复发自:无
- 修复版本:待发布
BUG-918 | 校正时间轴读数在 375px 手机上被裁成「已…」
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
frontend/src/app/globals.css.rectification-timeline/.rectification-timeline__readout的@media (max-width: 767px)段、frontend/src/components/rectification-timeline.tsx - 用户现象:iPhone 上常驻时间轴一行读作「04:48–05:07 20 分钟 代表分钟 04:53 已…」,第四项被省略号裁断,第五项完全不显示。
- 触发条件:视口宽 375 CSS px(iPhone SE 2/3、13 mini)打开生时校正,Case 已有代表分钟、已答题数与已对照事件数(读数五项齐全)。
- 根因:两条叠加。一是读数按「四项短文本」设计,
.rectification-timeline在 767px 以下仍取calc(var(--space-4) + var(--assistant-content-inset)),即每侧 16 + 44 = 60px,375px 的可用内容宽只剩 255px;条不在消息列里,这 44px 头像沟槽在手机上买不到任何可见对齐。二是cfb41daf又给读数追加了第五项已对照 N 件,把四项预算变成五项。按 13px 逐字保守估宽,五项加四个 12px 间隙需要约 383px,超出 255px 约 128px,于是__answered命中自身的text-overflow: ellipsis显示为「已…」,__dated被条的overflow: hidden整项裁掉。 - 修复:compact 段(
@media (max-width: 767px),与.is-compact的 768px 断点同源)内.rectification-timeline的padding-inline改为var(--space-4)单项;读数column-gap收到var(--space-2);.rectification-timeline__dated在该段display: none。桌面规则与条高(64 / 56px)一字未动,滚动锚定逻辑未动。组件仍无条件渲染全部五个 span,轴的aria-label仍由五项文本拼成,被隐藏的一项对辅助技术不丢失。 - 验证:
frontend/tests/rectification-mobile-timeline-readout-20260917.test.tsx6 条(渲染断言五项文本与aria-label一致;按保守逐字宽度断言 375 与 390 下四项 + 3×8px 间隙约 292px ≤ 343px / 358px,并断言旧口径 383px > 255px;compact 段声明断言去 inset、收 gap、隐藏第五项、未放开 nowrap 与条高;桌面段断言仍带 inset 与 13px)。frontend/tests/rectification-timeline-20260909.test.ts的移动端padding-inline断言同步改写并附原值 / 新值 / 原因。全套npm test3420 tests / 31 fail,失败清单与基线(3414 / 31,无 Docker 集)逐条相同;tsc --noEmit0 错、npm run lint0 error、next build --webpack后/仍○ Static、首屏 gzip 620106 → 620128 字节(+0.004%)。 - 防复发:新增测试把「compact 段不得含
--assistant-content-inset」「第五项在 compact 下隐藏」与宽度预算写成断言;globals.css与组件注释都改掉了原来「四项短文本在 375px 下还有余量」的错误说法,并写明再加一项必须重算预算而不是直接追加 span。 - 相关记录:BUG-478(跳到最新与滚动锚定归属)、BUG-919(同一次真机走查的另一条)。另记一条口径冲突待产品裁决:
frontend/DESIGN.md§10 的禁列表明确写「已对照 N 件经历」不上条、仍归交付卡,而cfb41daf把它加到了条上且未改 DESIGN;本轮只在手机上把它移出可见行,桌面上是否也撤掉未擅自决定。 - 复发自:无
- 修复版本:待发布
BUG-919 | 「跳到最新」浮层压住选项卡最后一个可点选项
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
frontend/src/app/globals.css.rectification-workspace__chat .conversation/.message-list的底部留白 - 用户现象:iPhone 上「跳到最新」胶囊浮在选项卡最后一行上,选项被遮掉一半,正中间不好点。
- 触发条件:校正对话滚到尾部附近、浮层可见(距底 > 96px)时,末条内容是一张多行选项卡。
- 根因:浮层
.jump-to-latest是position: absolute; bottom: 100%挂在 composer 顶边上,占据 composer 上方 44px 触控高 + 自身space-3= 56px 的带。而.message-list的底部留白恰好等于--rectification-jump-clearance(同样 56px),与浮层带严丝合缝,末条内容顶到浮层正下方;容器pointer-events: none只放行 44px 按钮本体,按钮正好落在选项行中央,于是「遮一半 + 中间点不动」。 - 修复:
.message-list底部留白改为calc(var(--rectification-jump-clearance) + var(--space-3));.conversation追加scroll-padding-block-end: var(--rectification-jump-clearance),让任何 scroll-into-view 也落在带之上。连同.conversation自身的space-4,静止时末条下方共 84px(56px 浮层带 + 28px 空气)。浮层的位置、居中对齐与可见条件(BUG-478 的归属)一字未动,--rectification-jump-clearance的取值不变(时间轴 56px 条高仍与它同源),use-conversation-scroll-anchor未动。 - 验证:
frontend/tests/rectification-mobile-timeline-readout-20260917.test.tsx两条声明断言(留白表达式与scroll-padding-block-end;浮层absolute/bottom: 100%/justify-content: center/pointer-events: none/ 44px 与conversationAnchorThreshold = 96未变)。全套门禁数字同 BUG-918。真机走查(375 / 390 宽)留在docs/testing/rectification-mobile-timeline-readout-20260917.md:本机无 Chrome、无真机。 - 防复发:测试锁住「留白 = 浮层带 + space-3」这条表达式,任何把它改回等于浮层带的提交会红。CSS 注释写明了为什么不做到「任何滚动位置都不重叠」——那需要留白 ≥
conversationAnchorThreshold(96px) + 56px = 152px,在 667px 高的手机上等于凭空吃掉一整行选项,而浮层只在读者已经向上滚过该阈值后才出现。 - 相关记录:BUG-478(两个聊天面统一用
useConversationScrollAnchor与JumpToLatestButton,并把浮层改成居中)、7a4360d8(同一现象 2026-08-25 的旧修法是把胶囊右对齐,已被 BUG-478 的居中统一取代,本轮未推翻它)、BUG-918 - 复发自:无
- 修复版本:待发布
BUG-920 | iPhone 键盘收起 / 刷新后整页上移,顶栏点不到
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
ViewportScrollLock、layout.tsx - 用户现象:刷新或收起键盘后顶栏滚出屏幕,底部留下空白,点不到 tab。
- 触发条件:iPhone Safari 上聚焦输入框弹出键盘,随后收起或带着偏移刷新。
- 根因:iOS 不支持
interactive-widget=resizes-content,键盘弹出时滚动window。html, body { overflow: hidden }挡住用户拉回,reload 又按scrollRestoration=auto还原scrollY。代码里原先没有 window 级复位。 - 修复:挂载时
history.scrollRestoration = "manual"并scrollTo(0,0)。visualViewportresize/scroll、pageshow、orientationchange、focusout(300ms)在键盘已收且scrollY > 0时复位。键盘打开期间不动。 - 验证:
frontend/tests/mobile-viewport-scroll-lock-20260917.test.ts。真机待核。 - 防复发:不得靠用户自己滚回去;键盘打开时不得抢 Safari 的露出输入框滚动。
- 相关记录:BUG-918、BUG-919、BUG-921
- 复发自:无
- 修复版本:待发布
BUG-921 | 顶栏积分块比「当前盘面」扁
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
.credit-button、.chat-header-rectification .rectification-board-peek、手机顶栏行高 - 用户现象:手机顶栏右侧积分「✦ N」又宽又矮,和旁边「当前盘面」不成一套。
- 触发条件:校正页顶栏同时出现盘面芯片与积分块。
- 根因:
.chat-header-actions button把高度写成 32px,比.credit-button更具体,积分块被压到 32px 却仍强制min-width: 64px、13px 字。盘面芯片用另一套 44px / 14px。两枚都塞在 46px 行里。 - 修复:两枚共用 40px 高、14px tabular、同圆角同内边距;去掉积分
min-width。::beforeinset -2px 把热区扩到 44px。≤767px 顶栏行52px + safe-area(原 64px 覆盖改为与任务书 52px 对齐)。 - 验证:CSS 声明断言高度/字号/圆角一致、无 64px min-width;
touch-target-contract仍绿。 - 防复发:顶栏两枚芯片必须走同一组声明,不得再各写一套高度。
- 相关记录:BUG-695、BUG-920
- 复发自:无
- 修复版本:待发布
BUG-922 | 咨询追问被提示词允许跳过排盘工具,整轮失败
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
windowJyotishInstructions、jyotishInstructions - 用户现象:申报时段会话连发短追问得不到回答,提示「Agent 未完成必要的方法与计算步骤,本次不会扣点」。
- 触发条件:本命或申报时段模式下发「?」「你在说什么鬼」一类追问。
- 根因:系统指令写「简单追问可复用已有 packet / context、不调工具」;
contractReady()却要求每次请求恰好一次成功排盘调用。跨请求没有可复用 packet(缓存只在单次请求内)。 - 修复:两处指令改为每一轮(含短追问、澄清、抱怨)都必须先调用排盘工具,并写明计算只在本请求内有效。
- 验证:
consultation-birth-time-mode.test.ts契约;既有 Agent 暴露工具测试仍绿。 - 防复发:不得再写「follow-ups may use the existing」。
- 相关记录:BUG-205、BUG-214、BUG-286、BUG-923
- 复发自:无
- 修复版本:待发布
BUG-923 | 第 0 步未强制排盘工具,提示词与运行合同对不齐
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
consultationNatalPrepareStep、consultationWindowPrepareStep、consult/route.ts申报时段分支 - 用户现象:同 BUG-922。回执只有 skill 与 runtime-contract-retry,没有任何 tool 步骤。
- 触发条件:同上。
- 根因:提示词是软约束,
toolChoice: "auto"不能保证第 0 步调用排盘工具。申报时段路线甚至没有prepareStep。 - 修复:第 0 步
activeTools仅排盘工具且toolChoice: "auto"(thinking 模式不得发 named / required,见 BUG-282 / BUG-937);后续步骤仍"auto"。窗口路线对称加prepareStep,首次 stream 与合同 retry 使用它。retryForAnswer/ 续写 / 分段仍不强制。未改contractReady()。BUG-937 撤回了本条最初落地的required。 - 验证:natal / window prepareStep 单测;
route.ts源码契约。既有「失败后重试成功仍过门禁」「契约未绿文本丢弃」仍绿。 - 防复发:本命与窗口第 0 步用
activeTools收窄排盘工具,toolChoice只能是auto。咨询侧源码契约禁止required与 namedtoolChoice。提示词侧每轮必调(BUG-922)保留。 - 相关记录:BUG-214、BUG-282、BUG-286、BUG-922、BUG-937、BUG-938
- 复发自:无
- 修复版本:
dc2f2a16(required 已由 BUG-937 撤回)
BUG-924 | 校正会话上普通输入框可用,问题发到 /api/consult
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
use-consultation-run.tssend()、page.tsx普通ChatComposer、use-session-management.ts回退路径 - 用户现象:在生时校正会话里打了一个普通问题,请求打到咨询接口,提示「咨询会话不存在」。
- 触发条件:校正会话已激活但校正面未开(刷新揭幕超时、打开失败不重试、删除或回退落到列表第一条校正会话)。
- 根因:发送链路以「校正面没开」推断「当前是普通会话」;
send()不看sessionType。BUG-505 只覆盖了selectSession。 - 修复:
send()对校正会话不发/api/consult,保留草稿并打开校正面。普通输入框在校正会话上只有禁用态。删除 / popstate /sessions[0]回退不再直接setActiveSessionId到校正会话。 - 验证:
rectification-session-composer-guard.test.ts、rectification-surface-contract.test.ts。 - 防复发:校正会话不得发出
/api/consult;普通输入框在校正会话上不得可用。 - 相关记录:BUG-505、BUG-925
- 复发自:无
- 修复版本:待发布
BUG-925 | 咨询接口把校正会话和「会话不存在」都回 404
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
api/consult/route.ts、use-consultation-run.ts错误映射 - 用户现象:校正会话误发咨询时只看到「咨询会话不存在」。
- 触发条件:
POST /api/consult的sessionId指向session_type !== consultation。 - 根因:查不到与类型不对共用一个 404。
- 修复:查不到仍 404「咨询会话不存在」;类型不对 409
session_not_consultation。前端收到该码不显示错误、不扣点,改走打开校正面。 - 验证:
chat-session-authority.test.ts、rectification-session-composer-guard.test.ts。 - 防复发:不得把两种失败再合成一个 404。
- 相关记录:BUG-924
- 复发自:无
- 修复版本:待发布
BUG-926 | 会话列表只按 id 排,游标按 updated_at,首页不是最近活动
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
local-postgres-client-core.tsorder()、GET /api/sessions、GET /api/payment/packages - 用户现象:侧栏列表每次进来都不一样;当天新建的校正会话不在首页 50 条里。
- 触发条件:打开
/或次级页拉会话列表。 - 根因:兼容层
order()只保留最后一次调用。.order("updated_at").order("id")实际只按id;游标却按updated_at, id。套餐列表同样只按created_at。 - 修复:
ordering改为数组,多次order()追加,SQL 生成order by a, b。路由调用未改。 - 验证:
local-postgres-order.test.ts。无 Docker,未跑test:db翻页重叠断言,见BLOCKED.md。 - 防复发:两次
order()必须都出现在 SQL 里;列表排序键与sessionCursorFilter键一致。 - 相关记录:BUG-553
- 复发自:无
- 修复版本:待发布
BUG-927 | / 与次级页各拉一份会话列表,切页就重拉
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
app/(app)/layout.tsx、session-list-context.tsx、page.tsx - 用户现象:对话、星盘、星历、报告四处侧栏不是同一份,每换一页都重新读一次。
- 触发条件:在
/与/chart/ephemeris/reports之间切换。 - 根因:
/不在(secondary)路由组,离开即卸载 Home;次级页另用useSidebarData再拉一遍。 - 修复:四个页面进
(app)路由组。一份SessionListProvider拉/api/sessions与/api/account。首页不再自己fetchSessions/fetchAccount。侧栏不随切页卸载。按任务书让步,改名/置顶/删除仍由首页注册,未注册时侧栏只读。 - 验证:
session-list-provider.test.ts、sidebar-contract.test.ts、chart-page-view.test.tsx。 - 防复发:
app/里SidebarProvider只许出现在(app)/layout.tsx。外壳搬家必须同步搬走针对page.tsx的源码合同,且交付前必须跑全量npm test与tsc --noEmit,不得只跑定向。 - 相关记录:BUG-745、BUG-926
- 复发自:无
- 修复版本:待发布
BUG-928 | 空的「新对话」落库堆积,两边列表还不一致
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
GET /api/sessions、startNewChat、首页启动落点 - 用户现象:首页 50 条里大量「新对话」;对话页把它们藏起来,星盘等页原样显示。
- 触发条件:点「新建对话」或启动时自动落一个空咨询会话。
- 根因:会话在用户开口前就
POST /api/sessions;列表页不过滤空咨询。 - 修复:列表
GET排除messages = [];详情仍可读。启动和「新建对话」复用已有空咨询。延迟到第一问才落库未做,见BLOCKED.md。 - 验证:
chat-session-authority.test.ts、session-list-filter.test.ts。无 Docker,未跑test:db空会话不入列。 - 防复发:列表路由必须带空咨询过滤;详情路由不得带。
- 相关记录:BUG-553、BUG-927
- 复发自:无
- 修复版本:待发布
BUG-929 | 会话标题类别在后,同名靠墙钟 HH:MM
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
resolveSessionTitle、侧栏行映射、GET /api/sessions的created_at - 用户现象:一列「新对话 / 9月14日 · 生时校正 / 9月14日 · 生时校正 02:14」,看不出类别,同日多条靠新建时的钟点区分。
- 触发条件:新建校正或今日节奏,或同日开第二条。
- 根因:
datedSessionTitle日期在前;uniquifySessionTitle用墙钟。侧栏副标题只在他人盘时出现。 - 修复:新标题「生时校正 · M月D日」「今日节奏 · M月D日」。删除 uniquify。两处侧栏共用
toSidebarSessionRow,副标题为上海时区创建时间。旧标题不批量改。 - 验证:
agent-reply.test.ts、chart-library-session.test.ts、session-open-preserves-identity.test.ts。 - 防复发:不得再给同名标题追加 HH:MM;打开已有会话仍不得改 title / updatedAt(BUG-699)。
- 相关记录:BUG-699、BUG-557
- 复发自:无
- 修复版本:待发布
BUG-930 | 主会话和生时校正回答落在结尾,读不到开头
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
useConversationScrollAnchor、主会话send()、生时校正面提交 / 点选项 / 开场、JumpToLatestButton - 用户现象:问一个问题后视口钉在回答最后一个字上,要读回答得不断上滑找开头。生时校正长旁白同样把本轮开头推出视口。
- 触发条件:主会话或生时校正发出一轮,模型输出超过一屏。
- 根因:跟随语义是「贴底」。
anchored初值为真,ResizeObserver在内容变高时把scrollTop设到scrollHeight;send()还会anchorToLatest()。对短回答两者等价,对长回答则把开头推出视口。 - 修复:同一 hook 改为新一轮把本轮开头(用户行,或没有用户行时的新助手行)钉在滚动容器顶部,流式期间不再跟随。回答长出视口时用已有的「跳到最新」贴底并恢复跟随。末尾动态留白让短回答也能把开头留在顶部。切换会话仍先落底。取代 BUG-041 / BUG-048 的贴底语义。
- 验证:
chat-notice-and-scroll-contract.test.ts源码合同与行为测试;校正面pinLatestTurn三处源码断言。验收方用 Chrome 真实布局骨架页跑过 S1–S4(长历史发问钉顶、短历史留白、先上滑再发问、切会话落底)。S5/S6 见 BUG-931 / BUG-932。 - 防复发:跟随只经
useConversationScrollAnchor,跳到最新只经JumpToLatestButton;send()必须调pinLatestTurn;校正面新轮只在提交文字、点选项、开场/无用户行助手到达时各 pin 一次。 - 相关记录:BUG-478、BUG-041、BUG-048、BUG-931、BUG-932
- 复发自:无
- 修复版本:待发布
BUG-931 | 无用户行的一轮钉不住,随后退回贴底跟随
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
useConversationScrollAnchorapplyTurnSpacer/pinLatestTurn;生时校正点选项、开场、自动续轮 - 用户现象:点选项或开场后新助手行不在顶部;流式一开始视口又钉到最后一个字。
- 触发条件:本轮没有
.message-user,pinLatestTurn()钉的是最后一条.message-assistant。 - 根因:最后一条助手行同时是 turn head 和留白承载。
applyTurnSpacer用head.offsetHeight写--latest-turn-head-height,量到的是「内容 + 上一帧留白」,公式自指后min-height坍成 0,scrollTo被夹住。随后distance ≤ 96解除holdUnpin,anchored翻回真。 - 修复:head 与
lastTurnTail是同一元素时--latest-turn-head-height写0px,该行min-height等于整个视口。选这一路而不是先clearTurnSpacer再量,是因为清变量后仍要等一次布局,offsetHeight在同一帧里不可靠。 - 验证:
chat-notice-and-scroll-contract.test.ts「a turn with no user row…」。 - 防复发:无用户行时 spacer 头高必须是 0;pin 后增高不得把
anchored翻回真。 - 相关记录:BUG-930
- 复发自:无
- 修复版本:待发布
BUG-932 | 闲置会话上滑后子元素尺寸变化被写留白
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
useConversationScrollAnchorfollow()非贴底分支 - 用户现象:在旧会话里上滑阅读时,窗口变宽、图片加载或思考块折叠会把最后一条助手行撑出一段空白,「跳到最新」也可能被顶出来。
- 触发条件:
anchored = false且本轮没有pinLatestTurn,滚动容器子元素高度变化。 - 根因:
follow()在未贴底时无条件applyTurnSpacer,把闲置上滑和「本轮已钉住」当成同一件事。 - 修复:只在
pinnedHeadRef.current非空时写留白。anchorToLatest()与resetKey变化时清 ref 和 CSS 变量。 - 验证:
chat-notice-and-scroll-contract.test.ts「idle history does not write a spacer…」。 - 防复发:未 pin 的上滑不得写
--conversation-viewport;latestBelowFold仍按尾部超出 96px 判定。 - 相关记录:BUG-930
- 复发自:无
- 修复版本:待发布
BUG-933 | 四条源码合同没跟着外壳搬家,门禁卡住
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
chat-navigation-a11y-contract.test.ts、rectification-history-open-20260909.test.ts、rectification-surface-contract.test.ts、chat-session-url.test.ts - 用户现象:会话列表单实现已落地,但 staging 门禁
npm test必红,今天的代码提交上不了线。 - 触发条件:
e4e73f56把<main className="chat-app">和侧栏 props 搬到(app)/layout.tsx,启动改成sessionListReady。 - 根因:四条合同仍按搬家前的
page.tsx形状断言。被保护的性质都还在,是合同陈旧。执行方只跑了定向测试。 - 修复:四条按「原值 / 新值 / 原因」改写,分别断 layout 与 homepage 注册两端,不得删测试。
- 验证:上述四文件;全量
npm test相对cc1a8980少这 4 条红。 - 防复发:见 BUG-927。
- 相关记录:BUG-927、BUG-745、BUG-939
- 复发自:无
- 修复版本:待发布
BUG-934 | 今日星语 / 生时校正入口合同仍找已删除的卡片类名
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
tests/test_daily_and_rectification_entrypoints.py - 用户现象:Python 定向跑这两条必红。不在
CORE_PYTEST_TARGETS,不挡门禁。 - 触发条件:断言
daily-starlanguage-card/birth-rectification-card。 - 根因:
62903b7a把空状态改成问候 + 输入框 + 两枚 pill,类名已全仓删除。既有欠账,不是会话列表单引入。 - 修复:改为断言
starter-entrypill 与startDailyStarlanguageConsultation/openRectificationFromHomepage,并断言旧类名不存在。 - 验证:
pytest tests/test_daily_and_rectification_entrypoints.py。 - 防复发:入口形态变了必须改这条合同,不得把已删除的卡片类名当存在。
- 相关记录:无
- 复发自:无
- 修复版本:待发布
BUG-935 | 列表只剩校正会话且无活跃会话时,普通输入框静默吞发送
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
page.tsxactiveSession回退、send()、普通ChatComposer - 用户现象:账号里只有校正会话时,普通输入框能打字;发送没有任何提示,字消失。
- 触发条件:
activeSession为undefined(列表全是校正,找不到非校正回退),校正面正在打开或打开失败。 - 根因:校正守卫把
?? sessions[0]改成find(非校正)。无活跃会话时activeRectificationSession为假,输入框可用;send()因!currentSession直接return false。 - 修复:
composerLocksAsRectification:无活跃会话但列表有校正会话时按校正占位锁定。send()无活跃会话时打开校正或提示「请先选一条对话」。 - 验证:
rectification-session-composer-guard.test.ts。 - 防复发:无活跃会话不得静默吞发送;列表全是校正时普通输入框不得可用。
- 相关记录:BUG-924
- 复发自:无
- 修复版本:待发布
BUG-936 | 首页永远停在「正在载入账户」,没有超时也没有报错
- 状态:兜底已补、根因待分流
- 首次发现:2026-09-17
- 最近更新:2026-09-22
- 影响面:
/首屏揭幕(app/page.tsx的!hydrated || (!account && !accountError)门)、AppLoadingIndicator、app/layout.tsx - 用户现象:产品负责人转述,一位已有账号的用户在 iPhone Safari 打开
https://staging.jyotisha.chat/,永远停在「正在载入账户 / 同步个人资料与对话记录」,进不去页面。转圈动画还在动。 - 触发条件:尚未定位到具体条件。已排除接口侧。
- 已确认事实(2026-09-17,线上
dc2f2a16):- 该用户登录态下
/api/account、/api/sessions?limit=40、/api/models、/api/rectification/cases/entry-summary全部 200,耗时 0.6–1.1 s,不是接口挂起。 /的预渲染 HTML 本身就含「正在载入账户」「同步个人资料与对话记录」与app-loading结构;转圈是.app-loading-orbit::after的纯 CSS 动画。因此这一屏在客户端 JS 一行都没执行时也会原样显示并继续转。- 兜底逻辑全部活在那段 JS 里:8 秒
bootstrapTimeout(写accountError并setHydrated(true))、prepare 阶段 4 秒揭幕、401 跳/login。JS 没跑 → 这三条都不会发生 → 永远停住、没有任何报错。这与现象完全吻合。 - 首页 HTML 引用 25 个
/_next/static/chunks/*.js,全部同步<script>,任何一个没加载或解析失败,React 就不会 hydrate。实测这 25 个当前都 200。 - 线上 bundle 的语法下限是 Safari 16.4:
1stw4tc266s7a.js(Next 自己的 app-router 运行时)含类静态块class y extends Component{static{this.contextType=...}};089-80cjt8-1t.js(本仓中文断句split(/(?<=[。!?;;])\s*/))与0dmsli_y64717.js(链接识别依赖)含正则后行断言。两者在 Safari < 16.4 都是解析期 SyntaxError,core-js 这类运行时 polyfill 救不了。 - 该下限不是新引入:
next: 16.3.1从仓库首个提交4aa0105f(2026-07-15)就在。所以如果这台设备以前能用,(5) 不是本次原因。
- 该用户登录态下
- 待定位(需要用户侧一条信息即可分流):① 设备 iOS 版本 < 16.4 → 命中 (5);② 某个 chunk 在弱网下没下全或被内容拦截器挡掉 → 无痕窗口 / 清除网站数据后恢复;③ Safari 缓存里留着坏掉的 chunk(chunk 带
cache-control: public, max-age=31536000, immutable)。 - 根因:未确认,不得提前写。兜底补上不等于这台 iPhone Safari 已经修好。
- 修复:根 layout 增加一段与 bundle 无关的内联经典脚本。13 秒后,若仍有
.app-loading且<html data-hydrated>不是"1",把加载内容换成「这个页面没能加载完」和只在点击时刷新的「重新加载」。揭幕成功后由首页装配 hook 写data-hydrated="1",正常路径不闪这一屏。匿名标记只留类别、构建标识和耗时,不留异常原文或请求体。接口超时仍走 bundle 里原来的「连接云端服务超时」,不并进这一屏。本仓断句的后行断言另见 BUG-998,那一项不能把 Next 运行时的语法下限降到 Safari 16.4 以下。 - 验证:
frontend/tests/first-paint-fallback-contract.test.ts锁定 ES5、超时大于 8000+4000、未揭幕才替换、按钮才刷新,以及 hydrate 超时 / chunk 404 / 解析失败 / 旧缓存 / 弱网 / 接口错误六类不互相冒充。未在 iPhone Safari 或断 chunk 的浏览器里走查。 - 防复发:首屏不得把「出错了」的唯一出口放在可能失败的模块 bundle 里。不得自动刷新。见
docs/tasks/TASK-home-bootstrap-reliability-20260922.md与docs/testing/home-bootstrap-reliability-20260922.md。 - 相关记录:BUG-716(
/chart白屏 45 秒,另一条链路)、BUG-998、BUG-1002 - 复发自:无
- 修复版本:未发布(本分支未推送,不得声称 staging 已部署)
BUG-937 | 咨询第 0 步 toolChoice: required 让全部咨询整轮失败
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
consultationNatalPrepareStep、consultationWindowPrepareStep;本命与申报时段两条咨询路线的每一轮 - 用户现象:staging 部署
dc2f2a16之后,本命与申报时段会话每一轮都失败,提示「Agent 未完成必要的方法与计算步骤,本次不会扣点。」回执只有 skill 与 runtime-contract-retry,没有任何 tool 步骤。 - 触发条件:已部署
dc2f2a16的 staging 上发一轮咨询。首轮固定开 thinking。同日晚间本命路线用gpt-5.6-luna发正经问题时,第 0 步强制工具调用被该供应商接受(事件流走到「正在计算本命盘」),所以「供应商一律拒收 required」不能解释所有模型。 - 根因:BUG-923 把两条路线的第 0 步改成
toolChoice: "required"。BUG-282 实证过至少一个 thinking 供应商拒绝非 auto 的tool_choice(原文Thinking mode does not support this tool_choice)。模型目录可切换,这条路仍会整轮失败。供应商拒收走 Mastraerror块,咨询流当时看不见,被翻译成合同未完成(见 BUG-938,本单第一优先)。同日反证:gpt-5.6-luna接受了required,那次截图另立单,不并进本条。 - 修复:第 0 步撤回
required,只留activeTools收窄到排盘工具,toolChoice为"auto"。BUG-922 的提示词(每轮必调、packet 不跨请求)不动。不在本单尝试第 0 步关 thinking 再发 required。activeTools+auto在所有供应商上都成立。 - 验证:
consultation-agentic-runtime.test.ts两条 prepareStep 断言;consultation-workflow-contract.test.ts源码契约禁止required与 namedtoolChoice。部署后真人走查见docs/testing/consult-followup-tool-contract-fix-20260917.md。 - 防复发:咨询侧
consultation-tools.ts不得再写toolChoice: "required"或 named{ type: "tool", toolName };thinking 模式只能用activeTools收窄。该约束以前只锁在校正 Agent 测试与 BUG-282 文字里,咨询侧无测试、任务书作者未检索到,所以没拦住 BUG-923。 - 相关记录:BUG-282、BUG-630、BUG-922、BUG-923、BUG-938
- 复发自:BUG-282
- 修复版本:
8b11ae7d
BUG-938 | 咨询流不处理 Mastra error 块,供应商拒收不可见
- 状态:resolved
- 首次发现:2026-09-17
- 最近更新:2026-09-17
- 影响面:
stream-agent-response.tsconsumeAttempt、agent-observability.ts - 用户现象:同 BUG-937。公开事件没有报错,回执没有 tool 步骤,
modelFinishReason为空,错误码被翻译成runtime_contract_incomplete。合同 retry 用同一套选项再失败一次。本命路线在gpt-5.6-luna上能进计算阶段,说明失败形状不只有「拒收 required」一种,真实错误只能靠本条落地后的日志看到。 - 触发条件:供应商调用失败(含 thinking 模式拒收非 auto 的 tool_choice,以及其它上游错误)。Mastra 1.50.1 把该失败 enqueue 成
{ type: "error" }后关流,不抛给fullStream迭代。 - 根因:
mapChunk/consumeAttempt不判断chunk.type === "error"。校正 Agent 在 BUG-282 已把这条路做成thinking_tool_choice_unsupported,咨询流没有对齐。本条是本单第一优先。 - 修复:遇到
error块立即结束本次 attempt,按原文分类为thinking_tool_choice_unsupported或provider_error,回执追加validation model-stream-error failed。这两个内部码不进公开run.failed枚举,公开层仍是calculation_failed。供应商错误不触发合同 retry。服务端打[consult-provider-error]日志(requestId、内部码、原文头 200 字,不含请求体)。 - 验证:
consultation-agentic-runtime.test.ts两条假流(thinking 拒收 / upstream 502):公开码calculation_failed、无loading-method、retry 不调用、onError收到内部码。agent-observability.test.ts把这两个码原样落日志。 - 防复发:咨询流必须识别 Mastra
error块;不得把供应商原文写进公开事件。合同 retry 只用于「没调工具」,不用于供应商拒收。 - 相关记录:BUG-214、BUG-268、BUG-282、BUG-937
- 复发自:无
- 修复版本:
8b11ae7d
BUG-939 | 出生时间旅程合同仍读已搬走的 app/page.tsx,staging 门禁全红
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
tests/test_birth_time_journey_contract.py_home_surface;Giteabackend-quality-gatevalidate;staging 无法发布e4e73f56之后的提交 - 用户现象:staging 门禁从会话列表单起连续失败,最新一次 run 2763 停在 Python 快速门。线上仍停在上次绿灯
dc2f2a16。 - 触发条件:push 到
staging且命中门禁路径。pytest 跑test_web_onboarding_uses_the_deterministic_free_journey。 - 根因:
e4e73f56把首页从frontend/src/app/page.tsx搬进(app)路由组。BUG-933/934 改了前端四条合同和两条 Python 入口合同,漏了这条已列入CORE_PYTEST_TARGETS的文件。_home_surface()对缺失文件if path.exists()静默跳过,于是断言变成「拼盘里找不到<BirthTimeRectification」,而组件仍在frontend/src/app/(app)/page.tsx。 - 修复:首页表面改为必读
(app)/page.tsx,hooks 仍可选。新增路径锁:PAGE.parts必须以(app)/page.tsx结尾,旧路径不得存在。 - 验证:
pytest tests/test_birth_time_journey_contract.py tests/test_session_management_entrypoints.py tests/test_supabase_user_data_contract.py tests/test_daily_and_rectification_entrypoints.py25 passed。未改产品代码。 - 防复发:扫首页源码的 Python 合同必须必读
(app)/page.tsx,不得对首页文件exists()跳过。外壳再搬家时,CORE_PYTEST_TARGETS里所有_home_surface都要一起改。 - 相关记录:BUG-927、BUG-933、BUG-934、BUG-940
- 复发自:BUG-933
- 修复版本:
39a0d7a9
BUG-940 | 校正守卫后 5 条前端源码合同仍按旧写法断言,门禁继续红
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
chat-composer-queue.test.ts、chat-session-url.test.ts、consultation-entrypoint.test.ts、consultation-recovery.test.ts;Giteabackend-quality-gatenpm test - 用户现象:BUG-939 修好 Python 合同后,门禁 run 2764 仍红。
npm test3484 条里 5 条源码正则失败。线上仍停在dc2f2a16。 - 触发条件:push 到
staging跑全量npm test。先前被 Python 快速门挡住,这 5 条从未在门禁里跑到。 - 根因:BUG-924/935 把校正会话判断收到
composerLocksAsRectification/activateFallbackSession/dropRectificationStoredPending。被保护的性质还在,合同仍按搬家前的字面量断言。BUG-933 只改了另外四条,漏了这五条。 - 修复:按「原值 / 新值 / 原因」改写这五条,不断性质、不删测试。
- 验证:上述四文件
tsx --test58 passed / 0 failed。未改产品代码。 - 防复发:改
page.tsx或校正守卫字面量的轮次必须跑全量npm test,不得只跑定向。源码合同跟着实现搬家,见 BUG-927。 - 相关记录:BUG-924、BUG-927、BUG-933、BUG-935、BUG-939
- 复发自:BUG-933
- 修复版本:
0378d9e0
BUG-942 | 思维链过滤器在句内删词,连词和孤儿标点留给用户
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
public-thinking.ts(已删)、stream-agent-response.ts、思考面板 - 用户现象:思考区出现
"这周哪些事最好先放一放" — are put .这类残句。 - 触发条件:模型 reasoning 含中英夹杂,旧 sanitizer 按句切、从第一个汉字截断、再删 ≥4 字母英文词。
- 根因:一个文本通道当三个用,下游用正则当刀。
- 修复:删除就地删词。provider reasoning 只打截断日志,不进事件。公开思考改为完整
think.step,校验不过整条丢弃。 - 验证:
public-thinking.test.ts5 条;agentic-runtime 两条 reasoning 合同改为「reasoning 不进公开通道」。 - 防复发:新增正则必须能回答「门还是刀」;刀不许进。
- 相关记录:BUG-943、BUG-944
- 复发自:无
- 修复版本:
94c1e81f
BUG-943 | 本命正文首节是参数罗列、尾节是技法审计表
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consultation-thinking-plan.ts、product-voice.ts、咨询正文 - 用户现象:回答先堆岁差/宫位,文末再贴技法审计表,问题被挤到后面。
- 触发条件:本命咨询走旧
REPORT_HEADING(统一参数 / 领域 / 审计表 / 现代生活)。 - 根因:方法学记账被当成用户读物。
- 修复:正文章节改为「先回答你的问题 / 盘里支持这个判断的地方 / 时间怎么看 / 这周可以做的一件事」。审计表继续走回执折叠面板,不写进正文。
- 验证:thinking-plan / voice / timeline 合同。
- 防复发:正文出现「统一参数」「技法审计表」算 Pass 4 检测命中。
- 相关记录:BUG-298、BUG-942、BUG-944
- 复发自:BUG-298
- 修复版本:
94c1e81f
BUG-944 | 整轮共用 110 秒闸门,分段写作被静默掐断后变成空回答
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consult/route.tscomposeSection、stream-agent-response.tscomposeByHeadings、领域时长常数 - 用户现象:等约 110 秒后「本次没有生成回答」。回执分不清模型没写和流被掐。
- 触发条件:工具耗时长(实测约 31s/领域),后续逐节写作共用同一个
AbortSignal.timeout(110_000)。 - 根因:分段写作 + 共享闸刀;abort 没有独立运行步。
- 修复:取消逐节
composeSection/section-empty-retry,一次成文。领域时长按 31s 重算。abort 记kind: "abort"。已有正文片段不得再以empty_answer结束。 - 验证:workflow-contract 与 agentic-runtime composeAnswer 合同。后台 job / 断线重放本轮未做,见进度记录让步。
- 防复发:每个 stream 自己的预算;abort 必须留痕。
- 相关记录:BUG-942、BUG-943
- 复发自:无
- 修复版本:
94c1e81f
BUG-945 | 三领域问题被 schema 整次拒绝,一个领域都不算
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consultation-tools.ts工具入参domains.max、executableDomainPlan - 用户现象:一次问事业+财富+家庭时整轮没有排盘,回答空或只剩道歉。
- 触发条件:模型列出 ≥3 个领域。
MAX_CONSULTATION_DOMAINS因 31s/领域降到 2,schema.max(2)在 execute 前拒绝整次调用。 - 根因:执行上限与计划上限绑在同一个常数上。工具描述承诺截断进
omitted_domains,zod 却整次拒绝,截断逻辑成死代码。 - 修复:schema 上限改为
MAX_CONSULTATION_DOMAIN_PLAN_VALUES = 6;真正执行仍按墙钟上限 2 截断,其余进omitted_domains。描述与 mastra 指令对齐。 - 验证:
consultation-agentic-runtime.test.ts超限计划runWorkflow≥1、omitted_domains非空、schema 接受 3–6 条拒绝 7 条。 - 防复发:计划长度与执行长度必须是两个常数;超限合同必须走到 execute。
- 相关记录:BUG-944、BUG-946
- 复发自:BUG-944
- 修复版本:
e32ce624
BUG-946 | 领域时长改 31s 后 6 条预算断言仍按 21s / cap=3
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consultation-agentic-runtime.test.ts - 用户现象:无(测试红)。门禁
npm test过不了,镜像不发布。 - 触发条件:
94c1e81f把领域时长改成 31s 后跑全量测试。 - 根因:实现改了常数,断言没跟。三领域调用被 BUG-945 的 schema 拒绝后,合并合同读
undefined。 - 修复:按 MAX=2 / 31s 重写 6 条断言,写原值/新值/原因。不得弱化成
cap >= 1。 - 验证:同文件预算与超限截断用例。
- 防复发:改
CONSULTATION_DOMAIN_DURATION_MS必须同步预算合同。 - 相关记录:BUG-944、BUG-945
- 复发自:BUG-944
- 修复版本:
e32ce624
BUG-947 | 校正流思考分片被条目门整条丢掉
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
rectification-agentic/v9/stream-mapping.ts、think-step-gate.ts - 用户现象:生时校正计算期思考行大面积空白。
- 触发条件:provider
reasoning-delta分片短于 8 字或没有句末标点。先核对经历。(7 字)被acceptThinkStepText判空。 - 根因:本命 Pass 2 的整条门被误用到校正分片通道。
- 修复:选任务书方案 (b)。校正流保留分片通道,改走
acceptThinkingFragment(只判 CJK / 工具名,不改写、不设 8 字下限)。另提供 assembler 给连续分片测可见行。 - 验证:
rectification-step-answer.test.ts,含「连续中文分片最终能产出一条可见思考行」。 - 防复发:条目门不得接到分片通道。新增正则必须能回答「门还是刀」。
- 相关记录:BUG-942
- 复发自:BUG-942
- 修复版本:
e32ce624
BUG-948 | 三类检测器没人调用,范围用户应期被旧口径挖空
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
timing-output-guard.ts、stream-agent-response.ts、consult/route.ts、出生时间模式 - 用户现象:只知道出生范围、或已校正后问应期,日期被挖成「[具体时间已省略]」;同时保证句和个人盘断言在恒等壳下什么都不拦。
- 触发条件:
94c1e81f把守卫改成恒等,Pass 4 只接了方法学记账检测。产品 2026-09-18 授权按模式分开处置。 - 根因:检测器保留了,生产路径没接线。身份壳看起来在保护。旧红线把分钟敏感主题一律挖日期,范围用户不能用。
- 修复:删除恒等壳。Pass 4 按模式:
exact-timing在有盘/窗口模式只记pass4-observe不改写;guarantee全模式pass4-reject,退回 Pass 3 一次,二次丢句;personal-chart仅general_no_birth_time退回,二次整段换成拒绝句。正文先 hold 再一次发出,避免闪两次。 - 验证:timing-output-guard / birth-time-mode / range-reading / birth-accuracy / agentic-runtime Pass 4 合同。范围用户含日期正文原样通过。
- 防复发:全仓不得再出现
guardPreciseTimingOutput/guardGeneralNoBirthTimeOutput/createBirthTimeModeOutputGuard。回执步骤名带pass4-observe:<kind>/pass4-reject:<kind>。 - 相关记录:BUG-298、BUG-943
- 复发自:BUG-298
- 修复版本:
e32ce624
BUG-949 | 思考计划变小后容量算术两条红
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consultation-session-capacity.test.ts - 用户现象:无(测试红)。门禁过不了。
- 触发条件:本命思考计划改成四标题后,1 域 JSON 1053 < 旧下限 1200;19 轮旧口径不再 > 200,000。
- 根因:断言按旧计划形状写死上下界。
- 修复:按 2026-09-18 实测重算:分节 1053/1267/1267;19 轮详情 533,113 B、旧口径 177,973;50 轮 1,402,384 B。三栏说明。19→50 轮产品口径不变。
- 验证:同文件两条容量合同。
- 防复发:改 thinking plan 形状必须重测这两条,不得放宽成「大于 0」。
- 相关记录:BUG-732、BUG-943
- 复发自:无
- 修复版本:
e32ce624
BUG-950 | Pass 4 整段 hold,正文不再逐字出现
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.ts、timing-output-guard.ts - 用户现象:本命 / 窗口 / 一般咨询的正文整段一次性蹦出来,打字机没了,首字延迟等于整段写完。
- 触发条件:
pass4Mode三条路径全开;三个text-delta只产出 1 条answer.delta。 - 根因:
holdAnswer把整段攒进fullOutput,等finishPass4判定完一次发出。948 让步「不闪两次」等于关掉流式。 - 修复:只缓冲当前未闭合句。句子碰到
。!?.!?\n就对这一句跑classifyPass4:无reject立刻send;有reject整句不发。exact-timing的observe不阻塞。methodology整篇重写只允许发生在还没发出任何正文之前;已经发出过句子就只丢后续命中句。流末残句再过一次门。 - 验证:三个
text-delta在verified_chart下 ≥3 条answer.delta;保证句不出现在任何answer.delta且回执有pass4-reject:guarantee;日期句照发且有pass4-observe:exact-timing。 - 防复发:Pass 4 是句门不是刀,不得再整段 hold。合同锁 ≥3 条 delta。
- 相关记录:BUG-948
- 复发自:BUG-948
- 修复版本:
cd4775ae
BUG-951 | 没有出生分钟时,一句拒绝顶掉整段回答
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
timing-output-guard.ts、stream-agent-response.ts - 用户现象:一般知识句和个人盘断言写在同一段时,整段被换成「这部分需要具体出生分钟才能判断」。
- 触发条件:
general_no_birth_time二次落地;无composeAnswer的一般路径没有重写机会。 - 根因:二次命中
personal-chart时整段替换成GENERAL_NO_BIRTH_TIME_REFUSAL。旧实现是按句挖掉。948 任务书「兜底句替代整段」措辞造成。 - 修复:
dropRejectedClauses只丢guarantee/personal-chart/methodology命中句。其余原样。只有全部句子都丢掉、正文为空时才发兜底句。保证句静默删句,回执留pass4-reject:guarantee。 - 验证:混合文本二次后一般知识句仍在,个人盘句与保证句不在,无残留
。。;全丢用例仍落到兜底句。 - 防复发:不得再对
general_no_birth_time二次整段替换;合同锁「preserving general knowledge」。 - 相关记录:BUG-948、BUG-950
- 复发自:BUG-948
- 修复版本:
cd4775ae
BUG-952 | 没有出生时间时的具体日期既不记录也不拦
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
timing-output-guard.ts - 用户现象:无出生分钟模式下写了具体日期,回执没有任何痕迹。
- 触发条件:
classifyPass4对exact-timing在general_no_birth_time直接continue。 - 根因:948 让步 4 把「日期不删字」写成「连 observe 也不记」,恰恰在最没有依据给日期的模式下一笔痕迹都没有。
- 修复:该模式下
exact-timing记observe,仍不拦、不删字。 - 验证:含日期正文原样通过,回执有
pass4-observe:exact-timing。 - 防复发:
exact-timing在所有pass4Mode下都要能观察到。 - 相关记录:BUG-948、BUG-950
- 复发自:BUG-948
- 修复版本:
cd4775ae
BUG-953 | 校正流 token 级 thinking 是死链,按 P2 删除
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
rectification-agentic/v9/stream-mapping.ts、think-step-gate.ts - 用户现象:无(用户看不见这条通道)。947 修好的是测试里的死函数。
- 触发条件:生产校正流对
reasoning-delta在step-answer.ts直接{ kind: "none" }。toPublicThinkingDelta/mapStreamChunkToThinking零生产调用方。 - 根因:上游 P2 规定 provider reasoning 永不外发。把 reasoning 分片推给浏览器本身就违反 P2。947 按任务书方案 (b) 留了这条没人走的路。
- 修复:删除
toPublicThinkingDelta、mapStreamChunkToThinking、InternalThinkingDeltaEvent、acceptThinkingFragment、createThinkingFragmentAssembler。acceptThinkStepText保留。测试翻转成否定合同:源码不得出现这些函数名,reasoning-delta必须丢弃。 - 验证:全仓
frontend/srcgrep 不到上述函数;相关测试翻转后仍绿,测试总数不降。 - 防复发:校正流不得再把
reasoning-delta映射成对外事件。将来校正面板若要显示判断依据,走咨询流 Pass 2think.step。 - 相关记录:BUG-947、BUG-942
- 复发自:BUG-947
- 修复版本:
cd4775ae
BUG-954 | 申报时段咨询自 08-21 起每轮秒败:窗口 Agent 绑了 skill 却没把方法块写进系统提示
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
src/mastra/index.tswindowJyotishInstructions/getWindowJyotishAgent、src/mastra/skill-binding.tsjyotishSkillBoundProcessor;declared_birth_window模式的每一轮 - 用户现象:staging 申报时段会话问「未来半年我事业如何?」,
run.failed code=runtime_contract_incomplete,公开事件只有run.started→skill.started/completed→activity loading-method→run.failed,回执零 tool 步。 - 触发条件:任何
consultationMode=declared_birth_window的请求,与问题内容、模型、供应商无关。 - 根因(服务端日志实证,requestId 脱敏为前八位
95de38fd):docker logs jyotisha-staging-web-1命中两条[WORKFLOW] Error executing step ... input-processor.step.processor:jyotish-skill-bound: Error: Jyotish skill method is not bound into the system prompt for jyotish-vedic-astrology(两次 attempt 各一条)。- 同一 runId 的
[agent-observability]:toolCalls: []、modelStepCount: 0、inputTokens: 0、outputTokens: 0、run.total durationMs: 69、retryCount: 1、errorCode: runtime_contract_incomplete。模型从未被调用,整轮 69 毫秒结束。 - 代码对应:
jyotishSkillBoundProcessor在输入处理阶段断言系统提示里含BOUND_METHOD_MARKER,缺失即abort()。jyotishSkillMethodBlock只在本命jyotishInstructions里插值;windowJyotishInstructions从未包含它,而getWindowJyotishAgent照样...jyotishSkillBinding()。 - 时间线:处理器由
d04fc30b(2026-08-18)引入;窗口 Agent 由9958e00a(2026-08-21)新建,诞生时就带绑定、不带方法块。申报时段路线自 2026-08-21 起每轮必败,约四周。
- 为什么没被发现:
abort()被翻译成与「模型没调工具」相同的公开码runtime_contract_incomplete。见 BUG-955。 - 修复:选任务书方案 (a)。抽出
jyotishSkillMethodCoreBlock(方法主体 + marker,不含本命 Level 2 报告骨架),窗口指令注入该块;本命仍用带骨架的jyotishSkillMethodBlock。窗口口径写明不采用 Level 2 骨架,并优先于方法块里的报告模板 / 应期表述。 - 验证:遍历式源码合同——凡
index.ts里 attachjyotishSkillBinding()的 Agent,instructions 必须含 marker;窗口 AgentgetInstructions()后jyotishSkillBoundProcessor不 abort。部署后仍需在 staging 真发一轮申报时段提问,回执出现run-jyotish-window-consultationcompleted。 - 防复发:源码遍历合同;不得用删掉
jyotishSkillBinding()让窗口路线通过。 - 关联记录:BUG-205、BUG-214、BUG-922、BUG-923、BUG-937、BUG-938、BUG-955
- 复发自:无(与 922/923/937 同现象、不同根因;那三条均未覆盖窗口 Agent 的提示词装配)
- 修复版本:
5b6abc23
BUG-965 | 侧栏「星盘 / 星历 / 我的报告」点了不跳转,账户入口也没反应
- 状态:resolved(由 BUG-967 修复)
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
AppSidebar的NAV_PAGES链接与页脚账户入口;staging877128ce - 用户现象:产品负责人在 staging 的对话页点侧栏「星盘」「星历」「我的报告」没有任何跳转,点「个人资料」也没反应。对话本身可用(同一时段成功发出并收到了申报时段回答)。
- 触发条件:标签页跨过部署。产品确认:刷新之后一切正常,刷新前点什么都没反应。该标签页在 2026-09-18 17:12 的 staging 部署(
9cdcf96b→877128ce,容器重建)之前就已打开。 - 已排除(本轮静态核对):
- 路由存在:
/chart、/ephemeris、/reports在 staging 均返回 200(未登录也返回 200,说明是客户端门禁而非路由缺失)。 - 当前构建自洽:
/chart首屏引用的/_next/static/**资源抽查全部 200。 - 链接是真链接:三项导航是
SidebarMenuLink→next/link的<Link href>,onClick={leaveChat}只做persistLoginSessionReturn()与关抽屉,没有preventDefault。 - 没有覆盖桌面侧栏的
pointer-events: none或inert:inert只出现在移动端抽屉、时间轴折叠体、SidebarInset(insetInert = modalOpen,只盖正文区不盖侧栏)。 - 次级页页脚是
Link href="/"(账户菜单只在/),因此「在次级页点头像」应当跳回首页而不是没反应。
- 路由存在:
- 根因:见 BUG-967。标签页跨过部署后,客户端导航要拉的构建产物已被新镜像替换;Next 16.3.1 自带的构建不匹配回退在自托管 + 预取缓存这条路上没有把用户带到整页重载,点击表现为静默无反应。已经加载好的对话页因为代码都在内存里,照常工作。
- 修复:BUG-967 的版本比对自愈。旧标签页在下一次站内跳转走
window.location.assign。 - 验证:见 BUG-967。
- 防复发:见 BUG-967。不得再把「刷新就好」当成侧栏无响应的结案。
- 关联记录:BUG-967、BUG-204、BUG-744
746、BUG-926929、BUG-936 - 复发自:BUG-204 同类(跨发布客户端导航),那次修的是
deploymentId/?dpl=,在 Vercel 上靠部署路由 404 触发 MPA;本仓是自托管单镜像,那条路径不成立。 - 修复版本:
9ccb65b7
BUG-966 | 星盘 / 星历 / 我的报告进入时矮等待块推挤成高正文
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
chart-page-view.tsx、ephemeris-page.tsx、personal-report-center.tsx、use-chart-page.ts、SecondaryHeader - 用户现象:从侧栏进「星盘 / 星历 / 我的报告」有一瞬间抖动。
- 触发条件:进入这三页,尤其是同一会话里每次进入(没有首屏缓存)。
- 根因:三页各写各的等待布局——先渲染 46px 标题加一行短等待文案,数据到达后整块换成高正文。
SecondaryHeader的note未到时不渲染,标题区二次填充。use-chart-page进页才 fetch,零缓存,每次都重放中间态。产品 2026-09-18 选定方案一:消灭中间态,不加 spinner,红线不动。 - 修复:三页共用
SecondaryPageShell(标题行高度固定、note 槽占位不塌、内容区撑满剩余视口,等待文案居中在该区)。星盘 / 星历 / 报告首屏进模块级缓存,同一会话再进直接用上次结果并后台静默刷新。侧栏三项在pointerenter/pointerdown预热数据。等待句仍是静态句(「这一张盘还没拿到。」等),没有 spinner / 骨架 / 「正在加载」。 - 验证:
frontend/tests/secondary-page-entry.test.ts(三页 import 共享外壳、暖缓存第二次不经 null 等待、预取命中同一 inflight、CSS 内容区 min-height:0 且等待居中);既有chart-page-view/ephemeris-page/ sidebar 合同。浏览器量高度见docs/testing/secondary-page-entry-20260918.md(无 Chrome)。 - 防复发:次级页进入不得再各写一套等待布局。揭幕后不得加 spinner / 骨架 / 「正在加载」。缓存必须模块级,组件 unmount 不能丢掉。
- 关联记录:BUG-716、BUG-717、BUG-745
- 复发自:无
- 修复版本:
9ccb65b7
BUG-967 | 跨部署旧标签页的客户端导航静默失效
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
AppLink、app-navigation.ts、StaleBuildGuard、侧栏NAV_PAGES与次级页账户入口、Web 镜像NEXT_PUBLIC_GIT_COMMIT - 用户现象:见 BUG-965。
- 触发条件:标签页在一次部署之前打开,部署后不刷新,点站内次级页或账户入口。
- 根因(查证 Next 16.3.1 源码,不得跳过):
- 不匹配回退写在
fetchServerResponse:RSC 不是text/x-component、或非 2xx、或x-nextjs-deployment-id/flightResponse.b≠getNavigationBuildId()时才doMpaNavigation。<Link>进页时已经把/chart/ephemeris/reports的 Flight 预取进内存;点击走缓存、不再 fetch,这条检查根本不跑。 - 自托管只有当前镜像。Caddy 对
/_next/static没有改写,只是reverse_proxy web:3000。?dpl=是 Vercel 按部署分流的参数,在本机被忽略。目标路由在新镜像上仍 200 返回新的 Flight,!res.ok也不成立。 handleHardNavError/useNavFailureHandler编译期开关__NEXT_APP_NAV_FAIL_HANDLING,standalone 默认关。旧 chunk 的ChunkLoadError若被 router reducer 吃掉,既有StaleClientRecovery(只听window.error/unhandledrejection)也看不见。客户端导航于是表现为点击无反应;挂起的useTransition还能让同一页的账户入口一起没反应。
- 不匹配回退写在
- 修复:编译期
NEXT_PUBLIC_GIT_COMMIT(Docker 构建时与NEXT_DEPLOYMENT_ID同值写入)对照/api/health.deployment.gitCommit。页面重新可见时拉一次 health,导航前读这份快照。不一致则window.location.assign,一致则继续客户端路由。health 失败不强制刷新。无轮询、无定时重载。 - 验证:合同测试——版本不一致走
assign,一致走传入的router.push/ 不 preventDefault;health 失败或 commit 为unknown不强制跳。真机跨部署见docs/testing/secondary-page-entry-20260918.md。 - 防复发:站内跨布局跳转走
AppLink/navigateAppPath,不得假定 Next 的 mismatch fallback 在自托管上会救场。不得用「每次导航都整页重载」或定时刷新糊过去。 - 关联记录:BUG-965、BUG-204
- 复发自:BUG-204
- 修复版本:
9ccb65b7
BUG-955 | 输入处理器 abort 与「合同未完成」同码,窗口装配失败藏了四周
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.ts、agent-observability.ts、jyotishSkillBoundProcessor - 用户现象:窗口路线 abort 后公开码仍是
runtime_contract_incomplete,与「模型没调工具」无法区分,观测上像 BUG-922。 - 触发条件:系统提示缺
BOUND_METHOD_MARKER,Mastra 发tripwire块且不调模型。 - 根因:
consumeAttempt把 tripwire 当未知块丢掉,流正常结束;contractReady()为假后走合同重试,最终抛同一公开码。 - 修复:识别
tripwire/ 「not bound into the system prompt」为内部码skill_binding_failed;回执追加validation skill-binding-abort failed;打[consult-binding-error](requestId + 内部码,不含提示词);不走合同重试。公开层仍用runtime_contract_incomplete,靠回执步骤名区分。无工具导致的合同未完成另记runtime-contract-incomplete。 - 验证:假流 tripwire vs 空流,回执步骤名分别为
skill-binding-abort/runtime-contract-incomplete;toAgentObservabilityErrorCode两个码都能落。 - 防复发:装配失败不得再并进合同未完成;参照 BUG-938 对供应商 error 块的同类处置。
- 关联记录:BUG-954、BUG-938、BUG-922
- 复发自:无
- 修复版本:
5b6abc23
BUG-956 | 合同未绿时静默丢弃模型正文,用户只看到「未完成」
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.ts - 用户现象:合同没绿时模型写过的正文被丢掉,用户只看到「Agent 未完成必要的方法与计算步骤」。
- 触发条件:
requireTool路径上工具成功次数为 0,但有非空text-delta。 - 根因:
outputText在contractReady()为假时直接 return,重试后仍未绿就抛runtime_contract_incomplete,正文从未发出。 - 修复:未绿期间仍缓冲正文。两次 attempt 后仍未绿、且零次成功计算、缓冲非空:交付该正文,文末附服务端说明「这次没跑完星盘计算,上面是模型直接写的,先看着。」,回执
validation contract-degraded failed,run.completed。无工具也无正文:维持runtime_contract_incomplete。不放宽contractReady()的「每请求一次真实计算」。二次成功计算仍失败,不走降级。 - 验证:假流无工具+有正文 → 正文 + 降级说明 +
contract-degraded;假流无工具+无正文 →runtime_contract_incomplete。 - 防复发:合同未绿不得再整段丢正文;既有「合同完成前的旁白不进可见回答」仍保留,成功计算后清空缓冲。
- 关联记录:BUG-954
- 复发自:无
- 修复版本:
5b6abc23
BUG-957 | 窗口计算由模型决定调不调,对结果无信息增益
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
consultation-tools.tsrun-jyotish-window-consultation、stream-agent-response.tswarmup、consult/route.ts申报时段分支 - 用户现象:只知道一段出生范围时,回答仍取决于模型是否调用计算工具;漏调就整轮失败或降级。
- 触发条件:
consultationMode=declared_birth_window。窗口工具inputSchema只有question,出生数据完全服务端绑定。 - 根因:由模型决定调不调对计算结果没有信息增益,只多一条失败路径(BUG-205/214/922/923/937)。
- 修复:缓存改挂在 agent context 上。服务端在模型循环前
precomputeWindowConsultation,进度经 warmup 写入同一 NDJSON 流;成功则把 packet 以服务端确定性消息注入本轮。工具仍留在窗口 Agent 上,再调用命中同请求缓存且consultationToolSuccessCount不得加第二次。预跑失败合同保持红,模型仍可调工具,既有 retry + 降级仍在。本命不预跑。没有toolChoice: "required"。contractReady()仍是「本请求恰好一次成功的真实计算」,预跑算那一次。 - 验证:预跑成功且模型不调工具 → 合同绿、回执有
run-jyotish-window-consultationcompleted、正文送达;预跑后模型再调 → 缓存命中、成功次数为 1;预跑失败且模型只写字 → 仍降级。源码合同锁窗口工具仍 attach、本命工厂不变。 - 防复发:窗口计算不得再依赖模型选不选工具;缓存不得回到工厂局部
let;咨询侧不得再写toolChoice: "required"。 - 关联记录:BUG-954、BUG-205、BUG-214、BUG-922、BUG-923、BUG-937、BUG-956、BUG-959
- 复发自:无(954 验证通过后按任务书单独做)
- 修复版本:
53d78137
BUG-959 | 降级正文绕过 Pass 4,保证性结论原样送达
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.ts合同未绿时的降级交付 - 用户现象:没跑完星盘计算时,模型自己写的「我保证你一定会升职」一类句子会进正文。
- 触发条件:
requireTool: true、零次成功计算、有非空正文、pass4Mode为有盘模式。 - 根因:BUG-956 的降级分支直接
send(uncontractedText),没有走releasePass4Sentences/classifyPass4。降级正文恰恰是没有任何计算依据时写的,保证句和个人盘断言最容易出现在这里。 - 修复:降级正文复用
releasePass4Sentences按句过门,reject整句丢弃,observe照记。CONTRACT_DEGRADED_NOTE是服务端文案,不过 Pass 4,贴在末尾。全部句子被丢弃时:general_no_birth_time发拒绝句再附说明;其余模式维持runtime_contract_incomplete,不发只有降级说明的空回答。不在句内改写。 - 验证:假流降级 +
verified_chart+ 保证句与安全句 → 保证句不出现在任何answer.delta,回执同时有contract-degraded与pass4-reject:guarantee;全丢不发 note-only;一般模式全丢发拒绝句 + 说明。 - 防复发:降级路径必须复用
releasePass4Sentences,不得另写分句或句内替换。Pass 4 是门不是刀。 - 关联记录:BUG-956、BUG-948、BUG-950、BUG-951
- 复发自:BUG-956(降级路径漏接 Pass 4)
- 修复版本:
5e8b2ac1
BUG-960 | 两次 attempt 的正文被拼起来一起送出
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.tsuncontractedText - 用户现象:合同重试后降级交付把第一稿和重试稿拼成一段,同一轮内容说两遍。
- 触发条件:第一次 attempt 写过字,合同仍红,retry 再写一段不同的字。
- 根因:
uncontractedText += text只在contractReady()为真时清零。合同一直红就一路累加,attempt 之间没有边界。 - 修复:每次
consumeAttempt开始时清空uncontractedText。降级只交付最后一次 attempt 的正文。 - 验证:假流 attempt1「第一段。」、attempt2「第二段。」、无工具成功 → 降级正文含第二段、不含第一段。
- 防复发:未绿缓冲按 attempt 隔离,不得跨 attempt 累加。
- 关联记录:BUG-956、BUG-959
- 复发自:BUG-956
- 修复版本:
5e8b2ac1
BUG-961 | 降级之后还会空跑一轮 compose
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
stream-agent-response.ts降级后的收口 - 用户现象:无(用户看不到 compose 产出)。本命形状的事件序列里出现
phase.started{phase:"compose"},白花一次模型调用与一段墙钟。 - 触发条件:降级交付成功,且该路径传入了
composeAnswer(本命线)。 - 根因:降级分支执行后流程继续走到
publishFindings/composeOnce。 - 修复:成功降级后设
deliveredDegraded,跳过 Pass 2 / Pass 3 / compose / answer-retry,直接run.completed。全丢的非一般模式在 compose 之前抛runtime_contract_incomplete。 - 验证:降级路径
composeAnswer调用次数为 0,事件无phase.started{phase:"compose"}。 - 防复发:降级是终态交付,不得再进 compose。
- 关联记录:BUG-956、BUG-959
- 复发自:BUG-956
- 修复版本:
5e8b2ac1
BUG-958 | 窗口指令「必须调工具」与「不得给应期」未写清,模型可能跳过计算或拒答
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
src/mastra/index.tswindowJyotishInstructions - 用户现象:应期类问题在窗口模式下可能因为「精确应期不可用」而跳过计算或整题拒答。
- 触发条件:申报时段咨询问到时间 / 应期。
- 根因:窗口指令写了必须调工具,也写了不得给精确应期,两句之间没有显式仲裁。
- 修复:补一句:应期类问题仍然要先调工具,再用稳定层给方向性回答,并说明哪部分需要出生分钟;不得因为精确应期不可用而跳过计算或拒答整题。
- 验证:
consultation-workflow-contract.test.ts源码断言。 - 防复发:窗口指令源码合同锁住该句。
- 关联记录:BUG-954
- 复发自:无
- 修复版本:
5b6abc23
BUG-962 | 聊天正文列表没有项目符号
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
.message-markdown ul.markdown-list/ol.markdown-list(聊天正文,不含个人报告 Markdown) - 用户现象:回答里的清单只是往右缩进的孤行,看不出是列表;正文段落靠左、列表块缩进,页面出现两级左边界。
- 触发条件:助手回答含 bullet / 有序列表,或
promoteDefinitionLists提升出的列表。Tailwind v4 preflight 把ul, ol的list-style清成none。 - 根因:
.markdown-list设了display: grid、gap、padding-inline-start,没有恢复list-style。 - 修复:
ul.markdown-list用list-style-type: disc,ol.markdown-list用decimal;::marker取--color-ink-secondary。保持display: grid。不改personal-report-markdown-view。 - 验证:
frontend/tests/chat-stream-layout.test.ts源码断言该选择器块含list-style-type(ul=disc / ol=decimal)。 - 防复发:
.markdown-list规则块必须含list-style;不得再只设缩进不设符号。 - 相关记录:BUG-356、BUG-963、BUG-964
- 复发自:无
- 修复版本:
dccedf37
BUG-963 | 四标题口语散文被自动提升成列表
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
promoteDefinitionLists(frontend/src/lib/chat-definition-lists.ts) - 用户现象:申报时段回答里三段带冒号的散文被改写成三条列表项,和文末真正的行动清单同一种样式。
- 触发条件:BUG-943 之后本命/窗口正文是四标题口语体,段落经常写成「你这半年的事业是 X:……」「推的是火星:……」。连续 ≥2 段被旧判据当成并列定义项。
- 根因:
DEFINITION_LINE = /^(.{2,80}?)[::](.+)$/的 term 允许含句号,body 只要求length >= 4。一段一百多字的多句散文照样算「解释」。 - 修复:收紧,不删除。连续 ≥2 段且同时满足:term 不含句末标点
。!?;!?;与换行;body 是单句(不含句末标点,或仅以一个句末标点收尾);body ≤ 40 字。仍跳过 fence 与已有列表。BUG-356 的并列短项(如「情绪与精力契合。」)继续提升。 - 验证:
frontend/tests/chat-definition-lists.test.ts既有 BUG-356 fixture 仍绿;新增申报时段口语 fixture 保持三段原文,不产出- **term**:。 - 防复发:不得放宽 term 句末标点或 body 长度上限来「多提升一些」;散文冒号不是列表。
- 相关记录:BUG-356、BUG-943、BUG-962
- 复发自:BUG-356(并列短项提升判据过宽,口语体冒号段被误伤)
- 修复版本:
dccedf37
BUG-964 | 思考条与正文之间空 56px
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
.consultation-thinking-report、.message-stage-and-answer、会话作用域.message-markdown h2 - 用户现象:思维链和正文中间间隔好大。harness 实测思考条底到正文首标题 56px。
- 触发条件:会话里助手回答以
h2开头(四标题口语体的「先回答你的问题」)。 - 根因:56px =
.consultation-thinking-report { gap: 24px }+ 首个h2的margin-top: 32px。.message-markdown > *:first-child { margin-top: 0 }被更高特指度的.conversation:not(.is-empty):not(.is-rectification) .message-markdown h2 { margin: var(--space-8) 0 var(--space-3) }盖掉。另一条路径.message-stage-and-answer的 gap 只有 8px。 - 修复:会话作用域补
.message-markdown > *:first-child { margin-top: 0 }(无!important)。两条路径的 gap 都改读--consult-think-answer-gap(var(--space-3)= 12px)。会话.markdown-list的 gap 从--space-4(16px)降到--space-2(8px)。 - 验证:
frontend/tests/chat-stream-layout.test.ts锁会话 first-child 清零、两处 gap 同一 token、列表符号恢复。headless Chrome:gapPx=12,h2MarginTop=0px,listStyle=disc。截图docs/testing/chat-markdown-list-20260918-after.png。 - 防复发:思考条与正文间距不得再各写各的;会话作用域不得让首元素
h2再带--space-8的 margin-top。 - 相关记录:BUG-962、BUG-356
- 复发自:无
- 修复版本:
dccedf37
BUG-968 | 账户弹窗打开后整个弹窗点不动,退出登录做不了
- 状态:resolved
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
src/app/(app)/layout.tsxAppShell、src/app/(app)/page.tsxAccountDialogOverlay渲染位置、use-home-shell-registration.tsinsetInert;staging877128ce - 用户现象:点侧栏「个人资料 / 退出登录」等任一账户入口,弹窗出现但关闭按钮、表单、确认退出全部点不动;正文区同时点不动。刷新后依旧。
- 触发条件:任何让
modalOpen为真的弹窗(五个账户弹窗、onboarding 付费墙)。 - 根因:
e4e73f56把SidebarInset搬进(app)/layout.tsx后,inert={modalOpen}罩住了整个children,而AccountDialogOverlay由page.tsx渲染、没有 portal,落在 inert 子树里。搬家前弹窗是SidebarInset的兄弟节点。 - 修复:选 (a)
createPortal(..., document.body)。AccountDialogOverlay与 onboarding 付费墙都挂到document.body;SidebarInset的inert={modalOpen}和移动端抽屉isMobile && openMobile分支不动。选 portal 而不是把节点交给 layout,是为了不改registerShellControls协议、并保住现有 DOM 结构与样式。 - 验证:
frontend/tests/account-dialog-inert-20260918.test.ts断言 overlay / paywall portal 到document.body、layout 仍 inertSidebarInset、role="dialog"渲染不含 inert;现有 overlay / sidebar / a11y 合同见定向测试。真机五弹窗点关闭/提交见docs/testing/account-dialog-inert-20260918.md。 - 防复发:合同测试断言弹窗 portal 到
document.body,且SidebarInset仍吃inert。 - 关联记录:BUG-744
746(侧栏统一)、BUG-926929(外壳搬家)、BUG-965(跨部署标签页,另一问题) - 复发自:无
- 修复版本:
824ecff0
BUG-969 | 生时校正「换一件事问」之后无下文:快照拿不到,两端都不吭声
- 状态:mitigated(快照失败根因仍未由线上日志证实,不得写 resolved)
- 首次发现:2026-09-18
- 最近更新:2026-09-18
- 影响面:
GET /api/rectification/cases/[caseId]、rectification-agentic-chat.tsxloadCaseSnapshot、rectification-surface-state.tshydrateRectificationCase、确定性回复的题干投递 - 用户现象:口述两轮经历后说「想不到了」,回复「已记录。这题先不计分,换一件事问。」然后没有题卡;右侧盘面停在占位、时间轴无候选点、输入框占位是「再说一件带年月的事」。
- 触发条件:真实 Case 上服务端已落库下一道财务探针(active,绑在该回复轮),GET 快照本地用真实数据行重算完整、客户端解析函数全部通过;客户端却仍是开场时的快照状态。
- 根因:未定。已确认的是链路两端都静默:GET 路由所有异常 503 不写日志,客户端快照失败一律吞掉,边缘与应用层都无 GET 访问日志,PG 未开函数统计。事后无法证明请求是否到达服务端。
- 修复:① GET 每一个非 200(401/400/409/404/503,含 supabase 配置失败)
console.warn一条 JSON(event=rectification_case_get_failed、case_id、status、code、reason=safeToolErrorCode,不含用户资料)。②loadCaseSnapshot/hydrateRectificationCase/refreshRectificationCase把非 2xx 或网络错误带回调用方;校正面显示「这一问还没读到。」+「重新读取」,不再装成采集等待。③ 下一题是可渲染选择题时,persistApplied与persistCollectDenialTurn用composeCollectSpokenAssistantText把题干追加进正文并随流发出;卡片仍由 GET 挂上,attachQuestionsToTurns的stripQuestionSentences去重。 - 验证:
frontend/tests/account-dialog-inert-20260918.test.ts覆盖 GET warn、503 文案与按钮、ack+stem 流式及挂卡后题干只出现一次;hydrate 失败带failure.status。真机清单见docs/testing/account-dialog-inert-20260918.md。若真机仍复现,凭服务端 JSON warn 把本条转 confirmed 并补根因。 - 防复发:确定性回复不得只靠 GET 挂卡投递题干;快照失败不得静默。
- 关联记录:BUG-525(题干经 asked_turn_id 挂上)、BUG-930(回答钉顶:只见最后一轮是设计)、BUG-965
- 复发自:待确认
- 修复版本:
824ecff0
BUG-970 | 设置弹窗内容区留白过大、导航入口语义像页面跳转
- 状态:investigating
- 首次发现:2026-09-19
- 最近更新:2026-09-19
- 影响面:设置弹窗的桌面布局、个人资料头像区、星盘资料列表入口。
- 用户现象:设置弹窗左侧导航过窄,右侧内容贴边且留下较大空白;分区按钮尾部箭头暗示会进入下一页;个人资料头像锚点偏小;星盘资料的添加入口位于列表末尾,资料较多时不稳定。
- 触发条件:登录后打开设置弹窗,在桌面宽度切换四个设置分区,查看个人资料或星盘资料列表。
- 根因:旧布局使用 176px 导航、三列导航按钮(为同弹窗内切换保留无实际用途的箭头列)、内容区没有明确内边距,表单子内容上限为 440px;头像编辑仍以 48px 预览布局;添加入口与列表内容耦合在列表底部。
- 修复:导航调整为 200px 并增加 hairline 边界,移除误导性尾部箭头;内容区增加 32px 桌面内边距,表单阅读上限调整为 560px;个人资料头像预览调整为 56px;“添加其他人”移动到“其他人”分组标题操作区。保留四分区共享
.settings-modal和vh+@supports (height: 1dvh)回退契约。 - 验证:
./node_modules/.bin/tsc --noEmit安装依赖后退出码 0;npm run lint退出码 0(0 errors、120 warnings);设置相关定向测试 21/21 通过。广泛测试筛选命令为 3481 tests / 3402 pass / 79 fail,失败集中在重复迁移、Windows 路径、Docker/数据库和技能 symlink 等环境或基线问题,不能作为本轮 UI 回归归因。npm run build已完成编译和 TypeScript,但在收集/api/daily-starlanguage页面数据时因 Windows 禁止创建 Skill runtime symlink(EPERM)失败,因此尚未确认/Static 或首屏 gzip;登录态浏览器走查仍待受控环境。 - 防复发:设置导航继续使用 icon + label 的两列结构且不添加页面跳转箭头;右侧滚动容器负责内边距,表单 cap 只作用于其子内容;列表视图默认不渲染表单,添加入口固定在“其他人”标题操作区;
frontend/DESIGN.md与合同测试同步锁定这些关系。 - 相关记录:BUG-554、BUG-698
- 复发自:无
- 修复版本:待发布
BUG-971 | 首页侧栏注册反馈循环阻塞客户端导航
- 状态:investigating(代码与定向回归完成;部署和登录态真人闭环待核对)
- 首次发现:2026-09-19
- 最近更新:2026-09-19
- 影响面:首页侧栏「星盘 / 星历 / 我的报告」客户端导航与共享外壳更新。
- 用户现象:测试站刷新后仍点了不切页,Network 出现
/api/ephemeris;该请求来自 pointerenter/pointerdown 数据预取,不代表 Next 导航成功。 - 触发条件:登录态 Home hydrated 后注册侧栏操作;useSessionManagement 每次 render 产生新的 visibleSessions/操作函数,注册 effect 随之再次发布。
- 根因:SessionListContext 同时承载业务数据和 registration。Home 写 registration 又订阅同一 value,setRegistration 广播反向使 Home 重渲染,形成默认优先级更新循环,阻塞 Next Link 使用的 startTransition。旧共享外壳测试只有源码正则,不能执行 effect,未拦住运行时反馈。
- 修复:会话数据 context 保留稳定 registrar,registration 独立只读 context 仅供 AppShell 订阅;映射显式接收 registration,children 原样透传。保留注册 effect 完整依赖、最新回调及卸载注销,不冻结首次闭包、不改成整页刷新。
- 验证:真实 React 最小原版实验 122 render / 118 注册 / 2 maximum-depth 告警(保护阈值主动终止);稳定数组和回调的因果对照为 3/1/0。另一次 transition 实验直到主动停止反馈后目的地才 commit。正式新生命周期测试旧实现 3 fail / 1 pass、修复 4/4 pass;含普通/StrictMode 空闲收敛、transition 注销重入、最新业务数据与回调、401。实际 SidebarProvider/Inset 包裹,但不模拟浏览器点击/布局;完整主会话复验、基线失败对照与发布结果见进度记录。
- 防复发:Home 不订阅自己生产的 shell registration,外壳不重建 children;保留真实客户端 React 回归而非仅 SSR/源码正则。未稳定数组/函数的测试输入不得改成固定常量来掩盖回归。
- 相关记录:BUG-927(共享外壳)、BUG-965 / BUG-967(同名导航现象但不同跨部署机制);本轮不扩修 health 快照时序漏洞。
- 复发自:BUG-927 共享外壳订阅边界的新增运行时回归,非已证实的旧构建不匹配复发。
- 修复版本:本轮
fix(frontend): isolate sidebar registration to unblock navigation提交;发布状态见docs/tasks/PROGRESS-sidebar-navigation-loop-20260919.md。 - 验收缺口:Windows Skill runtime symlink EPERM 阻止完整基线构建;Static/gzip 与真实登录态浏览器检查不得标通过。见
BLOCKED.md和docs/testing/sidebar-navigation-loop-20260919.md。
BUG-972 | 上游私人案例与本机路径残留进入本仓跟踪资料
- 状态:blocked(清理与引用修复已实施;上游哈希快照、完整验收与交付仍待闭环)
- 首次发现 / 最近更新:2026-09-19。
- 现象与触发:历史镜像同步带入私人案例、会话转录和本机路径,散落于测试、研究台账及旧快照。
- 已确认根因:研究用输入被复用为测试 fixture;存量资料未受仓库级隐私守卫覆盖。主业务入口未直接引用某台账,不足以证明该台账没有间接读者。
- 修复进展:T2 改为明确虚构的共享常量;产品批准补充范围和具体删除目标后删除 3,494 文件,修复 8 份 oracle 派生资料及 manifest,索引 185 → 174,缺失路径为零,新增 9 项合同通过。额外历史文档已脱敏,保留 blocked/reference-only 限制;受哈希绑定的上游快照及校正历史包保持不变。
- 验证:实际基线
35ca7299c;全量 Python 在收集阶段因缺少 mcp / hypothesis 报错,不能据此声称用例全通过。定向验证、数量差异与未完成项见docs/tasks/PROGRESS-owner-case-purge-20260919.md。 - 防复发:清理必须连同真实依赖审计;不得只检索主入口就宣称模型上下文和运行时不受影响。日志与记录只留符号定位,不复写私人输入。
- 关联记录:BUG-295(live 咨询入口与哈希历史包边界)、BUG-973(同步与仓库隐私防线)。
- 修复版本:本轮本地工作树,尚未提交、推送或部署;不得标记 resolved。
BUG-973 | 同步入口缺少隐私路径模式,咨询隐私守卫不覆盖仓库
- 状态:investigating(路径拦截与仓库守卫已实现;最终独立复验和交付尚未完成)
- 首次发现 / 最近更新:2026-09-19。
- 现象与触发:修改 mirror 映射时,原同步策略仅保护业务和凭据路径,不阻止任务书列明的私人案例与本机扫描资料路径;咨询输出检查不会扫描跟踪文件。
- 根因:导入工具缺少不可由外部 policy 移除的隐私路径规则;现有禁止标记直接以字面量写在测试源文件,原样全仓扫描将自命中。
- 修复:新增独立
REQUIRED_PRIVACY_PATH_PATTERNS,在load_policy同时拒绝镜像源、目标与 semantic_merge 路径,根目录与嵌套路径、大小写一致受约束;不改原商业保护集合,避免现有 policy 全部加载失败。报错不回显命中内容。 - 验证:新增 42 条参数化用例通过(mirror 28 + semantic_merge 14);同步工具整体 70 passed / 2 failed,两条失败均为基线已有 Windows 符号链接权限缺口(WinError 1314)。仓库守卫含实际扫描共 57 项通过,补齐链接只读目标文本与本地规则 BOM 回归。第二轮验收发现
pl9_1993模式撞答案键守卫,已删除该重复模式;答案键禁止字面量测试保持不变并通过。快速门缺少 mcp,未声称通过。 - 防复发:保留源/目标、根/嵌套、大小写回归;仓库扫描复用既有标记并检查用户目录前缀,已接入 quick。仅精确许可经过核实的声明/合成样例及无关数值节点,48 项自测通过;历史校正包正常扫描,不豁免整目录。该固定规则集不是通用 PII 检测器。
- 关联记录:BUG-190 / BUG-191 / BUG-192(导入约束历史)、BUG-261 / BUG-278(导入与保护边界)、BUG-972(存量残留)。旧防线覆盖范围有限,未拦截本次路径资料。
- 修复版本:本轮首轮提交
fe9489e6;第二轮 P0 修复尚未推送或部署,待远端认证恢复后发布并由验收方复核。
BUG-974 | 跟踪的 .venv 符号链接让 staging 隐私扫描 READ_ERROR
- 状态:resolved
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:
tests/test_repo_privacy_markers.py、stagingbackend-quality-gatevalidate - 用户现象:门禁 run 2807 红。Python 快速门 858 通过、1 失败:
test_tracked_repository_has_no_privacy_markers报files=1 hits=1/READ_ERROR .venv。publish跳过。线上仍停在上次绿灯d6fc4fb8。 - 触发条件:staging 推送触发门禁;validate 先
rm -rf -- .venv再python3 -m venv --clear .venv,然后跑仓库跟踪文件隐私扫描。 - 根因:
9375012f(文档提交)把名为.venv的符号链接写入跟踪树。.gitignore写成.venv/,Git 只忽略同名目录、不忽略同名符号链接。CI 用真实虚拟环境目录盖掉该链接后,扫描器对跟踪路径.venv做lstat得到目录而非普通文件,记为READ_ERROR。不是又扫到姓名或本机路径。 - 修复:从跟踪树删除该链接;
.gitignore改为.venv(文件、目录、符号链接都忽略)。合同测试锁定:git ls-files不含.venv,git check-ignore --no-index .venv命中。 - 验证:
pytest tests/test_repo_privacy_markers.py62/62;前端头像合同见 BUG-975。门禁 2809 成功,staging/api/health=12fdaed9。 - 防复发:不得把本地运行时目录或指向它的符号链接加入跟踪树;忽略规则不要只写带尾斜杠的目录形式。
- 相关记录:BUG-972、BUG-973
- 复发自:无
- 修复版本:
8dc460da
BUG-975 | 头像说明文案已改,源码合同仍锁旧句,门禁卡在 npm test
- 状态:resolved
- 首次发现:2026-09-19
- 最近更新:2026-09-20
- 影响面:
frontend/tests/beam-avatar.test.ts、frontend/src/components/profile-panel.tsx - 用户现象:门禁 run 2804、2805、2806 的
npm test红:renders the persisted Beam avatar in the sidebar, account menu, and profile editor。run 2807 先在 Python 步失败,未跑到这一步。 - 触发条件:设置弹窗改了头像区说明后,任何会跑
npm test --prefix frontend的 staging 门禁。 - 根因:产品文案已是「Beam 形象会在刷新和换设备后保持一致」,合同仍断言「Beam 形象由随机种子生成」。
- 修复:合同改为锁定现行文案。不改产品文案。
- 验证:
frontend/tests/beam-avatar.test.ts定向;断言三栏见进度记录。 - 防复发:改设置弹窗可见文案时同步源码合同,不得只改组件。
- 相关记录:BUG-970
- 复发自:无
- 修复版本:
8dc460da
BUG-976 | 普通对话寒暄被当作咨询:全量排盘、长判词与扣点
- 状态:blocked(独立审查、Linux 全量 3566/3566、标准 DB 40/40 及构建/gzip 通过;staging 部署与真人实测待闭环)
- 首次发现 / 最近更新:2026-09-20。
- 影响面:普通
POST /api/consult三种模式、流式 UI、咨询账务与历史恢复。 - 现象与触发:纯打招呼也进入强制计算与报告形状,并扣一次咨询点数。
- 根因:入口没有非咨询轮,预留与完整咨询链覆盖全部普通消息;原取消函数只退款不持久化回复,没有免费完成语义。
- 修复:所选模型一次短结构化分类并写寒暄回复,3 秒 fail-open,无 Skill/工具/出生资料注入;只看本轮问题、称呼与最后完整可见问答。普通入口命中后先原子免费结算,再发 answer.delta + 无 receipt 的完成事件。新 service_role-only RPC 沿用 advisory lock,校验所属/状态/回复,记录真实 usage、按 credit_amount 退款,完成请求并释放 reservation。完成重试不退款,已收费咨询不能借此退款。UI 标记持久化,寒暄不造步骤/思考/技法。
- 验证:假模型、schema、超时、断连、流协议和 SSR 展示定向通过;真实 PostgreSQL 17 覆盖退款幂等、回复冲突、跨用户/会话、取消竞态、已收费拒退、真实成本、事务回滚和无点数来源。Linux tsc 0、lint 0 error/120 既有 warnings;标准 DB 基线 39/39、候选 40/40,完整构建两侧首页 Static,首屏 JS gzip 621,299 → 621,605 B(+0.0493%)。Windows 迁移镜像与 Skill alias EPERM 的历史不删除,Linux 完整全量及部署证据见进度。
- 防复发:保留分类失败回咨询、服务端专有授权、先持久化后完成、刷新标记、取消/完成共享锁回归;不得加入寒暄关键词/正则/字数分类。独立审查补获 SDK strict 校验日志可能携带原文、且非法输出丢 usage 两项,已局部静默 SDK logger 并保留生成结果后严格解析,真实 SDK 假模型回归断言隐私、fail-open 与成本保留;相关定向主会话复跑 59/59,真实库定向复跑 1/1。
- 关联记录:BUG-922 / BUG-923 / BUG-937 / BUG-938;这是非咨询轮的新边界,不是旧每轮排盘防线复发。every turn、prepareStep、contractReady 与两处 requireTool:true 未改。BUG-280/305 的空回答或失败不得结算语义保持;BUG-347 的真实咨询思考历史不删除。
- 边界:reserve 仍在分类前,零点无订阅仍被拦;超时未知 usage 不伪称零,晚 usage 只观测不追写完成账本。
- 修复版本:
539d4dae,已合入 staging 并部署(/api/health的deployment.gitCommit与apiGitCommit均为该 SHA,latestMigration=20260920010000_consultation_free_completion.sql,2026-09-20 由 Claude 独立核对);详见 PROGRESS-consult-smalltalk-fastpath-20260920。状态保持 blocked:真人三模式与余额走查未做。
BUG-977 | 寒暄提示词豁免只存在于本命 Agent
- 状态:blocked(本地合同通过;真实三模式模型验收待执行)
- 首次发现 / 最近更新:2026-09-20。
- 影响面:productConversationVoice、natalSpokenReportContract,窗口/无出生分钟的简短社交回复。
- 现象与触发:只打招呼也套开场形状;窗口与一般模式拿不到原本命 chit-chat 豁免句。
- 根因:豁免写在仅本命拼接的常量内,共享 voice 的四步形状没有例外。
- 修复:将豁免移到三模式共享 voice,明确只回一句、≤20 字、无句号、无星盘主张;本命领域问题仍需原 opener-plus-skeleton。主修仍是 BUG-976 分流,提示词不代替计费与计算边界。
- 验证:三种指令模板展开共享 voice 后均含豁免,原 OPENER SHAPE 合同保留;未用真实模型假装语义验收。
- 防复发:共享语气规则不能只放单一模式专属合同;混合咨询/解释/纠错/抱怨仍走完整路径。
- 关联记录:BUG-922 / BUG-923 的边界之外,不撤销每轮咨询排盘;与 BUG-976 配套。
- 修复版本:
539d4dae,已合入 staging 并部署,Skill 版本不变。状态保持 blocked:真实模型三模式语义验收未做。
BUG-978 | 封存契约当前打分身份与资料审计状态过期
- 状态:resolved
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:封存 holdout 契约、当前实现评测身份与离线成绩归属。
- 用户现象:历史冻结值、历史 sidecar 当前值、当前代码实测哈希互不相同;契约仍写旧资料审计状态与评测日期,当前成绩无法正确归属。
- 触发条件:打分文件发生变化而契约未刷新,再读取契约判断当前引擎验证状态。
- 根因:人工维护契约;既有测试仅与旧报告相互核对,没有断言真实当前树身份与数据集状态。
- 修复:本轮新增固定口径重跑与 freshness 回归;执行与证据见
docs/tasks/PROGRESS-rectification-validation-20260920.md。保持发布六键与确认门不变。 - 验证:freshness 与确认门相关定向通过;独立复算逐例结果及汇总与冻结重跑 JSON 一致,六个 runtime 键未变。新增测试通过既有快速门 glob 桥接实际收集。旧 v2 哈希测试与 quick 环境失败与基线相同,详见进度。独立盲测因案例曾曝光仍 blocked,固定实现重跑不恢复未见性;resolved 仅指元数据过期与可追溯缺口,partial 指新独立盲测未完成。
- 防复发:当前打分文件 SHA-256、源审计状态与契约必须一致;新鲜度检查须进入实际快速门收集范围,不能仅存在于测试目录。新盲测还必须有未曝光样本和独立标注审核。
- 相关记录:BUG-098、BUG-427、BUG-428。
- 复发自:BUG-098 的独立 holdout 边界与 BUG-427 的身份绑定缺口;前者要求未被落实为独立封存流程,后者没有当前树 freshness 检查,旧 sidecar 自洽仍可过测试。
- 修复版本:本轮执行分支,未交付。
BUG-979 | 真值中心候选窗掩盖申报偏差导致的真值出窗
- 状态:resolved
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:离线候选窗评测与生产申报中心搜索窗的可比性。
- 用户现象:离线评测真值永在候选窗中心,无法衡量真实申报偏差大于搜索半径时的失败模式。
- 触发条件:用户申报偏离独立记录且偏差超过候选窗半径。
- 根因:旧评测从真值构窗,缺少独立的申报偏移变量和真值在窗覆盖指标。
- 修复:并行新增偏移敏感性评测,不修改历史候选函数;排名后才揭示真值,用既有 opaque 哈希破同分。
- 验证:完整修正版 900 组合、45 格,排除 0;新增偏差与 freshness/桥接回归 28/28 通过(包含桥接重复收集),真实引擎跨日分组与逐候选重算相同。报告见
docs/research/reported_offset_2026_09_20.md,口径与哈希见同名 JSON。resolved 仅指离线缺少偏差维度的缺口,不代表产品分钟准确或生产跨日问题已修。 - 防复发:锁定零偏移、窗外、口径字段、非真值破同分与跨日行为;窗口覆盖与排序命中分开记录,不以敏感性代替真实用户准确率。
- 独立审查追加:首版偏差评测的矩阵为全窗共用一个日期,跨日时辅助 transition proximity 有偏差。已在离线适配层按候选日期分组计算并与逐候选真实引擎结果对照,不修改生产打分;原初扫统计作废。生产同类日期处理不在本单修复范围,记入 BLOCKED,不能声称生产端到端等价。
- 后续生产问题:候选级 Dasha 日期修复单独跟踪于 BUG-981;本记录 resolved 仍仅限离线评测缺口。
- 相关记录:BUG-098、BUG-978、BUG-981。
- 复发自:BUG-098;既有防复发聚焦候选公开门与同案稳定性,没有离线与生产窗心可比性的测试。
- 修复版本:本轮执行分支,未交付。
BUG-980 | 缺少事件丰富且从未曝光的独立封存集
- 状态:investigating
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:分钟级评测可推广性与独立发布证据。
- 用户现象:旧封存集事件少;事件丰富的开放集用于调参、成绩已见,不能替代新的独立验证。
- 触发条件:把开放评价集或真值方向最佳答案回放当成产品分钟级准确率。
- 根因:开放集建设之后未建立独立采集、未曝光、独立人审的封存孪生集。
- 修复:本轮仅制定 v5 采集协议与不重叠公开候选池,不采事件、不生成新成绩。
- 验证:协议含事件/领域目标、独立来源、人审封存、曝光日志、工作量和发布边界;备选池与旧集去重通过(仅候选资格筛查,原文准入仍需复核)。v5 未建成,保持 investigating。
- 防复发:采集、标注、评分权限分隔,调参前封存,首次揭晓前不得以事件内容调参;拒绝自动置真人审标志和重新封存已曝光案例。
- 相关记录:BUG-098、BUG-428、BUG-978、BUG-979。
- 复发自:BUG-098;原记录明确延期独立分区与校准,本轮仍无真正独立样本,不能将文档协议写成验证完成。
- 修复版本:协议执行分支;数据集未就绪,不标 resolved。
BUG-981 | 跨午夜候选共用窗口日期导致 Dasha 邻近度偏移
- 状态:investigating
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:生产事件贡献矩阵 transition proximity、跨午夜候选排序与实现身份追溯。
- 用户现象:跨午夜窗口的部分候选按窗口起始日计算 Dasha 边界,结果可能改变分数与排序;此前离线评测用日期分组适配绕开,生产问题仍在。
- 触发条件:候选实际日期与请求
birth_date不同,进入merge_transition_proximity()。 - 根因:helper 将请求日期用于所有候选的 Vimshottari / Narayana 起始计算及缓存键;候选的完整
candidate_at未用于此层。 - 修复:按产品已批准的任务书,先红测再改候选级日期与缓存键,不调权重;冻结重跑和 V9 版本标记同步实施。进度见
docs/tasks/PROGRESS-rectification-cross-midnight-20260920.md。 - 验证:原实现新增测试 4 failed / 4 passed,修复后 9 项与 quick 桥接重复执行共 18 passed;另 3 项既有版本/枚举回归通过。19 个公开 AA 同日窗口、2299 个候选分数与矩阵字节不变;实跨日窗口全候选等于逐候选日期正确独算。性能配对中位 -0.87%,满足不超 +20%;口径 v3、raman/mean、±60、步长 1,旧 12 文件身份
b15d9ea15227cd58、新扩展生产身份7fffd1db612af1f2。独立主会话日期/隐私补验 80 项通过。尚无部署证据,保持 investigating。 - 验收边界:已有时段缓存与聚合回执身份导致 T4 未完整通过,见 BUG-984;另发现 BUG-982/983,不把候选级日期一致夸大为所有跨午夜路径端到端正确。
- 独立 review:
aa46da10核心修复本身通过;新增回归的门禁级跨机浮点哈希问题单独立号 BUG-985。F3 已确认时段重算经过已修 helper,证据未变的历史跨午夜时段缓存却会绕过新算法,故 BUG-984 是本项端到端验收阻塞项,不以测试修复代替缓存或部署闭环。 - 防复发:新增生产打分层跨午夜回归并接入 quick 收集;候选枚举正确不再被视为下游日期正确的充分证据。保留 BUG-427/428 的实现身份与已曝光边界。
- 相关记录:BUG-098、BUG-427、BUG-428、BUG-621、BUG-978、BUG-979。
- 复发自:BUG-098 的跨午夜兼容只覆盖逐分钟枚举,未覆盖后加的 transition-proximity 层;BUG-979 已在离线适配发现,但其范围禁止改生产,因而没有消除此生产缺陷。
- 补充回归:最终三个校正 Python glob 共 300 项,296 passed / 4 failed,四项失败与基线逐条相同、新增失败 0。算法 -8 的版本断言及真实引擎 golden 两项身份已精确同步,数值与比较器不变;独立定向 25 项通过。900/20 正式重跑的真实源码身份、历史字节与报告汇总经独立审查通过,确认门与独立性边界不变。
- 修复版本:
codex/rectification-cross-midnight-20260920核心修复完成,T4 未完整通过;按产品要求仅交付独立分支供远程 review,不合入 staging、未部署,推送结果以远端 SHA 核对为准。
BUG-982 | 跨午夜短簇按钟点排序被扩成全天跨度
- 状态:investigating
- 首次发现:2026-09-20
- 最近更新:2026-09-21
- 影响面:
candidate_contrast.py、decision_policy.py的簇范围与宽度。 - 用户现象:跨午夜连续三个分钟候选组成的短簇,被表示为从当天最早钟点到最晚钟点,宽度扩成全天。
- 触发条件:同一签名簇跨午夜,钟点字符串排序而没有候选日期或全窗序号。
- 根因:签名聚类及
_cluster_span()丢失日期,只用钟点决定首尾;这不是本轮 Dasha 日期修复新引入的行为。 - 修复进展:日期锚点任务已本地实现 ordinal 排序、真实 offset 计宽及声明 segment 内 envelope,Python 聚类/决策、TS credible union/plateau、transition/probe 与交付贯通;不连续段不补洞。算法身份更新为 scoring-9,历史不重标。三公开 AA 独立子进程同机 A/B 分数、矩阵、簇边界与确认门逐位相等。真实 native golden 经前端请求/推断/交付 SSR 验证;最终全量及部署待验,保持 investigating。
- 验证:纯虚构最小探针沿
build_candidate_decisions()实测一簇三个连续跨午夜成员被报为 1440 分钟。未涉及真实案例资料,未标 resolved。 - 统一验收进展(2026-09-21):final-1 隔离 Linux 快照前端3649/3649、真实DB56/56通过,诊断性三公开AA七项投影21/21零容差相等;Python广域338通过/11失败,其中4个既存失败、7个新增失败,quick未运行,整体验收未通过。新增项包括3个版本/回执契约失配及4个历史冻结身份拒绝;后者是更早BUG-981冻结记录不能代表当前实现时正确fail-closed,不是final-1停写后生产源码漂移。旧证据原样保留。Grok 接续:三合同测试独立复验34/34;新扩展身份补四模块后真实20/900重跑并派生 current contract;新最终快照 final-3 tsc/lint0、前端3649、DB56、quick通过、AA 21/21、Python广域352/4(0新增失败)。无部署/真人证据,保持 investigating。详见PROGRESS。
- 防复发:后续需完整日期/相对窗序号回归贯穿签名聚类、候选决策与交付范围,不能只测试辅助
_primary_cluster。 - 相关记录:BUG-623、BUG-624、BUG-639、BUG-981。
- 复发自:未发现同症状既有记录;BUG-624/639 的范围断言仍在但均是同日样本,跨午夜诊断测试覆盖的是另一条聚类路径,未覆盖本路径。
- 修复版本:实现
b85c4a68,2026-09-21 经 Claude 独立验收后快进合入staging;部署与真人验收尚未完成,故状态保持investigating。证据及待验项见docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md。
BUG-983 | 凌晨申报窗口的候选日期锚点可能偏到次日
- 状态:investigating
- 首次发现:2026-09-20
- 最近更新:2026-09-21
- 影响面:V9 搜索窗、
engineRequestBody()与_candidate_datetimes()的日期传递。 - 用户现象:申报在午夜后的范围向前跨日时,生产候选中心可能落到申报日期的次日。
- 触发条件:申报日内早凌晨分钟采用左右搜索半径,起始钟点位于前一天,而请求仍只传未调整的申报日期。
- 根因:前端只传钟点范围与原申报日期,生产枚举将起始钟点绑定该日、较小终止钟点推到次日;候选日期本身已错误,helper 按候选日期一致计算并不能修正上游锚点。
- 修复进展:按已批准 D1/D2/D3 传显式本地日期区间,凌晨申报锚在申报日、前半窗落前日;late_night 追问一次,未知/跳过保留同日两段。真实服务端 schema 回归发现新选项数字钟点被既有文案过滤拒绝,改中文钟点,未放宽过滤。D4 明确授权后新增兼容迁移,保存独立 active date/来源/实际候选 offset,原 birth_date 保留;report sampler 与 VedAstro 使用实际候选日期。SQL 采用权限合成测试不代表真实候选通过确认门;未修 IANA/DST。最终全量与部署待验。
- 验证:纯虚构窗口探针证明申报中心候选与申报日期的日期差为 +1 日;不含真实用户资料,未标 resolved。
- 补充回归:预冻结审计发现新widen wrapper仍会按baseline重猜日期,丢持久区间与D1侧别;真实DB红测复现后,限本单30000迁移修为普通分钟按持久绝对日期扩窗、D1允许集合服务端保存、C/skip缩到单段仍保留原允许两段。侧别冲突等拒绝必须全事务无写入;真实DB dossier/compute→评分service→score请求捕获验证日期保持,网络截停不冒充引擎评分完成。最终标准DB与统一门禁见PROGRESS,仍未部署。
- 统一验收进展(2026-09-21 Grok 接续):隔离 Linux final-3 全门 tsc/lint0、前端3649、DB56、quick通过、AA 21/21、Python 广域 0 新增失败。无部署/受控真人证据,保持 investigating。
- 防复发:必须从申报日/分钟经实际前端请求构建到后端枚举端到端校验日期,而非只验证钟点范围与“支持跨午夜”;扩窗不得重猜日期或将未知缩窄结果伪作用户选侧。
- 相关记录:BUG-098、BUG-198、BUG-979、BUG-981。
- 复发自:未发现同症状既有记录;BUG-198 时段开窗与 BUG-098 枚举回归仍在但不校验申报日期中心。离线 holdout 已有前日中心保护,未覆盖生产请求链。
- 修复版本:实现
b85c4a68,2026-09-21 经 Claude 独立验收后快进合入staging;部署与真人验收尚未完成,故状态保持investigating。证据及待验项见docs/tasks/PROGRESS-rectification-midnight-date-anchor-20260920.md。
BUG-984 | 时段旧分数缓存不校验算法身份且聚合回执可能显示新版本
- 状态:investigating
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:V9
block_scan缓存、工具活动回执与 turn 级聚合。 - 用户现象:升级算法后,同证据的时段模式仍可能直接返回旧分数;开始活动显示新版、成功结果实际属于旧版,聚合字段又可能展示新版。
- 触发条件:历史时段缓存证据指纹未变;本次运行默认版本已升,而缓存算法身份仍旧。
- 根因:
scoreAndPersistCurrentEvidence()的时段分支在版本探测之前按证据指纹提前返回;成功回执使用旧结果身份,但 SQL 将不同阶段的engine_version取字符串最大值,而不是成功结果来源。分钟模式另有环境覆盖/探测失败回退边界,不能宣称身份无条件一致。 - F3 调用链查证(2026-09-20):前端时段分支 →
runV9BlockScan()→ POST/api/rectification/v5/block_scan→_compute_rectification_v5_block_scan()→api_service.block_scan()→score_candidates()→build_event_contribution_matrix()→merge_transition_proximity();时段支持率确实消费该候选分数。故本项升级为 BUG-981 端到端验收阻塞项。受影响的是证据未变、未触发三轮转分钟的历史跨午夜时段缓存命中;新建/无缓存/证据变化仍走已修路径。late_night299 分钟跨日;unknown1439 分钟虽同样走时段扫描,其首轮00:00–23:59本身同日,不能据此声称有跨日偏移,选中跨午夜时段后才触发。逐层行号见docs/tasks/PROGRESS-rectification-cross-midnight-gate-fix-20260920.md。 - 修复:补单策略 b 已批准,前轮认证 blocker 已由主会话解除(前置代码
3f39bafc已合 staging,部署另验)。本轮仅完成 F2 A:compare/diagnostics 的 started/failed 不再写版本,completed 不回退前端常量。真实工具同 turn 混版本 completed 已实测成立,触发 B,但 SQL 未授权,停止进一步实施;F1/F3/F4 pending,BUG 保持未解决。进度见docs/tasks/PROGRESS-rectification-cross-midnight-fix-20260920.md。 - 验证:独立纯虚构 TS 探针使用替身 RPC/fetch,旧时段缓存返回
cached:true、算法尾号 -7、fetch 次数 0;真实工具调用路径记录 started 尾号 -8 / completed 尾号 -7。聚合 SQL 的max(engine_version)已核源码,未真跑 DB,因此不伪称数据库端到端通过。 - 防复发:后续必须分别覆盖分钟/时段缓存、实际版本接口、部分环境覆盖与接口失败,成功回执必须绑定实际结果来源;历史打开与 Skill 绑定不变,不得重标或删除旧结果来掩盖问题。
- 相关记录:BUG-427、BUG-621、BUG-981。
- 复发自:未发现同症状既有记录;既有分钟算法身份缓存门不覆盖提前返回的时段分支,回执测试未组合新版 started 与旧缓存 completed。
- F2 补充实测:原生虚构输入 golden 经真实 compare/diagnostics 工具顺序执行,仅替换算法标签模拟滚动版本;同一 turn 可写 completed scoring-9 与 scoring-10,而字符串 max 为 scoring-9。既有聚合 RPC 不暴露各成功行身份,历史 fingerprint 无法还原,应用层 A 不足以闭环。定向身份 9/9(含缺陷诊断)、tsc 0;五文件回归 61 项中 57 pass / 4 Windows symlink EPERM,未冒称 DB 聚合已验。
- 授权恢复与修复:产品明确授权 B 后,新增兼容函数体迁移,仅将身份聚合改为选中成功/最新 attempt 的 completed 非空来源、按时间/id稳定排序;表/已应用迁移/历史行与权限不变。分钟/时段统一完整算法+策略pair与输入指纹;未知身份保留旧值只读且不强制重算,服务端采用/确认/候选选择独立重查。历史GET/刷新和工具均标注;新算响应与当前pair不一致也只读。可信当前版本已知的stale历史保留显式「重新比较」,未知身份没有入口,避免只读锁死升级。以上替代授权前局部状态,前文保留事故与停点历史。
- 恢复验证:真实native虚构golden的minute/block旧缓存重算及当前缓存复用、部分env冲突/接口超时与故障、缺source/非最新result、历史read→compare路径均回归;F1先红4项后绿。B真实PostgreSQL先红8≠7后绿,标准DB40/40(无skip);Python日期/bridge/memoization32/32。Linux完整基线3575/3575→最终diagnostics补丁快照3588/3588(0fail/skip),首页Static、完整28资源gzip+0.040085%,tsc/lint0error;主会话独立DB40/40、最终定向40/40+tsc通过。不提前算部署通过。
- diagnostics追加复核:旧failure回归未调用诊断工具,新增调用先得16绿/6红,确认未知身份仍会重计算;现未知身份+旧结果仅返回只读来源/notice,不执行诊断或方法,不允许分钟确认。可信身份下诊断新响应pair不一致也只读、确认false,成功来源仍是真实9/10;修后身份22/22、身份+spoken37/37;改后全量3588/3588与build已新快照复跑通过,旧delivery证据另外保留,主会话也已独立40/40及tsc0。
- 修复版本:
codex/rectification-cross-midnight-fix-20260920本地实现,未推送;前置3f39bafc已由主会话确认run2819及web/API双SHA部署,本补丁仍待独立验收、发布与受控真人走查,保持 investigating。
BUG-985 | 同日不变性回归写死浮点分数哈希导致跨机门禁失败
- 状态:resolved
- 首次发现:2026-09-20
- 最近更新:2026-09-20
- 影响面:
tests/test_dasha_transition_proximity_cross_midnight.py及快速门 bridge;阻塞aa46da10的合入验收,不是打分修复本身的回归。 - 用户现象:执行机器测试绿,换到不同浮点环境后 ordinal 2/3 红;选择源文件与 bridge 时重复为四项失败,门禁无法通过。
- 触发条件:同日 121 个浮点分数的 canonical JSON SHA-256 与某台机器写死的历史字面量比较。
- 根因:把“同环境新旧实现相同”的相对不变量写成跨环境绝对不变量。四位分数本身就可能不同,不是序列化尾噪。BUG-733 的既有防复发与同机差分测试仍在,但没有阻止新文件重引绝对浮点哈希,属同形复发。
- 修复:保留三个 ordinal 和 121 个候选;A 使用真实 contexts,B 仅在 transition helper 边界去掉
candidate_at进入既有 legacy 回退,主事件引擎继续使用完整 contexts;同机分数与矩阵 canonical 字节均严格相等,不加容差、不删测试、不 skip/xfail。bridge 顶部记录重复收集原因与独立用例数。 - 验证:Windows 原测试 18 通过;隔离 Linux 原测试 14 通过/4 失败,复现跨环境差异。修改后两环境定向(含 memoization)均 32 通过、校正 glob 均 216 通过;内存回退候选日期后两环境均同日三项绿、真实跨午夜一项预期红。生产红线文件与历史 golden 本轮不改。
- 独立机器验收闭环:2026-09-20 产品提供并批准第三套独立 Linux/Python 3.13 环境 review(记录于
0ca3871d的任务索引):定向 18 项全绿;日期回退同日 3 绿、跨午夜相关 4 红;校正 glob 从 staging 200 通过到修复版 216 通过,零新增失败。两台机器要求已满足,本项 resolved 仅指测试缺陷,不代表 BUG-981/984 部署与缓存闭环。完整 quick 的既有环境失败仍见进度记录。 - 防复发:同日/不变性类回归一律用同机相对比对,不得写死跨机浮点字面量;参照
tests/test_rectification_engine_memoization.pydocstring 的跨机舍入教训。同日不变性与跨午夜正确性必须分开,以日期回退探针分别验证绿/红;桥接覆盖和重复计数必须明示。 - 相关记录:BUG-981、BUG-733、BUG-712;F3 同步确认 BUG-984 为 BUG-981 端到端阻塞项。
- 复发自:BUG-733(同进程可证的等价关系被跨机 golden 代替)。
- 修复版本:
25232ce4,第三环境 review 通过;本次按产品授权合入 staging,详见docs/tasks/PROGRESS-rectification-cross-midnight-gate-fix-20260920.md。
BUG-986 | staging publish 冷构建在 22 kB/s 镜像源下撞 60 分钟超时
- 状态:investigating
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:Gitea
backend-quality-gatepublish job、deploy/railway-api.Dockerfile、deploy/reclaim-runner-disk.sh - 用户现象:run 2828 的 validate 通过后,
Build and publish exact-SHA ACR images在 API 镜像pip install pandas期间被取消;结论failure,context deadline exceeded。 - 触发条件:向 staging 推送代码后 publish;runner 空闲 677 GiB,但距上次命中的 API 依赖层已超过 72 小时。
- 根因:
reclaim-runner-disk.sh在磁盘充足时仍执行docker builder prune --filter until=72h,丢掉 BuildKit 层缓存。随后 Aliyun debian/pypi 约 22 kB/s:apt-get update9.4 MB 用 7 分、102 MB 软件包约 40 分,pyswisseph 6.9 MB 又 5 分,pandas 12.4 MB 下载未完成即达 publish 的 60 分钟上限。业务测试未失败。 - 修复:磁盘不低于
MINIMUM_FREE_GIB时保留 BuildKit 缓存;API 镜像 apt 与 pip 分两层,apt/pypi 改走华为云镜像(与 SWR 基础镜像同路径)。不改 workflow 超时、评分或确认门。 - 验证:定向更新
staging-backend-workflows.test.ts与test_railway_deployment.py;远端以新 staging 门禁为准,本记录不提前标 resolved。 - 防复发:磁盘充足时不得 prune BuildKit;API 依赖安装不得把 apt 与 pip 绑在同一层以致超时后整层作废。
- 相关记录:BUG-142
- 复发自:BUG-142(publish 超时预算;本次是缓存被过早丢掉后的冷构建)
- 修复版本:待本修复合入 staging 的门禁 run
BUG-987 | 列表用 messages=[] 过滤,生时校正会话全部从侧栏消失
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
GET /api/sessions、excludeEmptyConsultations、侧栏历史 - 用户现象:staging 侧栏一条生时校正都没有;深链
?c=仍能打开同一会话。 - 触发条件:打开
/或次级页拉会话列表。校正会话的chat_sessions.messages永远是[]。 - 根因:
e4e73f56用全表messages <> '[]'当「有没有内容」。该判据只对咨询成立。客户端isListedSidebarSession放行校正,服务端先把行拿走。源码正则not("messages", "eq", [])从未拿真实行验证。 - 修复:过滤收窄为
session_type.neq.consultation,messages.neq.[]。归档视图同一条规则。校正内容在 case 表,不在messages列。 - 验证:
database-session-list-visibility.test.ts(真实 Postgres,无 Docker 时 skip)、session-list-filter.test.ts跨层对断、chat-session-authority.test.ts。 - 防复发:不得用「某列为空」推断会话有没有内容。列表过滤必须带
session_type限定。不得用源码正则代替真实行测试。 - 相关记录:BUG-928、BUG-704
- 复发自:BUG-928(空咨询过滤写成全表
messages <> '[]') - 修复版本:
df329fa9(staging 已部署,门禁 run 2833 全绿)
BUG-988 | 侧栏副标题用创建时间,排序用最后活动时间,列表读起来是乱的
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
sessionSidebarSubtitle、SESSION_LIST_COLUMNS - 用户现象:侧栏时间不单调(9/18 → 9/17 → 9/7 → 9/16),9/7 创建、9/16 仍在用的会话落在「最近 7 天」却显示 9/7。
- 触发条件:会话创建后隔天继续使用。
- 根因:
e4e73f56把created_at加进列表列,副标题读创建时间;排序、分组、游标仍用updated_at。产品 2026-09-21 推翻 BUG-929 的「副标题为创建时间」。 - 修复:副标题改
updatedAt。列表列去掉created_at。详情接口仍返回created_at。 - 验证:
session-groups.test.ts(早创建晚活动:位置按活动时间,副标题显示活动时间)、chat-session-authority.test.ts列合同。 - 防复发:显示、排序、分组、游标必须用同一个时间字段(
updated_at)。SESSION_LIST_COLUMNS不得再含created_at。 - 相关记录:BUG-929、BUG-553
- 复发自:BUG-929(副标题改创建时间后与排序键分叉)
- 修复版本:
df329fa9(staging 已部署,门禁 run 2833 全绿)
BUG-989 | 点「新建对话」复用旧空会话,第一问之前就落库
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
startNewChat、send()、GET /api/sessions的draft、首页启动 - 用户现象:点新建落到 9/17 的旧「新对话」;连点不会开新的。空咨询在开口前就已经在库里。
- 触发条件:点「新建对话」,或启动时并入
draft。 - 根因:BUG-928 让步成「复用空咨询 + 服务端 draft」。复用既不 bump 时间也不改创建时间。产品 2026-09-21 收掉该让步。
- 修复:
startNewChat只在本地创建,不POST、不写?c=。第一问send()先POST /api/sessions再走既有 append。落库失败撤销本地会话并保留输入框草稿。删除draft字段、draftRow、findReusableEmptyConsultation。 - 验证:
session-list-filter.test.ts源码合同(新建无 POST / 无 URL,send 时 create,失败不setDraft)、chat-session-url.test.ts、session-list-lifecycle.test.tsx仍覆盖无 draft 的列表响应。 - 防复发:第一问之前不得
POST /api/sessions。未落库会话不得写入?c=。列表响应不得再带draft。 - 相关记录:BUG-928、BUG-987
- 复发自:BUG-928(延迟落库让步未收口)
- 修复版本:
df329fa9(staging 已部署,门禁 run 2833 全绿)
BUG-990 | GET /api/sessions?archived=1 在自托管 Postgres 上 500
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
LocalPostgresQueryBuilder.not()、GET /api/sessions?archived=1、归档视图 - 用户现象:当前无用户可见现象——归档视图自
92558ee6(2026-08-22)起没有 UI 入口(见 BUG-991),前端不会发出archived=1。发现路径是 BUG-987 新加的真实 Postgres 测试第一次跑到归档分支。接口层面确为 500「聊天记录暂时无法读取」。 - 触发条件:自托管本地 Postgres 上请求
?archived=1。托管 Supabase 不受影响。 - 根因:兼容层
not()只实现 PostgREST 算子子集eq/cs。applyArchiveFilter(..., true)调用not("archived_at", "is", null),构造期不报错,真跑 SQL 时 throwunsupported not filter,被列表路由外层 catch 成 500。该调用自a1956deb(BUG-553)就在,此前没有真实数据库测试走到归档分支。 - 修复:
not()增加is分支,新 filter kindisNot,编译为is not null/is not true/is not false。不得写成<> null(恒为 NULL,会静默 0 行)。 - 验证:
local-postgres-not.test.ts(本机绿);database-session-list-visibility.test.ts归档分支在 Gitea 门禁 run 2833 转绿(ok 1238,3653 条全过、0 红 0 skip),deploy-stagingrun 2835 成功,/api/health的deployment.gitCommit=df329fa9。执行方本机 Docker 网段耗尽未跑test:db,见BLOCKED.md。 - 防复发:兼容层新增算子必须有真实 Postgres 覆盖,不得只加源码正则。
- 相关记录:BUG-553、BUG-926、BUG-987
- 复发自:BUG-926(同一兼容层的不同缺口:当时是
order()只留最后一键) - 修复版本:
df329fa9(staging 已部署)
BUG-991 | 会话归档后没有任何入口能看回来或恢复
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:会话行菜单「归档 / 恢复」、
GET /api/sessions?archived=1、archived_at存量 - 用户现象:会话行菜单里可以「归档」,归档后它从侧栏消失;此后没有任何界面能列出归档记录,行菜单里的「恢复」也跟着不可达。除非记得
?c=<uuid>深链,归档等于单向丢失。 - 触发条件:在会话行菜单点「归档」。
- 根因:
92558ee6(2026-08-22)删掉了分组页眉上的归档切换按钮,但保留了整条数据链路。产品 2026-09-21 拍板整体下线,不装回入口。 - 修复:删行菜单归档/恢复、删
archived=1查询与 PATCH 写archived_at(旧 bundle 仍发送则 2xx 忽略)、迁移把存量archived_at清空且不 bumpupdated_at。删除会话能力不动。archived_at列本轮不删。 - 验证:Gitea 门禁 run 2837 全绿(3656 条 / 3656 过 / 0 红 / 0 skip)。真实 Postgres 测试
ok 1240 - retire archive migration clears archived_at without bumping updated_at:执行从迁移文件切出的update后archived_at归零,同一行的pinned/messages/updated_at与执行前逐字节相同,重跑仍 0 行;ok 1239断言archived_at非空的行仍不入列;ok 3444锁定行菜单只剩重命名/收藏/转发/删除;ok 788断言 PATCH 收到archived_at回 2xx 不落库。local-postgres-not.test.ts原样保留。migrate-staging-databaserun 1435 与deploy-stagingrun 1436 成功,/api/health的deployment.gitCommit=d2cec179、database.latestMigration=20260921010000_retire_chat_session_archive.sql、八项 check 全 ok。验收方独立变异测试:把「归档」菜单项注回sidebar-session-row.tsx后,Python 合同与sidebar-contract.test.ts同时转红。真机走查(归档项消失、旧归档会话回列表且时间未变、删除仍可用)见docs/testing/,仍欠。 - 防复发:删除某个视图的唯一入口时必须同轮下线其数据链路,或留一条断言入口存在的测试。
- 相关记录:BUG-990、BUG-992、BUG-553
- 复发自:无
- 修复版本:
5a4b8738+d2cec179(staging 已部署)
BUG-992 | 归档下线后门禁 Python 合同仍要求归档入口存在
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
tests/test_session_management_entrypoints.py - 用户现象:无直接用户现象。Gitea 门禁 run 2836 红,
5a4b8738未部署。 - 触发条件:向 staging 推送删掉归档入口的前端改动。
- 根因:Python 合同对前端源码做字符串断言,要求
toggleArchivedSession等必须出现。前序单 grep 只扫frontend/,扫不到tests/。同类第四次。 - 修复:那五个 token 从「必须出现」翻成「必须不出现」,且只对
sidebar-session-row.tsx与use-session-management.ts做反向断言。归档测试重写,保住writeChatSession的 POST/PATCH 合同,并把「归档不走 DELETE」改成「删除仍走 DELETE」。frontend/AGENTS.md要求删符号前git grep -- tests/ frontend/。 - 验证:
python -m pytest tests/test_session_management_entrypoints.py3 条全绿(验收方独立复跑);门禁 run 2837 全绿、publish不再跳过。变异测试:把「归档」注回行菜单后该合同转红。 - 防复发:删除或重命名任何前端符号、类名、可见文案之前,必须
git grep -n "<符号>" -- tests/ frontend/,不得只 grepfrontend/。 - 相关记录:BUG-933、BUG-934、BUG-939、BUG-991
- 复发自:BUG-939(Python 合同盯着已搬走的前端源码)
- 修复版本:
d2cec179(staging 已部署)
BUG-993 | 侧栏当前会话的标题和三点菜单又拆成两块高亮
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:侧栏会话行选中态、
.session-menu-trigger - 用户现象:选中一条会话时,标题一块底、右边「⋯」另一块底,看起来像两个控件。
- 触发条件:侧栏展开并选中任意带副标题的会话。
- 根因:复发自 BUG-024。整行选中底已经在
.session-row上,但.session-main仍有独立圆角,.session-menu-trigger固定 44px 高并在悬停/展开时刷--color-canvas。旧契约只锁了「不是 50% 圆」,锁不住这块独立画布。 - 修复:选中/悬停只画在
.session-row;标题和「⋯」背景透明、无独立圆角;「⋯」拉高到整行。悬停不再铺 canvas。 - 验证:
sidebar-contract.test.ts「one unified row surface」补 overflow、标题圆角 0、trigger stretch、hover 不含--color-canvas。 - 防复发:会话标题和行内操作必须共享同一个行级状态面。不得给
.session-menu-trigger单独的 canvas/选中底。 - 相关记录:BUG-024
- 复发自:BUG-024(旧测试没锁住 trigger 的 canvas 悬停底)
- 修复版本:
f9685f28(已进 staging;部署被 BUG-994 / migrate run 2841 磁盘写满挡住)
BUG-994 | staging 迁移拉 digest 镜像时 containerd 磁盘写满
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-21
- 影响面:
deploy/run-staging-migration.sh、deploy/run-staging-deploy.sh、staging 主机 Docker 镜像层、migrate-staging-databaserun 2841 - 用户现象:无终端用户可见功能回归。
f9685f28门禁 validate 全绿、镜像已发布,但测试站没有切到该 SHA:迁移在主机docker pull新 web digest 时失败,后续deploy-staging未执行。当时线上仍是上一版d2cec179。 - 触发条件:向 staging 推送会触发门禁的改动后,publish 派发
migrate-staging-database,主机再拉一枚新的 exact-SHA web 镜像。 - 根因:每次部署留下不可变 digest 镜像,脚本在 pull 前不回收未使用镜像。run 2841 在
Apply digest-pinned migration under host lock拉copse/jyotisha@sha256:a00887de…时,containerd ingest 报write /var/lib/containerd/io.containerd.content.v1.content/ingest/631c40bfaf7318a3ce6ca6ead3b0f8ce0eb25e2a2e93851c76bfc9eb5060c25d/data: no space left on device。门禁 runner 的reclaim-runner-disk.sh只管构建机,不管 staging 主机。 - 修复:迁移与部署脚本在 pull 前打印
docker system df,执行image prune --force再image prune --all --force。正在跑的jyotisha-staging容器会保住当前层。禁止volume prune,避免误删 Postgres 数据卷。 - 验证:源码合同
staging-backend-workflows.test.ts锁定 prune 出现在 pull 之前且不得volume prune --。门禁 run 2842 全绿。migrate run 2843 在 pull 前Images 166 / 77.64GB / reclaimable 70GB,image prune --all回收 75.38GB 后剩 4 张在用镜像,随后 digest pull 成功、无待应用迁移。deploy run 2844verified_sha=a06829b3。GET /api/health的deployment.gitCommit=a06829b3,八项 check 全 ok。staging 其后仅文档前进到26c1fbc6(BUG-995 任务书)。 - 防复发:staging 主机拉 digest 前必须先回收未使用镜像。不得用
volume prune换磁盘。 - 相关记录:BUG-150、BUG-993
- 复发自:无
- 修复版本:
a06829b3(staging 已部署;migrate 2843 / deploy 2844)
BUG-995 | 寒暄隐私测试接管 stdout,吞掉 node:test 自己的 TAP 报告
- 状态:resolved
- 首次发现:2026-09-21
- 最近更新:2026-09-22
- 影响面:
frontend/tests/consultation-smalltalk.test.ts三条default Mastra adapter protects privacy and usage on *用例的报告通道 - 用户现象:无用户可见现象,纯测试基础设施。 这三条失败时,报告可能只剩
not ok 1 - <文件绝对路径>,没有用例名和断言消息。退出码仍是 1,门禁不会漏掉失败。 - 触发条件:
t.mock.method(process.stdout/stderr, "write", …)全局接管标准输出,且不把内容交回原始write。 - 根因:要验证的「私密文本不得出现在进程输出」与
node:test的 TAP 报告共用process.stdout。mock 只logs.push并硬编码返回true。t.mock.restoreAll()在finally里,但报告行在测试回调返回之后异步写出,下一条 scenario 已经重新装上 mock,于是上一条 TAP 落进logs。 - 修复:装 mock 前
bind保存原始write;mock 先把 chunk 记入logs,再把chunk/encoding/callback交给原始write并返回其返回值。console.*五个 mock 维持原样。隐私断言logs.join(" ").includes(sentinel)不改宽。 - 验证:单文件
tsx --test tests/consultation-smalltalk.test.ts连续三次均为# tests 33/# fail 0/default Mastra adapter的ok行每次 4 条。变异:invalid_schema注入PROBE后出现not ok 21 - default Mastra adapter protects privacy and usage on invalid_schema且PROBE可见;bad_json为not ok 22同样可见。隐私探针:mock 装上后process.stdout.write(sentinel),三条均not ok且断言文案SDK must not log private text可见;探针已撤回未提交。本机全量npm test因 Windows Docker/symlink 既有缺口# fail 89,但default Mastra adapter仍报 4 条;Linux 门禁以推送后 run 为准。 - 防复发:测试不得全局接管
process.stdout/process.stderr而不透传。需要断言进程输出时,mock 必须把内容原样交回原始write。比较测试规模时比用例名列表 diff,不比# tests汇总数。 - 相关记录:BUG-976、BUG-977
- 复发自:无
- 修复版本:
2503c019
BUG-996 | 更新出生资料后星盘仍可能显示旧盘或无法区分失败原因
- 状态:resolved
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:
PATCH /api/account、GET /api/account、GET /api/chart-view、frontend/src/lib/secondary-page-data.ts的星盘快照缓存 - 用户现象:已经更新出生时间,打开星盘仍像没有可显示内容,或点开后看不到刚保存的日期和时间。现场这一户的数据库行没有取到,不能把某一种残缺资料写成已经证实的唯一原因。
- 触发条件:账户资料 PATCH 成功后星盘模块缓存仍留着上一张快照;或
profiles读取失败、已采用日期缺 offset、时区解析失败、引擎 429 / 超时 / 坏结果被收成同一种「资料不完整」或同一句失败。 - 根因:三处代码断点同时存在,不是一条已经证实的现场数据。第一,chart-view 忽略 profile 查询错误,空行会落到
birth_profile_incomplete;已采用日期缺 offset 或时区解析抛错时没有稳定分类。第二,chartCache不按账户和资料指纹换键,PATCH 成功后也不丢弃旧快照。第三,账户响应和星盘排盘各自读日期、时间、offset,确认后的普通编辑虽不覆盖 active time,但调用方看不到同一份服务端真值。guard_adopted_birth_date会把 adopted date、offset、provenance 一起清空,不会留下「有日期、没 offset」的半截元组,因此本轮不加迁移。 - 修复:账户 PATCH / GET 与 chart-view 共用
resolveServerOwnedChartBirth。chart-view 把查询失败、资料不完整、adopted 计算不完整、时区失败、引擎忙、超时、坏结果和响应格式失败分开,查询失败不再写成资料不完整。PATCH 成功后按账户和资料指纹钉住星盘缓存,进行中的旧请求不能把旧盘写回去。确认状态的普通编辑仍不覆盖 active time。不把chart_profiles当作/chart真值,不放宽 accepted / confirmed。 - 验证:
frontend/tests/chart-profile-update-consistency.test.ts覆盖 reported、accepted、confirmed、跨午夜、缺 active offset、查询失败、时区失败、引擎分档、schema 失败、旧快照失效和 D1。旧行为下查询错误没有独立 reason、缓存也没有账户指纹键,这些断言不会过。现场单户数据库状态仍未取证。 - 防复发:数据库查询错误不得映射成
birth_profile_incomplete。已采用日期存在而 offset 缺失时不得回退到声明 offset 或清空active_birth_time。星盘快照键必须包含账户和资料指纹;引擎缓存键仍包含日期、时间、offset、ayanamsa、node mode。失败文案不得写「过一会儿再打开」或没有上下文的「没有可显示内容」。 - 相关记录:BUG-073、BUG-076、BUG-715、BUG-716、BUG-717、TASK-chart-profile-update-consistency-20260922
- 复发自:无。BUG-073 仍要求服务端不信任客户端咨询模式;BUG-076 仍要求
profiles而不是星盘库 JSON 为真值;BUG-715 至 717 的星盘打开、分档日志和开页只打/api/chart都还在,本条补的是资料真值和快照失效。 - 修复版本:本分支,尚未推送、尚未部署
BUG-997 | 切换人物后普通聊天仍用登录用户本人的出生资料
- 状态:resolved
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:普通聊天
/api/consult的排盘对象,以及POST/PATCH /api/sessions的人物绑定 - 用户现象:会话已经记下另一位人物,继续提问时计算和咨询上下文仍使用当前登录用户的
profiles。对方资料被删除后,发送还会退回本人。 - 触发条件:会话绑定
chart_profile_role=other与某个chart_profile_id后调用普通咨询;或客户端同时提交另一套出生字段。 - 根因:
chart_profile_id/name/role只是客户端可写的展示快照。咨询读取会话时不取chart_profile_id,准备阶段无条件loadProfile(userId)读取登录用户的profiles。客户端出生字段因此可以和实际排盘对象不一致,缺失或越权的 other 资料也会落回本人。 - 修复:新增服务端 subject resolver。
self与未绑定的旧会话只读当前用户权威profiles;other只读当前用户拥有的chart_profiles行,并在服务端重新派生姓名与角色。会话展示名、客户端name/year/month/day/hour/minute/city/lat/lon/tz都不能覆盖该结果。另一用户的 id、随机 id、角色不一致、删除、缺失或不完整资料一律失败,不退回本人,也不扣点、不调用模型。空会话可以由服务端按 id 填上角色和姓名;已有消息的会话拒绝改绑。刷新与深链只使用库里的绑定,不读取全局activeChartId。未改表结构、计费语义或模型选择,也未接到报告、每日星语或生时校正。 - 验证:
frontend/tests/consultation-subject-binding.test.ts10 pass / 0 fail,覆盖 self/other、越权与随机 id、角色与伪造姓名、删除/缺失/不完整、客户端出生字段冲突、咨询准备拿到 other 资料、失败不扣点且不进入准备、有消息后改绑拒绝、刷新保留绑定、并发删除后发送失败。与consultation-route-service、application-billing-contract、chat-session-write、chat-session-authority、consultation-workflow-contract、consultation-birth-time-mode合跑 88 tests、87 pass / 1 fail。单独再跑consultation-birth-time-mode为 9 pass / 1 fail;失败项是本机无法创建 skill symlink(EPERM),同文件的咨询 route 顺序断言通过。tsc --noEmit通过;npm run lint0 error。未部署。 - 防复发:普通咨询在扣点前必须经 subject resolver。other 查询必须同时约束
id与当前user_id,失败不得调用loadProfile作为退路。客户端出生字段不得进入toolInput。已有消息的会话绑定必须与库存值一致,否则拒绝整次 PATCH。 - 相关记录:BUG-001、BUG-010、BUG-011、BUG-018、BUG-073、BUG-076、BUG-188
- 复发自:无
- 修复版本:本次分支提交(未推送,未部署)
BUG-998 | 本仓中文断句使用正则后行断言,Safari 16.4 以下在解析期失败
- 状态:resolved
- 首次发现:2026-09-17(写在 BUG-936 的已确认事实里,当时未单独编号;BUG-937 / BUG-938 已被咨询故障占用)
- 最近更新:2026-09-22
- 影响面:
frontend/src/lib/sentence-split.ts;原先后行断言在个人报告断句、校正开场句、引擎含义展示、采用旁白、采集提示、轮次旁白 - 用户现象:无单独的用户报告。若设备 Safari 低于 16.4,含后行断言的 chunk 会在解析期抛出 SyntaxError,React 不会 hydrate。这不能解释「以前能用、这次不能用」的设备,也不能单独修好 BUG-936。
- 触发条件:浏览器解析本仓断句 chunk,且不支持正则后行断言。
- 根因:八处
split使用后行断言来「在句末标点后切开并保留标点」。这是语法,不是运行时 polyfill 能补的。 - 修复:共用
splitAfterSentencePunctuation,用手写扫描在标点后切开并保留标点。frontend/src不再出现后行断言。Next 运行时里的类静态块改不了,语法下限仍是 Safari 16.4。 - 验证:
frontend/tests/sentence-split.test.ts用表驱动用例和旧正则对照,确认切开结果逐字一致;源码合同扫描frontend/src无后行断言。校正与报告的既有断言未改。 - 防复发:
frontend/src不得再写后行断言。测试文件可以保留旧正则作对照。 - 相关记录:BUG-936
- 复发自:无
- 修复版本:未发布(本分支未推送)
BUG-999 | 普通报告导出仍带出内部技法与工作流字段
- 状态:resolved(本地候选,未部署)
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:咨询 Markdown 导出、个人报告阅读、普通 Markdown 下载、
GET /api/reports/:id与报告列表摘要。聊天消息行不在本次回归里。 - 用户现象:聊天里已经不显示内部证据徽章,但导出的报告和已存 Markdown 仍可能出现
technique_truth、workflow_route、workflow_status、precise_timing、missing_layers,以及评分、内部地址、密钥样式字段、模型调试和 job/attempt/provider 元数据。 - 触发条件:从咨询会话复制或导出报告;打开或下载一份已经写好的个人长报告,包括命中旧缓存 Markdown 的情况。
- 根因:聊天层按 BUG-011 隐藏了这些字段,报告阅读、导出和 API 没有同一套普通用户 allowlist。普通下载还把
/professional-reference当成正文来源。可见性不能靠接口名字判断。 - 修复:新增
frontend/src/lib/report-public-projection.ts。文档类型显式区分聊天导出、个人报告详情、普通 Markdown 下载和专业参考。普通输出只保留正文、结论、行动建议、自然语言限制、图盘围栏和安全的引擎 SVG。内部限制改写成白话,不写出内部键。阅读 API、详情分类、普通下载和列表摘要都走这层投影。专业参考响应标明documentKind: professional_reference;本轮没有单独专业权限,所以它的正文同样 fail-safe 到普通投影。普通下载改为读GET /api/reports/:id,不再请求专业参考。 - 验证:
frontend/tests/report-public-projection.test.ts覆盖字段在与不在、旧缓存、专业参考种类、图盘围栏、HTML/script/iframe/object/embed/img、危险 URL,以及普通输出不含内部 URL、密钥和模型字段。consultation-report-export、personal-report-longform-md、报告归属与 ready 检查的测试名保留。tsc --noEmit通过,npm run lint0 error。没有登录态,浏览器导出未验收,见docs/testing/report-public-content-20260922.md。 - 防复发:普通报告的公开字段以 allowlist 为准,不能只靠删除若干字符串。缓存命中和已存 Markdown 必须再过投影。专业参考不得再被普通下载当作 fallback。不得放宽 Markdown 的 XSS、危险 URL、HTML 和
jyotish-chart围栏合同。 - 相关记录:BUG-011、BUG-058、BUG-188
- 复发自:无
- 修复版本:本分支
fix(report): project ordinary reports onto public prose only(未推送,未部署)
BUG-1000 | 普通聊天顶栏不能在开口前选择人物
- 状态:resolved
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:普通聊天顶栏的人物入口;空会话的待发送绑定;已有消息会话的换人路径
- 用户现象:服务端已经能按会话绑定人物,但顶栏仍是一段不能点的名字。新对话沿用账户里上次选中的星盘,开口前看不见、也换不了人。开口之后没有「新建会话并使用此人物」这条路。
- 触发条件:打开普通聊天,想在第一条消息前换成另一位本人名下的人物;或在已经说过话的会话里点另一个人。
- 根因:BUG-997 只补了服务端真值。
startNewChat仍把activeChartId抄进新会话,顶栏.chat-header-chart仍是静态span,没有选择器。历史会话打开时还会把该人物写回账户级activeChartId。 - 修复:普通咨询顶栏改为「当前星盘」按钮。本人是看得见的默认。空会话选择只更新该会话的绑定,已落库的空会话 PATCH 只提交 id 和角色,姓名由服务端填写。已有消息的会话不改绑,只提供新建会话。人物列表失败、未读完或不完整时不写入绑定。资料已删除的旧会话仍可阅读,发送阻断,不退回本人。报告、每日星语和生时校正不挂这个选择器。
- 验证:见
docs/tasks/PROGRESS-chat-subject-picker-20260922.md。没有登录态,浏览器点击未验收,见docs/testing/chat-subject-picker-20260922.md。 - 防复发:新普通会话的人物 id 固定为本人,不读
activeChartId。打开历史会话不得改写该会话绑定,也不得把绑定抄回activeChartId。有消息的会话不得 PATCH 人物。列表未就绪时不得创建绑定。 - 相关记录:BUG-997
- 复发自:无
- 修复版本:本分支
fix(chat): choose the chart person before the first message(未推送,未部署)
BUG-1002 | 首屏揭幕标记在没有 documentElement 时抛错,外壳注册测试整组失败
- 状态:resolved
- 首次发现:2026-09-22
- 最近更新:2026-09-22
- 影响面:
useHomeShellRegistration的揭幕标记;frontend/tests/session-list-lifecycle.test.tsx - 用户现象:无单独的用户界面现象。门禁 run 1445 在
6d06fca0变红,发布因此停在1bc6a954。随后 run 1448 在3fed71ab同样失败。 - 触发条件:外壳测试挂载这个 hook 时
document存在,但document.documentElement不存在。 - 根因:BUG-936 的揭幕标记写在独立 effect 里,只判断了
document,然后直接读document.documentElement.dataset。测试环境没有documentElement,effect 抛TypeError,同文件的外壳注册用例整组失败。真实浏览器有documentElement,这条不是那 7 条后来才出现的 CSS 断点 / composer 失败。 - 修复:没有
documentElement时直接返回,不写标记,也不抛错。有根节点时仍写data-hydrated="1"。 - 验证:
session-list-lifecycle.test.tsx里原先抛错的四条(home registration 两种 StrictMode、shell callbacks、401 signed-out)恢复通过;first-paint-fallback-contract.test.ts锁住「没有 documentElement 就返回」。 - 防复发:揭幕标记不得在
documentElement缺失时抛错。源码合同要求先判断!document.documentElement。 - 相关记录:BUG-936
- 复发自:无
- 修复版本:本分支,尚未部署
BUG-1003 | 对外原始附录带出工程状态词与第三方页码
- 状态:investigating(原实现已合入 staging,本轮清洗一致性修复待验证,未确认部署)
- 首次发现:2026-09-22
- 最近更新:2026-09-23
- 影响面:
/api/professional_report_reference的对外 Markdown、报告独立原始附录下载;内部审计与 CLI 不变。 - 用户现象:附录出现内部函数名、工程状态词、第三方引擎名与产品页码。
- 触发条件:原始引擎导出作为读者附录返回,而非普通正文投影。
- 根因:内部审计与读者副本未分开;BUG-999 的普通正文 allowlist 不覆盖附录。初版 Python / TS 两份规则又因 Unicode 单词边界不同漏掉 Python 第三方页码。
- 修复:
bbd96d3b增加读者副本清洗;本轮 F4 将 Python / TS 规则收敛为 scripts 下共享 JSON,正则采用相同 ASCII 边界,保留行数、原始字段、英文散文与图盘围栏,不以整行删除代替翻译。 - 验证:本轮主会话报告定向 165/165、Python 定向 101/101 通过,含完整虚构 golden 两端逐字一致、页码归零及行/表/数字/图盘字节保留。前轮相差 36 行的问题已有回归覆盖;完整门和部署仍未通过,详见 PROGRESS。
- 防复发:保留
test_reader_appendix_language.py与前端原始附录清洗 / golden 回归;F4 必须覆盖同一真实引擎虚构 fixture 两侧逐字一致及页码归零。普通正文投影与安全边界不变。 - 相关记录:BUG-999、BUG-1004;
TASK-report-density-fix-20260923.mdF4 - 复发自:BUG-999 同现象族的未覆盖通道,不是撤销普通正文限制。
- 修复版本:原实现
bbd96d3b;本轮工作树修复待验证,未确认部署。
BUG-1004 | 读者没有独立完整原始附录入口
- 状态:investigating(入口已实现,受控浏览器与部署待验)
- 首次发现:2026-09-22
- 最近更新:2026-09-23
- 影响面:报告下载动作、
POST /api/reports/[reportId]/raw-appendix、新长报告计算快照。 - 用户现象:普通下载只提供净化正文,完整计算表不能作为独立附录取得。
- 触发条件:用户需要核对原始表格,但只有 BUG-999 建立的普通正文下载通道。
- 根因:缺独立附录产物与入口;不能把完整表格塞回普通正文投影作为补救。
- 修复:
bbd96d3b新增独立附录路由和下载动作,普通 Markdown 仍走原投影。新计算通过既有 section RPC 持久 snapshot,重试从快照恢复结构化数据;快照行从章节列表及错误聚合排除。旧 Markdown-only 缓存不回填。 - 验证:前轮验收入口与快照读写静态核对通过;
personal-report-density-retry.test.ts已含中断、并发、损坏绑定、旧缓存等用例。测试存在不等于本轮通过;登录态下载与重试真人证据仍缺,见 PROGRESS。 - 防复发:
personal-report-raw-appendix.test.ts、personal-report-density-retry.test.ts与report-public-projection.test.ts分别守住独立附录、快照恢复和普通下载不变;不能让 snapshot 成为 writer 章节,不能借附录授权放宽 XSS / 危险 URL 合同。 - 相关记录:BUG-999、BUG-1003、BUG-1005
- 复发自:无;为经产品授权新增的独立通道,普通下载限制仍有效。
- 修复版本:
bbd96d3b已合入 staging;未确认部署。
BUG-1005 | 个人报告没有可承载引擎事实表的结构化层
- 状态:investigating(事实层已实现,可读性与打印补修待验)
- 首次发现:2026-09-22
- 最近更新:2026-09-23
- 影响面:报告契约、服务端装配、报告页事实表与 writer 边界。
- 用户现象:引擎已有大运、力量、功能吉凶等表,但报告只有叙述与图盘;初版补表后又把路径清单显示给读者,见 BUG-1009。
- 触发条件:生成包含上述已计算层的个人报告。
- 根因:原报告没有
factTables;初版未规定用户列结构,writer 拒绝表格与打印闭合也缺回归覆盖。 - 修复:
bbd96d3b增加 8 组服务端事实层,不给 writer 可写字段;保留证据状态。F1 补真正列结构,F5 补 writer 提示与拒绝测试,F6 补全折叠时打印。Sade Sati 只展示源中已有框架,三轮日期计算范围外。 - 验证:本轮报告定向 165/165、Python 定向 101/101 通过;writer 去拦截变异使 4 个拒绝用例全部变红,正常对照仍绿。Chrome 153 隔离真实组件 28/28,全部折叠与混合状态 PDF 各 5 页、各 130/130 行完整,打印后恢复;不是受控登录 E2E。完整构建受 Windows symlink EPERM 阻塞,尚未提交或部署。
- 防复发:
report-fact-tables.test.ts真实 golden 与逐行来源回查不可削弱;writer 的 Markdown / HTML 表格或factTables输出必须 fail closed;打印测试必须从全折叠状态开始并核对打印后恢复。 - 相关记录:BUG-1009、BUG-153、BUG-999;修复单 F1/F5/F6
- 复发自:BUG-153 的打印合同遗漏新增事实表 disclosure;旧静态合同不能证明关闭的 details 进入 PDF。
- 修复版本:原实现
bbd96d3b;本轮补修待验证,未确认部署。
BUG-1006 | 报告契约分盘种类上限不足
- 状态:investigating(16 种契约已实现,本轮构建与真机待验)
- 首次发现:2026-09-22
- 最近更新:2026-09-23
- 影响面:
CHART_IDS、长报告分盘装配、报告页布局。 - 用户现象:引擎可提供更多分盘,但报告契约只接受 9 种,无法交付约定的 16 张。
- 触发条件:新生成完整个人报告并查看分盘。
- 根因:契约枚举和服务端交付层未承载完整目标分盘集合。
- 修复:
bbd96d3b将枚举扩为任务书指定 16 种,装配真实宫位与星体,沿用 BUG-616 grid/card 和 BUG-617 memoized 文章树;旧 v1 枚举兼容保留。 - 验证:前轮验收记录
/Static、gzip +0.007%,golden 覆盖 16 盘完整宫位;本轮未重跑,不作为修复版通过。16 张盘不重叠、滚动不重建及旧报告读取待受控浏览器复核。 - 防复发:
report-fact-tables.test.ts检查图盘完整性;personal-report-markdown-view.test.tsx中 grid、禁止 float / 负 margin 与文章无滚动态合同仍在,不得只检查是否存在 SVG。 - 相关记录:BUG-616、BUG-617、BUG-1005
- 复发自:无;分盘扩容须持续遵守旧布局与性能防线。
- 修复版本:
bbd96d3b已合入 staging;未确认部署。
BUG-1007 | 辅助大运族缺少读者可理解的适用性说明
- 状态:investigating(新生成路径已实现,回归与真人待验)
- 首次发现:2026-09-22
- 最近更新:2026-09-23
- 影响面:新报告原始附录的辅助时间轴适用性说明。
- 用户现象:辅助大运只剩状态词,不能看出是否适用、缺什么,以及只能交叉核对的边界。
- 触发条件:读取辅助大运族数据,包括引擎已算出但仍限制可断言程度的层。
- 根因:对外附录缺读者语言说明,未把引擎适用性和使用边界一同交付。
- 修复:
bbd96d3b对 16 个大运族提供逐族说明,读取引擎applicable而不自行放宽;标明第二时间轴、不单独生成应期。新快照保存说明,旧 Markdown-only 缓存不补算、不回填。 - 验证:前轮验收记录 16 族各有白话说明、模块不自行判定;本轮清洗变化后的回归与受控下载待验。
- 防复发:真实 golden 的附录 / durable retry 回归须保住适用性与限制;普通正文投影不能接纳这些替代时间轴成为断言,旧缓存缺证据必须如实降级。
- 相关记录:BUG-1003、BUG-1004、BUG-999
- 复发自:无;不把展示授权当作证据等级升级。
- 修复版本:
bbd96d3b已合入 staging;未确认部署。
BUG-1008 | 任务书写入真实个人出生资料并推到 staging
- 状态:mitigated(staging 历史已重写,不再含该资料;旧提交在 Gitea 上仍可按 SHA 匿名访问,待服务器垃圾回收)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:
docs/tasks/TASK-report-density-20260922.md,自 stagingbaeec66f起;Gitea staging 历史。GitHub 只读镜像最后同步于 2026-08-14(12414971,落后 1,314 个提交),尚未包含这份资料。 - 用户现象:无用户界面现象。隐私门
tests/test_repo_privacy_markers.py::test_tracked_repository_has_no_privacy_markers在下一次代码推送f968cb21上命中 R001 / R003,指向该任务书第 13 行,使该实现的门禁变红。 - 触发条件:撰写任务书的对照实测段落时,把对照报告那张盘的出生日期、时刻与经纬度原样写入;同一文件第 7 行还写入了含个人姓名的本机路径。
- 根因:纯文档推送不触发门禁(
docs/**不在deploy/gated-paths.txt),隐私扫描只在之后第一次含门禁路径的推送时运行。撰写者推送前没有自跑扫描。任务书的硬红线第 6 条写了"出生资料不进仓",但撰写者自己的实测段落没有过同一把尺。 - 修复:两处改为不含资料的描述(
TASK-report-density-fix-20260923同一提交)。 - 验证:修复后在 staging-docs 树上重跑同一扫描,只剩 1 处,为新 fixture 的数值碰撞(已独立重跑确认是虚构输入的引擎时间戳,见 BUG-1010)。
- 防复发:凡是写入实测数字的文档,推送前必须跑
python3 -m pytest tests/test_repo_privacy_markers.py -q,结果写进提交说明。实测段落只写"真实个人资料"或虚构 / 公开名人输入,不写具体值。建议(需产品负责人决定,涉及.gitea/workflows/**):让纯文档推送也跑隐私扫描。 - 历史清除:产品负责人 2026-09-23 决定清除。按原作者、日期与说明重建泄漏之后的三个提交,唯一改动是该任务书换成已清理版本:
baeec66f→a7f82dc2、f968cb21→bbd96d3b、fcca56fe→abf28321,以--force-with-lease推送。核对:新旧 head 的树逐字节相同;泄漏 blob 在新历史中出现 0 次;此前没有其他分支建在该段历史上。 - 未决:Gitea 仓库可匿名访问,旧 SHA 的 raw 地址重写后仍返回 200。须 Gitea 管理员执行服务器垃圾回收,之后以匿名请求确认返回 404 才可改 resolved。在此之前 GitHub 只读镜像不得重新同步。
- 相关记录:BUG-999
- 复发自:无(与 2026-09-20 的本仓个人案例清除属同一类资料)
- 修复版本:
abf28321(HEAD 清除);历史重写见上
BUG-1009 | 普通报告事实表显示引擎字段路径而不是可读列
- 状态:investigating(F1 修复执行中,待定向与独立验收)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:
assembleReportFactTables、事实表渲染、普通报告可见单元格。 - 用户现象:8 组表为“原始字段 / 数值或状态”两列键值清单;出现 snake_case、数组下标、内部时间字段,主运年数还可能被出生时剩余量替代。
- 触发条件:查看原报告密度实现新增的普通报告事实表。
- 根因:原任务书只要求可追溯,未明确用户列结构;实现把来源路径直接当行名。普通正文的 BUG-999 投影不覆盖这块新增结构化渲染。
- 修复:F1 按修复单逐组改成主运 / 分运 / 小运、SAV / BAV、六分量、功能吉凶、行星状态、特殊点、阶段框架、年度与 Saham 表。来源保留在不可见
sourcePath,数字不补算,主运年数与出生剩余量分开。 - 验证:前轮 948 个叶子行的缺陷证据不再作为新表行数合同。本轮真实 golden 的 8 组、11 张实际表、130 行、545 单元格在 Chrome 完整渲染;可见 snake_case、数组下标、超两位小数均为零;Node 回归核对来源路径、原始数值与缺值不补算。首段主运直接取 full_years 显示 18.00,余额 0.72 仅说明;年度源无行星位置,保留空表,不冒用本命数据。构建与登录 E2E 缺口见 PROGRESS,尚未部署。
- 防复发:使用真实引擎虚构 golden,同时测试可见内容与来源追溯;不能只通过“值来自引擎”就宣称面向读者可读,不能削弱 BUG-999 普通正文安全合同。
- 相关记录:BUG-1005、BUG-999;
TASK-report-density-fix-20260923.mdF1 - 复发自:BUG-999 同现象族的新增表格通道,旧正文测试未覆盖。
- 修复版本:缺陷引入
bbd96d3b;修复在codex/report-density-fix-20260923工作树实施,待验证,未确认部署。
BUG-1010 | 新虚构 fixture 的隐私扫描数值碰撞未登记
- 状态:investigating(F2 按精确数值碰撞机制处理,待本轮验证)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:
tests/test_repo_privacy_markers.py与frontend/tests/fixtures/report-density-fictional-reader.json。 - 用户现象:无用户界面现象;质量门隐私扫描命中虚构引擎时间戳的数值片段,阻断发货。
- 触发条件:新增真实引擎虚构 golden 后运行仓库隐私门。
- 根因:时间戳数值与私有标记发生碰撞,但新 fixture 未登记到既有已审阅数值碰撞机制;不是 fixture 包含真实个人资料。任务书真实泄漏另见 BUG-1008,不得混称误报。
- 修复:F2 只登记已审阅的单处数值碰撞,说明来自虚构输入的引擎时间戳;不更改私有标记、扫描规则,也不豁免整份 fixture 或目录。
- 验证:前轮独立重跑已复现该虚构时间戳,本轮没有重跑引擎。主会话转述 F2:精确登记及反例 62 passed / 1 deselected;完整专项用 audit hook 尊重
pending_packets禁读,62 pass / 1 fail(17 个保护文件 READ_ERROR),其余 4,647 文件无命中。完整隐私门未绿,不能标 resolved;不复制碰撞数值或私有 marker。 - 防复发:新增 golden 必须保留虚构来源声明并跑隐私扫描;数值碰撞逐处核实、精确登记,真实泄漏必须分别清除,不能靠扩大 allowlist 隐藏。
- 相关记录:BUG-1008、BUG-1009;
TASK-report-density-fix-20260923.mdF2 - 复发自:无;既有扫描正确触发,缺的是已核实碰撞的精确登记。
- 修复版本:缺陷引入
bbd96d3b;F2 工作树修复待验证,未确认部署。
BUG-1011 | 快照落库的数据库测试把 42 万字节 SQL 当一个命令行参数,Linux 上启动即失败
- 状态:investigating(根因已复现,修复待执行;数据库层从未真跑过)
- 首次发现:2026-09-23
- 最近更新:2026-09-23(门禁 run 2857 确认为唯一失败)
- 影响面:
frontend/tests/database-personal-report-sections.test.ts中报告密度快照一例;backend-quality-gate的数据库测试步骤。生产写入走 RPC 请求体,不经命令行,不受影响。 - 用户现象:无用户界面现象。门禁在含该测试的提交上变红;报告快照行在真实 Postgres 里的写入、幂等与越权隔离从未被验证。
- 触发条件:在 Linux 上以 Docker 跑
npm run test:db(即门禁环境)。 - 根因:
tests/helpers/postgres-fixture.ts的psql/psqlAs用psql -Atc <sql>把整段 SQL 作为单个 argv 元素传给docker compose exec。该测试把整份快照(含 374,097 字节的附录 Markdown)内联进 SQL,单个参数 428,557 字节,超过 Linux 单参数上限MAX_ARG_STRLEN131,072 字节。验收时用同一段 SQL 以同样方式调用/bin/true,稳定得到spawn E2BIG。执行方两轮都因 Docker 地址池耗尽未跑数据库测试,验收机无 Docker,因此一直没暴露。 - 修复:待执行,见
TASK-report-density-fix2-20260923.mdG1。不得改共享的psql/psqlAs:它们被 29 个文件调用 522 次,-c的多语句单事务语义被依赖(selectAsAuthenticated的set_config(…, true)只在当前事务内有效)。新增单事务的大脚本专用函数,只给本测试用。 - 验证:待执行。
- 防复发:超过数十 KB 的 SQL 一律走单事务的标准输入专用函数,并显式设
maxBuffer;专用函数须有测试覆盖超过 131,072 字节的 SQL,并证明事务内set_config(…, true)对后续语句可见。 - 相关记录:BUG-1005、BUG-1009
- 复发自:无
- 修复版本:—
BUG-1012 | 首页入口新增 375px 断点违反既有白名单
- 状态:resolved(run 2857 的 H1 门禁通过;staging 总门禁仍因 BUG-1011 失败,尚未部署)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:首页入口响应式样式与 staging 门禁。
- 用户现象:无独立已复现线上现象;新增切点导致断点合同失败,阻断发布。
- 触发条件:运行 viewport-breakpoint-contract 的 CSS width breakpoints stay on the published allowlist。
- 根因:45ec4617 新增 375px 规则,未遵守 BUG-697 收敛口径;此前红色门禁掩盖新增失败。
- 修复:按任务决策将两条防溢出规则并入既有空 480px 块,不扩白名单,不更改按钮尺寸。
- 验证:修复前真实 run 2856 / job 6345 证实失败;修复后测试和五宽度浏览器证据见 PROGRESS-staging-gate-red-20260923.md。
- 防复发:保留断点白名单测试;360/375/390/414/480 宽度核对溢出,376~480 若出现可见变化须停止报告。
- 相关记录:BUG-697、BUG-1002;TASK-staging-gate-red-20260923.md。
- 复发自:BUG-697 断点收敛合同仍在,红色门禁下未逐项辨别新增失败。
- 修复版本:
36a737616c67a251610662ccab76845b193993fb(staging;未部署)。
BUG-1013 | 聊天人物选择器恢复焦点可能滚动面板
- 状态:resolved(run 2857 的 H2 门禁通过;staging 总门禁仍因 BUG-1011 失败,尚未部署)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:普通聊天人物选择器关闭后的焦点与面板滚动。
- 用户现象:恢复顶栏入口焦点时浏览器可能自动滚动聊天面板;scroll guard 合同失败。
- 触发条件:选择器关闭或 Escape 后调用 triggerRef.current.focus。
- 根因:2a3013f2 两处新增 focus() 未携带 preventScroll,旧滚动守卫仍有效但失败被已有红色淹没。
- 修复:两处均显式 focus({ preventScroll: true }),不改变焦点落点或选择人物的业务。
- 验证:修复前真实 run 2856 / job 6345 证实失败;修复后 guard、焦点回归与浏览器证据见本轮 PROGRESS。
- 防复发:保留全组件 scroll guard,不增加豁免;picker 两条恢复路径都必须带 preventScroll。
- 相关记录:BUG-1000、BUG-1002;TASK-staging-gate-red-20260923.md。
- 复发自:聊天面板既有 focus 防滚动合同的新增组件遗漏。
- 修复版本:
36a737616c67a251610662ccab76845b193993fb(staging;未部署)。
BUG-1014 | 输入框源码合同未跟上已删除人物的发送限制
- 状态:resolved(run 2857 的 H3 门禁通过;staging 总门禁仍因 BUG-1011 失败,尚未部署)
- 首次发现:2026-09-23
- 最近更新:2026-09-23
- 影响面:chat-composer-queue 源码合同,不是输入框业务回归。
- 用户现象:源码正则仍期待旧表达式,门禁失败;生成中可输入的业务性质仍成立。
- 触发条件:BUG-1000 在 inputDisabled 加入 subjectDeleted || 后运行旧合同。
- 根因:合同固定整个旧串,没有同步合理的已删除资料限制,且未独立检查禁止生成态禁用的性质。
- 修复:只更新测试期望,保留 page.tsx 业务;新增 inputDisabled 不含 isLoading/cancellationPending 的检查。
- 验证:修复前真实 run 2856 / job 6345 证实失败;正常/变异验证及原值、新值、原因见本轮 PROGRESS。
- 防复发:原测试名和断言目的保留;临时注入 isLoading || 时新检查必须失败,不通过修改业务迎合源码测试。
- 相关记录:BUG-1000、BUG-1002;TASK-staging-gate-red-20260923.md。
- 复发自:无;源码合同落后于授权业务改动。
- 修复版本:
36a737616c67a251610662ccab76845b193993fb(staging;未部署)。