staging 手测 run 951a841e 第一次工具调用发出 tool.failed 后重试成功,但回执里 steps 只有 skill 与那次成功的 tool,stepBudget.used 为 2——失败的那次完全不存在。 客户端看见失败过一次,回执说没有,两边都查不到为什么。 工具的 inputSchema 是 strict 的,模型参数不合法时 Mastra 在调用 execute 之前就拒了, 于是工具体内一切都没跑:调用不计数、失败步不记录、连 chart-calculation 活动事件都没 发出(这也是本次定位的证据——失败那次没有任何 activity,重试那次有)。工具无法记录 一次它从未收到的调用。叠加两处:safeToolError 把非超时非取消的错误全塌成 calculation_failed,而即使失败落进工具体的 catch,consultationWorkflowFailureCode 对非 ConsultationWorkflowError 返回 undefined、append 处又写成可选省略,于是最需要 解释的那条记录恰好是唯一没有原因的记录。 改为在流层补记:流是唯一能观测到全部工具失败的位置,无论失败在 schema 这侧还是 execute 那侧,且它持有 startedAt 因而能给出时长。tool-error 分支比对「流已见的错误数」 与「state 里已有的失败 tool 步数」,只在前者更多时补一条,工具仍记录它能看见的失败, 两者不重复计。另新增 consultationToolFailureCode,令每个错误都解析出一个码。 failureCode 刻意仍不进公开回执:白名单与「the public receipt never carries the internal failure classification」是刻意约束,workflow_rate_limited 这类后端内情不该 上线到客户端。原因走可观测日志的 toolCalls[].failureCode。客户端能看到「有一步失败」, 运维能在日志里看到为什么。 另记入 BUG-268 的线上实测值:单领域 referenceReads 两次均为 2,多领域为 0——不是 从不读方法,而是最需要方法的多领域路径一份都没打开。 Co-authored-by: Cursor <cursoragent@cursor.com>
606 KiB
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
- 复发自:无
- 修复版本:本次自动滚动修复提交
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
- 修复版本:待提交(本地可测)
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,保留精确完整集合,不放宽为子集/包含断言。 - 验证:本地目标节点
/Users/jesse/Documents/Jyotisha/.venv/bin/python -m pytest tests/test_api_server_security.py::test_capability_audit_scans_registry_and_local_sources -q通过(1 passed);git diff --check通过;远端 staging gate 需在 follow-up 提交/推送后更新确认,本记录不提前声称远端收口。 - 防复发:新增或删除任何
frontend/src/app/**/page.tsx页面时,必须在本变更中同步运行test_capability_audit_scans_registry_and_local_sources(或完整test_api_server_security.py聚焦切片),并在本地验证通过后才进入推送门禁;前端本地矩阵不能替代该 Python 能力审计节点。 - 相关记录:BUG-126、BUG-132
- 复发自:BUG-126(模式:新增页面未同步能力审计精确路由集合)
- 修复版本:待 follow-up commit / gate
BUG-144 | db/migrations 副本依赖业务 schema 导致 identity-only fixture 迁移失败
- 状态: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(本地修复,待提交与发布)
- 首次发现:2026-08-18
- 最近更新:2026-08-18
- 影响面:
/首页“今日星语”卡片、首页 hero 的问候与标题、/api/daily-starlanguage的失败归因与超时预算、Onboarding Agent 生成的欢迎语。 - 用户现象: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(首页文案第一人称与真实性边界)
- 复发自:无
- 修复版本:
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(同为路由与合同之间的口径不一致)
- 复发自:无
- 修复版本:本地未提交候选
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 里删掉)
- 复发自:无
- 修复版本:本地未提交候选