docs: gender-optional records (PROGRESS, checklist, CHANGELOG, BLOCKED) (T4)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-09-27 18:30:58 +08:00
co-authored by Claude Opus 5.5
parent d79dada970
commit 5bba952793
5 changed files with 134 additions and 1 deletions
@@ -0,0 +1,82 @@
# PROGRESS · 星盘资料加「性别(选填)」(2026-09-27)
任务书:`docs/tasks/TASK-consult-gender-optional-20260927.md`。执行:Claude 子代理(产品授权直接执行)。分支 `codex/consult-gender-optional-20260927`,工作树 `.worktrees/consult-gender-optional-20260927`。**未推送、未部署。**
## 基线
- 开工基线 `origin/staging` = `7a06aa00`(已含数据卡 v2 `5671039a` 与 BUG-1060)。之后 staging 只多了一条纯文档提交 `dd85d996`(BUG_HISTORY 三行),与本分支合并无冲突。
- 基线实测(Node 22.14,独立基线工作树):前端 4174 条 / 24 失败 / 28 跳过(24 条均为无 Docker 的 DB / 部署套件);Python 门禁集全绿(1 跳过);`next build --webpack` 后 `/`、`/chart`、`/ephemeris`、`/people` 均 ○ Static;首屏 rootMainFiles gzip(9) 660,412 字节。
## 提交
| 任务 | 提交 | 内容 |
| --- | --- | --- |
| T1 数据层 | `b912f63c` | 迁移 `20260927010000_profile_gender.sql`;账户 GET/PATCH、人物 POST/PUT/GET、主体解析、普通对话路由带上各自的性别;DB 测试 |
| BUG-1062 | `3abae68f` | 迁移 `20260927020000_profile_adopted_birth_service_role_select.sql` + 静态合同;BUG-1062 / BUG-1063 记录 |
| T3 婚恋卡 | `228ada22` | 数据卡婚恋段 `gender` = 当前人物的值;精简清单按有无切换;方法论缓存按性别分键 |
| T2 界面 | `53c657a8` | 人物编辑表单与「设置 → 个人资料」的选填单选;详情显示;DESIGN / VOICE;截图 |
| T4 记录 | 本提交 | CHANGELOG、本文件、真机清单、任务索引、BLOCKED |
## T1 数据层
- 取值:存 `female` / `male`,不填 = `null`(界面显示女 / 男 / 不填;与 `ayanamsa` 一样用英文键存)。
- 迁移只做加法:`profiles.gender`、`chart_profiles.gender` 两列可空、无默认、无回填;CHECK 只约束新列(`gender is null or gender in ('female','male')`);列授权照邻近出生列:`profiles` 给 authenticated `UPDATE`、service_role `SELECT/INSERT/UPDATE`(账户 PATCH 走服务角色,BUG-600 教训),`chart_profiles` 给 authenticated `INSERT/UPDATE`;两表 SELECT 本来就是表级授权 + 仅本人 RLS。admin_runtime 不授权(与 birth_date 同级)。
- 为什么对已部署代码向后兼容:旧代码从不 select / 写 `gender`,旧的人物插入列集照样成功、新列留空;回滚应用后列闲置。
- 读写位置:
- 本人:`PATCH /api/account` 接受 `gender`(zod `enum(female, male).nullable().optional()`,strict schema 其他值 400);`GET /api/account` 返回;普通对话路由 `loadProfile` 选列加 `gender`。
- 他人:`parseChartSubjectWrite` 只有请求里**带了** `gender` 键才写这一列(不认识这个字段的调用方不会把它清掉),`null` / `""` 清空,非法值 400;jsonb 回滚镜像不放性别;`libraryProfileFromSubjectRow`、`CHART_SUBJECT_SELECT` / `CHART_SUBJECT_API_SELECT` 带上。
- 主体解析:`chartProfileToConsultationRow`、`resolveSubjectBirth`(他人 accountRow)带各自的值;`ServerChartConsultation.gender` 取当前主体,不进 `toolInput`(引擎收不到)。
- 前端 `Profile.gender?`、`readProfile` 只在有值时带出。
- `ACCOUNT_BIRTH_SELECT` 未加 `gender`(报告、星盘、星历等不需要,缩小影响面)。
## BUG-1062(开工时顺带查出,在本单关键路径上)
- 本人性别走 `PATCH /api/account`,这条路由用服务角色读 `profiles`。用迁移全集静态比对:PATCH 与 `ACCOUNT_BIRTH_SELECT` 读取的列里,`active_birth_date / active_birth_timezone_offset / active_birth_provenance`(`20260920020000` 加的)没有任何 service_role `SELECT` 授权,按 BUG-600 的机理会 42501 → 500。本机无 Docker,未在真实库复现,状态写 investigating。
- 修复:补 SELECT 授权(只读、幂等);新增静态合同 `profile-service-role-grants-20260927.test.ts`(去掉新迁移时红、加回后绿)。`database-profile-gender.test.ts` 第二条在真实 PostgreSQL 上走 PATCH,门禁 DB job 会顺带证实。**这是任务书外的改动,单独一个提交,验收方可按需拆出。**
- BUG-1063:他人报告 worker 用服务角色读 `chart_profiles`,但服务角色对这张表没有任何权限(`20260718100000` revoke all 后没授回)。授权范围需要决定,本轮只记录。
## T3 婚恋卡
- `buildEvidenceCard(sources, { gender })`:婚恋段 `gender` = 「女」/「男」,没填 = 「性别未知」(规格位 `genderUnknown` 改名 `gender`,只有婚恋段有)。标签仍是卡片唯一自写字符串(`EVIDENCE_CARD_LABELS` 加 `genderFemale` / `genderMale`)。
- 精简清单:`condensedConsultationChecklist(domain, { gender })` 只替换那一行。填了时按 `strict-workflow-router.md` §4「先确认性别与昼夜盘」与仓内 `references/oracle/gender_interpretation_contract_2026_07_19.json`:女 = 夫星以木星为主、部分传承兼看火星;男 = 妻星取金星;都只作补充,核心仍是 7 宫、7 宫主、D9、UL、DK,不得只因配偶星的大运 / 过运定婚期。行数不变(7 行)。
- `consultationMethodologyForDomains(domains, { gender })` 的缓存键加上性别——否则第一位用户的清单会被缓存后发给下一位(测试覆盖了这条)。
- 系统提示里旧句「If gender … is present in future profile context」改为指向数据卡的 gender 字段(`src/mastra/index.ts` 一行,内容规则不变)。
- 遥测只记字段 id,测试确认性别值不进遥测、不算引用字段。
- 测试(真实 `getJyotishAgent` + 记录提示词的假模型,golden 引擎数据):填女 / 男 → 卡上有性别、清单无「性别未知」;不填 / null → 相反;同一进程依次跑 本人(女) → 他人(未填) → 他人(男) → 本人(女),每轮取当轮人物的值(经 `prepareConsultationRoute` 真实解析,本人是女时他人未填仍是「性别未知」)。
## T2 界面
- 一个组件 `ProfileGenderField`(女 / 男 / 不填,44px,一行说明),两处用:
- 星盘档案编辑表单(出生地点之后、岁差之前),随「保存」提交,「不填」写显式 `null` 以便清空。
- 「设置 → 个人资料」(本人唯一入口):选中即单独 PATCH `{gender}`,显示「已保存。」,保存中禁用、无 spinner。出生资料保存(`persistProfile`,含开场)不带性别,不会误清。
- 详情卡加「性别」行(没填「未填」);本人下面一行灰字「在「设置 → 个人资料」里改」,本页不给本人第二个入口(产品偏好:少入口)。
- 开场 / 注册不问(测试锁住 `home-onboarding-shell.tsx` 不含性别)。
- `page.tsx`:只多解构一个 `persistGender` 并传给 `ProfilePanel`(+1 行,useState / useRef 数不变)。
- 截图(本地 `next build --webpack` + `next start` + Chrome 无头,临时 harness 页渲染真实组件与虚构人物,harness 未提交):`docs/testing/consult-gender-optional-20260927/` 下 390 与 1280 各 4 张(他人详情、本人详情、编辑表单、个人资料选中「女」后)。CDP 检查:无横向溢出,性别按钮全部 ≥44px,点「不填」后草稿跟着变,个人资料点选后出现「已保存。」。
## 既有断言变更
无。既有测试文件只改了 `tests/consult-natal-agent-test-support.ts`:`runNatalAgent` 加可选 `serverChart` 参数(默认值不变),没有改任何断言。
## 验收数据(交付 HEAD,Node 22.14)
| 项 | 基线 `7a06aa00` | 交付 |
| --- | --- | --- |
| `tsc --noEmit` | — | 0 错 |
| `npm run lint` | — | 0 error |
| 前端全量 | 4174 / 24 fail / 28 skip | 4192 / 24 fail / 30 skip |
| 失败名单 | 24 条 DB/部署 | 与基线逐条一致;消失的测试名 0;新增 18 条(2 条 DB 在本机 skip) |
| Python 门禁集(含 growth contract) | 全绿,1 skip | 全绿,1 skip |
| `next build --webpack` | `/` `/chart` `/ephemeris` `/people` ○ Static | 同 |
| 首屏 rootMainFiles gzip(9) | 660,412 B | 661,444 B(+0.16%) |
## 环境缺口
- 无 Docker:`npm run test:db` 未跑。新增 `frontend/tests/database-profile-gender.test.ts`(列形状、CHECK、RLS 本人可读写 / 他人不可见不可改、旧插入列集照样成功、service_role 写、admin_runtime 无权;真实路由 POST/PUT/GET 人物与 PATCH 账户、陌生人 404 / 列表为空),本机两条 skip,**结果以门禁 DB job 为准,run 号待推送后补记**,不得写成已通过。
- 无登录态与模型凭据:婚恋回答是否真的按性别取夫星 / 妻星,按 `docs/testing/consult-gender-optional-20260927.md` 部署后真机走。
- Skill 文本未改(SKILL.md 基础段描述加 D9 摘要 / 性别字段一项不是必须,本轮不做,不 bump)。
## 未做 / 留给验收方
- 推送、部署、门禁 run 号。
- BUG-1063 的修法决定。
+1 -1
View File
@@ -133,7 +133,7 @@
| `TASK-consult-evidence-card-research-20260927.md` | `PROGRESS-consult-evidence-card-research-20260927.md` | **普通对话数据卡调研**:引擎输出逐项分五类计量(现约 4 万 token、父母问题相关约 3.5%);四处领域→技法来源对账并起草各领域数据卡(家庭拆父母/子女);卡体量与逐字一致性;按卡算的提速空间;反馈迭代埋点方案。只调研不改线上 | 已验收(待产品拍板 7 项) | `codex/consult-evidence-card-research-20260927` 快进 staging;报告 `docs/research/consult_evidence_card_research_2026_09_27.md`;投影缺口记 BUG-1054 investigating |
| `TASK-consult-evidence-card-20260927.md` | `PROGRESS-consult-evidence-card-20260927.md` | **普通对话改用数据卡**(产品 09-27 同意调研 7 项 + 补取一次):先修 BUG-1054 投影丢 Narayana 当前段/PD;新增父母/子女领域(引擎仍走 family);模型只读答题契约 + 基础段 + 领域段,研究/西洋/状态/不适用留后台;MEVG/真实案例只留一行;补取工具每轮 1 次;反馈 6 项日志 | 已验收(真机欠) | `75a1844c`…`20b6473c`(T1–T7);BUG-1054/1059;Skill 6.9.17;遗留 3 项见 PROGRESS 验收节 |
| `TASK-consult-evidence-card-v2-20260927.md` | `PROGRESS-consult-evidence-card-v2-20260927.md` | **数据卡 v2(采纳占星顾问意见)**:基础段加 D9 摘要;年运卡加年盘(年主/Muntha/Mudda/Tajika 相位,参数敏感);时运加罗睺计都、双重过运、木土 SAV/BAV、分盘随领域;Chara Dasha 移出卡;事业/婚恋/财富按逐项标注增补,清单精简为「必看+分三层+禁写」;引擎接入已有模块(Python) | 已验收(真机欠) | `d5616c34`…`8097adca`;新模块 `consultation_native_layers.py`;单领域全部 ≤ 1.2 万字符;BUG-1060 另修 |
| `TASK-consult-gender-optional-20260927.md` | `PROGRESS-consult-gender-optional-20260927.md` | **星盘资料加性别(选填)**:本人与星盘档案可填女/男/不填;婚恋卡按性别取夫星/妻星,未填两边都看;加可空列(test:db 以门禁为准) | 待领取 | 串行在数据卡 v2 之后 |
| `TASK-consult-gender-optional-20260927.md` | `PROGRESS-consult-gender-optional-20260927.md` | **星盘资料加性别(选填)**:本人与星盘档案可填女/男/不填;婚恋卡按性别取夫星/妻星,未填两边都看;加可空列(test:db 以门禁为准) | 待验收 | 分支 `codex/consult-gender-optional-20260927`(未推送);迁移 `20260927010000` + `20260927020000`(BUG-1062);test:db 以门禁 DB job 为准;BUG-1063 记录未修 |
| — (产品 09-27 拍板 D1–D3,直接执行) | `PROGRESS-consult-single-pass-answer-20260927.md` | **普通咨询回答没看过星盘(BUG-1053,引入 `04463e9a`,生产不受影响)**:本命主循环工具结果后那一步看得到证据、正文却被丢弃,用户看到的是另开的 compose 流(只有历史 + 问题、判断依据恒空)写的。D1 删 compose / interpret / drain,主循环最后一步的正文即回答(按步取答,调用工具的那一步正文整段丢);写作要求搬进用户轮;续写带计算结果;工具 110 秒不变,拿到计算结果后循环交给 70 秒答案钟,最坏 180 秒;BUG-1051 结算规则保留(按写回答那一步判定)。D2 观测字段名不变、含义改为写回答那一步 | 已验收(真机欠) | `codex/consult-single-pass-answer-20260927`(本地,未推送);新回归 15 条用真实 `getJyotishAgent` + 记录提示词的假模型 + golden 计算数据;全量失败名单 0 新增;Python 948/1;`/` ○、gzip +0.24%;真机清单 `docs/testing/consult-single-pass-answer-20260927.md` |
| `TASK-scroll-anchor-hook-fixes-20260926.md` | `PROGRESS-scroll-anchor-hook-fixes-20260926.md` | **滚动锚两处老问题**:直接打开已有会话时监听未挂上(BUG-1043)、校正长回答钉顶后因 96px 阈值被拉到底(BUG-1044)。排在 BUG-1042 合入后。挂载改由容器元素本身驱动(每次提交比对元素 / active / resetKey);钉顶只由用户滚动手势解除 | 已验收(Claude 09-26 直接执行:子代理复现两处根因并修复;Claude 独立复验 tsc/lint 0、全量 3970 条失败名单与基线逐条一致、四路由 ○、gzip 不变;iOS 惯性滚动留真机清单) | `da2613ff`(随 `f1d16405` 部署,health 一致) |
| `TASK-latest-turn-actions-gap-20260926.md` | — | **最后一轮正文与点赞 / 踩之间空大半屏**:BUG-930 钉顶留白(`min-height: 视口 − 本轮开头`)加在 `.message-assistant` 上,把兄弟节点 `.message-actions` 推到留白之后;改为加在整轮外层,按钮紧贴正文、空白落在后面;不动滚动 hook | 已验收(Claude 09-26 直接执行:子代理实现,Claude 独立复验 tsc/lint 0、全量 3961 条失败名单与基线逐条一致、四路由 ○、gzip 不变;CDP 实测间距 450–600px → 23px,钉顶仍在) | `080ea5ca`(已部署 `509987b9`,health 一致) |
@@ -0,0 +1,37 @@
# 真机清单 · 星盘资料加「性别(选填)」(2026-09-27)
对应任务书 `docs/tasks/TASK-consult-gender-optional-20260927.md`。部署到 staging 后照做;只用自己的测试账号和虚构人物,不要把真实出生资料写进任何记录。截图(虚构数据,本地构建)在 `docs/testing/consult-gender-optional-20260927/`。
## 0. 部署前提
- [ ] 门禁 run 里 DB job 跑过 `database-profile-gender.test.ts` 两条(不是 skip),`Migrate Staging Database` 已应用 `20260927010000_profile_gender.sql` 与 `20260927020000_profile_adopted_birth_service_role_select.sql`。
- [ ] `https://staging.jyotisha.chat/api/health` 的 `deployment.gitCommit` 等于这次推送的 SHA。
## 1. 本人(设置 → 个人资料)
- [ ] 打开「设置 → 个人资料」:昵称、登录邮箱下面有「性别(选填)」,三个选项「女 / 男 / 不填」,默认选中「不填」,下面一行「用于婚恋解读里判断夫星 / 妻星,不填也能用。」
- [ ] 点「女」:马上出现「已保存。」,没有转圈。关掉设置再打开、刷新页面,仍是「女」。
- [ ] 点「不填」:「已保存。」;刷新后是「不填」。
- [ ] (BUG-1062 顺带验证)新注册一个测试账号走一遍开场:填称呼、出生时间、出生地点都能保存,不出现「暂时无法核对现有出生资料」。开场全程不问性别。
## 2. 星盘档案里的其他人
- [ ] 「星盘档案 → 添加一个人」:表单里出生地点下面、岁差上面有同一个「性别(选填)」,默认「不填」。选「男」保存后,详情里「性别 男」。
- [ ] 「编辑」这个人,改成「不填」保存:详情显示「性别 未填」。
- [ ] 本人详情:「性别」下面有一行灰字「在「设置 → 个人资料」里改」,这一页不能直接改本人的性别。
## 3. 婚恋回答按当前人物的性别取夫星 / 妻星
每条都新建一段对话、问同一句「今年婚恋怎么样?」。回答下方「本轮技法」能看到数据卡时,一并看婚恋段的「gender」。
- [ ] 本人填「女」、当前星盘是本人:回答把木星当夫星来讲(可能也提火星),同时仍讲 7 宫、7 宫主、D9、UL、DK;不会说「性别未知」。
- [ ] 本人改成「不填」、再问:回答金星、木星两边都看,不把某一颗说成「夫星」或「妻星」定论。
- [ ] 顶栏切到另一个人(填了「男」)、新建对话再问:回答把金星当妻星来讲。**不能**沿用本人的「女」。
- [ ] 切到另一个没填性别的人:回答两边都看;即使本人填了「女」,也不能按女盘讲。
- [ ] 同一段对话里接着问一次追问:取法不变(人物不变,性别不变)。
## 4. 隐私
- [ ] 管理后台、反馈(点踩)记录、校正统计里都看不到任何人的性别。
有任何一条不符合,截图 + 写下是哪一步、当前人物是谁(用称呼,不写出生资料)。