Files
Jyotisha/docs/tasks/TASK-readonly-pages-fix-20260916.md
T
Jesse_ChenandClaude Opus 5 fadb64fbcf 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-15 09:35:49 +00:00

205 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | 通过 |
| 派别参数化 | **未通过 · P1BUG-710** |
| staging 测试是否全绿 | **未通过 · P1BUG-711)、P2BUG-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-700703 已落 `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**700703 qizheng、704706 与 708709 被另一会话的校正单占用、707 星历)。本单从 **BUG-710** 起,开工时重新核对。
## 进度与记录
- 进度记录:`docs/tasks/PROGRESS-readonly-pages-fix-20260916.md`
- 三条 BUG 必须在同一变更内写进 `docs/BUG_HISTORY.md`,并关联原单。
- 环境缺口写 `BLOCKED.md`
- 索引:`docs/tasks/README.md` 追加本单,并把三份原单的状态从「待验收」改成「已验收(带修复单)」。