# 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 的修法决定。 ## 验收(Claude,2026-09-27) rebase 到 `dd85d996` 后独立复跑(Node 22.14,顺序执行):tsc 0;lint 0 error;`npm test` 4192 / 24 fail / 0 cancelled / 30 skip,失败名单与 BUG-1060 分支基线逐条一致、无消失测试名(新增 2 条 DB 测试本机跳过);Python 门禁集退出 0;`/`、`/chart`、`/ephemeris`、`/people` ○ Static;rootMainFiles gzip(level 9)130933 B。迁移只加可空列 + CHECK + 列级授权,对已部署代码向后兼容;BUG-1062 补丁只加 service_role 列级 SELECT(我核对了 `20260920020000` 确实未授予)。截图核对:编辑表单「性别(选填)」三选一 + 一句说明。 待门禁:`database-profile-gender.test.ts` 与 `profile-service-role-grants` 的真实库结果以本次推送的门禁 DB job 为准(run 号补记)。BUG-1063(报告 worker 以 service_role 读 `chart_profiles` 无授权)待产品/安全决定。