From fadb64fbcf79c216d9fd53490a38779881a0faa9 Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Tue, 15 Sep 2026 09:35:49 +0000 Subject: [PATCH] docs(tasks): accept the three read-only page briefs and file the fixes Re-ran the checks independently rather than taking the progress notes at their word. Most red lines hold: the api server sits at 11334 of 11363, vendor/** is gated, the image installs nodejs and copies vendor, NOTICE carries the Apache-2.0 attribution, the forbidden CLI subcommands are blocked, neither new BFF imports billing or mastra, vedic-chart-svg did not move, tsc is clean and lint has no errors. Three findings. The qizheng adapter validates ketu_mode and sidereal_mode, echoes them into calculation.*, and never passes them to the engine: asking for descending-node returns 200 with a chart still computed on the apogee school and no warning. The ephemeris brief asserted that app-sidebar must not mention /ephemeris, which contradicts the chart-page brief that was told to add both entries, so staging is red. The ephemeris_events golden compares full-precision floats and regenerates itself when missing. Staging also still reports deployment.gitCommit 2d7698ea, six commits behind, with gated paths changed in between. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45 --- docs/tasks/README.md | 7 +- .../tasks/TASK-readonly-pages-fix-20260916.md | 204 ++++++++++++++++++ 2 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 docs/tasks/TASK-readonly-pages-fix-20260916.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 53d4ffab..ec249fa1 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -223,9 +223,10 @@ | `TASK-rectification-house-lord-gochara-research-20260913.md` | `PROGRESS-rectification-house-lord-gochara-research-20260913.md` | 研究单:宫主触发与木星/土星过运(合冲本命宫主、罗睺紧密合、年精度、用于 block 选上升)四种放宽,20 例公开 AA 离线量 block 层与 minute 层两组指标;引擎里已有宫主/功能吉凶/受控过运,只量缺的四条 | 待验收(无收益,关闭;不立实现单) | `codex/rectification-house-lord-gochara-research-20260913` | -| `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | **后端单(独占 `scripts/jyotish_api_server.py`)**:vendored `stem-branch` 0.8.0(Apache-2.0)归档 + API 镜像 Node runtime + 七政适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ 三个只读端点 `/api/qizheng`、`/api/western`、`/api/ephemeris_events`。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。BUG 段 700–703 | 已推 `origin/staging` `e776cf9d` | `codex/qizheng-native-chart-20260915` | -| `TASK-chart-page-20260915.md` | `PROGRESS-chart-page-20260915.md` | **前端单(独占 `app-sidebar.tsx`,同时加星盘与星历两个入口)**:P0 只读星盘页,五个 Tab(星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余),中宫排盘参数卡,三套坐标系各自标注且禁止互相换算。不扣点不调模型不出 spinner;不碰 `page.tsx`(1951/2000);**不搬 `vedic-chart-svg.tsx`**(rectification-board 也在用)。任务书预占 704–706,Bug 历史未写入;校正 P0 落地占用了 704–706 / 708–709 | 待验收 | `codex/chart-page-20260915` | -| `TASK-ephemeris-page-20260915.md` | `PROGRESS-ephemeris-page-20260915.md` | **前端单**:P1 星历页,今日五要素 + 当日行运(相对本命宫位)+ 未来九十天换座与停滞,底部「带这天去提问」出口。页面不得出现任何运势判断。含实证缺陷:panchanga 写死 Lahiri 与账户 Raman 分裂(关联 BUG-703;本单标注为 BUG-707)。侧边栏入口由 chart-page 单交付。BUG 段 707–709 | 待验收 | `codex/ephemeris-page-20260915` | +| `TASK-qizheng-native-chart-20260915.md` | `PROGRESS-qizheng-native-chart-20260915.md` | **后端单(独占 `scripts/jyotish_api_server.py`)**:vendored `stem-branch` 0.8.0(Apache-2.0)归档 + API 镜像 Node runtime + 七政适配器(计都派别与宿度坐标系参数化、boundary 按实测重写)+ 三个只读端点 `/api/qizheng`、`/api/western`、`/api/ephemeris_events`。实证三条:四柱时柱按 UTC 算(BUG-700,本轮不修不调用)、`ketuMode` 写死未暴露(BUG-701)、boundary 把空神煞与未闭合庙旺说成已生成(BUG-702)。BUG 段 700–703 | 已推 `origin/staging` `e776cf9d` | 已验收(带修复单) | +| `TASK-chart-page-20260915.md` | `PROGRESS-chart-page-20260915.md` | **前端单(独占 `app-sidebar.tsx`,同时加星盘与星历两个入口)**:P0 只读星盘页,五个 Tab(星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余),中宫排盘参数卡,三套坐标系各自标注且禁止互相换算。不扣点不调模型不出 spinner;不碰 `page.tsx`(1951/2000);**不搬 `vedic-chart-svg.tsx`**(rectification-board 也在用)。任务书预占 704–706,Bug 历史未写入;校正 P0 落地占用了 704–706 / 708–709 | 已验收(带修复单) | `codex/chart-page-20260915` | +| `TASK-ephemeris-page-20260915.md` | `PROGRESS-ephemeris-page-20260915.md` | **前端单**:P1 星历页,今日五要素 + 当日行运(相对本命宫位)+ 未来九十天换座与停滞,底部「带这天去提问」出口。页面不得出现任何运势判断。含实证缺陷:panchanga 写死 Lahiri 与账户 Raman 分裂(关联 BUG-703;本单标注为 BUG-707)。侧边栏入口由 chart-page 单交付。BUG 段 707–709 | 已验收(带修复单) | `codex/ephemeris-page-20260915` | +| `TASK-readonly-pages-fix-20260916.md` | `PROGRESS-readonly-pages-fix-20260916.md` | 三份只读页单的验收修复:**BUG-710** 七政 `ketu_mode`/`sidereal_mode` 收了请求却从不传给引擎,`calculation.ketu_mode` 回写请求值而非实际值(实测请求 descending-node 仍返回 apogee 盘,无警告);**BUG-711** 星历单断言 sidebar 不得含 `/ephemeris`,与星盘单按任务书添加的入口直接冲突,staging 现在是红的;**BUG-712** `ephemeris_events` golden 存全精度浮点跨机不稳,且 golden 缺失时自动重建。另附部署缺口:`deployment.gitCommit` 仍是 `2d7698ea`。BUG 段 710+ | 待领取 | — | | `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是全仓 3 处靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → 重新冻结行数 baseline(余量 300→50)。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,314 → 约 9,230 行。BUG 段 710+ | 待领取 | — | ## 命名与归档 diff --git a/docs/tasks/TASK-readonly-pages-fix-20260916.md b/docs/tasks/TASK-readonly-pages-fix-20260916.md new file mode 100644 index 00000000..0259c1e8 --- /dev/null +++ b/docs/tasks/TASK-readonly-pages-fix-20260916.md @@ -0,0 +1,204 @@ +# TASK-readonly-pages-fix-20260916 · 三份只读页单的验收修复 + +## 基线 + +- 验收对象:`origin/staging` = **`4161222b`**,含 + `25851dd3` + `e776cf9d` + `f7386e07`(qizheng 单)、`830799fa`(chart-page 单)、`d3a2c48b`(ephemeris-page 单)。 +- 验收人:Claude(独立复跑,未采信进度记录的自述)。 +- 三份原单的绝大多数红线**已通过**,详见「已通过项」。本单只处理未通过项。 + +## 验收结论摘要 + +| 项 | 结论 | +| --- | --- | +| 后端红线(行数 / gated-paths / Dockerfile / NOTICE / 禁用子命令) | 通过 | +| 前端红线(不碰 `page.tsx` / 不搬 SVG / 不扣点不调模型 / 无 spinner / tsc 0 / lint 0 error) | 通过 | +| 派别参数化 | **未通过 · P1(BUG-710)** | +| staging 测试是否全绿 | **未通过 · P1(BUG-711)、P2(BUG-712)** | +| staging 是否已部署 | **未达标**,见「部署缺口」 | + +## 未通过项 + +### P1 · BUG-710|`ketu_mode` / `sidereal_mode` 收了请求但从不传给引擎 + +`scripts/qizheng_chart_engine.py` 的 `_run_cli()`(`:172-181`)把命令行写死成 +`--date / --lat / --lng / --seven-governors / --json`,**请求里的派别参数一个都没传下去**。 +而 `_normalize()`(`:245-248`)把**请求值**写进 `calculation.ketu_mode`,把**实际值**另放在 `calculation.engine_ketu_mode`,两者不一致时既不警告也不拒绝。 + +验收实测(虚构资料 `1990-04-09T13:24:00+08:00 / 31.19N 121.44E`,请求 `ketu_mode: "descending-node"`): + +``` +请求 ketu_mode : descending-node +引擎实际 ketu_mode : apogee +计都实际位置 : 亢 15.46 ← 仍是月孛派 +月孛实际位置 : 亢 14.23 +罗睺实际位置 : 女 5.09 ← 对点应在此处附近,并非计都所在 +有无 warning 字段 : 无 +``` + +调用方读 `calculation.ketu_mode`——这是最自然的那个字段——拿到的是自己要的值,不是算出来的值。**这比没有这个参数更糟**:它让派别看起来被采纳了。 + +原任务书决策记录 4 写的是「不接受引擎默认静默生效」,实现成了「静默不生效但假装生效」。 + +原单的测试没兜住这条:`tests/test_qizheng_chart_engine.py:50` 的 live 用例只断言了**默认值** +(`result["calculation"]["ketu_mode"] == "apogee"`),没有任何用例请求非默认派别。 + +**要做什么**(二选一,由执行方按引擎实际能力定,并在进度记录里说明选了哪条): + +- **A(首选)**:确认 vendored 引擎能接受派别入参(库函数 `getSevenGovernorsChart` 的 options,而非 CLI 旗标),把 `ketu_mode` / `sidereal_mode` 真正传下去,并断言回读的 `engine_ketu_mode` 等于请求值。 +- **B(引擎确实不支持时)**:非默认派别一律 **400 拒绝**,错误码 `ERR_QIZHENG_KETU_MODE_UNSUPPORTED`,并在 `boundary` 里写明本引擎固定 apogee 派。**不得继续接受并回写。** + +无论 A 还是 B,`calculation.ketu_mode` 必须**只表示实际生效的派别**。 + +**验收标准** + +- 新增用例:请求 `ketu_mode: "descending-node"` 时,要么计都位置随之改变且 `engine_ketu_mode` 跟随(走 A),要么返回 400(走 B)。 +- 新增用例:`calculation.ketu_mode` 与 `calculation.engine_ketu_mode` **永不**出现不一致的 200 响应。 +- `sidereal_mode` 同样处理,同样加用例。 +- 前端 `frontend/src/lib/chart-view-mapper.ts:602` 的 `ketuMode` 映射跟着改到实际生效字段。 + +### P1 · BUG-711|星历单的守卫与星盘单的侧栏入口互相矛盾,staging 现在是红的 + +`frontend/tests/ephemeris-page.test.tsx:70`: + +```js +assert.doesNotMatch(readFileSync(".../app-sidebar.tsx", "utf8"), /ephemeris/); +``` + +而 `TASK-chart-page-20260915` 任务 3 明确要求 chart-page 单**同时加「星盘」「星历」两个入口**,它也照做了(`app-sidebar.tsx:236/241`)。两单合入后这条断言必然失败: + +``` +The input was expected to not match the regular expression /ephemeris/. Input: + ' router.push("/ephemeris");\n' + + ' isActive={pathname === "/ephemeris" || pathname.startsWith("/ephemeris/")}\n' +``` + +`tests/ephemeris-page.test.tsx` 单跑:**5 pass / 1 fail**。 + +根因是星历单把「本单不改 sidebar」写成了「sidebar 里不得出现 ephemeris」。前者是文件归属,后者是产品事实——两者不是一回事。 + +**要做什么**:把第 70 行的断言改成**只约束本单的文件归属**,例如断言 `git diff` 里本单没改过 `app-sidebar.tsx`,或直接删掉这一行(归属已由前两行 + 任务书保证)。**不得反过来删掉 sidebar 的星历入口。** + +**验收标准** + +- `npx tsx --test frontend/tests/ephemeris-page.test.tsx` 全绿。 +- 侧栏「星历」入口仍在,三态(桌面 288 / 平板 64 / 手机抽屉)可达。 +- 改断言要按 AGENTS.md §7.3 写「原值 / 新值 / 原因」三栏。 + +### P2 · BUG-712|`ephemeris_events` 的 golden 存了全精度浮点,跨机器不稳 + +`tests/golden/ephemeris_events_raman_20260915_90d.json` 把 `speed_longitude` 存成全精度浮点, +`tests/test_ephemeris_events.py:27` 直接 `assert result["events"] == golden` 做整体相等比较。 + +验收机(Linux / Python 3.13 / 仓库 `.venv`)实跑: + +``` +At index 2 diff: ... 'speed_longitude': 1.42928209 != ... 'speed_longitude': 1.4292821 +``` + +golden 生成在执行方本机(Windows / Anaconda 3.11.7)。同一天、同一区间、同一岁差,事件的**种类、日期、星体、星座全部一致**,只有浮点尾数差一位。 + +这不是计算错,是 golden 的比较口径太严——**任何与生成机 pyswisseph / 星历数据不完全一致的机器都会红**,包括 CI。 + +`tests/test_ephemeris_events.py` 还有一处更该修的:第 24-25 行在 golden 不存在时**自动写出 golden**。这会让「golden 丢失」静默变成「golden 重新生成并通过」,等于没有 golden。 + +**要做什么** + +- 比较时对浮点做量化:`speed_longitude` 与 `longitude` 各保留固定小数位(建议 6 位,与仓库既有坐标精度口径一致)后再比。事件的 `kind` / `date` / `body` / `from_sign` / `to_sign` 保持严格相等。 +- 删掉自动写 golden 的分支;golden 缺失应当**失败**并提示用专门的生成脚本重建。 + +**验收标准** + +- `.venv/bin/python -m pytest tests/test_ephemeris_events.py` 在验收机与执行方机器上都全绿。 +- golden 文件被删除时该测试**失败**,不是自动重建。 + +## 部署缺口(不在本单实现范围,但必须一起解决) + +`https://staging.jyotisha.chat/api/health` 的 `deployment.gitCommit` = **`2d7698ea`**, +而 staging HEAD 是 **`4161222b`**,中间有 6 个提交且**含门禁路径改动**(`scripts/**`、`frontend/**`、`deploy/**`、`vendor/**`)。 + +按 AGENTS.md §2.4,这就是**未部署**。BUG-711 / BUG-712 两条红测试很可能就是门禁没过的原因;此外 `frontend/tests/chart-view-route.test.ts:150` 的 +`assert.ok(newlines <= 1951)` 也是红的——`page.tsx` 现在 **1964** 行。 + +**那条 page.tsx 超限不是本批三单造成的**:`git log -- frontend/src/app/page.tsx` 显示唯一改动者是 +`f51e494c fix(chat): keep rectification sessions findable...`(+14/−1),另一个会话已在 `4161222b` 立了修复单。本单**不处理它**,但修完 711 / 712 后若门禁仍红,请先确认那一单是否已落地。 + +**验收标准**:本单推送后,`deployment.gitCommit` 等于最近一次含门禁路径改动的 staging 提交。 + +## 已通过项(复跑确认,不必重做) + +**qizheng 单** + +- `scripts/jyotish_api_server.py` **11334 行**,上限 11363(净增 21,任务书允许 ≤32)。`tests/test_api_server_growth_contract.py` 通过。 +- `deploy/gated-paths.txt:29` 有 `vendor/**`;`deploy/railway-api.Dockerfile` 装 `nodejs`、`COPY vendor ./vendor`。 +- 根 `NOTICE` 齐备:包名、0.8.0、Apache-2.0、Copyright、上游地址。 +- `FORBIDDEN_CLI_FLAGS` 覆盖 `--pillars / --luck / --polaris / --qimen / --liuren / --chuanren`,调用处主动校验。 +- `coordinate_system = qizheng_mansion_degrees_from_jiao`;boundary 无「神煞」;`dignities.status = unclosed`、`may_enter_conclusions = false`、`runtime_promotable_count = 0`。 +- 定向测试:`test_api_server_growth_contract` / `test_qizheng_chart_engine`(10)/ `test_qizheng_api_productization`(4)/ `test_readonly_chart_endpoints`(5)**25 passed**。 +- BUG-700~703 已落 `docs/BUG_HISTORY.md`。 + +**chart-page 单** + +- 独立 route `frontend/src/app/chart/page.tsx`;`page.tsx` **未被本单改动**(唯一改动者是 `f51e494c`)。 +- `vedic-chart-svg.tsx` **未搬家**,导出签名零改动;引用方从 3 个变 4 个(新增 `chart-page/chart-vedic-tab.tsx`)。 +- `frontend/src/app/api/chart-view/route.ts` **不 import** `consultation-billing`、不 import `@/mastra`。 +- 侧栏「星盘」「星历」两个入口齐备(`app-sidebar.tsx:224-241`)。 +- `chart-page-view.test.tsx` **7/7 通过**。 +- `frontend/DESIGN.md` 新增「§15 只读星盘页」。 + +**ephemeris-page 单** + +- 独立 route `/ephemeris`;不碰 `page.tsx`、不碰 `app-sidebar.tsx`。 +- BFF 不 import 计费与 mastra。 +- 五要素段有 Lahiri 标注(`ephemeris-page.tsx:154`),行运段没有——与任务书一致。 +- 全页无 spinner / 骨架 / 「正在加载」。 +- 逐词检查**无运势判断**(`宜 / 忌 / 吉日 / 运势 / 建议 / 有利于 / 不利于 / 会带来` 均无命中;`不利时段` 是引擎原标签,属照搬)。 +- BUG-707 已落 `docs/BUG_HISTORY.md`。 +- `frontend/DESIGN.md` 新增「§16 星历页」。 + +**全局** + +- `./node_modules/.bin/tsc --noEmit` **0 错**。 +- `npm run lint` **0 error**(119 warning 全部是既有的)。 + +## 环境缺口(验收机做不了,不算未通过) + +| 项 | 说明 | +| --- | --- | +| **Node 版本** | 验收机 `node v20.19.2`,仓库标准是 **Node 22**(`deploy/railway-web.Dockerfile:1`、`.gitea/workflows/*` 均为 `node:22`)。`frontend/tests/ephemeris-route.test.ts` 用 `mock.module`(Node 22+),在验收机上 3 条子进程用例退出码 1,**无法判定通过与否**。请执行方在 Node 22 上复跑并把结果贴进进度记录。 | +| **Docker** | 验收机无 Docker:API 镜像构建、容器内 `node --version`、`COPY vendor` 是否真的进镜像,**全部未验证**。镜像体积变化未知。 | +| **登录态与 Chrome** | 两个新页面的浏览器级走查未做,留给 `docs/testing/`。 | + +## 让步顺序 + +1. **BUG-711 优先**:它是 staging 变红的直接原因之一,改动最小(一行断言)。 +2. 其次 BUG-712(测试口径)。 +3. 最后 BUG-710(派别参数)。它目前**没有用户可见影响**——`/api/chart-view` 不传派别参数,所以请求值恒等于默认值——但接口一旦被别处调用就会误报,不能留。 + +**不可让步**:不得靠删掉 sidebar 星历入口来让 711 变绿;不得靠放宽 `dignities` 或 boundary 口径来省事;不得调高 `jyotish_api_server.py` 的行数契约。 + +## 开工前置命令 + +```bash +cd /workspace/Jyotisha +git status -sb +git fetch origin --prune +git worktree add -b codex/readonly-pages-fix-20260916 \ + .worktrees/readonly-pages-fix-20260916 origin/staging +cd .worktrees/readonly-pages-fix-20260916 +node --version # 必须是 22.x,否则 mock.module 用例跑不了 +python3 scripts/pre_work_check.py --remote-timeout 8 --command-timeout 45 +.venv/bin/python -m pytest tests/test_ephemeris_events.py -q +cd frontend && npx tsx --test tests/ephemeris-page.test.tsx tests/chart-view-route.test.ts +``` + +## BUG 编号起点 + +`origin/staging` 当前最大号 **BUG-709**(700–703 qizheng、704–706 与 708–709 被另一会话的校正单占用、707 星历)。本单从 **BUG-710** 起,开工时重新核对。 + +## 进度与记录 + +- 进度记录:`docs/tasks/PROGRESS-readonly-pages-fix-20260916.md`。 +- 三条 BUG 必须在同一变更内写进 `docs/BUG_HISTORY.md`,并关联原单。 +- 环境缺口写 `BLOCKED.md`。 +- 索引:`docs/tasks/README.md` 追加本单,并把三份原单的状态从「待验收」改成「已验收(带修复单)」。