diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f47f13d..8a3a2d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 印度占星 Skill 更新日志 +## 2026-09-09 — 历史生时校正按它自己那一版打开 + +从历史对话点开生时校正时,不再要求和当前注册的 Skill 版本完全相同。Case 仍钉住创建时的版本;只要那一版快照还在注册表里,就按那一版打开。首页「开始校正」仍用当前版本。打开失败时,错误出现在被点的那一行下面,不会再看起来像没点到。Skill 版本不变。 + + ## 2026-09-09 — 长报告星盘改成两栏卡片,滚动时不再整页重建 个人长报告里的本命盘、婚盘、月亮盘和 Vargas I 不再叠在同一块:每张盘和它的标题进一张卡片,连续的卡片走原来的两栏网格;窄屏和打印仍是单栏。滚动目录高亮时,正文里的图不会被拆掉重画。导出的 `.md` 仍带引擎 SVG、不含网页用的 JSON 围栏。Skill 版本不变。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 64c01043..5c897f38 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -9636,6 +9636,22 @@ - 复发自:无 - 修复版本:待发布 +## BUG-621 | 升 Skill 版本后历史生时校正从列表点不开 + +- 状态:resolved +- 首次发现:2026-09-09 +- 最近更新:2026-09-09 +- 影响面:`openRectificationCase`、`open_agentic_rectification_case_v2`、历史会话列表、`KNOWN_RPC_ERROR_CODES` +- 用户现象:历史对话里的生时校正(含当天较早创建的)点了没反应,主栏也不换会话。 +- 触发条件:生时校正 Skill 已从 10.0.15 升到 10.0.19;点开绑定旧版本的历史校正。 +- 根因:08-14 不可变注册表让 Case 钉住自己的 `skill_name/version/sha256/source_commit`,agent 也按绑定版本跑。打开 RPC `open_agentic_rectification_case_v2` 却把**当前注册版本**与 Case 做相等比较,任一不同就 `skill_identity_mismatch`。该错误码未映射,变成 500「校正服务暂时不可用」;客户端再兜成「暂时无法打开生时校正」。错误只画在首页起始卡下,而 BUG-599 之后 `selectSession` 等 Case 打开成功才切会话,历史列表点击看起来像没反应。 +- 修复:`intent === "session"` 时先走 v1 按会话取 Case,再读绑定身份,用 `resolveExactSkillPackage` 核验快照存在且 sha 一致,核验通过后把**绑定身份**交给 v2。`homepage` / `new` 仍用当前注册版本。映射 `skill_identity_mismatch` → 409。历史打开失败把错误画在被点的那一行下面,起始卡不重复同一条。不改迁移、不改采用门。 +- 验证:`frontend/tests/rectification-v9-case-service.test.ts`、`frontend/tests/rectification-history-open-20260909.test.ts`、`frontend/tests/skill-registry.test.ts`。Docker DB 套件本机跑不了(环境缺口);本轮无新迁移。 +- 防复发:按会话打开必须用 Case 绑定身份,不得把当前注册版本交给 v2 做相等比较。升 Skill 版本后,历史校正必须仍能从历史对话打开,且以其绑定版本运行。废弃版本快照须仍可 `resolveExactSkillPackage`。 +- 相关记录:BUG-599、BUG-583、BUG-593、BUG-595、BUG-597、BUG-604 +- 复发自:无 +- 修复版本:待发布 + ## BUG-622 | staging 质量门因两条过期前端合同红掉,Deploy staging 未派发 - 状态:resolved diff --git a/docs/tasks/PROGRESS-rectification-history-open-20260909.md b/docs/tasks/PROGRESS-rectification-history-open-20260909.md new file mode 100644 index 00000000..a054f61b --- /dev/null +++ b/docs/tasks/PROGRESS-rectification-history-open-20260909.md @@ -0,0 +1,51 @@ +# PROGRESS · 历史生时校正打开时用绑定 Skill 身份(BUG-621,2026-09-09) + +工作树:`.worktrees/rectification-history-open-20260909` +分支:`codex/rectification-history-open-20260909` +任务书:`docs/tasks/TASK-rectification-history-open-20260909.md` +基线:任务书写 `origin/staging` @ `af70ae77`;本 worktree 从任务书提交 `e8d46f12` 切出,开工后快进合并 `c41afe26`(BUG-622,对齐过期合同测试)。 + +本单状态:**待验收**。未 push,未合入 staging/main。合入时更新落点 SHA。 + +预检:本机 `python3 scripts/pre_work_check.py` 失败(系统 Python 3.14 无 pytest、无 swisseph,ERR-078)。不把预检失败写成门禁已过。前端用 worktree 里指向其他 worktree 的 `frontend/node_modules` 符号链接。 + +未改:数据库迁移、`adopt-skill` 路由、采用门 / 确认门、只读终态、`homepage` / `new` 仍用当前注册版本、Skill 版本号。 + +## 做了什么 + +- **决策 1** `openRectificationCase` 在 `intent === "session"` 时:`open_agentic_rectification_case`(v1)按会话取 Case → `get_agentic_rectification_skill_identity` 读绑定身份 → `resolveExactSkillPackage` 核验快照与 sha → 把绑定身份作为 `p_skill_*` 调 v2。绑定版本不在注册表 → `skill_identity_missing`。legacy sha 为空仍走既有 `skill_identity_unverifiable`。 +- **决策 2** `KNOWN_RPC_ERROR_CODES` 增加 `agentic_rectification_skill_identity_mismatch` → 409,文案「该校正绑定的规则版本与当前不一致,请刷新后重试」。 +- **决策 3** 历史打开失败把 `rectificationErrorSessionId` 绑到被点会话;侧栏行下 `.session-open-error` 一行;`StarterHome` 收到空字符串,起始卡不重复。成功或另选会话则清除。 +- **记录** `docs/BUG_HISTORY.md` BUG-621;`CHANGELOG.md`;`docs/testing/rectification-history-open-20260909.md`。 + +## 三栏 + +| 用例 | 原值 | 新值 | 理由 | +| --- | --- | --- | --- | +| 历史会话绑定 10.0.17(快照仍在) | v2 收到当前 10.0.19 → mismatch → 500 | v2 收到 10.0.17,打开成功 | 决策 1:按绑定版本打开 | +| 绑定版本不在注册表 | 同上,落到 500 | `skill_identity_missing`,不调 v2 | 决策 1:核验失败走既有 missing | +| homepage / new | 当前注册版本 | 仍是当前注册版本 | 只有 session 改绑定身份 | +| `skill_identity_mismatch` | 未映射 → 500「校正服务暂时不可用」 | 409,请刷新后重试 | 决策 2 | +| 历史列表点开失败 | 错误在首页起始卡下;会话不切 | 错误在被点那一行下;起始卡不重复 | 决策 3;BUG-599 让点击不再先切会话 | + +## 测试 + +- `frontend` `./node_modules/.bin/tsc --noEmit`:exit 0 +- 本单改动文件 `eslint`:0 error(`page.tsx` / `app-sidebar.tsx` 等既有 hooks warning 未新增) +- 全仓 `npm run lint`:4 error / 108 warning。4 error 全在 `rectification-agentic-chat.tsx` 的既有 `react-hooks/refs`(约 L1461),本单未改该文件 +- 针对性 `tsx --test`(case-service / history-open / agentic-entry / skill-registry / sidebar-contract / surface-contract / home-bootstrap-reveal):**147 passed / 0 failed** +- 任务书切片 `ls tests/rectification-*.test.ts tests/skill-registry*.test.ts tests/home-bootstrap*.test.ts | grep -v database | xargs tsx --test`:**1093 passed / 0 failed** +- Docker DB / `test:db`:本机无 Docker,记环境缺口;本轮无新迁移,不触发该硬要求 +- 浏览器真人走查:无登录态,未见真实历史 Case。走查清单见 `docs/testing/rectification-history-open-20260909.md` + +## 偏离 + +1. 任务书基线 `af70ae77`;执行分支从已推任务书的 `e8d46f12` 切出,并快进 `c41afe26`(BUG-622)。 +2. 侧栏错误用 `.session-open-error`(overline + danger),不再引入已删除的 `composer-notice` 类名。 +3. 未跑 Docker 中的打开 RPC 真库套件。 + +## 环境缺口 + +- 本机系统 Python 3.14 无 pytest / swisseph,预检失败(ERR-078)。 +- 无 Docker,不能跑 `test:db`。 +- 无 staging 登录态,不能真人点历史校正。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 0b56c532..c5fc0226 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -88,7 +88,7 @@ | `TASK-rectification-conversation-economy-20260909.md` | `PROGRESS-rectification-conversation-economy-20260909.md` | 对照竞品后产品拍板三条:开场三句讲做法 + 一次收多件(推翻 opening brief「不要一次说完/不举例」与 SKILL L52);采集题「没有 / 记不清」按钮(不做示例骨架条);每轮只留一句(方法句进活动记录、点选旁白去领先落后、证据轮正文一句复述 + 服务端裁剪);Skill 10.0.19 | 已验收通过(2 条 P3 备注) | `aa7ccb30` + `1453fb16`(BUG-604~606,Skill 10.0.19) | | `TASK-consultation-daily-empty-answer-20260909.md` | `PROGRESS-consultation-daily-empty-answer-20260909.md` | 首页「深入看今日」计算完成却 `empty_answer`:入口定的 `timing` 被模型改成 `general`(`canonicalDomainPlan` 以模型为准),分段写作标题与今日格式错位,分段 `maxSteps=1` 且工具仍可调 → 模型在分段里再调工具、零正文;思考流按 chunk 过滤漏出缺词英文 | 待验收 | `codex/consultation-daily-empty-answer-20260909`(BUG-612~613);走查 `docs/testing/consultation-daily-empty-answer-20260909.md` | | `TASK-rectification-compare-card-polish-20260909.md` | `PROGRESS-rectification-compare-card-polish-20260909.md` | 三列卡两列经历对照 0、三列无未来窗:引擎按引擎前三算 by_time,卡片按推断前三查表,键不匹配(BUG-614);交付旁白被 BUG-606 裁成一句(BUG-615);设计减法:相同性格句只写一次、经历对照说人话、删小标题、右栏本命宫位改北印星盘、换升时刻默认折叠 | 待验收 | `codex/rectification-compare-card-polish-20260909`(BUG-614~615) | -| `TASK-rectification-history-open-20260909.md` | `PROGRESS-rectification-history-open-20260909.md` | 历史生时校正打不开:`open_agentic_rectification_case_v2` 把 Case 绑定 Skill 身份与当前注册版本做相等比较,两天内 10.0.15→10.0.19 五次 bump 让所有旧 Case 被拒;错误码未映射成 500;错误只画在首页卡下 → 表现为点了没反应。决策:session 打开用绑定身份(v1 取 Case → 读身份 → 注册表核验 → v2),不改迁移 | 待执行 | `codex/rectification-history-open-20260909`(BUG-621) | +| `TASK-rectification-history-open-20260909.md` | `PROGRESS-rectification-history-open-20260909.md` | 历史生时校正打不开:`open_agentic_rectification_case_v2` 把 Case 绑定 Skill 身份与当前注册版本做相等比较,两天内 10.0.15→10.0.19 五次 bump 让所有旧 Case 被拒;错误码未映射成 500;错误只画在首页卡下 → 表现为点了没反应。决策:session 打开用绑定身份(v1 取 Case → 读身份 → 注册表核验 → v2),不改迁移 | 待验收 | `codex/rectification-history-open-20260909`(BUG-621) | | `TASK-rectification-window-cluster-cap-20260909.md` | `PROGRESS-rectification-window-cluster-cap-20260909.md` | **P0** 真实用户:一小时窗口分 17 个签名簇,`select_signature_representatives` 按时间取前 12 个,14:40 之后整簇丢弃(本机复现表);区间按代表分钟跨度而非簇覆盖;中途说出真实时段时助手口头答应却未改 → 改为固定回复"范围开始时按资料定、中途不改"(产品否决口头改范围);intake 自定义范围等产品答复 | 待执行 | `codex/rectification-window-cluster-cap-20260909`(BUG-623~625,Skill 10.0.20) | ### 聊天主链路与首页 diff --git a/docs/testing/rectification-history-open-20260909.md b/docs/testing/rectification-history-open-20260909.md new file mode 100644 index 00000000..d2ee748d --- /dev/null +++ b/docs/testing/rectification-history-open-20260909.md @@ -0,0 +1,31 @@ +# 生时校正 · 升 Skill 版本后历史对话仍能打开(2026-09-09) + +对应 BUG-621。自动化锁的是打开 RPC 身份与侧栏错误位置,**锁不住**真实历史 Case 在 staging 上按绑定版本跑完一轮。 + +## 0. 确认测的是新版本 + +浏览器打开 `https://staging.jyotisha.chat/api/health`,看 `deployment.gitCommit` 前 8 位是否等于本单合入 staging 后的提交。不一致 = 先别测。 + +## 1. 升版本之后,旧校正仍能从历史对话打开(P0) + +前置:账号里至少有一条在当前 Skill 版本之前创建的生时校正(例如 10.0.17 / 10.0.18 那天建的)。当前注册版本更高。 + +1. 停在首页,不要先点「生时校正」入口。 +2. 在左侧历史列表点那条旧校正。 + +- ✅ 预期:主栏打开该校正,沿用它绑定的那一版规则继续;不是空白、不是「暂时无法打开」。 +- ❌ 失败:点了没反应;或首页起始卡下才出现错误;或打开后按当前新版本重跑。 + +## 2. 绑定版本若已从注册表消失(P1) + +只有在故意缺少快照的环境才测。正常 staging 应仍保留 10.0.15–10.0.18。 + +- ✅ 预期:被点的那一行下面出现一行错误(Skill 版本不可用),首页起始卡不再重复同一条。 +- ❌ 失败:仍像没点到,或主栏/起始卡出现 500「校正服务暂时不可用」。 + +## 3. 新开一场仍用当前版本(P1) + +从首页点「生时校正」新建。 + +- ✅ 预期:新 Case 绑定当前注册版本(现为 10.0.19)。 +- ❌ 失败:新 Case 也去套某个旧版本。 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index f14db588..42028953 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -764,6 +764,18 @@ button:disabled { cursor: default; opacity: .45; } font-weight: 500; letter-spacing: .02em; } +.session-open-error { + grid-column: 1 / -1; + margin: 0 var(--space-2) var(--space-2); + padding: 0; + border: 0; + background: transparent; + color: var(--color-danger); + font-size: var(--type-overline); + font-weight: 500; + line-height: 1.35; + letter-spacing: .02em; +} .session-main small, .session-subtitle { color: var(--color-ink-tertiary); line-height: 1.3; font-size: var(--type-overline); } .sidebar-group-label { margin: var(--space-2) 0 var(--space-1); color: var(--color-ink-tertiary); font-size: var(--type-overline); font-weight: 500; letter-spacing: .02em; } .session-list-sentinel { height: 1px; } diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index cf092267..3a21b831 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -298,6 +298,7 @@ export default function Home() { const [rectificationLoading, setRectificationLoading] = useState(false); const [rectificationMutationPending, setRectificationMutationPending] = useState(false); const [rectificationError, setRectificationError] = useState(""); + const [rectificationErrorSessionId, setRectificationErrorSessionId] = useState(null); const [rectificationReadonly, setRectificationReadonly] = useState(false); const [rectificationShouldStartOpening, setRectificationShouldStartOpening] = useState(false); const [rectificationTurns, setRectificationTurns] = useState([]); @@ -448,7 +449,7 @@ export default function Home() { modelPersistence, modelSelectionVersions, modelSyncFailures, pendingSessionId, profile, rectificationSessionId, sessionDetailInFlight, sessionSelectionSource, sessions, sessionsRef, setActiveChartId, setActiveSessionId, setBirthTimeConsultationConsent, setCreatingSession, - setDraft, setDraftEntrypoint, setDraftTheme, setRectificationError, setRequestError, + setDraft, setDraftEntrypoint, setDraftTheme, setRectificationError, setRectificationErrorSessionId, setRequestError, setSessionDetailLoadingId, setSessionFullPrompt, setSessions, sessionsCursor, setSessionsCursor, showArchivedSessions, setShowArchivedSessions, uiPreview, visibleSessions, openRectificationSession: (exactSessionId) => rectificationSessionOpenerRef.current(exactSessionId), @@ -489,7 +490,7 @@ export default function Home() { rectificationOpenInFlight, rectificationSessionId, resumeRectificationSession, sessionSelectionSource, sessions, setActiveSessionId, setDraft, setDraftEntrypoint, setDraftTheme, setOnboardingStep, setProfileNotice, setRectificationCaseId, - setRectificationEntrySummary, setRectificationError, setRectificationLoading, + setRectificationEntrySummary, setRectificationError, setRectificationErrorSessionId, setRectificationLoading, setRectificationPendingQuestion, setRectificationReadonly, setRectificationSessionId, setRectificationShouldStartOpening, setRectificationSnapshot, setRectificationOpeningSessionId, setRectificationTurns, setSessions, uiPreview, @@ -1710,6 +1711,8 @@ export default function Home() { charts={sidebarCharts} activeSessionId={activeSession?.id ?? null} openingSessionId={rectificationOpeningSessionId} + openErrorSessionId={rectificationErrorSessionId} + openErrorMessage={rectificationErrorSessionId ? rectificationErrorMessage : ""} account={sidebarAccount} accountMenuOpen={accountMenuOpen} accountTriggerRef={accountTrigger} @@ -1876,9 +1879,9 @@ export default function Home() { rectificationMutationPending={rectificationMutationPending} openRectificationFromHomepage={openRectificationFromHomepage} rectificationEntrySummary={rectificationEntrySummary} - rectificationError={rectificationError} + rectificationError={rectificationErrorSessionId ? "" : rectificationError} rectificationSurfaceOpen={rectificationSurfaceOpen} - rectificationErrorMessage={rectificationErrorMessage} + rectificationErrorMessage={rectificationErrorSessionId ? "" : rectificationErrorMessage} starterThemes={starterThemes} starterSuggestions={starterSuggestions} startSuggestedConsultation={startSuggestedConsultation} diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx index a622ab8e..7cb2c9ce 100644 --- a/frontend/src/components/app-sidebar.tsx +++ b/frontend/src/components/app-sidebar.tsx @@ -62,6 +62,9 @@ export type AppSidebarProps = { activeSessionId: string | null; /** A rectification session whose Case is being opened and hydrated; its row says so, statically. */ openingSessionId?: string | null; + /** Open failed for this history session; shown under that row only. */ + openErrorSessionId?: string | null; + openErrorMessage?: string; account: SidebarAccount; accountMenuOpen: boolean; accountTriggerRef: Ref; @@ -86,6 +89,8 @@ export function AppSidebar({ charts, activeSessionId, openingSessionId = null, + openErrorSessionId = null, + openErrorMessage = "", account, accountMenuOpen, accountTriggerRef, @@ -159,6 +164,7 @@ export function AppSidebar({ session={session} active={session.id === activeSessionId} opening={session.id === openingSessionId} + error={session.id === openErrorSessionId && openErrorMessage ? openErrorMessage : undefined} disabled={sessionControls.disabled} menuOpen={sessionControls.menuSessionId === session.id} onMenuOpenChange={(open) => sessionControls.onMenuSessionChange(open ? session.id : null)} diff --git a/frontend/src/components/sidebar-session-row.tsx b/frontend/src/components/sidebar-session-row.tsx index 5e777e17..8502452e 100644 --- a/frontend/src/components/sidebar-session-row.tsx +++ b/frontend/src/components/sidebar-session-row.tsx @@ -46,6 +46,8 @@ type SidebarSessionRowProps = { readonly active: boolean; /** Its Case is being opened: a static note, no spinner. */ readonly opening?: boolean; + /** Open failed for this row: one line under the title, same notice scale as composer. */ + readonly error?: string; readonly disabled: boolean; readonly menuOpen: boolean; readonly onMenuOpenChange: (open: boolean) => void; @@ -61,6 +63,7 @@ export const SidebarSessionRow = forwardRef