# Helix Roadmap

- **DAX catalog scheduling removed (2026-09-09):** Production dataset admission settings are no longer reconciled from the repository every morning; catalog imports require explicit manual dispatch and default to dry-run.


- **助手的门面与语言(已完成,2026-09-09,4-PR 主题):** 员工在飞书点开「使用帮助」看到的是一整屏纯文本,而助手无论对方用什么语言写来都按其平台语言设置作答。本主题把入口做成可以点的卡、把回复语言交给员工。**PR1 已完成(0 迁移 / 0 端点 / 0 MCP)**:两级帮助卡(八类 × 四句开场白,点一句 = 打一句,与打字进来的一轮共用限速、回合锁与投递路径),分类口径沿用员工已经熟悉的飞书服务台六类 + Helix 才有的两类;顺手修掉「空对话里点转人工会开出一张空工单」—— 现在先问一句,下一条消息直接成单、不过模型。**PR2 已完成(0 迁移 / 0 端点 / 0 MCP)**:回复语言改为「这句话 → 这个会话上一次的语言 → 这个人的偏好」三步判定(`detect_text_lang` + `reply_language.for_turn`),飞书与网页同一个判定;`resolve_lang` 仍是通知与卡片文案的唯一权威,ADR 记在 DECISIONS 2026-09-09;顺带修掉「没绑定的人收到的指引恒为中文」。**PR3 已完成(0 迁移 / 0 端点 / +1 MCP → 630)**:帮助卡底部的语言自助(三语按钮 + 显式选过才打勾),一次点击改的是全平台读的那一列;写法收进 `identity/preferences.py`(个人设置页 / 卡片 / MCP 三处同源、同一条审计),并补上 MCP 工具 `set_my_language` 与它的 curated 卡片。**PR4 已完成(0 迁移 / +1 端点)——「助手的门面与语言」4 个 PR 全部落地**:网页与移动端求助页的开场白改由同一份注册表服务端下发(`GET /support/copilot/starters`),按页面当前语言给;三个面同源、各自按画布铺开,前端不再自带兜底文案。
- **助手进群(in flight,2026-09-08 起,7-PR 主题):** 「转人工」把员工和 IT 负责人拉进一个飞书群,而机器人在里面**一句话不说** —— 群消息在入口第一行就被丢掉(不是权限问题:`im:chat` / `im:message` 都在)。本主题把群做成通道的一等公民,并把工单群做成工单的第二个面。**PR1 已完成(迁移 632)**:入口判据「我被 @ 了」(而不是「消息被投给了我」)、`ReplyTarget` 统一答复目标、群守则进提示词、两个开关 + 每群额度种进平台设置、`chat_kind` 分得清私聊/工单群/任意群、进群自我介绍与被移出记账。**PR2 已完成(迁移 633,+1 端点 / +1 MCP)**:`assistant_ticket_chats` 成为「工单 ↔ 群」唯一注册表(历史群回填)、群里那张工单卡与三颗按钮、群内命令(状态/认领/转给/拉人/优先级)、三个面都能开群、工单状态变化后台同步回群;并修掉「转人工拉了人却不指派工单」这个一直在的洞,`assignee` 从此是这张工单的处理人(RBAC 变化,判据在支持域里只有一份)。**PR3 已完成(0 迁移)**:`总结` 按需读一次房间 → 四栏结构化小结,逐字发到群里并写进工单时间线(新消息种类 `note`,不动状态与 SLA);`交接给 @某人` = 总结+转派+拉人一条命令;每次读群落审计、模型不可用时什么都不写。**PR4 已完成(0 迁移)**:群里能把事情收掉 —— `解决了 <说明>`(处理人原话直接成为结案说明)、「我弄完了」按钮先在群里提一段小结等确认(带摘要校验)、求助人专属的「问题解决了 / 还有问题」、三档满意度发在群里;卡片按钮改成状态机(最多三颗,`resolved` 不算终态),并给每次点击先发布 in-flight 卡以免慢的那一下把工单卡换掉。**PR5 已完成(迁移 634)**:每日收尾 cron —— 结了的群按保留期(默认 7 天)解散、没人管的群(默认 14 天)问一句而**不自动关单**;删之前保证工单上有过程记录(做不出来就留着房间),删失败绝不标已删;三个入口手动解散(群里 / 门户后台按钮 / MCP,后者因不可逆而 OPT_OUT);机器人被移出群会落状态。**PR6 已完成(0 迁移,+1 端点 / +1 MCP)**:`/admin/assistant-handoff` 重做成运营台 —— 四个 KPI(转人工次数与拉起群比例、开着的群与停滞数、等收尾、**讨论还没落到工单上**)、一张一行给出全部处境的房间清单(进群/看工单/解散,解散只在终态出现)、路由表行上直接带这一类的判中次数与平均置信度;停滞阈值与「等收尾」谓词都与收尾任务/默认视角同源(有测试钉住)。**PR7 已完成(迁移 635 = 两列)——「助手进群」7 个 PR 全部落地**:路由表加备份负责人,拉人之前先查「他今天在不在休假」(判据与对外可用性接口逐字同源;查不到 = 在);群内 SLA 催办复用既有阶梯的水位与冷却(不另立时间表),催到第 3 轮还没动就把备份负责人拉进房间 —— 只一次、且不自动改派。
- **安全宣贯漫画不再只有中文画面(2026-09-07,0 迁移):** 25 张英文长图落地 —— **21 条**已有素材的 `en` / `ja` 画面与封面换成英文版(标题只有 7/25 与线上完全一致,映射逐条人工核过、解析不到即报错,因为线上有四条不同的社交主题漫画),**4 条**线上没有中文版的主题(AI 工具泄密 / 公共 WiFi / 移动介质 / 居家办公)新建为漫画。日文指向英文画面;只有英文画面的四条三种语言都指向它,中日文标题摘要另写。生产漫画 31 → 35 条,其中 21 条英文画面 ETag 与源文件md5 逐条相等、真实端点三语取图 HTTP 206。**仍有 10 条只有中文画面**(需要新一批英文/日文插图),`mt_langs` 刻意未动(见 CHANGELOG)。
- **安全每日一题现在按员工语言出题,安全内容库不再是空的(2026-09-07,迁移 628;内容走仓库外导出 + `scripts/load_security_content.py`,不进 git):** 题库(**337 道**)三语齐备 —— 存量 309 道补齐 en+ja,新增 28 道覆盖此前一条都没有的六个面(AI 使用 / 研发与供应链 / 云 / 事件上报 / 合规隐私 / 出行远程)。语言由服务端决定一次(`?lang=` → `Accept-Language` → 本人 `preferred_language`),缺语言时页面明说「今天这道题还没有你所选语言的版本」。🔴 **选项 key 是契约**:`correct_key` 记的是选项字母,key 集合与基线不一致的译文块在读路径被拒(回退基线选项)、在写路径 422、在种子侧直接抛 —— 判卷逻辑因此完全不用动。同批灌入 **12 条三语宣贯素材**(学习中心此前本地 0 条、线上 30/46 只有中文)与 **35 条答复库条目**(22 员工可见 + 13 条合规证据口径,带 `backing_control_keys`)。**漫画/海报仍需管理台上传** —— 每一页是 S3 对象,迁移造不出来。`/security-quiz/admin/coverage` 现在同时回报语言覆盖率:「94% 的人答了题」在其中三分之一用非母语作答时是一句更弱的合规主张。
- **硬件资产生命周期的四个断点已收口(2026-09-05,五个 PR / 迁移 604–605 + 608):** 领域健康分上三个坐在最差档的指标(Day-1 就绪率 22.5% → 30 分、长期闲置电脑盈余率 21.9% → 20 分、资产财务完整度 27.6% → 30 分)都指向同一件事 —— **实物的变动发生了,台账里的动作没跟上**。① 预留即就绪 + T+0 自动领用,把「提前备机」变成同时也得分的那条路(生产实测:29 of 31 的 Day-1 失败只差硬件这一维;预留工具存在但零使用,因为用了不算);② 长期闲置电脑清单 + 资产菜单 11→9;③ `purchase_cost_source` 让估算值和发票金额可区分 + 台账健康度自检页;④ 财务固定资产台账常设对账(unmatched = 财务在折旧一台台账里没有的设备,这个发现在别处看不到);⑤ 离职未归还设备的时钟(取 HRIS 离职日)与责任人 + 备机预留僵尸的每日清理。**仍未关闭**:**回填已于 2026-09-05 在生产实跑完毕**(dry-run → apply,94 行:42 行补币种 + 52 行同型号中位数,全部带审计行可回溯),完整度 **27.6% → 36.6%**(290/1050 → 384/1050);**天花板是实测的 36.6%,不是此前估算的「约 50%」**(692 件缺金额的资产里只有 52 件同型号有价样本够 3 个,502 件不够),过 90% 要财务把固定资产台账给到对账页;Day-1 是六个月滚动历史指标,5–6 月那 15 条失败要到 11–12 月才滚出窗口。

> **Per-PR changelog**: [docs/CHANGELOG.md](docs/CHANGELOG.md) — dated digests, newest first. This file holds only current state + open work.
> **Source of truth on what exists**: [docs/architecture/PLATFORM.md](docs/architecture/PLATFORM.md)
> **Source of truth on user-approved decisions**: [docs/architecture/DECISIONS.md](docs/architecture/DECISIONS.md)
> **Cloud lifecycle ops**: [docs/ops/cloud-lifecycle-ops.md](docs/ops/cloud-lifecycle-ops.md)

---

## TL;DR — where Helix is today

**定时任务域优化(2026-09-05 起,分批进行中)。** 起点是一次只读的生产测量:344 行台账 / 294 启用、30 天 12,322 次运行,对上 202 个已注册 handler。**结论是不该合 handler** —— 同一云账号一天的全部同步串起来最差 5,042 秒,超过单次 1,800 秒超时,而按「云账号 × 能力」拆行是既有 ADR。真正的问题是三个:一个任务(云资源目录刷新)占了重量级 77% 的算力却三次里两次无变化;夜间是队头阻塞而非容量不足(全部云同步一天只要 3.5 个槽小时 / 容量 96,却让只跑 9 秒的任务排队 1,384 秒);以及台账里 35 行绑在已停用集成上、每天各占一次重活槽只为发现无事可做。**PR1–PR5 全部落地(迁移 613–617),本轮收尾**:五条节奏纠偏(每条都带「管理员改过的不动」守卫)、集成前置闸(拿槽之前判定,失败开放,只记状态转移)、台账健康态五分类 + 执行容量模型 + `/admin/scheduled-jobs` 新增「容量与体检」Tab、日体检 cron `scheduler.ledger_reconcile`(只报不改,唯一动手的是「集成已删除」的孤儿行)。**PR2**:执行器韧性 —— 被部署打断的运行记为 `interrupted` 而非 `failed`(30 天 452 次 scheduler 失败里 386 次是这个假象)、单飞锁在排队期间自动续期、僵死行清理器靠「锁还在不在」把「死了」和「跑得久」分开(6 小时 → 一跳)。**PR3**:链式触发原语 —— 「排在 X 之后」从两个 cron 分钟数之间的空隙变成一条声明(`trigger_type='chained'` + 自引用 `chain_after_job_id`),执行器直接拉起是快路径、每 5 分钟的 `scheduler.chain_advance` 是保证;链推进的条件是「上游跑完」不是「上游成功」(否则会**降低**覆盖);首批转换六条边,三对故意不转并写明理由。**PR4**:云 IAM 三件套(scopes → roles → grants)按账号串链 —— 它们共用同一把锁却各跑各的 cron,`cmdb.scopes.aws-primary` 因此 31 天里有 29 天没跑;另修掉一条昨天刚上线的反向依赖(`usage_signal_refresh` 自称排在 `idle_reclaim` 之前,实际晚 40 分钟,消费者一直在用晚一天的信号)。账单路径刻意一条边都不连,并有测试守着。新增顺序清单棘轮:任何新的「排在 X 之后」声明不记录决策就构建失败。**PR5(收尾)**:链路兜底次数进管理台(一个成员天天被对账捞回来 = 它的快路径坏了,而链照样在推进、一切看起来都是绿的);并**否掉了自己此前提的两件事** —— 三处到期提醒不接统一引擎(读完代码发现它们是四个不同的产品,不是同一个实现了四遍;统一必须选一种产品行为,而保留各家阈值又共用只能把引擎变成策略外壳,代码更多不是更少),云资源目录增量投影暂缓(重量级占用已降到 13%,而最直觉的 `max(synced_at)` 水位会**静默漏掉消失的资源** —— `_safe_sweep` 只写 `deleted_at` 不碰 `synced_at`)。两条连同理由记进 DECISIONS,因为它们在任何一次体检里都会自动重新浮上来。另有三条失败经查是平台在正确报告别的域的真问题(netsuite full sweep 停摆 / Zoom 数据转移目标不存在 / AI 简报接地闸拒绝 + 一处 MissingGreenlet),已各自单开任务并附证据。**netsuite 那条已于 2026-09-06 查清并修复** —— 停摆区间实为 8-20~8-23(NetSuite 401/406 + dept 阶段失败),8-23 16:15 已自愈并追平积压,「自 8-19 起」是看门狗只认成功 sweep 的措辞造成的误读;真正的 bug 在另一半:`run_due_targets` 的 `ran_failed` 闸因 ORM identity-map 陈旧读从未生效过,近 30 天 10 次 failed sweep 全部静默(详见 CHANGELOG 同日第四轮)。

**转岗复核:现状。** **四个 PR 已全部落地,并做过一轮合并后对抗式复审(7 处修复,3 处变异验证)。** 推送仍然是关的 —— 打开它现在是一个**有 preflight 可看**的决定,而不是一次纵身一跃;真正解锁它的前提仍然是有人去配 `provision_policies`,而页面会一直把覆盖率摆在那里。复审里最重的两条:**「回收全部」的形状在 role 粒度上又回来了一次**(policy 判定现在要求「策略写到了这个角色」而不只是「策略写到了这类东西」),以及**夜间 cron 的一颗 MissingGreenlet**(`IdentityPerson.primary_department` 是走关系的 property,过期实例上一碰就炸,只在一条回退分支上触发)。

**转岗复核 PR4 · 把开关交回给人(2026-09-05,0 迁移 / +2 端点 / 0 MCP):** ① **重新启用的 preflight** —— 此前想知道重开推送的爆炸半径,唯一办法就是打开它(9/5 那九张卡就是这么来的)。新端点一封不发,用发送路径**自己的**谓词跑一遍开放队列,两半都报(会发多少 / 被拦多少、为什么),并且**报到人**;刻意用「开关强制 ON」模拟,因为用当前关着的状态算会返回 0 而那只会教人这件事很安全。② **云 scope 负责人终于能回答那张问他的卡** —— sweep 一直同时发给负责人和 IT,而端点 `require_admin`、落地页 admin-only,负责人常常是普通员工:与经理那张卡同一个缺陷换了个域。端点改成「管理员**或本条授权所在 scope 的负责人**」,卡片按收件人分发不同落地页,负责人落到「我的待办」第 14 个 tab。⚠️ 刻意**不是** scope 详情页 —— 那一页的「pending」是等他审批的申请,是另一种 pending,他会落在一个不包含卡片所问之事的页面上。③ 通知分类 `role_change` 从 `lifecycle` 拆出(桶不对 + 关掉它会连带关掉入职离职 HRIS procurement)。顺带删掉空转的 `admin_digest_enabled` —— 一个没有任何代码读的配置开关是一句谎话。

**转岗复核 PR3 · 差异引擎(2026-09-05,迁移 611 / 0 新端点 / 0 MCP):** 补上这件事从一开始就缺的那个计算。三个信号刻意不平等:`policy`(转入岗位配置好的开通策略,唯一默认解锁推送的 —— 它是意图声明不是观察)/ `provenance`(在刚离开的部门时开的 —— 不是「他该有什么」的意见,是「他为什么会有」的事实)/ `peer`(新同事分布,只排序不指控)。🔴 **`policy_universe` 是让 policy 安全的那条规则**:策略只能就它本来可能被写到的持有物说话,否则一个只提到 3 个坐席的部门会把转岗者携带的另外 37 样东西判成「不在新岗位策略里」—— 存量数字换身衣服又回来了;空策略表 ⇒ 零判定 ⇒ 推不出去,由构造保证。部门历史从 `role_change` 账本反推且**在前端开口**(比账本更早的授予是 `unknown` 而不是归给最早那个部门)。七种判定只有两种计数,其余五种存在的理由是让页面区分「查过了没问题」和「没有信号能说话」—— `coverage` 一起存下来,因为 delta=0 且 coverage=0 与 delta=0 且 coverage=0.8 是两个结论。dev 实测:同一份包裹从「16 项待判断」变成「这次转岗让 1 项需要重新判断(名下共 16 项)」。**后续 PR4**:重新启用的闸、通知分类拆分、收件人收窄。

**转岗复核 PR2 · 控制台产品化(2026-09-05,0 迁移 / 0 新端点 / 0 MCP):** 这一页最宽的两列此前是 `JSON.stringify(before/after)` —— 一个部门**代码**加一个**裸 UUID**,而 9/5 那 8 张卡唯一动了的 manager 轴就藏在里面。现在服务端把变动解析成三根具名轴(部门 / 汇报给 / 职级),`changed` **按原始值算而不是按标签算**(改名后两个不同 code 会渲染成同一串字,按标签比会把唯一动了的轴藏起来),部门标签优先取包裹里存的当时路径。新增基线覆盖率横幅:把开关状态和「0 条策略 / 88 个有人的部门」放进同一句话 —— 「推送是关的」单独看像是谁忘了打开,第二个数字才让正确的动作(去配策略)可见;没基线时开关在前端就是禁用的。补上此前完全没有的三列:复核人、复核进度、**通知三态(已通知 / 已拦下 / 待通知)** —— 中间那个是重点,一份被刻意拦下的包裹和一份只是还没轮到的长得一模一样。标题从「转岗与云资源复审」改成「转岗权限复核」(包裹覆盖 24 类,云授权只是其一)。**后续 PR3–PR4**:差异引擎 → 重新启用的闸与收件人收窄。

**转岗复核 PR1 · 推送停掉,落地页补上(2026-09-05,迁移 610 / +2 端点 / 0 新 cron / 0 MCP):** 用户问「这个是什么逻辑、开关在哪、是不是只要岗位有变化就推送」——查生产后答案是:`provision_policies` **0 行**,平台没有任何「某个岗位该有什么」的基线,唯一算差异的 `preview_role_change` 恒得「回收全部」且**返回值根本没被接住**,于是复核卡报的是这个人的**存量**不是这次转岗的**增量**。9/5 09:30 真发出去 9 张卡(CTO 5 张共 115 项、CEO 1 张),八张的两侧部门完全相同、只有汇报线变了;223 个 item **0 次决断**;而卡片 CTA 指向 admin-only 路由,4 个收件人里 3 位只有 `employee` —— **被打扰的人恰恰是打不开的人**。本轮:两条推送 cron 停掉并固化;积压盖 `notify_suppressed_at` 戳(重新启用不会把积压一次轰出去);`platform_settings['role_change.review']` + `baseline_source`/`delta_items` 两列让「**没有基线,就不要推送**」成为发送路径每次都要过的不变量(打开 `scheduled_jobs.enabled` 不足以触达任何人,peer 同侪分布刻意不解锁推送);`/admin/role-changes` 换成调度器,已投递的死链对普通员工重定向到新的 `/me/role-change-review/:id`(移动端同名页 + deep-link 映射),侧栏与「我的待办」补上入口。
**转岗复核 PR1 · 推送停掉,落地页补上(2026-09-05,迁移 610 / +2 端点 / 0 新 cron / 0 MCP):** 用户问「这个是什么逻辑、开关在哪、是不是只要岗位有变化就推送」——查生产后答案是:`provision_policies` **0 行**,平台没有任何「某个岗位该有什么」的基线,唯一算差异的 `preview_role_change` 恒得「回收全部」且**返回值根本没被接住**,于是复核卡报的是这个人的**存量**不是这次转岗的**增量**。9/5 09:30 真发出去 9 张卡(CTO 5 张共 115 项、CEO 1 张),八张的两侧部门完全相同、只有汇报线变了;223 个 item **0 次决断**;而卡片 CTA 指向 admin-only 路由,4 个收件人里 3 位只有 `employee` —— **被打扰的人恰恰是打不开的人**。本轮:两条推送 cron 停掉并固化;积压盖 `notify_suppressed_at` 戳(重新启用不会把积压一次轰出去);`platform_settings['role_change.review']` + `baseline_source`/`delta_items` 两列让「**没有基线,就不要推送**」成为发送路径每次都要过的不变量(打开 `scheduled_jobs.enabled` 不足以触达任何人,peer 同侪分布刻意不解锁推送);`/admin/role-changes` 换成调度器,已投递的死链对普通员工重定向到新的 `/me/role-change-review/:id`(移动端同名页 + deep-link 映射),侧栏与「我的待办」补上入口。**后续 PR3–PR4**:差异引擎(policy / provenance / peer 三信号,先算不推)→ 重新启用的闸与收件人收窄。

**访问对象闭环 PR1 · 撤销要落地(2026-09-05,迁移 603 / +7 端点 / +2 cron / +6 MCP → 597):** 五路全生命周期审计后选定的下一块最低分的板 —— 访问对象「撤销只在 Helix 里发生」。SSO 撤销现在开一条**必须带凭证才能完成**的 IdP 侧移除任务给系统负责人,每日按 3/7/14 天阶梯催办、14 天开合规事件,并用 Google Admin Reports 的 SAML 登录审计核对(撤销后仍有登录 → `contradicted` 重开任务;确认满 7 天无登录 / 账号已不存在 → `verified`;应用名对不上 → 记「无法核实」不猜);历史撤销标 `unverified_legacy` 由管理员批量开任务,不做推测性回填。API Key 所有权变更(含离职强制转移)即入队轮换,不可轮换的提供方发「需手动轮换」卡给新持有人 + IT;新 cron `apikey.idle_check` 每月问「上月 $0 的 Key 还要吗」,IT 月报始终发、员工那张卡按 2026-09-05 决定**默认关、逐类灰度**。半年度自证里 `sso_grant` / `api_key` 的 `manual_required` 死路改成真的走撤销 / 释放流并镜像结局。资产侧那批缺口已交给并行会话(见 CHANGELOG 2026-09-05)。**后续 PR2–PR5**:数据访问授权与权限目录进生命周期 → 离职 T+1 安全网按 `RES_*` 注册表驱动 + 账号终态保障推广到名册快照 → 多厂商使用信号与闲置扩面 → 自证的 AI 建议与后果。

**访问对象闭环 PR2 · 数据集授权进入生命周期 + 权限目录站内审批(2026-09-05,迁移 606 / +16 端点 / +2 cron / +13 MCP → 613):** DAX 个人授权盖 180 天到期(可自助续 2 次,之后重新申请;管理员可延长),服务账号授权由技术负责人半年度确认(确认推钟 / 拒绝回收 / 宽限后按策略回收),到期与逾期一律走 DAX 自己的 revoke → apply-absent reconciler;员工推送与自动回收四个开关默认关、自动回收拒绝在推送关着时打开(2026-09-05 决定①③)。权限目录 `requires_approval` 从死路变成站内一键批:新状态 `pending_approval`,软件负责人 / IT 联系人(无则 IT)批准可给有效期,`expired` 由每日 sweep 真正落下(决定②)。离职 T+0 / 转岗复核包 / T+1 残留 / 半年度自证四处补齐 `data_access_grant`。新页「数仓 ▸ 授权清单与生命周期」+ 我的待办两个新 tab。**后续 PR3–PR5**:离职 T+1 安全网按 `RES_*` 注册表驱动 + 账号终态保障推广到名册快照 → 多厂商使用信号与闲置扩面 → 自证的 AI 建议与后果。

**访问对象闭环 PR5 · 自证的 AI 建议与后果(2026-09-06,迁移 621 / +2 端点 / +1 cron / +1 MCP → 626):** 半年度访问自查此前有两个洞:持有人面对几十行厂商内部命名,没有可以据以判断的东西,于是人人全留、复核产出证据而不改变任何东西;而没人回答的条目一律盖成 `no_response`、权限原样留着,半年后同一份清单再来一遍。前者用 PR4 的使用信号做成 `mover_review_ai` 同形的建议(不能编数字/不能发明类别/什么也不决定,外加一条代码级检查:「无法确认」不许被说成「闲置」);后者自动开一个补审 campaign 把决定权交给直属经理,走既有回收流水线、不自动撤销任何权限。**并补齐了经理能进的门**(自作用域端点 + 通知 + 我的待办页签)—— 只设 `assigned_reviewer_person_id` 的话,既有两条 decide 路径都门在 auditor 角色上,经理一个都进不去。**访问对象闭环 5 个 PR 至此全部落地。**

**访问对象闭环 PR4 · 多厂商使用信号 + 闲置回收扩面(2026-09-05,迁移 612 / +2 端点 / +1 cron / +1 MCP → 617):** 闲置坐席回收此前只认 Atlassian(唯一一个 Helix 会读活跃度的厂商),Google / Zoom 的付费坐席只能靠人注意到。新表 `asset_seat_usage_signals` 把「最后活跃」做成厂商无关的持久化信号,回收状态机原封不动、只换证据来源。四态 `active` / `never` / `unknown` / `unsupported` 一路带到界面,`unknown` 与 `unsupported` 绝不折叠成「闲置」;`never` 的闲置时钟从发放日起算;超期快照降级回 `unknown`,所以采集器坏掉是**停止回收**而不是乱回收。新页「软件管理 ▸ 坐席使用信号」把六个拿不到活跃度的厂商逐条写明原因 —— 「没有闲置坐席」和「这个厂商看不见」意思相反。**后续 PR5**:自证的 AI 建议与后果。

**访问对象闭环 PR3 · 离职 T+1 安全网按注册表驱动 + 账号终态推广到名册(2026-09-05,迁移 609 / +0 端点 / +1 cron / MCP 保持 615):** T+1 安全网此前手写枚举了 `RES_*` 注册表 29 种里的 14 种(VPN / AI 工具 / GitHub 组织 / 邮件组 / 各类负责人 / DAX 全没扫),且四种网络资源用的是它自己的名字。现在残留 = 注册表 × 谓词,谓词默认复用转岗复核包的那一个,差异(离职者的 pending / MDM 坐席算残留)与豁免(`software_account`)各自明写,新增覆盖闸让「加了类型忘了 T+1」直接构建失败。新增三种此前看不见的持有:对象存储桶负责人、手动指定的证书负责人(探测来的不算)、**静态邮件组成员资格**(人绑权限,T+0 在源头移除,且必须等所有权转让先落地)。账号终态保障从 Google / Atlassian 两个系统推广到每一个可回收的身份同步目标(`isync:<slug>` + 每目标快照心跳),「快照拍在离职之前」与「根本没有快照」都判 `unknown` 而不是 `ok`;页面按钮按人复用既有回收路径。**后续 PR4–PR5**:多厂商使用信号与闲置扩面 → 自证的 AI 建议与后果。

**助手四 PR 的合并后复审已完成(2026-09-03,两轮共 35 处修复,迁移 595 / +1 端点):** 四路对抗式复审(评测台 / 知识循环+安全语料 / 安全 / 网页-飞书对等),每条都自己核过才动手。最重的四条:四个带着同事原话的只读 MCP 工具被桥进了助手工具面;`/copilot/escalate` 的兜底可以用一个随机 uuid4 换成调用方自己编的对话;提示词要求的门户链接被引用检查判成捏造(阻断级,系统性压低通过率);评测回退的比较有两个假分母(部分运行的选择偏差 + error 灌水)。另有四条「点了也关不掉的队列行」和三条网页侧缺陷(canned 失败可被评价 / 页面不读服务端线程 / 补原因会把已认领的缺口翻回 open)。**第二轮对着这批修复又扫了一遍**(放宽一处收紧另一处是本仓新缺陷的第一来源),两位复审各自独立找到了同一个由修复引入的新缺陷 —— 镜像页降级产出的是一条永远接受不了、而且会吞掉后续同类发现的提案;另有「`payload_override` 能改提案目标」「重新水化会把已过期的 offer 画成剩余 60 分钟」「Redis 抖一下会藏起活着的 offer」等 13 条。详见 CHANGELOG 与 PLATFORM。

**网页 IT 求助和飞书 bot 现在是同一个助手(2026-09-03,0 迁移 / +3 端点 / +0 MCP):** `/me/support` 是 IT 求助的正门,而它一直在裸调模型内核 —— 没有 propose 工具(提交不了任何东西)、没有只读工具桥(14 个工具 vs 150 个)、没有服务端接地(会对着一篇已发布的文章说「知识库里没有」)、**没有对话记录**(管理端、知识缺口队列、漏斗、评测题库里看不到这个页面上说过的任何一句话)。这些洞每一个都被诊断和修复过一次,修在当时正在检修的那个渠道上,第二个面静悄悄保留了旧行为。现在两侧共用同一条 turn;三路接地抽成 `build_preflight`,「offer 送到哪」抽成 `offer_delivery`(那一寸就是网页没有写路径的全部原因)。⚠️ **新的确认端点拒绝 PAT,全平台只有这一个端点这么做** —— proposal 的意义是「一个人读过再点头」。⚠️ **上下文改归服务端**:页面只发一句话,历史按人存在 Redis 里;以前页面把整段对话回传后端,在有了写路径之后那是一个可以往助手嘴里塞话再据此行动的位置。

**飞书助手第一次答得了安全制度问题(2026-09-03,迁移 594):** 答案库的 71 条是客户安全问卷口吻,员工问的是「我能用个人电脑写公司代码吗」——员工端的安全 FAQ 页为此做了混合检索,而助手那一侧对答案库的引用一直是 0 处,于是它给一件公司早就写下来的事开工单。现在 `search_security_policy` 装在**共享内核**上,网页 IT 求助和飞书 bot 同时拿到;工具严格只读、只返回条目与是否覆盖。新增 `employee_phrasings` 列(员工的原话),接进词项打分 / 整串包含 / 相关性排序 / 嵌入文本**四处**,少接一处等于没接。答案库同时接入知识循环,但**只接受「补员工问法」一种提案** —— 缺少某个立场的答案本身要由安全团队撰写并复核,一份生成的安全立场草稿是这整套东西能产出的最危险的东西。

**知识循环已上线(2026-09-03,迁移 593 / +1 cron / +4 MCP → 584):** 知识库此前只会长大不会变好 —— 每个新知识来源产出的都是一篇全新草稿,没有任何东西会更新已有条目、合并重复、补上员工真正用的那个词。这正是那条已知召回天花板(语料写「电脑」从不写「笔记本」)背后的机制。现在有了一个提案队列(core 拥有,安全答案库将复用)、知识库的目录与互链、工单结案的 ingest 回写、每周四趟自检(重复 / 冲突 / 缺词 / 失效关联),以及把评测失败提回语料的通道。**所有机器改动一律待人工确认**,不自动改任何已发布内容。**两条读法上的硬要求**:相似度只决定「给不给模型看」、从不单独产出提案(向量分不清「同一件事」和「同一件事的反面」);`PAIR_SIMILARITY_FLOOR` 目前没有量过,第一次真实运行的接受/拒绝比才是该拿来调它的。

**助手评测台已上线(2026-09-03,迁移 592 / +1 cron / +4 MCP → 569):** 飞书 IT 助手的质量此前只有两个信号 —— 员工按的 👎,和有人偶尔手打一句话试试 —— 两者都不可重复、不可比较。现在有了固定题库 + 以模拟员工身份走生产路径的干跑 + 确定性检查与模型评委两半判定 + 与上一次运行的逐条对比,周一 03:30 自动跑一遍并在明显回退时点名是哪几条用例。**读法上有两条硬要求**:通过率看变化不看绝对值(评委与被评是同族模型,绝对值偏松),以及每条题至少问 3 遍(界面推 6 遍)—— 一遍出来的数是样本量为 1 的估计,而且完全看不见「时好时坏」的那些用例。**已知基线为不及格的一块被刻意钉在报表上**:安全制度类问题(「我能用个人电脑写公司代码吗」)—— 安全答案库是另一套语料,当前没有接进助手的工具集,后续 PR 处理。

**GitHub 组织域上线第一阶段(2026-08-29,迁移 566/567):** 平台此前完全不知道谁在我们的 GitHub 组织里 —— 「申请加入 GitHub 组织」的飞书审批走完后是两个**人工办理**节点(有人去点邀请、申请人去点接受),于是**离职流程从未回收过任何一条组织成员资格**。本阶段只立地基、不碰 GitHub API:五张表(组织登记 / 成员台账 / 只追加事件流 / 申请单 / 自动加入规则),5 个组织连同飞书表单选项值一并播种;新增第 11 个规范角色 `github_admin`(研发侧 org owner 本人,GitHub 域内读写、域外零权限,迁移只建角色不授予人);`/admin/github` 管理页(组织登记 + 成员台账两个 tab);3 个 MCP 工具(总数 533 → 536)。台账的自然键是 `(org, handle)` 而非 `(org, person)`,「GitHub 上有、Helix 认不出是谁」因此是一行正常记录、一个单独的 KPI —— 那正是自动回收够不着的那部分。**驱动层已完成(2026-08-29,第二阶段,无迁移)**:新 driver kind `github_org`,**GitHub App(RS256 JWT → 每组织 installation token)与 PAT 两种鉴权都实现**,凭证方式因此不再是需要提前拍板的问题;邀请 / 移除 / 成员 / 待接受邀请 / 团队 / 取消邀请全套;「这个组织能不能自动操作」收成一个入口(`resolve_org_driver`),结果直接出现在组织列表每一行上。**申请链路已完成(2026-08-29,第三阶段,迁移 569)**:自助入口搬进 Helix(`/me/github`),但**审批仍在飞书**(复用现有定义 `AAF99056-…`,widget id 存 `payload_schema` 不写代码);Helix 发起与飞书直接提交**两个来源共用一个执行分支** —— 少了外来那一半,过渡期从飞书提交的人会静默拿不到自动化;执行有**四种结局**(邀请已发 / 本来就在 / 转人工 / 失败),其中「没配凭证」是转人工不是失败,而失败**不落台账**;新 cron 每 4 小时对账邀请状态,过期自动重发并通知本人,拒绝则不重发。**自动加入规则已完成(2026-08-29,第四阶段,无迁移)**:部门按**子树**匹配(`Engineering` 命中 `Engineering / TiDB` 但不命中 `Engineering Support`);「命中」离「进组」隔着三道闸(组织总闸 / 人员资格 / **有没有登记 GitHub 用户名** —— 生产上在职员工只有 63% 有),缺 handle **不是跳过而是一件待办**(单独计数 + 审计 + 通知本人去补);入职钩子挂在 `_dispatch_lifecycle_coordinators` 与 `hire_day` 重放**两处**,少一处就漏掉所有未来日期入职的人。**离职回收已完成(2026-08-29,第五阶段,无迁移)—— 这是整件事最初要修的那一半**:新资源类型 `github_org_membership` 进 T+0 立撤桶(不吃 24h grace);一条成员资格一行 blocker;豁免的社区组织与「认不出是谁」的记录刻意不入队;预览与实际逐条对齐;**驱动失败绝不标已回收**,没配凭证时抛出并带上真实原因(静默会让 IT 看到一个没有解释的 critical 事件)。**双向对账与存量导入已完成(2026-08-29,第六阶段,迁移 570)—— 至此这条链路完整**:每日对账两个方向(GitHub 上有 Helix 没有 / Helix 说在 GitHub 说不在),顺带回填 `person_id`(**注意它只救得了以后补登记 handle 的人** —— 实测存量的 217 个认不出的 handle 里,能靠字面对上企业邮箱前缀的是 **0 个**,那批要靠第八阶段的画像 + AI 建议清);带一道**空列表刹车**(GitHub 说 0 个成员而台账有 ≥3 条活跃记录时拒绝对账 —— 那张表正是离职回收读的);存量导入与例行对账只差一个 `apply_removals` 开关。**GitHub ID 自助修改已完成(2026-08-30,第七阶段,迁移 579)—— 补的是上面反复出现的那个 63%**:`identity_persons.github_id` 此前只在入职采集、之后没有任何更新入口(417 名在职员工中 154 人没有 handle),而自动加入、对账认人、离职回收三处全靠它。员工在 `/me/settings` 基本信息提交 → 自己的 **HR Ops**(缺失回退 **HRBP**,两者皆无落 HR 兜底队列)站内确认 → **先写 SF、再写 Helix、再推飞书**;5 个端点 + 5 个 MCP 工具(546 → 551),桌面「我的待办」`?tab=githubId` 与移动 `/m/github-id/approvals` 两面都能决定。**技术核心是 `identity_persons.github_id_locked_at` 这道防回滚闸** —— HR 同步对该列是 write-when-present,没有闸的话「Helix 改了、SF 还没落地」这个中间态会让下一次同步把员工刚改的值静默改回去;SF 报回同样的值时闸自动清掉。SF 那一跳的 payload 形状(是否 effective-dated / 能否新建 / 还有无必填字段)做成 `platform_settings.hris.github_writeback` 配置,因为答案在 HR 的 SF 管理员那里而不在我们这里。

**handle 标签体系已完成(2026-09-01,第十一阶段,迁移 586 / +5 端点 / +2 MCP)**:585 那个「两值枚举 + 自由备注」不是标签体系 —— 枚举互斥(一个 handle 可以既是「ByteDance 的人」又是「社区 maintainer」),自由文本必烂(三个人写 `ByteDance`/`字节`/`bytedance 的人`,「筛出 ByteDance 的人做年度复核」就永远做不成)。**而 585 还把层级挂错了**:实测 217 个认不出的 handle 摊在 331 行上,`sre-bot`/`ti-chi-bot`/`crazycs520` 各在 5 个组织里 —— 「这个 handle 是什么」是 handle 的属性,挂成员资格上意味着标一次写 5 行、5 行会漂移。趁**已确认外部=0、备注=0**(改模型免费)改成 handle 级词表 + 多对多,四个分类各回答一个不同问题(`account`/`employer`/`community`/`purpose`,播种 11 个 —— 空词表没人用)。规则从「只建议 bot」推广到「可建议任意标签」,仍然只建议不落库。顺手消除 585 与 586 两个策略存储的重叠(bot 策略改为从标签规则派生,否则同一问题两个界面两个答案)。停用不删除 —— 「当年被标成什么」是复核要回看的。**同日补齐台账接线(+2 查询参数,0 迁移)**:586 只把标签交付到治理面的建议流里,成员台账那一页 `grep -c labels` 是 0 —— 而「把 ByteDance 的人筛出来做年度复核」这个动作发生在台账上,筛不出人的标签体系等于备注。现在 `GET /github/memberships` 支持 `label=`(可重复,或语义)与 `unlabelled_only=`,台账每行显示标签并内嵌打标签浮层(按 `can_manage` 渲染)。两个筛选都是 `EXISTS` 子查询而非 join —— join 会按命中标签复制行,把 `total` 和分页一起弄错。**标签规则可启用(2026-09-01,迁移 588 / +3 端点 / +2 MCP → hint 565)**:规则本来一直只**建议**;新增 `enabled` 决定它是否自动落库(每天成员对账尾部跑,不新增 cron)。AI 基于「未打标签 handle 上 company 字段的实际取值分布」提议规则,**一律 enabled=false**、带 rationale,启用由管理员在界面上点 —— 而开关旁边显示「这条规则现在会命中多少个 handle」,因为没有那个数字它就是个盲开关。`account:*` 永远不能启用(终态,已知反例 `robot-liu`)。自动应用只加不减,且不碰人已标过的 handle。MCP 刻意不提供「启用」工具。

**机器账号分类已完成(2026-09-01,第十阶段,迁移 585 / +3 端点 / +1 MCP)**:「已确认外部」拆成 `bot` / `outside_human` 两类 —— 别家公司的**人**持有访问权(复核问「他还需要吗」),机器账号问的是「这个自动化还在用吗、token 谁管」,混一格就都答不了。**关键发现:GitHub 自己的 `type` 字段用不了** —— 7 个机器账号实测全报 `type=User`(`Bot` 类型只给 GitHub App 身份,而这些是人建的普通账号当机器人用)。所以只能靠命名约定,而命名约定会误伤真人(`robot-liu` 命中 `*robot*`),**因此策略只建议+批量,写入永远人确认,没有自动 cron**;有一条测试专门断言 `robot-liu` 会被命中,好让将来想加自动标记的人先面对反例。策略存 `platform_settings`(空策略被拒 —— 静默零匹配和「没有机器账号」看起来一样)。顺带:坐席链接改为 `/settings/billing/seats`(加坐席按钮不在账单总览页);`github_admin` 增至 3 人。

**总览报表已完成(2026-09-01,第九阶段,0 迁移 / +1 端点 / +1 MCP)**:「待处理」回答今天要做什么,「总览」回答我们把这件事做成了吗。**这一页的重点刻意不是成员总数** —— 1016 这个数几乎不动,说明问题的是构成:`import` / `reconcile` 是 Helix 上线时和每日对账**发现**的存量(证明的恰恰是在此之前没有任何流程管过),只有 `approval` / `auto_join` 才是自动化真的交付的。生产现在是 **0% automated**(1016 条全是发现来的),这个比例往上走多少才是真实进度。趋势读只追加的事件流而非当前状态倒推(否则「进来过又走了」会被算成从没发生过);空台账时覆盖率是 `null` 不是 0%;口径与治理面强制一致并有测试钉住(还没认出=`count_unlinked`、离职未回收=`count_leavers_still_in_orgs`、坐席走同一个 `seat_remaining`)。刻意不做「健康分」这类合成指标。

**治理待办已完成(2026-08-31,第八阶段,迁移 583)—— 补的是「说得出问题」和「有人能处理」之间那一段**:上线第一天 `leavers_still_in_orgs` 就是 1(一个已离职的人还在 `tikv` 里)、`pingcap-inc` 的坐席只剩 3 个(260/263),而这两件事只活在台账和一张 KPI 卡上 —— **没有任何东西会去告诉该处理的人,也没有任何地方能点一下处理掉。** 新增「待处理」tab(现在是这一页的默认 tab)+ 每周一 09:30 的摘要(`github_admin` + `global_admin`),四类待办各配一个真能动手的按钮:一键移除(走和离职回收完全相同的执行路径)、**带账单页直链的加坐席**(加坐席只能在 GitHub 做,Helix 能做的是把人直接送到那一页)、去 GitHub 手工邀请、关联到人。**这一阶段最关键的改动是数据把原设计否掉之后才出现的**:「认不出是谁」原本只有「关联到人」一条出路,而抽样拉 GitHub 个人资料发现 30 个里有 12 个的 company 写着 ByteDance / Linux Foundation / 高校 —— 他们本来就不是员工,永远认不出来,按原设计那 331 条会永久躺在待办数里。所以新增「**确认是外部人员**」这个终态,让这个队列**能收敛**(仍留在台账里,只是不再算待办 —— 离职回收对不在 HRIS 里的人本来就不适用)。AI 认人是**画像优先**:实测 217 个 handle 靠字面 0 命中,而抽样 30 个画像里 21 个有真实姓名、10 个有公开邮箱(其中一个直接是企业邮箱);管线是富化 → 打分 → AI 在给定候选集内复核 → **人确认**,**任何置信度下都不自动落库**(挂错人的后果是那个人离职时**别人的**访问权被撤)。坐席只信有真实上限的计划(`free` 报余量 -111、`enterprise` 报整个池子,都是假数)。+6 端点 / +1 cron / +5 MCP(551 → 556)/ 0 新角色。

**整体复审已完成(2026-08-29,三轮收敛)**:抓到并修掉 3 个真缺陷,都是「两件语义不同的事共用了一个表示」——「他有访问权了吗」被按 handle 问而不是按人问(换了 GitHub 账号会被重复邀请);「邀请已发出」和「等人工去发」共用 `pending_invite`(前者会在 7 天内被误判成不可重发的 `declined`,后者会被对账读成「上游已移除」)。顺带补了一个缺口:**已离职的人还在组织里,此前没有任何地方会喊**,现在它是管理页最醒目的那张 KPI(应恒为 0)+ 逐条 failure 审计。安全扫描与逻辑扫描各 10 条新测试,GitHub 域共 146 条。

**GitHub 组织自动化整体已交付**:自助申请 → 飞书审批 → 自动发邀请 → 提醒接受 → 过期重发 → 部门规则自动加入 → 离职自动移除 → 每日对账。**专用 GitHub App 已就位(2026-08-31)**:App ID `4778566`,只勾 `Members: Read and write`,已装到 5 个组织(刻意不复用 ti-chi-bot —— 它同时持有 administration/contents/secrets 写权限);实测可读坐席(pingcap 余 22 / **pingcap-inc 余 3** / tidbcloud 余 46;tikv 与 PingCAP-QE 计划无真实上限,正确地报 `null` 而不是假数)。**飞书侧已于 2026-08-31 全部改完并发布**:① 人工节点已删除:3 个 `Execute` + 1 个 `Accept the invitation`,节点数 16 → 11,API 复核 5 个 org owner 审批节点与 Helix 依赖的 widget/option 全部保留,在途单子不受影响(飞书按定义版本快照跑在途实例)。至此审批链走完不再卡在人工上。② **发起入口已关闭**(`谁可以提交该审批` 由「全员」改为「均不可提交」,列表页显示「所有人不可发起」)—— 这个申请现在只能从 Helix 发起。关闭入口本来卡在一个没验证过的问题上:Helix 是**以申请人身份**通过 API 建单的(`create_instance(user_id=申请人)`),收窄提交范围会不会连带拒掉 API 建单?原计划在生产发一张测试单验证 —— 后来在审批列表页拿到了**更强的现成证据**:同租户下已有 **10 条 Helix-API 建单的流程本来就设成「所有人不可发起」并正常工作** (`AI API KEY Apply` 的描述里就写着「IT平台来调用生成的,不会手动创建的」;还有 `软件坐席申请` / `SSO Apply` / `域名DNS解析申请` / `Authing Apply` / `电脑归还申请` / `电脑更换申请` / `开通云账号(项目)申请` / `全员公告发送权限申请` / `Application for Introduction of AI Function/Products`)。既然它们能工作,就不必再发测试单。⚠️ **改这条流程时踩到一个会毁掉线上定义的坑**:飞书审批编辑器打开时会弹「是否打开上次编辑的草稿」,而在这个弹窗被响应之前,**表单是空的、标题显示「未命名审批」、`谁可以提交该审批` 显示成「指定人员」**。此时若直接点「发布」,就会把一个空定义发布到一条有 16 张在途单子的线上流程上。正确做法:先点「放弃草稿」,**确认名称 / 分组 / 流程管理员三处都已填充**再动任何东西;发布后一律用 `GET /approval/v4/approvals/{code}` 复核节点数与表单控件,不要只信「审批更新成功」那个弹窗。**离职策略也定了(2026-08-31)**:~~`pingcap` / `tikv` 是否改 `exempt`~~ —— **不豁免,五个组织一律离职即回收。** 那个具体案例(`liucong@pingcap.com` / handle `innerr`)已按此清理:GitHub 侧已移除,并用三个独立端点交叉复核 —— **DELETE 返回 204 之后立刻 GET 仍报 `active`,8 秒后三个端点才一致说不在了;GitHub 读副本有延迟,即时回读不是验证**。台账由每日对账跟上。③ **给 Helix 的 SF API 用户开 `cust_Other` 的写权限**(2026-08-30 提出,两个正式租户 api30.sapsf.cn / api55.sapsf.eu 都要),并回答三个问题:该 MDF 对象是否 effective-dated、能否为没有该行的人新建、还有没有其它必填字段 —— 三个答案都是`platform_settings.hris.github_writeback` 的一次配置修改,不需要改代码。**权限到位前,自助修改仍然可用**:变更在 Helix 与飞书正常生效,SF 那一跳记 `hris_write_status='skipped'`,防回滚闸保证它不会被下一次同步冲掉。注意开关**默认关** —— 拿到写权限之后还要显式打开一次,因为 effective-dated 的 MDF 对象缺生效日期时可能不是报错而是写出一条错的时间片。

**我的 IT 宇宙·完整清单（2026-08-30）:** 日常员工视图接入转岗复核的全量持有物目录，覆盖访问、责任和设备三种关系；与离职词表同源。完整清单由按需的 `GET /users/{id}/holdings` 读取，绝不把故障当作空数据，也不让日常的 IT 总览/关系图为一次全量生命周期扫描付出延迟；两条读面共用同一目标解析、RBAC 和协调器，不另建资源表、扫描器或流程。飞书助手同步获得自身范围的只读入口，可解释“我有什么 / 我负责什么 / 变动会影响什么”，动作仍走既有确认与审批。

**DAX Owner 离职交接（2026-08-25，本地实现）:** Helix 离职流程完成时复用既有终态交接人解析（流程交接人→经理链→平台兜底），在同一事务把离职者当前持有的非 deleted 数据集与数据表 Owner 改为交接人；suspended 资源也交接，deleted 历史保留。只有确实持有 DAX 资源却解析不到在职交接人时才阻断完成，并在人员变为 `left` 前失败关闭。申请/审批快照、binding/effect/revoke 与 BigQuery IAM 不回写；无迁移、API、MCP、RBAC 或 Provider mutation。

**DAX Owner 自助转让（2026-08-25，本地实现）:** 数据集与表都改为「当前 Owner 只能转让自己拥有的资源」；数据集转让原子带走继承型表，独立表 Owner 保持不变，目录刷新不覆盖人工转让。新增 dataset owner PUT + MCP twin（总数 519），表接口/MCP 原名兼容；无新 RBAC、迁移、Provider IAM 或既有授权回写。

**资产权限改挂受管位置（2026-08-24，迁移 535–540，已合并待发布）:** `asset_admin` 角色与资产域全部区域写闸的事实源从 `asset_office_admins` 改为新表 `asset_location_admins`；资产→区域从四层推断阶梯塌成两层直查（`asset.location` → 持有人 HR 工作地，同一个 `ManagedLocationResolver`）。办公室上的管理员名单保留但收窄为「当地 Admin」（表改名 `asset_office_local_admins`），只服务 Day-1 欢迎信抄送、入职硬件收件人、离职交接建议三条人锚定流程，**不授予任何角色**。位置字典与管理员名单的写权限收窄到 `global_admin` + `asset_global_admin`（名单即授予集，留在区域层就是自我提权路径）。位置管理页重做为权限配置面：覆盖率统计、缺口位置高亮与点名、管理员多选 + 显式主管理员。两根反规范化列（`asset_location` 桥接列、`admin_person_id` 镜像列）分别由 536 / 540 删除，对外契约不变。区域覆盖 280/416 → 343/416 名在职员工，零回退。

**DAX Provider apply 目录单一事实（2026-08-24，本地已实现、待发布）:**
`service_account_apply_dataset_allowlist` 已从运行时合同移除。2026-09-07 代码进一步退役 principal
allowlist（待发布），保留全局 apply 与 SA apply；present effect 在 reconciler 写 Provider 前重读当前资源，只允许
active/requestable/owner-resolved 的 dataset/table。Absent/revoke 不受资源下架阻断，保证已授权限
仍可收回。遗留 dataset gate 配置将被忽略并在下一次 integration apply-mode merge 时清掉。

**Test Estate Reset 第二批（2026-08-22，2026-08-24 重基）:** 继续按风险清理后端管理读模型测试，
删除 53 个围绕列表、搜索、导出、分析、AI 辅助呈现和移动聚合的 `test_*.py`。相对
`main@e564df053`，测试文件从 1,181 降到 1,128，物理行从 404,347 降到 394,489
（净减 9,858），静态测试函数从 10,754 降到 10,502，pytest 收集场景从 12,522 降到
12,260。两批累计净减 24,931 行。写路径、对象级 RBAC/隐私主套件、审批 callback/intent、
Provisioning dispatcher、Support 生命周期/去重、JML、DAX/Provider、金额计算、迁移/TiDB 与
全部前端测试继续保留；被删除读模型不再承诺逐筛选/逐导出字段的回归保护。

**Test Estate Reset 第一批（2026-08-21）:** 在微服务拆分前按业务风险重置后端测试资产：
Arcade、Gamification、Security Quiz 等低风险功能整包退出，Dashboard、Feed、Assistant、
Intelligence、Me/Team 只删除展示、历史 review、重复编排和预测/读模型型保护；44 个分散的
路由认证/员工拒绝测试收成统一 L0 权限矩阵，9 份重复 DAX Provider 写入边界扫描收成 10 文件
参数合同，四组同形 schema/时间/widget/object-key 用例改为参数表。相对
`main@a7801f65`，`test_*.py` 从 1,247 个 / 417,797 行 / 11,339 个静态测试函数 / 13,129 个
收集场景降到 1,175 个 / 402,724 行 / 10,710 个 / 12,480 个，测试源码净减 15,073 行。
RBAC 对象范围、隐私、幂等/并发、生命周期、DAX effect/readback、Provider mutation、金额/审计、
迁移/TiDB 与全部前端行为保护继续保留。

**统一流程与审批（2026-08-20）:** 管理员待办、历史列表、详情、导出和分析已共用同一审批来源注册表，覆盖资产/软件、云账号、云访问与续期、DNS、域名、VPN、AI 工具、公告、API Key 释放、身份消费方、邮件组加入、SSO、数据访问、SOC2 外部审批和离职双签。SSO 详情可追到应用创建与 IdP 配置交付结果，并在通用详情中强制隐藏 Metadata XML 与证书正文。Support 与没有真实审批链的软件权限不混入审批中心。

**IT Agent 的第二路语料 + 员工知识库（2026-08-30，本地已实现、待发布）:** 飞书服务台自带的
124 条问答已可导入 Helix（`support_kb_articles`、`source='helpdesk_faq'`、迁移 578）。与只读的
wiki 镜像不同,这是一次交接:导入之后 Helix 就是编辑面,管理员在「知识与制度 → 知识库」里
增删改查,导入本身幂等、支持预演,并可按来源/语言筛选。检索侧新增「相似问题」别名列并进入
embedding 文本;员工端词法检索改为 CJK 二元组 shingle + Jaccard 排序(拿真实语料实测:
分词匹配下中文问句返回 0 条、英文问句返回 124 里的 116 条)。员工端「制度中心」重做成「知识库」:一个搜索框同时搜制度库与已发布
知识条目,FAQ 就地展开读答案（路由仍为 `/policy-center`）。右下角 IT 小助手改为跳站内
`/me/support`,不再离开产品去飞书服务台。

**统一 IT 服务运营（2026-08-20，本次发布）:** Agent 求助、Helix 内置工单与飞书服务台并行数据统一到同一服务请求口径；运管驾驶舱使用独立“工单分析”报表，IT 支持台只保留操作队列。新生命周期覆盖消息级需求归属、终态后新需求自动开新生命周期、员工确认转人工、建单归因、首响/解决 SLA、当前责任方催办、满意度与评价覆盖、Agent 好评/差评原因、未触达人工与七日无动作放弃，以及知识缺口带处理说明关闭。迁移 527 随本次版本发布。

**数据仓库表级申请与负责人（2026-08-19，本地已实现、待发布）:** 新的个人账号与
Service Account 申请改为先打开数据集、再勾选具体表/视图；每张表默认继承数据集负责人，
数据集负责人可在同一抽屉把单表委派给其他在职员工，后续该表由被委派人审批。审批、binding、
compiled effect、reconciler 与“我的权限”均保留完整三段 locator，实际回写 BigQuery
table/view IAM；历史 dataset 级 Group/个人/SA 记录继续兼容。迁移 523、MCP 对等工具与
Terraform 最小权限已随代码准备，尚未部署或触碰生产。

**我的数据集权限（2026-08-19）:** 员工侧数据仓库新增统一授权台账，只展示由当前员工发起且
已经形成 binding 的 BigQuery 数据集权限，并明确标注当时使用的是个人账号、Google Group
还是 Service Account。列表支持按身份筛选、按数据集或邮箱搜索、查看生效/待执行/失败/撤销
状态并回到来源申请；仅因当前 Group membership 继承的访问不计为“自己名下申请”。HTTP、Web
和 MCP 共用同一 requester-scoped 读模型，不读取或修改 BigQuery。
**飞书 Multi-Geo 入职建号（2026-08-19 上线；2026-08-27 补齐兜底）:** 新员工不再用会落入租户默认留存地的 Contact v3 创建接口；Helix 在 Directory Create Employee 创建阶段显式写入数据留存地，HRIS `residence_country=CN` 写 `cn`，其余有效国家/地区写 `jp`。国家归属缺失或无效会拦截创建，避免合规默认值误落；既有员工不被同步重迁，近期错配记录按飞书 User Migration 任务逐项纠正并回查结果。**换接口时老 Contact 建号那一段连同其上的兜底一起变成了死代码**（#3271 改红测试、#3274 删除）；2026-08-27 补回其中确属回归的一条：手机号无效（41004）去掉该槽重发一次、账号照建、号码记成 dropped slot，并给建号后的投影 PATCH 加上 `allow_recreate=False`，杜绝读写传播窗口导致的二次建号。41010 与 `employee_type` 默认值刻意不补——前者在 Directory v1 上的错误码没有实测依据，后者只是 Contact v3 强制字段的权宜。

**Google SSO 申请流的返程 + IT 治理面（2026-08-18，迁移 515）:** 这条链路里 Google
自己是 IdP —— IT 在 Google Admin 手工建完 SAML 应用后,Google 生成的 SSO URL /
Entity ID / X.509 证书必须回到申请人手里,对方才能配自己那侧。此前 Helix 既没有字段
存这三项,批准路径也不给申请人任何通知,这一跳全靠 IT 记得手动发消息;忘了就悬着,
而系统里 `status=approved` + 目录行齐全看起来已完成。现在 `/admin/sso-apps` 每个应用
可以录入 IdP 配置并一键发给申请人,发送成功才盖 `config_delivered_at`(发送失败不盖章),
「IT 还没把配置发出去」因此成为可查询状态。同时补上 IT 侧治理面:新增授权台账 tab
(全员 SSO 授权列表 + 带原因回收)和 MCP `list_sso_grants` / `revoke_sso_grant` ——
这两个 HTTP 端点自迁移 101 就存在但前端从未调用过。申请时的 X.509 证书改为选填
(Google-as-IdP 不消费 SP 证书)。**仍待实测**:生产飞书审批模板是否带 ACS/Entity/证书
四个控件 —— 现在缺控件会打 WARNING 列出未渲染字段,不再静默丢弃。

**数据仓库可申请目录（2026-08-19，部分已发布）:** 仓库 SSOT 现含 23 个可申请项：此前
整理的 22 个数据集，以及经用户批准、已核验存在于 `us-west2` 的
`gcp-digital-bi.metadb_new`（负责人徐凯）。在用户明确授权下，Production 已对
`dax-bigquery-prod` 直接新增该精确 catalog resource；独立读回确认其为 active/requestable/resolved，
运行时可申请目录计数从 19 增至 20，binding/effect/effect-run 保持 `12/12/12`。另外 20 个历史
catalog identity 在仓库 SSOT 中保留但不提供新申请。每个可申请项已有基于实时 BigQuery 对象和 dbt
血缘（MetaDB 为其直接 BigQuery 元数据）的中文业务说明，写入既有
`data_access_resources.notes` 并投影为 `description` 供申请页、搜索和飞书审批共读。完整 repo
catalog seed 与 Terraform allowlist 应用尚未执行；本次仅写入 Helix catalog，不改变 BigQuery IAM。

**数据仓库表结构浏览（2026-08-20，已发布到 Staging）:** 申请页的数据集名称可打开按需 schema
抽屉：先列出该可申请数据集内的表/视图并用于精确选择，再只读取选中表的字段完整路径、类型、mode 和
BigQuery 字段说明。后端每次重查 active/requestable 目录边界，前端不持有 GCP 凭证；专用
DAX 自定义角色扩 `tables.list/get` 元数据权限和精确表 IAM 的 `get/setIamPolicy`，仍无表数据、查询作业、项目 IAM 或宽角色。
表目录采用精选投影与 Provider 按需发现：抽屉首屏只列 dbt 血缘引用及已按需物化的表，不自动读取字段；其余 BigQuery 表默认折叠，通过 opaque cursor 每批最多 20 张，仅在展开或搜索时读取粗略元数据。发现不会自动写入 Helix，只有申请或 owner 委派才物化 table resource；兼容的 `scope=all` 读路径保留。
Staging 当前故意不持有 BigQuery 凭证：Provider 元数据不可用时，抽屉只读列出镜像内已发布血缘关系、禁用申请勾选/字段结构/owner 修改并自动进入血缘页；Production 的完整表选择仍要求独立的 DAX Provider 集成。

**数据仓库表级血缘（2026-08-20，已发布到 Staging）:** 同一个表详情抽屉新增“血缘”页签，按
`原始来源 → 暂存/转换 → 展示数据表`组织选中表的上游关系。后端每次仍先验证 active/requestable
dataset，再从随 Helix 镜像发布、带 source revision 的 dbt manifest 投影读取关系；不运行 BigQuery
查询、不读表数据、不改 DAX 目录/IAM/审批。未记录在该版本 manifest 的表明确显示为未跟踪，而不是根据
表名猜测；HTTP 与 MCP 读取面共用这一边界。当前目录已由仓库固定的 `dbt-core/dbt-bigquery 1.9.0`
从 `dbt main@5139557c` 禁用 partial parse 后重新生成：全量 parse exit 0；构建时把指向同一
`project.dataset.table` 的 dbt 别名折叠为一个规范物理 relation（优先 dbt 产出的 model/snapshot/seed，
再到 source），最终为 1231 个唯一 relation / 1825 条直接 edge，重复 locator 为 0。存量未匹配
patch/test warning 仍存在，但 Fusion compatibility error 已不再作为发布阻断。
血缘中仅把规范化后仍为 dbt `source`、且实际位于至少一条依赖边上游的节点定义为外部源头；artifact
含 38 个 source 声明数据集，其中 32 个参与依赖，当前缺失的 23 个已加入 repo 目录
（另外 9 个原本已存在），默认负责人朱晓鹏，并明确 `dbt_generated=false`。用户确认被引用源数据集应
直接出现在申请页；Production Provider 回读确认 `usage_us_west2` 已不存在，因此该稳定目录身份保留为
suspended/non-requestable，其余 31 个 source locator active/requestable。与原 23 个业务范围去重后共有
47 个可申请数据集、65 个 active controller-scope 数据集。中间 model/seed/snapshot 不会因此进入目录。血缘页在图上方按源数据集去重展示
负责人，HTTP/MCP 也仅在 source 节点序列化 owner；父级为空但不是 source 的 dbt 节点标为“血缘起始节点”，
不会冒充“原始来源”。分层概览不再用通用列间箭头冒充真实连线；下方逐条展示 artifact 中精确的
`upstream → downstream` 直接依赖，因此多分支和跨层 edge 也可核对。Provider 健康时源表按既有
table-level owner/审批/IAM 路径申请；无 Provider 时静态 source relation 也可浏览，但仍禁选、无字段结构。
Terraform 项目范围显式限于 `gcp-digital-bi` 与 `gcp-digital-bi-dev`，并只管理低频关键控制面：
专用身份、每个项目内同构的最小角色、active catalog dataset 的精确 controller binding 与密钥入 SSM。
`requestable` 只控制新申请准入，不会自动删除历史授权观察/撤销所需的 controller binding；
catalog seed 与 Terraform apply 仍需各自生产门禁。客户端 DEV 密码自动登录不随功能发布。
Production 目录仅在显式手动导入时使用 reviewed inventory 和版本化 dbt lineage artifact 对账；
2026-09-09 已删除每日自动覆盖生产配置的调度。投影只从 active/requestable 的 dbt 结果数据集向上遍历，遇到
source 即停止；单独源表不会继续扩展，无法从可申请结果到达的自动派生源头退出投影，人工目录与 owner
保持不变。该手动导入不增加 `bigquery.datasets.list`、不扫描全项目、不运行 Terraform；表清单/字段仍按需
懒加载。负责人标签只显示姓名，姓名缺失才回退邮箱。

**AI 大脑 · 云订阅基线（2026-08-17）:** 所有有效云订阅每日预计算确定性健康基线；即使尚未点击 AI 生成，成员也能看到库存、监控、成本和同步每个数据源的实际准备状态与补齐方向。若预计算尚未来得及写入，首位已授权查看者会在读取时就地生成同一份可去重的确定性共享快照；这不是模型生成、不消耗 Token，后续成员复用结果。页面读取始终不消耗模型 Token。模型生成失败会按安全分类说明究竟是事实核验、模型服务、平台凭证还是隐私边界问题，明确数据是否受影响和下一步责任，绝不把系统错误含糊归因给订阅成员。
新版本发布后 worker 会立即启动一次性自动回填，生成已有订阅的第一份基线，避免每日排程已过时产生长达一天的空窗。
云订阅评分现提供逐项的可解释基线：当前事实、固定评分规则、得分、权重、可信度、数据时间、处理方向与按 RBAC 裁剪后的证据下钻均可查看；无数据明确标为不可判断，而非伪造零分。桌面与飞书 H5 读取同一公开评分合同，页面打开仍为零模型调用。

**Qwen PR review（2026-08-18，已移除）:** Owner retired the Qwen review and
review-driven auto-merge workflows, then removed the dedicated adapters,
publisher/controller, prompt/schema, tests and `go-ci` invocation. New PRs no
longer invoke a model review, publish `Qwen Review / Required`, or trigger the
App merge controller. The dedicated runner and its runner-local provider
credential are not changed by this repository removal. Historical runner
evidence is retained in [the retired review record](docs/architecture/DEEPSEEK_REVIEW.md).

**AI 大脑增量（2026-08-17）:** 离职风险与所有权连续性已接入生命周期快照，覆盖交接、特权项、逾期阻塞、接手人和关联对象暴露面；桌面/H5 经营展示均为确定性读模型。资产优化已接入需求保护后的闲置电脑盈余、未确认库存、财务数据缺口和未来需求缺口，事实版本为 `asset-optimization-v1`。每日跨域简报、建议状态审计和 ROI 读模型已接入统一事实 Hash 调度，读取不调用模型。生产已启用 `intelligence.enabled`；所有授权管理角色的默认桌面入口为 AI 大脑，首页以本期优先事项和“定位—理解—处理—复验”引导收敛分数、证据和既有工作台；每个领域均可进入独立解释工作区，逐项呈现当前事实、固定规则、得分、权重、可信度、数据时间、证据数量与处理入口。建议处理会先展示由事实引用本地构造的交接方案：涉及领域、跨域范围、处理次序、审批边界和新快照复验标准都可见，再进入既有工作台；模型不能决定 URL、审批人或写入参数，历史交接单也会安全升级。处理后列表会将交接基线与最新同受众快照做只读比对，清楚显示等待同步、事实未变或事实已变但仍须复核，绝不把快照变化误记为建议已解决或实际节省。每日首次出现可信度足够且领域分数落入高风险档的新确定性快照时，系统会通过 Outbox 向 IT / 全局管理员发送一张聚合通知；模型的叙述和优先级不会触发自动通知，同快照按收件人去重。已启动的云资源回收/配置建议会用匿名证据在本地精确关联现有云效率实际节省事件，每个事件只归因一次；ROI 实际金额继续由 CMDB 成本账本实时自校正，页面单独标明自动核验来源。订阅级 AI 分析位于详情顶部 Tab，并明确展示可信度、口径、处理路径与证据下钻。订阅资源效率现优先使用云同步的账号/订阅/项目/资源组归属，账单资源 ID 仅作回退，避免将已有机器误标为数据不足。生成层现按 subject 明确注册缓存、冷却和合同；任何模型调用即使在后续异常失败也保留 Token 审计。花钱的权限已与只读权限分开（2026-08-18）：订阅 AI 分析的生成需要「云域写权限或与该订阅有真实关系（owner / Azure 父级 owner / 有效授权）」，成员共享按点击调用不变，而只读全量订阅的 auditor / finance / management 不能在无关订阅上扣配额；四个 HTTP 写操作补齐平台审计行；建议状态机有显式跳转表；失败的生成尝试计入冷却。飞书 H5 的 AI 大脑已与桌面建议闭环对齐（2026-08-18）：全部写操作走 `useMobileMutation` 的重入闸与幂等键，生成失败按错误码分流成六种独立结论（冷却与额度透传服务端剩余窗口），建议列表读全量并可展开（不再有永久不可见的第 4 条），ROI 分开呈现人工填报与已核验云节省，交接抽屉补齐跨域提示/涉及领域/验证方式，并新增通知管理员、强制刷新（需确认，花模型钱）、带原因忽略与解决时填报实际收益。读取仍为零模型。确定性评分口径升至 **`health-v2`**（2026-08-18）：修正 `lifecycle.leaver_failure_rate`（旧判据把「重试过但已解决」的 blocker 与常态在途的 `blocked` 状态都算成失败，生产 78 单窗口误报 12 单、真实未解决失败 0 单，且随重试历史累积单向恶化），并退役永久取不到数的 `lifecycle.transfer_recovery`（唯一信号源 `provisioning_tasks.source='role_change'` 生产历史 0 行），其 15% 权重重分给 `day1_ready`/`leaver_failure_rate`/`owner_continuity`。生产复算 lifecycle 39.41 → 65.00、域可信度 85% → 100%、平台总分 60.16 → 64.00；历史 `health-v1` 快照原样保留，不回填不重算。同日再升 **`health-v3`**：资产维度六个指标里三个是取数缺陷 —— 「盘点准确率」把未回复条目当成记录错误（关闭盘点会把剩余条目冻结在 pending，等于永久钉死），闲置时钟读 `updated_at` 而「回复盘点」这个动作就会把它清零（07-14/15/21 三天 134/108/87 行被更新、其中 134/104/85 行是盘点回写，当天真实资产操作只有 3 条），并退役从无生产者的 `assets.warranty_coverage`（全表 1,099 行 0 行写过、前端无录入控件），.15 权重与其余四项拉平为各 .20。生产复算资产域 59.00 → 70.00、域可信度 100% → 98.68%。同日第三轮（同属 `health-v3`，落地时尚无 v3 快照，故版本含义自首行起一致）：`lifecycle.onboarding_sla` 从读当前态 `sla_breach_level`（终态一律 `none`、blocker 清零即回 `none`，测的是「此刻没变红」）改为按已上线的 72h 判历史达成，分母只取 SLA 窗口已关闭的入职单，生产 67.57% → 45.71%（停止虚报，分数不变）；同批修正三处 software_api 取数：`key_expiry_coverage` 分母收敛为 Helix 自己签发且在用的凭据（剔除控制台存量纳管 Key 与已批未签发申请，分子改为「有有效期」）24.59% → 80.00%，`reconciliation_coverage` 分子加上已备案保留的外部持有者（与对账面板 `_netted_categories` 对齐）89.38% → 92.80%，并新增 `over_recorded_quota_plan_count` 事实标出配额记录过期的档位。生产复算 software_api **67.00 → 75.50**、lifecycle 65.00 不变。**资产域剩下的也是真运维债**：145 台电脑最后一次真实流转在 90 天前（扣掉近期入职需求后 132 台净盈余，需要真人再分配/调拨/处置），762 台缺采购成本或币种需要补录。**未修的是真运维债**：`day1_ready` 16.22% 与 `capacity_headroom` 72.73% 是真账 —— 前者由 12 个未完成入职驱动，全部只卡 `hardware_allocation`（这 12 人在资产台账与 Jamf 里都没有任何设备记录，平台已开 11 条 open 的 `onboarding_day1_gap` incident），补台账需真实设备信息；后者是 3 个档位的 `max_seats` 配置过期（Zoom Large Meeting 记 1 实占 148、Zoom Webinars Plus 记 2 实占 190、Confluence Standard (agent) 记 10 实占 101），订正配额数字需业务确认，且 `over_quota_policy=reject` 会真的挡住下一次开通。 **确定性评分口径升至 `health-v4`(2026-09-01):纯改名,零口径变化** —— `people_org` 的四个指标 key 由 `people.*` 改为 `people_org.*`、`platform_ops` 的六个由 `platform_operations.*` 改为 `platform_ops.*`,使全部 40 个指标的「发布名」与「模型自然拼法」一致(改名前静态实测 30/40 可引用,改名后 40/40);历史 `health-v3` 及更早快照原样保留、不重写不重算,趋势上留一次台阶。新增 import 期闸 `validate_metric_key_shape_is_uniform`,禁止一个目录里混用两种寻址形状(云订阅目录统一用 `cloud.<组>.<叶子>`,合法且同受该闸保护)。见 DECISIONS 2026-09-01。
**Ask Helix 助手（in flight，2026-08-16 起，5-PR 主题）:** 把 Helix 的能力送进全员每天已经在
用的界面——飞书。**PR1 已完成**：跨域编排层 `server/app/assistant/`，复用现有飞书自建应用
（新增 `im.message.receive_v1` 订阅，落到既有已验签的审批回调 URL——一个应用只有一个事件
订阅地址）、现有开放 API 客户端与凭证；服务端解析发信人身份、`event_id` 去重、每人限速、
群聊与机器人消息忽略、三语确定性回复、逐条审计；总闸 `assistant.enabled` fail-closed，
关闭时机器人不回任何话。**PR2 已完成**：接上已在生产运行的 `support/copilot.py` 内核
（不建第二套 Agent 框架），Redis 短期记忆按 person+chat 分键、每 chat 串行锁、慢回合占位
消息、模型故障三语降级；迁移 483 落 `assistant_conversations`/`assistant_messages` 可审计
转写（失败回合同样入库）；对话转写按显式 `seq` 排序（同一秒内 `created_at` 排不出顺序）。
**PR3 已完成**：写路径机制——白名单动作注册表、提案存库（迁移 489）、参数快照 sha256 绑定
一次性 action token、飞书确认卡片（复用既有卡片构建器 + URL 按钮）、公开确认页
`/assistant/confirm`（三语）；首个动作＝转人工建单，关键字确定性触发、草稿由既有纯函数生成、
执行走 `SupportService.create`。**PR4 已完成**：白名单扩到 VPN 申请与设备借用/报修，模型获得 `propose_<key>` 工具面（只提案、
无提交工具，当事人取自已验签信封而非工具入参）；软件坐席待先做逻辑提取（router 与 MCP 各内联
一份约 250 行）。**后续**：PR5 MCP 只读桥（白名单流程的提案 + 参数快照哈希绑定的一次性

消息、模型故障三语降级；迁移 485 落 `assistant_conversations`/`assistant_messages` 可审计

消息、模型故障三语降级；迁移 486 落 `assistant_conversations`/`assistant_messages` 可审计
转写（失败回合同样入库）。**后续**：PR3 确认卡片写路径（白名单流程的提案 + 参数快照哈希绑定的一次性
token + 既有审批流执行）→ PR4 MCP 只读桥（`mutates` 硬过滤 + DESTRUCTIVE 排除 + 两段式
工具检索，工具面随 MCP-sync 规则自动生长）→ PR6 管理端对话审计面与成本入账。
决策见 DECISIONS 2026-08-16；运维见 [docs/ops/assistant-feishu-channel.md](docs/ops/assistant-feishu-channel.md)。

**飞书原生 IT Agent（2026-08-19 状态）:** 主题已全量上线，并完成**四轮**「审修复本身」复审 + 一次安全扫描。
当前能力面 = 知识库 + **276 个只读 MCP 工具**（`mutates=False`，按本人收口，自动执行）
+ **60 个自助流程**（`mutates=True` 去掉破坏性 / 管理面 / 替他人决定后自动派生，**只提案**）；
执行发生在员工点飞书原生确认卡片的那一刻，回执就地渲染,不跳网页。动作检索走语义（百炼 embedding，
阈值 0.30），中文同义词与错字不再导致「流程不存在」。回答闭环：出处链接 + 有用/没解决 + 转人工，
未解决进知识缺口队列。复审修复见 CHANGELOG 2026-08-18；判定管理面授权改为读**声明的授权语句**
（owner 型条款不算管理面），可提案面 100 → 75；复审这一轮修复本身时补上 `can_*` **谓词**分支（`Requires can_manage_security` / `RBAC: can_view_finance` 这类不带 `role` 字样的写法），75 → 72。`/help#ai-copilot` 已按新形态重写。

**2026-08-19 检索与字段补齐（见 CHANGELOG）：** 一次生产问答（「Netsuite 的负责人是谁」→
「Helix 没有记录」，而目录里写着邱芳）拆出三层缺陷并逐层修：①工具不返回被问的字段（软件目录的
负责人/IT 联系人、`list_my_assets` 的保修/采购日期）；②检索语料被截到 400 字符，273 条描述里
**116 条超长**，`whoami` 结尾的「我的工号…」从没被 embed 过 —— 现整段入语料；③只读桥把超长结果
静默切成半截 JSON（软件目录整表 29k ⇒ 只看到 57 个里的 17 个），现返回显式截断信封。补上 MCP
**从来没有的组织读面**（`list_directory_people` / `list_departments`，+2 → hint 487），提示词点名
知识库（246 篇已发布飞书镜像文档）与「关于本人先 `whoami`」。以 50 条真实中文问句做检索评测，
**31 条 top-3 改变**。
另外逐个工具拿生产数据实测字段面，又抓出三个「答案是 UUID」的同类缺陷并修掉：`whoami` 的经理
（只给 person_id，而解析 person_id 的工具是 HR 闸门 ⇒ 员工问「我的经理是谁」拿不到答案）、
`list_my_cloud_grants` 的云订阅名、`get_my_profile` 的下属检索不到。知识库语义检索原先**每问一次
拉 6.3 MB**（246 篇的向量+正文全表），改为只读 `(id, embedding)` + 进程内语料缓存（签名探针 + TTL）。


**2026-08-19 加固（10 个 PR，见 CHANGELOG）——两处「从上线起就没工作过」：** ①飞书
`card.action.trigger` 回调**不带任何 `X-Lark-*` 头**，被共享 webhook 的强制时间戳规则整类 401
（客户端显示 200671），所以**每个卡片按钮从上线起没成功过一次**（`assistant_message_feedback`
0 行）；②DashScope embedding **单次最多 10 条输入**而预热一次发 273 条，于是**语义检索从
#3054 上线起没工作过一次**，一直静默降级到词法匹配（英文语料 + 中文查询 = 0 命中），助手因此
告诉员工「OpenAI 不在软件目录里」。两条都已修，并各自补了观测（降级必打 warning）。
同批还修了：提示词注入员工本地时区的当天日期（模型原先回落训练期年份 2024）、日期方向声明在
MCP 字段上（`FutureDate`/`PastDate`）、缺字段与没流程分流（不再降级建工单）、卡片回调 3 秒
预算（超时改为「正在提交」+ 结果补发）、评价保留出处（迁移 518）、转录 seq 唯一约束 + 冲突重试
（迁移 519）、reset 的 epoch CAS、pre-hire/leaver 提交闸。
**四轮复审的数据**：每轮审「修复本身」都找到新缺陷（12 → 9 → 9 → 1），其中一处是安全回归
（`create_time="nan"` 让 300 秒鲜度窗口失效）；「员工什么都收不到」这一个失败模式**换了三次机制
回来**。最终安全扫描 ≥8 置信度零发现。**待办（已登记未做）**：`Box` vs `Dropbox` 的反向匹配需
词边界判定；`budget + hop` 算术在含两次 embed 预热时不闭合（≈215s > 客户端 180s）；
`_first_leaf` 让某些超时被记成 `NETWORK` 而非 `TIMEOUT`。

**AI 大脑增量（2026-08-16）:** 离职风险与所有权连续性已接入生命周期快照，覆盖交接、特权项、逾期阻塞、接手人和关联对象暴露面；桌面/H5 经营展示均为确定性读模型。资产优化已接入需求保护后的闲置电脑盈余、未确认库存、财务数据缺口和未来需求缺口，事实版本为 `asset-optimization-v1`。每日跨域简报、建议状态审计和 ROI 读模型已接入统一事实 Hash 调度，读取不调用模型。生产已启用 `intelligence.enabled`；所有授权管理角色的默认桌面入口为 AI 大脑，首页以本期优先事项和“定位—理解—处理—复验”引导收敛分数、证据和既有工作台，订阅级 AI 分析位于详情顶部 Tab，并明确展示可信度、口径、处理路径与证据下钻。建议通过由事实引用确定的受控交接单进入既有工作台，包含审批标记和事实复验基线；不新增 AI 直连写入。

**AI 大脑增量（2026-08-16）:** 离职风险与所有权连续性已接入生命周期快照，覆盖交接、特权项、逾期阻塞、接手人和关联对象暴露面；桌面/H5 经营展示均为确定性读模型。资产优化已接入需求保护后的闲置电脑盈余、未确认库存、财务数据缺口和未来需求缺口，事实版本为 `asset-optimization-v1`。每日跨域简报、建议状态审计和 ROI 读模型已接入统一事实 Hash 调度，读取不调用模型。生产已启用 `intelligence.enabled`；所有授权管理角色的默认桌面入口为 AI 大脑，首页以本期优先事项和“定位—理解—处理—复验”引导收敛分数、证据和既有工作台，订阅级 AI 分析位于详情顶部 Tab，并明确展示可信度、口径、处理路径与证据下钻。订阅资源效率现优先使用云同步的账号/订阅/项目/资源组归属，账单资源 ID 仅作回退，避免将已有机器误标为数据不足；低可信度现在逐项说明机器关联、监控效率、账单或同步的实际缺口与处理方向。

**审计 / 合规 Copilot（in flight, 2026-08-16 起, 12-PR 主题）** — 目标不是再做一个问答机器人，
而是把审计季反复出现的取证问题变成**确定性查询目录 + 可复现证据包**，再让自动化把证据平时就攒好：
M1 证据查询目录 + Evidence Bundle + auditor 取证工作台（零模型）；M2 月度证据预编译 cron、
离职即留痕、授权类漂移接 findings 工单；M3 才引入叙述层（问卷答案保鲜 + 月度合规摘要），
且只走 `intelligence.generation` 唯一包装。**PR5 shipped**：HTTP 5 端点 + MCP 2 孪生（`run` 标 `mutates=True`）+ 证据包新增
`access_evidence` 模块（渲染已冻结的 bundle，不重跑查询）；金额沿用平台脱敏闸，只指向内嵌
audit detail 以免过度打码。顺带校正了 main 上落后 6 的 `_REGISTERED_TOOLS_COUNT_HINT`。
**PR4 shipped**：目录补齐到 12 条 —— 单人足迹（复用各名册 + 审计行）、离职回收证明
（不发明阈值，全量出行 + `open_at_termination`/`still_held` 标志）、对象生命周期
（并入 `cmdb_network_owner_history`，否则网络对象换主人查不到）、访问复核证明（含未决与逾期）。
**PR3 shipped**：目录扩到 8 条查询 —— 云权限（按持有周期出行，含从 `_resurrection_history`
还原的历史段）、软件权限（读 484 区间台账）、API Key、BigQuery 数据访问，以及审批链反查
（`approval_steps` 与云权限专属的审计行两条权威路径，标注 `authority_path`）。
**PR2 shipped**：证据查询目录（`server/app/domains/auditor/evidence_query/`）+ 冻结的证据 bundle
（迁移 489）——目录是唯一能被提问的问题集合，没有自由查询、没有模型生成 SQL；一次运行冻结成
不可变快照（问题 + 版本 + 规范化参数 + 结果 hash + 逐行出处），重跑 hash 不同即「底层数据动了」。首批 3 个 handler（软件坐席 / SSO 授权区间名册、平台特权角色时点名册）。
**PR1 shipped**：迁移 484 补上两个时点盲区 ——
`auth_user_role_cycles` / `user_permission_cycles` 两张 append-only 区间台账，
由 `app/core/access_cycle_listener.py` 集中写入（`main.py` + `worker.py` 双注册），
并把 5 处绕过 ORM 的 `delete(AuthUserRole)` 转成 ORM delete + AST 闸挡住第六处。
详见 [PLATFORM.md §8 Point-in-time access ledgers](docs/architecture/PLATFORM.md)。


**All three phases of the original plan are shipped.** Helix is the unified IT platform for a ~500-person org: it owns identity, HR lifecycle, hardware assets, software seats, API keys, cloud accounts (8 providers), permissions, approvals, audit, and security governance. The foundation is operationally mature — every domain has scheduled jobs, every user-visible mutation goes through the notification outbox, every offboarding hits the Leaver Blocked gate, every cloud grant has expiry semantics.

**Current phase: operational maturity & UX polish.** Recent work is no longer about adding domains — it's about closing edge-case 500s, redesigning self-service UX, and tightening governance loops (auditor freshness, billing close-month, scope decommission).

**AI 大脑 / Intelligence Center（生产已启用，2026-08-16）:** 已完成的八维确定性健康、
跨域平台简报、建议生命周期/ROI、入职与转岗预测、离职风险、资产与容量优化，以及云订阅
共享按需分析现已对授权角色开放。授权角色登录默认进入 `/admin/intelligence`；员工没有独立
菜单，只有在其有权的云订阅详情顶部看到共享的「AI 分析」页签。总览和下钻读取均为零模型
调用；全平台或订阅分析只有显式生成才消耗 Token，且继续受事实 Hash、缓存、冷却、额度、
RBAC、审计和现有审批/执行闸约束。下方早期“入口关闭”描述是当时 PR 的历史发布状态，
不代表当前生产状态。

**AI 大脑 / Intelligence Center（历史实现说明；生产已启用）:** 目标是把人员、
入转离、资产、软件/API、云网络、成本、安全和平台运行汇成确定性八维健康、
预测、跨域 AI 简报与建议闭环。内核、统一生成合同和八维真实事实已经完成：
版本化评分、事实 Hash、匿名证据、本地引用映射、隐私闸、低可信度闸、数据库
单飞、额度/冷却、双语封闭 Schema 及分析/建议长期账本均已落地。八维全部复用
现有业务账本；共享成本只有业务口径，实付数据未进入共享快照，现有云节省也不
重复计算。
显式 Provider Registry 与 `intelligence.snapshot_daily` 已注册，每日生成
`business_shared` 确定性快照；同日/同事实 Hash 不新增历史且模型调用恒为零。
区域资产受众已按有效办公室集合稳定归并：同范围共享、跨范围隔离，全局资产
角色不建区域副本，领域可信度按领域自身归一而不受其平台权重压低。
九类角色的维度级访问计划与公共脱敏投影已完成；区域生成/读取共用有效办公室
边界，公共结构不包含本地对象映射，实付成本与管理能力为独立权限位。
总览、领域与历史三个只读 HTTP 已接同一策略；隐藏开关关闭时统一 404，读取零
模型调用，缺来源/版本漂移显式降级，区域与全局历史不伪造时间轴对齐。
前端错误仅保存日期、端类型和次数，按最近七个完整 UTC 日与活跃人员归一；
自动开通超过 24 小时未完成会进入平台失败率。
**历史发布状态（现已启用）：** 当时 `intelligence.enabled` 为 false，尚无菜单曝光或模型后台调用。三个平台只读
MCP 孪生工具已与 HTTP 共用数据面，并共用独立每用户读取预算；
前端 overview/domain/history 类型与零模型客户端已经锁定；桌面经营总览已完成综合
分、八维分布、领域健康和 90 天真实趋势；领域按需下钻已完整展示指标口径、权重、
原始值、可信度、数据时间与匿名证据。飞书 H5 已有完整原生总览、轻量趋势和按需
领域下钻；桌面与移动均从后端版本化目录读取评分解释，但仍不发布菜单。下一批接
订阅按需分析、预测优化、
建议闭环和 ROI 已接入每日事实 Hash 调度与审计读模型；管理员手动生成与强制确认也已接入同一生成内核。整套验收后一次开放正式入口。
云订阅分析的 `cloud-scope-health-v1` 五维权重、18 项确定性指标和真实订阅事实包
已经完成：权限/角色、Owner、账单归属机器、既有效率结论、六个月业务折前成本、
预算预测与异常全部匿名聚合；租户级与 Azure 资源组复用现有唯一成本路由，旧实付
预算不会进入共享 burn rate。`cloud_scope + business_shared` 不可变共享快照已经接通，
同事实复用、变化留历史、证据对象变化也会更新 Hash。共享按需分析服务层也已完成：
Owner/有效成员/父订阅继承和管理员域角色使用同一非枚举权限闸；读取绝不调用模型，
生成才刷新事实并复用 7 天缓存、冷却、额度和单飞，强制重算仅 global/cloud admin
且显式确认，失败保留上一成功版。公开投影也已锁定：旧结论绑定旧快照，普通成员
不能解析他人授权角色，Owner/管理员按原权限映射真实对象，跨订阅/损坏关联 fail
closed 但不遮蔽确定性健康分。订阅最新结果、历史和显式生成的 3 个 HTTP + 3 个
MCP 入口也已接通（工具提示 466）：共用同一权限/投影/生成 facade，GET 零模型，
生成明确标记成本 mutation；证据对象还要通过订阅唯一成本归属复核，不能在同一
integration 下串订阅。Web 强类型合同也已锁定最新、历史和唯一显式生成方法，页面
读取不能隐式 POST，生成使用独立 120 秒请求预算。管理员、Owner 与有效成员的现有
订阅详情已经嵌入同一桌面共享分析卡：确定性五维分、双语结论、风险/建议/机会、
数据缺口、证据链接、缓存与生成元数据、强制费用确认和并发轮询均已完成；release
gate 未确认前完全不渲染。共享历史已通过显式打开的只读抽屉交付，页面首屏不预读；
路由切换和三类异步请求均按订阅独立隔离，入口未证明启用前继续 fail-hidden。
飞书 H5 原生分析卡已嵌入管理员订阅详情，完整保留五维分、叙事、证据、显式生成与
按需历史；生成中持续轮询到终态，手动重试可恢复轮询，10 分钟后会暂停而不会重发
生成请求。有效成员与 Owner 的普通订阅深链也已进入独立原生详情并嵌入同一共享分析，
不新增员工菜单；成员页不显示账号联系邮箱、预算、成员清单或管理动作，带具体授权的
续期提醒仍回到原授权处理页。云订阅共享按需分析的桌面/H5 交付至此闭合；下一批进入
30/60/90 天入职、转岗与资源需求预测，不把评分交给模型。
`demand-forecast-v1` 确定性内核已经完成：地点/部门/岗位分层样本、最小 5 人、60%
资源持有率、低置信度禁强建议、区域/全局供给、候选人级预留承诺、确认在途、采购提前期、累计缺口
和按实际到岗日计算的 Day‑1 风险均已版本化；离职、取消和异常对象不进入样本或需求。
真实事实适配也已完成：未来有效入职与在职对照样本复用 HRIS 主组织和资产办公室解析，
硬件库存/候选人预留、有限及无限软件席位、VPN 和云授权均转换为包内匿名引用；模型事实不
包含姓名、组织标签、软件名或访问角色。已明确分配给候选人的资源是业务承诺，可突破 60%
历史持有率，但不能绕过最小 5 人样本闸。
预测现已作为 `lifecycle.demand_forecast` 进入既有平台/生命周期每日不可变快照：事实 Hash
变化才留新历史，页面读取和快照任务仍为零模型调用。Provider 的聚合匿名引用会在编译时
重排到快照全局引用空间，避免与其他领域证据同名错绑；完整事实包到快照已有真实账本回归。
桌面总览现已交付 30/60/90 天经营视图：累计新人、资源需求、容量缺口、Day‑1 风险、
低样本和不完整数据分开呈现，优先缺口按风险排序且不向 UI 暴露匿名编号；读取仍为零模型
调用。飞书 H5 也已用原生触控横向卡和紧凑优先缺口列表交付同一完整口径，直接消费首屏
overview 而不增加请求；375px、明暗主题和空数据均已验收。两端现共用展示选择器，明确
区分旧快照未生成、真正零需求和有结果三态，排序/汇总不会漂移。下一批进入转岗新增/回收
的双向预测。`transfer-forecast-v1` 确定性内核已完成：30/60/90 天累计输出新增、回收、
保留和人工复核，物理硬件继续遵守随员工保留政策，低置信度或不完整目标策略不能生成强
动作。真实事实适配已接入最近/未来 90 天转岗事件、Provisioning 目标策略、软件席位、
权限、硬件和待复核云授权，全部采用匿名对象和批量读取；未闭合历史差异继续作为逾期事项，
已解决事项退出当前视图。转岗预测现已进入生命周期每日不可变快照，与入职需求包先合并
到无碰撞匿名引用空间，事实变化才留历史且仍为零模型调用。桌面与飞书 H5 的强类型合同和
共享三态/优先级展示口径已锁定；桌面经营卡已完整展示双向变更、复核、逾期和数据质量且
不暴露匿名编号。飞书 H5 原生经营视图已在后续分支交付；离职风险与所有权连续性也已
进入同一确定性事实与建议链路。

**AI 大脑生产闭环状态（2026-08-16）：** 八维快照、云订阅共享分析、入职/转岗预测、
离职风险、资产与容量优化、每日平台简报、管理员手动生成、建议生命周期、通知 Outbox、
ROI 结果回写以及桌面与飞书 H5 展示均已交付。建议动作继续复用既有领域 API 和审批闸；
通知是唯一可直接进入 Outbox 的通用动作，其余动作 fail closed 并要求对应业务工作流。
**历史发布状态（现已启用）：** 当时发布开关 `intelligence.enabled` 保持关闭，待整体生产验收、数据校验和运维确认后一次性开放。
该开关状态是有意的发布闸，不代表能力缺失。

**Post-merge CI trigger reduction (2026-08-18):** ordinary `main` code merges run only the three-job `go-ci` (`Backend Release Guard`, `Frontend Release Image`, `go-ci / Main`). Repository CodeQL runs weekly or manually instead of on every merge. Terraform Validate runs automatically only when `infra/**` or its own workflow changes, with a manual fallback. This changes trigger frequency only; it does not weaken any check's internal logic or combine release/deployment into CI.

**DeepSeek PR review (in flight, 2026-08-14):** Claude Code supplies the single
DeepSeek agent loop through DeepSeek's Anthropic-compatible API. It retains
NexusCRM's one lenient standard review pass; no second pass, shell access,
parallel protocol, quota system or calibration framework is permitted. The
first live run showed an unacceptable single-read 33-second PASS and a
fail-closed stale engine binding, so the feature remains disabled. A pre-PR
runner re-review of real code PR #2735 then passed with 18 grounded tool turns
in 86 seconds. The replacement source now requires candidate/trusted-base
evidence and must still prove exact-head check, visible comment, guarded squash
merge and post-merge Go CI before re-enabling. See
[DeepSeek Review](docs/architecture/DEEPSEEK_REVIEW.md).

**Shared Staging deployment is dispatch-only (2026-09-06):** the daily
`schedule:` was removed. A run fails closed unless the isolated Staging Branch
is already at the release's Alembic head, and `main` lands 3–13 migrations every
working day, so the cron succeeded on only 4 of 24 runs — all inside one
migration-free weekend — then failed 13 consecutive nights (08-24 → 09-05) with
Staging pinned at revision 527. **Staging is only as fresh as the last manual
dispatch, and is currently ~93 migrations behind `main`;** recovering it is a
cloud-admin action (migrate → re-run the sanitizer → dispatch with migrations
off), so treat production releases as having no staging verification behind them
until that is done. Restoring a daily cadence is a safety-boundary decision (may
an unattended run migrate and re-attest Staging itself?), not a cron change — see
the 2026-09-06 ADR. Description of the original design follows.

**Shared Staging deployment (source ready; activation blocked):** a separate
manual workflow now binds an ancestor-of-`main` SHA, its successful exact
`go-ci / Main` receipt and immutable Staging ACR image digests before invoking
Cloud Assistant on the one Staging ECS host. It is deliberately disabled until
GitHub `staging` Environment, Alibaba OIDC/RAM roles, private ACR repositories,
ECS runtime role and the host's non-production runtime config are bootstrapped.
The host-side contract also requires the pre-existing Caddy-attached Docker
network and adopted Staging Redis volume; a run never migrates databases
unless it was dispatched with `apply_migrations=true`, and never starts
`worker`. See [Shared Staging
deployment](docs/deployment/staging.md). Activation is also blocked on a
source-bound sanitizer receipt: the current TiDB Branch is a copied Production
snapshot, not safe Staging data. This is not production deployment or
business-effect acceptance.

**2026-08-25 production-hardening closure:** subscription details now embed the permission-scoped all-resource directory while retaining billing allocation; subscription AI uses the same all-resource ownership, monitoring, cost and change facts with locally re-authorized evidence links. The directory theme described below is shipped end to end.

**Cloud resource directory platform (2026-08-24, production hardening complete):** migration 529 establishes the provider-neutral resource index, open resource-type catalog, credential-redacted semantic change ledger, and a 15-minute self-healing materialiser for all currently observed CMDB resources. Explicitly enabled AWS Resource Explorer, Azure subscription Resources and GCP Cloud Asset adapters add broad read-only discovery with pagination and incomplete-scan sweep fences, so new provider kinds register automatically. Migration 530 adds strict relation-scoped RBAC, Helix-local stewardship, deterministic governance rules/findings and indefinite daily trends; only global/cloud admin/auditor see all, management/finance do not bypass scope, and auditor stays read-only. Migration 534 and the unified desktop experience now expose 资源清单 / 治理中心 / 效率与成本 / 数据看板与报表 / AI 洞察 as tabs under one 云资源目录 entry while retaining 云资产总览 separately. Users control weekly/monthly/off delivery, channels, timezone, hour and scope; dispatch rechecks live permissions, and CSV/report history stays recipient-scoped. Migration 537 makes owner inheritance and monitoring/cost coverage explicit and truthful; AI is available to ordinary employees but receives only aggregate facts already cut by the same live visibility predicate. Migration 538 replaces per-resource correlated employee scope checks with pre-resolved indexed pairs, lazily loads the active desktop/H5 tab, and expands reports/push to rule, coverage and resource-change dimensions across every discovered type. Seventeen MCP twins keep AI clients on the same visibility, governance, audit and report contracts (registered-tool hint 507); CSV remains a bounded HTTP download.

**DAX service-account grants (authoring repaired 2026-08-18; principal gate retired in code 2026-09-07, pending release):** Service Account is free-text input, not a pre-registered directory choice. Helix syntax-validates and records the normalised email transactionally, then uses the same requestable dataset catalog and `read:v1` entitlement as personal access. The approval chain is the selected resources' dynamic owners only (SA definition/mapping aligned 2026-09-07, pending Feishu publication); historical catalog/profile metadata is observation-only and cannot gate a request. Submission and callback remain no-provider-write; the reconciler alone manages exact BigQuery `userByEmail` / `serviceAccount:` entries while preserving unrelated and conditional ACLs. Provider apply requires integration mode, SA mode, an approved active binding and a current active/requestable/owner-resolved resource row with matching locator. Neither principal pre-registration nor a separate dataset config allowlist is required: approval authorizes the typed SA identity and the live catalog owns the resource boundary. Existing eligible pending/retryable SA effects drain through the same scheduler after release.

**DAX personal employee grants (merged source, not deployed, 2026-08-19):** [PR #3080](https://github.com/pingcap-inc/Helix/pull/3080) merged as `main@9837b7523d0bbfd69cb3380db7a3b52bde24ee6b`. The default personal tab adds a self-only route: an active employee chooses the same requestable datasets but supplies no personal email or target user. Helix derives `google_user` from the logged-in UUID and current work-email snapshot, uses a separate owner-only Feishu flow (the repository definition must also be published to Feishu), and compiles only exact `user:<work_email>` / `userByEmail` dataset-reader effects. The Group new-request tab and `POST /requests/group-grants` are retired; legacy Group requests, callbacks, bindings, revokes, and read-only history remain operational so existing access stays auditable and revocable. The scheduled identity sweep revokes the exact old grant if the employee leaves or the work email changes. Personal effects remain provider-blocked by default through `personal_apply_enabled`; before activation publish/read back the dedicated personal Feishu definition, run isolated DB + driver regression, then validate the staging request→approval→effect→revoke chain and explicitly enable the flag before any production apply.

**AI 接入体系 (in flight, 2026-07-09 起, 4-PR theme)** — 把 Helix 的 AI 接入面收成一套完整、公司级示范的体系:**PR1 shipped**: 官方 Claude Code 插件 `helix@helix`([plugin/](plugin/) — OAuth 零 token 接入 + 5 skills + 5 slash commands)+ S3 分发管线(`plugin-release.yml` → `plugin-dist/`,镜像 cli-dist)+ 后端 `/api/v1/plugin/{manifest,download}` + MCP `plugin_get_download_manifest`(hint 407)+ `helix plugin {install,update,status}` CLI 子命令(sha256 校验、zip-slip 防护、自动注册 Claude Code)。**决策**: 不做 MCPB 桌面包(仅支持本地 stdio,劣于已有 OAuth Connector 路径,见 DECISIONS 2026-07-09)。**PR2 shipped**: /help「AI 集成」扩全——六路径选择矩阵主题 + Claude Code 插件主题(含 PluginDownloadCard)+ llms.txt + 三语 35 key + 存量修正(320+→400+、ja cursor.body 坏翻译重写)。**PR3 shipped**: MCP 满配——407 工具全量注解(AST 从 `tool_runtime(mutates=…)` 推导,危险子集与 CLI 确认清单测试锁等)+ Resources ×2 + Prompts ×5,external-api Part C 契约增补。**PR4 shipped**: 公司级双语知识库手册(docs/kb/ MD 源 + 生成器入库,docx ×2 交付,图 6 张含产品实截)。**四 PR 主题全部交付**;收尾三路审查(正确性/安全/逻辑)7 项发现全修(2 功能缺陷:update 缓存语义+发版版本闸;1 P3 加固;4 文案)——安全面零可利用。待收尾 PR 合并即完结。

**Shipped (2026-06-05): 移动端 (Feishu H5) 生产级重做.** Turned the H5 surface from a read-only desktop mirror ("一堆方框") into a genuinely task-completing role workspace. Delivered W0→W5 across 17 PRs (#733–#750) + 2 post-review fix PRs (#751 code-review / #752 security+logic), all squash-merged to main and **deployed to production** (`sha-ce3bd9a3…`). What shipped: unified cross-domain **action inbox** (`/m/aggregate/inbox` + `mobile_action_inbox` MCP tool) + action-centre home; the **write primitives** (`useMobileMutation` idempotent + re-entry-guarded / `MActionBar` / `MDecisionSheet` / `MStepForm`) with the four write categories standardised on the idempotent path (approval/leaver-override decide, asset-transfer confirm/reject, inventory response, provisioning complete/fail/retry, API-key request, auditor **finding decide** worklist); full **deep-link coverage** so IM cards land on the right `/m/*` page; **dignified `MHandoff`** (zero `action-on-desktop` dead-ends — Excel import / IAM policy / connector creds / multi-step procurement carry the desktop link over); the 4 legacy hand-rolled pages rebuilt on antd-mobile + shared `--m-*` tokens; **infinite scroll** on the admin browse lists. Review: 3-lens code review + adversarial security scan (zero exploitable) + logic scan (fixed an actor-field/IDOR-shape provisioning bug + two 404 handoff paths). i18n zh/en/ja parity 865/865/865. See [docs/CHANGELOG.md](docs/CHANGELOG.md).

**Shipped (2026-07-10): 移动端能力整改 — 6-PR round (#1848–#185x).** Follow-up deepening after user feedback ("能看到别人的云权限审批 / 依然很垃圾"). Root-caused the todo pollution + closed the actionability gaps. (1) **待办数据面重建**: removed the company-wide `approval` provider (Feishu-mirrored, no Helix approver attribution → noise), tightened the bar to "waiting for ME to act IN Helix", added 7 in-Helix-decidable providers (access-attestation / support-reply / attendance / inventory / onboarding-ack / support-assigned / leaver-override), each deep-linking to a working page. (2) **壳目录收敛 + 通知修复**: consolidated the 4 already-modern legacy pages into `web/src/mobile/pages/`, added owner-scoped `GET /notifications/inbox/{id}` (killed the #1804 30×100 paging hack), unread count → server truth. (3) **云权限审批移动闭环** (the headline fix): grant + scope approvals now decidable IN the app — the "webview can't step-up" premise was wrong (the gate is PAT-vs-JWT; the webview JWT passes); new grant-approval + consolidated cloud-approval queue pages + 3 MCP tools (hint→410). (4) **审批域整修**: detail endpoint split by viewer authority, approval-chain timeline, Feishu applink jump, admin approvals → observation board. (5) **首页场景化**: real pinned-apps quick-access row (`pinned_apps` on the home aggregate) + search entry + cloud-approvals in the apps catalog + software search. (6) **走查+精致化**: live preview walkthrough caught + fixed a home-aggregate infinite-retry storm (one-shot guard). See [docs/CHANGELOG.md](docs/CHANGELOG.md).

**移动端生产级整改 (in flight, 2026-07-28 起, 7-PR theme)** — 起因是一条生产反馈「云权限审批点了没有反应」，全面盘查后发现问题远不止一页：**整个移动端的确认弹窗与 toast 从 #448（2026-05-21）起从未工作过**。antd-mobile v5 的命令式渲染读 `react-dom` 主入口的 `createRoot`，React 19 移除了该导出 → 静默空操作（不抛错、不打日志、无 DOM），影响 `web/src/mobile/` 下 30 个文件：需要确认弹窗才执行的写操作全是死按钮，直接调 API 的写操作则从未显示过任何成功/失败提示。同时盘出深链断点（后端出现频次最高的通知 URL `/my/requests` 无移动映射；`FeishuMobileGuard` 丢 query 使所有参数提升规则在卡片首点路径失效）、写路径分裂（破坏性操作恰好在无幂等键那侧）、列表 50 行天花板、静默吞错、以及员工手机上最该有却缺失的能力（手册/制度公告/通讯录/假期/个人设置/通知偏好/已办）。**PR1 shipped (2026-07-28, 0 迁移)**：React 19 渲染桥 + 三层结构性护栏（唯一 importer 是 feedback 层〔挂 routes.tsx 会把 antd-mobile 拖进桌面主 chunk，已实测〕、ESLint 精确拦裸命令式调用、DOM 级回归测试拆桥必红）+ 统一两条 confirm 分支语义（非 danger 分支旧实现在 onConfirm 抛错时 promise 永久悬挂 + unhandled rejection）+ `partialWarning` 升为写原语一等语义（后端 200+warning 不再误报成功，云授权批准的兄弟页同修）+ 引入 jsdom/@testing-library 组件测试基建（FE 测试 91→111）。**PR2 shipped (2026-07-28, 0 迁移)**：BE↔FE 深链契约（`contracts/mobile-deep-links.json` + 两侧测试，AST 扫 4 种 sink 形态、按前端路由表一级路径剔除第三方 API 噪声）+ **7 处线上死链修复**（后端在发、两侧都无路由，静默 404 数月）+ apps 瓦片/action-inbox deep_link 可达性硬断言 + `FeishuMobileGuard` 补传 query（此前所有参数提升规则在卡片首点路径全失效）+ 通知无映射不再静默 + 两个新决策面（季度特权访问复核、邮件组加入审批）。**PR3 shipped (2026-07-28, 0 迁移)**：`@idempotent` 路由原语（接端点从六行样板变一行）+ 14 个「重放会产生第二次外部副作用」的端点接入（云授权四件套 / provisioning 四件套 / 角色授予 / scope 下线 / 席位回收 / Google 组与别名创建）+ 修掉共享原语里的缓存重放时间戳格式 bug（空格分隔 vs ISO-8601，WebKit 上是 Invalid Date）+ 23 处裸写收编 + 错误态与空态可区分（经理主 tab 风险徽标、报表页 403 vs 故障）。**PR4 shipped (2026-07-28, 0 迁移)**：列表契约统一（11 页脱掉 50 行天花板，接 `useInfiniteList` + 常驻「已显示 X / 共 N」服务端真值）+ 6 个审计端点补真 `search`（此前是什么都不做的假搜索框，而假搜索框比没有更糟）+ `DesktopOnlyBanner` 17 处全量迁到 `MHandoff` 并删除组件 + 设计 token 补齐语义槽并清掉 118 处硬编码 hex（`#cbd5e1` 一个值就占 51 处）+ 两个非 tab 页补顶栏标题。**PR5 shipped (2026-07-28, 0 迁移)**：员工知识面移动化 5 页（系统手册列表+章节、公告、制度中心、办公地址目录）。共享 markdown 渲染器加注入槽而非 fork —— live-table 用 antd 会污染移动 chunk、mermaid chunk 2.3MB 在飞书 webview 里不可接受且 375px 读不了，故移动端注入 antd-mobile 原生 live-table + 点击才加载的全屏 mermaid。**PR6 shipped (2026-07-28, 0 迁移)**：个人域移动化 5 页（`/m/leave` 假期余额 + SF 回源、`/m/notification-preferences` 通知偏好、`/m/done` 已办、`/m/api-tokens` 个人访问令牌、`/m/me/settings` 邮箱别名 + 我的邮件组）+ `/m/bills` 补个人预算与附加费用。三个不做写清楚：无请假申请端点故不做提交请假、已办报表留桌面、`SoftwareCostPage` 是全公司管理台不整页搬。`already_absent`（嵌套组间接成员，退出是 no-op）如实告知且不删行。深链契约：`/me/settings?tab=aliases|groups` 从 `desktopOnly` 移入 `mapped`。**PR7 shipped (2026-07-28, 0 迁移)**：前端崩溃可见 —— 两个错误边界此前只在 DEV 打 console，生产白屏零留痕（不可复现由构造决定）；新增 `POST /api/v1/observability/client-errors`（认证 + 按用户限流 fail-open + 服务端封顶 + `extra="forbid"` 且刻意无 context 字典，因为页面局部状态正是 PII 所在），上报器绝不送 query string（一键落地页的 query 本身就是凭证）、30s 去重、永不抛错；移动端首页七区块各自加边界（此前一个 null 字段白掉整个第一屏）；`__APP_VERSION__` 构建戳替掉写死的 `'1.0.0'`；`/auth/feishu/h5-login` 无调用方但**不删**——认证端点上「本仓库无调用方」远弱于「世界上无调用方」，改为打 WARNING 收集一个部署周期的静默作为移除证据。**PR8 shipped (2026-07-28, 迁移 430)**：PR1–PR5 合入后的对抗式审查发现全量修复（12 条，2 条 P1）——6 个 `@idempotent` 端点的前端 client 函数根本没有接收键的参数（守卫在不可逆的云订阅下线上是 no-op）+ BE↔FE 幂等键契约测试；MFA/登录异常搜索框写着「搜索员工」而模型没有姓名列 → `person_fk` outer-join + LIKE 通配符转义 + 6/6 端点测试；12/14 handler 自己 commit 导致 `mark_failed()` 被 `get_db` 回滚抹掉（同键重试 409 满 24h）→ 独立 session 延后释放 + 10 分钟弃置接管；深链契约扫描器的 `spa_segments` 白名单恰好跳过它自己要根治的死链形状 → 按「模块是否说 HTTP」区分 + 扫描范围扩到 alembic；**迁移 430** 修复 051 seed 在生产上从未生效的死链（insert-if-absent，改 seed 文件只影响全新安装）。**PR9 shipped (2026-07-28, 0 迁移)**：飞书 H5 首屏 JS 从 1530 KB gzip 降到 531 KB —— 桌面 antd chrome 抽成 lazy 的 `DesktopChrome` 并按 pathname 门控（`/m*` 不挂）、`manualChunks` 按真实 dependents 重划 `ui-shared` / `antd-icons` / `antd-mobile`（antd-mobile 的 rc-* v5 依赖此前被 `rc-` 前缀规则误判给 `antd-ui`）、Vite `__vitePreload` helper 钉到 `react-core`（此前落在 mermaid chunk 里，害得每次页面加载都 eager 下载求值 2.3 MB mermaid）；140/140 移动页 chunk 不再 import `antd-ui`，`bootBundleContract.test.ts` 锁死引导路径无 antd。**PR10 shipped (2026-07-28, 0 迁移)**：发 PR9 时发现 `helix-web` 镜像**自 #2068 起连续 7 次 main 合入构建失败**（`context: ./web` 看不到仓库根的 `contracts/`，而生产 `tsc -b` 把测试文件也编译了），生产前端一直停在 `b56a8e0b0` —— PR2~PR9 的前端改动此前均未上线。修法：`tsconfig.app.json` 排除测试、测试类型检查移到 `tsconfig.test.json` + `pnpm typecheck`（CI 原「Type check」步骤是 no-op，一并修掉）、`web/Dockerfile` 把 `pnpm@latest` 钉到 `9.15.9`。**未决（运维）**：main 上 `image-release` 失败无任何告警，正是它让这件事烂了七次合入才被发现。**PR11 shipped (2026-07-28, 0 迁移)**：八 PR 合入后两轮独立扫描（安全 + 逻辑）+ 三轮回归确认的全量修复 —— **PAT 明文被幂等缓存写进 DB 存 24h** 且 replay 绕过 `require_browser_session`（`redact_cached` + 闸改 `Depends`）；14 个 scope 补调用者身份（`lookup()` 在 handler 体之前返回，体内鉴权在 replay 上不执行）；弃置接管对不可逆 fan-out 不安全（`takeover_minutes=None`）；延迟释放会标记**别人的**预留（改按预留行 id 匹配，已被可执行测试证明）；崩溃遥测走共享 client 导致 401 强制登出循环、`extra=` 字段其实一条都没进日志、生产版本戳恒为 `dev`；PAT 重试撞 409 变「创建失败」而令牌已存在（新增 `IdempotencyInProgressError` 独立 code）；契约扫描器漏掉页面级直调（两处席位回收无键）；假期同步在 SF 全挂/零实例时报「已同步」（三个前端 + MCP 孪生）；markdown 无语言围栏在两端都渲染错。**循环中三次抓到「修复引入的新回归」**（行跨度判块 → hydration 错误、`no_hris_instance` 文案说反、`min_length` 拒掉中文单字搜索），均在同一轮内修掉。见 [docs/CHANGELOG.md](docs/CHANGELOG.md)。

**No replacement for the shipped platform phases is queued.** The platform is feature-complete relative to its original charter. Future work falls into three lanes: (1) operability hardening, (2) self-service expansion, (3) optional integrations. Data-access management V1 is now active in lane (2) as an optional self-service expansion; DAX has shipped the M1 semantic request lifecycle, M2 observe foundation, M3 apply mutation path, the first M4 request authoring/detail/group-access/admin operations surfaces, and the first M5 catalog-federation ingestion slice (contract + constants + Feishu flow mapping gate + bootstrap parser + read-only backend router/service/schema skeleton + `data_access_resources` resource projection with catalog labels and catalog ids + `data_access_entitlements` read:v1 semantic catalog + `data_access_requests` / `data_access_request_items` semantic intent tables + `data_access_bindings` semantic desired-state table + `data_access_compiled_effects` compiled desired-output table + requestable BigQuery dataset validator + Google Group principal validator + owner/member requester authorization + dry-run compiler + Feishu payload builder + stateful request submission service that creates the pending data-access request, linked approval request, and Feishu instance, plus group-owner Feishu CC for member-submitted group requests, plus the Feishu approval callback transition, DAX-018 staleness hardening that creates Helix bindings/effects only after fresh revalidation, the DAX-019 Feishu-free revoke service that creates `revoked_pending` bindings/absent effects for authorized owner/admin/system cleanup paths, DAX-020 audit coverage for request submission, callbacks, binding creation, compile output, revoke intent, and failure states, DAX-021 `kind='data_access'` / `provider='bigquery'` driver registration separate from the existing GCP cloud driver, DAX-022 locator-scoped dataset IAM driver interface, DAX-023 BigQuery Service Account credential loading plus one-dataset IAM/read-model observation via the dedicated driver, DAX-024 observe-mode reconciler delta recording from persisted effects, DAX-025 row-level `FOR UPDATE SKIP LOCKED` idempotency plus explicit resource/effect narrowing for observe runs, DAX-026 per-effect observe/apply run history with request/binding reconcile roll-ups, DAX-027 unmanaged provider-side grant reporting for selected federated dataset locators, DAX-028 negative guard coverage proving no project IAM, `bigquery.jobUser`, broad BigQuery scan, or cloud-grant path exists, DAX-029 BigQuery dataset-IAM least-privilege credential proof for `get` / `getIamPolicy` / `update` / `setIamPolicy`, DAX-030 dataset ACL canary planner that preserves unrelated/conditional `access` entries and appends canonical `groupByEmail` grants before future `UPDATE_ACL` writes, DAX-031 guarded driver-level `set_dataset_iam` apply/revoke with apply-mode, Terraform-managed dataset scope, `If-Match`, and provider 409/412 conflict handling, DAX-032 audited reconciler apply gate that classifies apply-eligible pending effects without mutating desired state, DAX-033 reconciler-owned apply-present runner that can add allowed Google Group dataset-reader grants for allowed datasets with run history, roll-ups, and audit, DAX-034 reconciler-owned apply-absent runner that can remove exact revoked-binding Google Group dataset-reader grants for allowed datasets, promote revoked bindings, and audit idempotent/conflict outcomes, DAX-035 already-present adoption proof that records/audits `already_present_at_first_apply=true` for exact existing provider grants and proves later revoke removes that exact effect, DAX-036 failure-semantics proof that BigQuery auth/read failures stay retryable/error without a provider write and partial absent failures do not mass revoke unrelated locators, DAX-037 end-to-end apply audit-trail proof tying request/callback/binding/compile/apply/revoke/provider outcomes to traceable audit and effect-run evidence, DAX-038 HTTP request facade plus `/my/data-access/requests` UI for one Google Group, fixed `read`, many datasets, one Feishu approval per submission, and no dataset-picker enumeration for employees who are neither group owners nor live group members, DAX-039 read-only request detail API/UI for Feishu status, item/staleness state, compiled preview, apply rollups, provider run history, resource-owner-scoped projections, and no-leak authorization, DAX-040 read-only `/my/data-access/group-access` UI plus `GET /api/v1/data-access/principals/{principal_email_group_id}/access` for group owners/admins to inspect active, revoke-pending, and revoked-history dataset bindings without single-user access or provider scans, DAX-041 admin failed-apply queue plus `/admin/data-access/failed-apply` UI with audited retry into the existing reconciler path and no reapproval, DAX-042 global-admin `/admin/data-access/reconcile-runs` UI plus `GET /api/v1/data-access/admin/reconcile-runs` for backend-owned reconciler run-history inspection with provider outcome/error/trace evidence and no provider call, DAX-043 visible notices that show admin-only suspended catalog rows and deleted-group revocations while preserving submit blocking before Feishu/provider state, DAX-044 data-access i18n lint guard that keeps all data-access UI copy localized across en/zh/ja, DAX-045 `catalog_feed.project_catalog_dataset_feed(...)` for audited idempotent catalog feed upsert into `data_access_resources` without raw-catalog reads in request/callback/revoke paths, DAX-046 callback staleness that classifies catalog `source_version` / `source_etag` drift as `resource_catalog_version_stale` before any binding/effect writes, DAX-047 catalog-missing suspension that turns omitted active catalog-owned rows into audited suspended resources without auto-revoking existing bindings/effects, DAX-048 confirmed resource removal that compiles revoke-pending absent effects only on explicit deletion/removal confirmation, DAX-049 deterministic bootstrap replacement by catalog id/provider locator with owner-mismatch and history-preserving locator-drift suspension, DAX-050 global-admin cutover validation report for bootstrap/catalog/suspension/duplicate/owner/drift/invalid blockers, and the 60-second `data_access.bigquery_apply_reconcile` scheduled worker runner that drains pending/retryable present/absent effects through the existing apply-mode-gated BigQuery reconciler; service-account MCP submission is available, while no request/callback direct BigQuery mutation path, broad provider mutation path, table/single-user/project IAM, `bigquery.jobUser`, dataset creation, manual catalog UI, Feishu template authoring, or Google Group membership reconciler).

**AWS org cross-account fan-out — shipped but gated OFF, waiting on one IAM grant (2026-07-21, lane 1):** Cloud efficiency (inventory + utilization) is live for GCP/Azure/AliCloud with real prod data, but **AWS shows 0 machines** — the `aws-primary` credential is the Organization management/payer account (`317766989874`, self 0 EC2); all compute is in 90 member accounts it can `ListAccounts` but not `sts:AssumeRole` into. The cross-account fan-out (`org_fanout` opt-in on the AWS driver, default OFF, changes nothing until enabled) is merged. **Blocked on the AWS org owner granting the management user `sts:AssumeRole`** (quick: built-in `OrganizationAccountAccessRole`; least-privilege: a `HelixCloudReadOnly` StackSet — see [docs/ops/cloud-lifecycle-ops.md](docs/ops/cloud-lifecycle-ops.md)). Requested via cloud team (Plan A) 2026-07-21. When granted: set `org_fanout=true` on `aws-primary` + enable the AWS inventory/metrics crons → 90 accounts flow in, new accounts auto-covered. See `reference_aws_org_two_integrations_assume_wall`.

**DAX M4 request surface start (2026-06-11; updated 2026-06-26):** group owners, live group members, and global admins can author the V1 request shape from `/my/data-access/requests`: one active Google Group they own/administer or belong to, entitlement `read`, and many requestable BigQuery datasets with resolved owner authority. The router facade delegates to the existing no-provider-mutation submission service, gates dataset-picker enumeration to request authors, and member-submitted Feishu instances CC synced group owners after creation; the regression proves 10 datasets still produce one semantic request and one Feishu approval.

**DAX M4 request detail surface (2026-06-11):** `/my/data-access/requests/:requestId` now reads a backend-owned detail model with Feishu status, item state, staleness reason, compiled preview, apply status, and recent provider run history. The detail endpoint is read-only and does not call BigQuery; resource-owner-only readers get only their owned items/effects/runs, while unauthorized employees receive 404 without dataset/group leakage.

**DAX M4 group access view (2026-06-11):** `/my/data-access/group-access` now lets current Google Group owners and global admins inspect Helix-owned BigQuery dataset `read` bindings for one active Google Group. The backend projection shows active, pending-apply, failed, revoke-pending, and revoked-history rows from semantic bindings/effects/run history only; unowned groups return 404 without dataset/group leakage, and no single-user access or provider scan surface is introduced.

**DAX M4 failed-apply operations (2026-06-11):** `/admin/data-access/failed-apply` now gives global admins a Cloud Assets operations page for failed/retryable compiled effects. The backend queue reads Helix effect/binding/resource/run state only, and retry records an admin retry run/audit, moves failed effects back to retryable, then delegates to the existing apply-mode-gated reconciler apply runner without creating a new Feishu approval.

**DAX M4 run-history operations (2026-06-11):** `/admin/data-access/reconcile-runs` now gives global admins a read-only Cloud Assets operations page for reconciler run history. The backend endpoint lists V1 BigQuery dataset `read` effect-run rows with current semantic effect/binding/resource/Google Group context, provider outcome/error/etag, trace id, and mode/status filters; it reads Helix run history only and never calls provider APIs.

**数据仓库 一级菜单 + 治理总览 (2026-06-30):** the BigQuery data-access surfaces are regrouped under a new top-level 数据仓库 menu (clicking the 一级 title lands on a page; the first child is the same landing). Employees land on `/my/data-warehouse` — a rich usage guide (申请流程 Steps + 使用逻辑 cards + a live requestable-dataset count). Global admins land on `/admin/data-warehouse` — a governance overview backed by the new read-only `GET /api/v1/data-access/admin/overview` (active datasets / active bindings = permissions / people-reach = Σ active-group members / pending-approval / applied / failed+retryable + apply-mode banner + binding-state donut + top-datasets bar + top-groups & recent-requests tables). The former 失败执行 + 执行历史 admin pages are merged into one 数仓访问历史 (two tabs) under the same menu. Read-only dashboard, no MCP.

**DAX M4 visible notices (2026-06-11):** suspended catalog-missing datasets are now visible to global admins in `/my/data-access/requests` but disabled with a backend notice, while employee request authors still see only active/requestable datasets. `/my/data-access/group-access` now renders backend notices for catalog-missing suspensions and `group_deleted_without_bridge` revocations; deleted-group DAX access rows remain admin-only and do not open provider scans or a new mutation path.

**DAX M4 i18n closeout guard (2026-06-11):** `npm run lint` now includes a data-access frontend i18n guard for `web/src/pages/dataAccess/*.tsx`: no hardcoded visible JSX/Ant Design/message strings, all static `t(...)` keys exist in en/zh/ja, dynamic `dataAccess.*` namespaces exist, and the flat `dataAccess.*` locale namespace stays in parity. This is a guardrail only; it adds no backend authority, provider path, or new UI workflow.

**DAX M5 catalog feed projection (2026-06-11):** `project_catalog_dataset_feed(...)` now validates catalog-shaped BigQuery dataset feed entries and upserts them into `data_access_resources` with `owner_source='catalog'`, owner authority, labels, optional catalog id, requestability/status, source version/etag, source payload digest, and audit outcomes. Request validation, Feishu callbacks, revoke services, and compiler paths still read only the projected resource row and do not import raw catalog feed input.

**DAX M5 catalog staleness (2026-06-11):** approved Feishu callbacks now distinguish catalog `source_version` / `source_etag` drift from generic owner/locator context drift. Version or etag changes while a request is pending mark the request `stale_requires_resubmit`, mark items stale with `resource_catalog_version_stale`, audit the refusal, and create no bindings or compiled effects.

**DAX M5 catalog-missing suspension (2026-06-11):** authoritative catalog feed omissions now suspend previously active catalog-owned BigQuery dataset resources with an audited `catalog_missing_suspended` outcome. New request authoring is blocked by the existing inactive-resource validator and DAX-043 notices, while existing bindings/effects remain unchanged and no revoke/provider mutation is created.

**DAX M5 confirmed resource removal (2026-06-11):** provider-confirmed catalog deletion and trusted admin removal now move the resource to `status='deleted'` / `requestable=false`, convert active bindings to `revoked_pending`, and compile absent effects from the original grant-time target snapshots for the reconciler. This is the explicit removal path; catalog-missing suspension still preserves existing bindings/effects without auto-revoke, and no request/callback/provider direct mutation path is added.

**DAX M5 bootstrap replacement (2026-06-11):** catalog and bootstrap dataset inputs may carry `catalog_id`, persisted as `data_access_resources.catalog_resource_id`. The catalog feed matches by catalog id before provider locator, preserves resource id plus requests/bindings/effects/run history on owner-resolved replacement, rejects duplicate bootstrap/catalog ids, suspends owner-mismatch rows without granting bootstrap or catalog resource-owner authority, and suspends catalog-id locator drift with existing history instead of silently retargeting applied access. No Feishu approval, revoke intent, absent effect, provider call, or new UI/API surface is added.

**DAX M5 cutover validation report (2026-06-11):** global admins can now call `GET /api/v1/data-access/admin/cutover-validation` to read a Helix-semantic report over `data_access_resources`, request items, bindings, compiled effects, and effect runs. It counts bootstrap remaining/requestable/historical, catalog-fed active, suspended, duplicate locator/catalog id, locator-drift-with-history, owner-mismatch, unresolved/fallback owner, invalid, unmatched bootstrap, and unsafe unmatched-bootstrap revoke cases, and returns `cutover_ready` only when blocking categories are zero. The report is read-only and creates no Feishu approval, revoke intent, absent effect, provider call, or raw catalog/bootstrap read path.

**DAX prod wiring (2026-06-24, updated 2026-06-25):** the DAX stack now has explicit operational entry points for production rollout: a manual GitHub Action deploys the repo-stored Feishu approval definition and upserts the Helix approval-flow mapping, `seed_data_access_resources.py` loads dataset/owner inventories with per-dataset descriptions, and the `data_access/bigquery` integration exposes Terraform-managed BigQuery service-account credentials and dataset scope from the repo seed, plus the runtime apply switch through normal integration config. Migration 341 seeds `data_access.bigquery_apply_reconcile` as a 60-second scheduled job so approved requests no longer rely on an admin manually retrying pending compiled effects.

---

## What's shipped — by domain

### Foundation (done)
- Google SSO + JWT + refresh-cookie + Personal Access Tokens (PAT) for MCP/CI
- RBAC: 9 canonical roles (`global_admin`, `employee`, `auditor`, `asset_admin`, `asset_global_admin`, `cloud_admin`, `hr`, `finance`, `management`) + manager-derived scope (no DB role for managers). `management` (added 2026-05-02, migration 099) is a read-only leadership view: it sees every admin surface but is excluded from all 7 admin-broadcast notification fan-outs (cloud scope-request approvals, lifecycle escalations, provisioning failures, leaver SLA, HR digest) — leadership browses the platform without operational paging. `asset_global_admin` (全局资产管理员, added 2026-06-11, migration 275) is the manually-granted global tier of the asset domain: full asset write authority over EVERY managed location (all region gates bypass via `rbac.has_global_asset_scope`), zero authority outside the asset domain; holders co-derive `asset_admin` so every existing entry gate / frontend surface admits them unchanged. **Since migrations 535–540 (2026-08-24) `asset_admin` is scoped by managed LOCATION, not office**: it is auto-derived from `asset_location_admins`, and the office's admin list survives only as the 当地 Admin — a local IT contact for the person-anchored HR flows that grants no role. Editing the location master or any location's administrator list takes `require_global_asset_admin()` (global_admin + asset_global_admin), because that list IS the grant set.
- Identity model: `identity_persons` is the single authoritative person table; `auth_users` aliases it for back-compat
- Append-only `audit_logs` (DB trigger enforces insert-only)
- Trace-ID middleware + standard envelope (`success / data / meta / errors`) on every response
- Notification outbox with retry, resend (clone a delivered row into a fresh dispatch), dead-letter, multi-channel (email / Feishu / Slack)
- Outbound copy is trilingual by contract (2026-08-09): [`app/core/locale.py`](server/app/core/locale.py) is the single recipient-language resolver, all 98 notification templates ship zh + en + ja (per-domain modules under `app/notifications/templates/`), and a parity test fails CI on a missing language, a placeholder that diverges between languages, or a `template_key` that is sent without a registered template
- Workflow journal — cross-domain step ledger powering the lifecycle person flow page
- MCP server (PAT-authenticated) at `/api/v1/mcp` for Claude / agent integration
- Idempotency keys, encrypted credential storage (AES-256-GCM via `CREDENTIAL_ENCRYPTION_KEY`)

### Identity & HR (done)
- SuccessFactors HRIS sync — full / incremental / window modes, multi-instance support (data residency)
- Staged HRIS sync flow: pull → diff → admin-confirm → apply (with batch failure isolation)
- Single-person on-demand refresh (2026-07-22): HR picks one employee → pull from SF → apply to Helix → push to Feishu (+ background fan-out to other targets), for when an SF record edit must reflect downstream now instead of waiting for the daily sync + per-target cron. Reuses the incremental staging + confirm machinery (`preview_single_person_sync` / shared `_stage_user_lifecycle_event`); refuses genuine hire/rehire/termination transitions (routes to onboarding/offboarding). `POST /hr/people/{id}/hris-refresh` + MCP `hr_refresh_person_from_hris`; HRIS-sync page card
- Lifecycle automation: hire-day IT notify, advance, post-leaver verify, daily digest
- **邮箱名黄金记录（2026-08-31，迁移 582，+1 cron，0 端点 / 0 MCP）**：HR 在 SF `PerPersonal.customString4` 指定的邮箱名此前只活在事件载荷上，由只认 `action in {create, reactivate}` 的 dispatch 闸读一次即弃 —— P10026 的 HR 在 create 两天后才填，正确值此后每 4 小时被读到一次共 40 多次、一次都没生效，Day-1 建号用的还是 11 天前派生的 `nicholas.muniz`。现在落成 `identity_persons.hris_email_name`（`_upsert_person` 每次 apply 都写，含 onboard `skip` 的 refresh-only 通道；apply-only 不回删），**建号那一刻**优先按它取 local-part，reconcile-first 同时探「HR 指定地址」与「存量 work_email」两个候选（任一查询报错 fail closed）。兜底 cron `hr.email_name_reconcile`（每天 08:20）：未建号且不符自动重跑 reserve，已建号且不符推卡给 `global_admin` 走人工 rename。与 #2020 / customString4 两次修复的区别是那两次修的都是「读不到」，这次修的是「读到了不用」。
- **Mover (转岗) — repaired 2026-08-29 (migration 565, +1 cron)**: previously a stub in practice. Production held onboard 557 / offboard 133 / **role_change 1** over fourteen months and all 5273 `cmdb_cloud_access_grants.review_status` rows were NULL — the org-move path had never been walked. Three independent defects each severed it: `is_initial_seed` was `sync_mode == 'full'`, so the WEEKLY reconciliation sync inherited one-time-import semantics and discarded every move it recovered; an active employee's staged `action` was unconditionally `skip` whenever the onboard ledger row existed, so the ordinary move (hire date unchanged) never reached the role-change gate on ANY sync mode while still being applied to the person row; and `title_changed` read a `person_data['job_title']` key staging never wrote, making it a constant `False` with `job_title: null` on both sides of every before/after blob. Now: `hr_sync_batches.is_seed` separates seed from reconciliation (resolved by predicate at batch creation, overridable via `?as_seed=`), org drift is an INPUT to the action decision, title is staged and compared cache-free, a symmetric **mass-onboard brake** replaces the safety net the seed-suppression was accidentally providing, and `role_change.dispatch_reconcile` (daily) asserts every staged drift row eventually has a lifecycle event — the confirm path calls the coordinator inline and the coordinator swallows its own exceptions, so a failed dispatch previously left no trace. Issue #623 policy is unchanged (no auto-revoke; grants flip to `pending_review` for a human, seats are previewed not applied, assets stay with the employee). **Pre-fix history is not reconstructable** — those moves landed as `skip`, the person row was rewritten in place, and manager changes leave no audit trail.
- **Mover review packet — 2026-08-29 (migration 568, +5 endpoints, +2 crons, +3 MCP tools)**: with detection fixed by 565, the mover's *review* was still one category wide. It looked at cloud grants only; the offboarding scanner has enumerated ~24 kinds of holding for years. Measured on production, a senior engineer's real holdings are **238 items across ten categories** — 23 of which no move had ever examined, and none of which lapse on their own. `hr_role_change_reviews` / `hr_role_change_review_items` now cover the same `RES_*` vocabulary offboarding uses (guarded by a structural test against drift), grouped **by category** because 238 rows is not a reviewable surface: one bulk answer per category, never overwriting an item somebody answered individually. **Routed to the NEW MANAGER**, not IT — the per-packet `reviewer_person_id` gate admits an ordinary employee with no admin role, which is the single change that makes the review answerable at all (an admin cannot know whether a colleague still needs a grant); a stranger gets 404 rather than 403 because the existence of a packet is itself a disclosure. The model works at category granularity and its narrative is discarded whole if it cites a number the packet never published (including arithmetic it did itself); `transfer` is outside its vocabulary because naming a successor is not a suggestion-box decision. Issue #623 unchanged: the packet records answers and executes nothing.
- **Cards became conversations — 2026-08-29 (migration 571, 0 endpoints)**: measured on production over 30 days, the platform delivered **3,785** notification cards while the assistant handled **98** messages — from **3 distinct people in its entire history**, with 14 proposals created and **zero** executed. The two surfaces were never far apart: cards go out through the same Feishu app the assistant listens on, into the same 1:1 chat, so a reply under a card always reached it. Three things were missing and all three are one link — `_send_feishu` discarded the API response (so the card's `message_id` was never recorded), `IncomingMessage` never parsed `parent_id`, and nothing told employees they could reply. Now a reply resolves back to its card and that card's `related_resource` is injected into the turn, so 「这台我早就还了」 has a referent; `root_id` falls back for a reply-to-a-reply. Only the recipient's own cards resolve (a forwarded card carries a real message id), the card BODY is never restated to the model (it was true at send time; the tools read live state), and every ordinary miss — a fresh question, a pre-571 card — degrades silently to the previous behaviour. Audit records `is_card_reply` and `card_context_resolved` because the two failure modes (nobody replies / replies resolve to nothing) look identical from outside and have opposite fixes. **Group chat remains deliberately out of scope**: the assistant's tools are RBAC-scoped to the caller, and in a channel the answer is visible to everyone — opening it needs a publishability rule, not just an @-mention convention.
- Termination-reason exclusion (2026-07-30): 人员分类转换 / 跨DC调动 / 兼岗结束 (EcEvenReason2 D03/D04/D07/D08, `EmpEmploymentTermination.customString85` resolved via `customString85Nav`) never close accounts — every offboard ingest path (webhook / full / incremental / single-person / window import) checks the per-instance `excluded_termination_reason_codes` config; webhook hits land as first-class `offboard_skipped` ledger rows; cross-DC transfer's target-tenant onboard is classified update (never a second mailbox/Google account) via `owned_company_codes`. ⚠️ needs post-deploy config on both prod integrations
- Leave / time-off (假期, 2026-06-23, migration 324): SF EC Time-Off mirrored as a first-class domain — `hr_leave_accounts` (one row per SF TimeAccount: 法定/福利年假 by year, 带薪病假, 调休假 each its own account) + `hr_leave_postings` (every ledger line, with running balance / creator / comment). Data-driven sync (`hr.leave_service.sync_leave_data`, all account types not just legal+welfare) derives the legacy `annual_leave_*` columns; daily `hr.leave_account_sync` cron + admin "refresh now". Self/manager/HR read scope: employee self-service `/me/leave` + admin/manager person-detail 「假期」 tab + `get_my_leave_balances` MCP tool
- Leave-health insights (休假健康洞察, 2026-06-29, migration 359 — PR1 backend): HR/management governance view that reframes **low annual-leave utilisation by BU / team / lead as a team-health risk signal** (not diligence). Utilisation = `used/entitled` over current annual buckets (entitled = upfront full-year grant); **relative-to-company-median** health bands (seasonality-fair, configurable via `hr.leave_insights.thresholds`); 调休 banked-overtime + 临期未休 surfaced separately; min-group-size privacy guard; lead = full reporting subtree. Read-only (`require_hr_read`), **no pushes**. 5 `/hr/leave/insights/*` endpoints + xlsx export + daily `hr.leave_insight_snapshot` trend cron + `get_leave_health_insights` MCP. Frontend (PR2, 2026-06-29): `/admin/hr-tools/leave-health` dashboard — KPI strip + reverse-intuition explainer + By-lead/BU/team ranking (health-coloured utilisation bars) + health-distribution donut + drill-in drawer (member detail + trend line); three-language i18n. Review hardening: `uncovered` state (0-covered teams — non-CN, since SF EC Time-Off only carries the CN entity; 237/419 covered) split from `insufficient_data` + hidden by default; drill-down privacy guard (members/trend 4xx for sub-min groups, metrics redacted); FY-from-data + pace-floor
- **年假到期提醒（2026-08-17，2 PR 全部交付，迁移 506+507，+1 MCP→hint 479，+1 收尸 cron）**: HR 需求「2025 年发放的年假 9/30 清零，提前提醒员工休假，并在通知里呈现该员工当时的额度」。到期判定平台早就有（`forfeits_at_period_end` + `expires_in_days`），缺的是主动告知。新增 `hr_leave_reminder_batches` + `/api/v1/hr/leave/expiry-reminder/*`：HR 自选口径（假期类型码表从真实数据长出来、财年、剩余阈值、到期窗口）→ 全量名单预览（含覆盖面与 HRIS 新鲜度）→ 真实收件人渲染过目 → 群发（飞书互动卡片 + 邮件 + 站内文本快照）→ 批次送达记录 / 只重发失败项。**HR 自助改三语文案**（`email_template.hr_leave.expiry_reminder` 覆盖层，写入前挡未知占位符/位置参数/空必填）；**预览即发送**（HR 确认时的数字冻结进批次快照，后台只读快照）；**没有任何自动提醒** —— 唯一的发送入口是 HR 手动触发，「不新增员工自动推送」的规矩不动（新增的 `hr.leave_reminder_reap` cron 只给僵死批次收尸，永不发送）。RBAC = `require_hr_admin()`（平台第一次把通知群发交给非 global_admin，写侧另加 `require_browser_session`，MCP 只读）。**PR2 前端已交付**：`/admin/hr-tools/leave-expiry-reminder` 三 tab（圈人与发送 / 文案与预览 / 发送记录）+ 新写的飞书卡片预览组件（`card_stats` / `card_actions` 画成飞书卡片的样子，全仓此前没有这类组件）；勾选即发送对象、口径一动清勾选、改文案不清名单。**收口复审已完成**（三路复审：正确性/回归、安全、逻辑口径 → 11 条必修 + 12 条应修全部修掉，其中最重的一条在共享原语 `enqueue_then_drain` 上：phase-1 rollback 会 expire 掉后续 envelope 的 recipient 实例 ⇒ 一行失败级联到全部失败；并给它加了 `on_progress` 回调让心跳不必分块）。**三轮复审全部收口**（第三轮又在共享原语上抓到「级联修复只修了一半」——第 3 行失败仍会让异常抛出 `enqueue_then_drain`，影响全部 5 个调用方；已按 `fail_on=1/3` 参数化 + 变异验证钉住）。已发布生产（2026-08-17）。**2026-08-18 按 HR 修改单改版**：福利年假（`CHN_PL`）确认同样 9/30 清零 ⇒ 进 `_DEFAULT_FORFEIT_CODES`（员工假期卡片的倒计时、休假健康洞察的「临期未休」、提醒措辞三处同时纠正，**临期未休总数会变大 —— 那是此前少报，不是回归**）；圈人门槛从账户级改成人级合计（余额 0 的桶照常展示、两个桶都为 0 的人不发）；卡片额度区只留放大的余额（新增 `[h]` 行内标记）、按钮收敛成一个指向 HR 指定的请假入口（出厂值是飞书 applink）、副标题关闭、三语文案重写。 **第二轮（迁移 516）**：真机截图暴露「放大数字」一直没生效（stat grid 二次加粗 ⇒ 嵌套 `**` 把加粗和放大一起丢掉，`[r]` 也一直是坏的）已修；格子标题改成「截至目前未休法定年假(FY26)」（FY 取**周期结束年**，比 `core/fiscal_year.py` 的编号大一年）；新增**不提醒名单**（默认不勾选、不是硬闸）、名单搜索、以及 HR 可维护的**结尾寄语**（随机/固定，随机按批次且可复现）。
- Org tree (manager_person_id self-reference) drives Manager scope at request time; the 组织架构 tree view (`get_org_tree`) lists only active/leaver_pending people (departed hidden, 2026-06-17)
- Concurrent-employment aliasing (2026-08-18, migration 514): `identity_hris_user_aliases` maps a person's SECOND SF `userId` onto their canonical row, so `EmpJob.managerId` / `EmpJobRelationships.relUserId` pointing at a global-assignment employment still resolves. SF keys people by `personIdExternal` and employments by `userId`; Helix keys people by `userId`, and before this 6 active employees whose manager holds two open employments resolved to no manager at all (and the backfill re-missed them every sync). Resolution order is primary column → alias table; also stops duplicate userIds from re-staging as phantom new hires and from resurrecting retired ghost rows.
- Department→platform-role auto-derivation (2026-06-17, migration 296): Finance BU members auto-hold `finance`, HR members auto-hold `hr`. Real-time on HRIS membership upsert + daily `identity.department_role_reconcile` backstop + one-time backfill. Guarded by `auth_user_roles.grant_source` (`derived` vs `manual`) so manual grants are never clobbered; department→role map overridable via `identity.department_role_map` setting
- Employee-number system (工号系统, migrations 249 + 252): standalone allocation ledger `hr_employee_numbers` (format `P`+5-digit, `MAX(seq)+1` auto-increment, seeded once from `identity_persons.employee_no`); HR-tools page with ledger + **工号维护** (manual next-number override, forward-only, operator + reason logged in `hr_employee_number_overrides`) + report tabs, **一级部门 (BU) field** on create / CSV (validated against live BUs, backfilled from primary-membership org_path), CSV import (skip-bad-rows) / export, **per-row 维护 + 删除 by creator/global_admin** (工号 immutable; hard-delete frees the number so deleting the current-max reclaims it, a gap below a higher number is not reclaimed; both audited), 5 MCP tools
- Email aliases (per-person) + Google Workspace mail group ownership/transfer + employee self-service membership view & leave (我的邮件组 tab; live `groups.list(userKey)`, sole-owner-leave guard, owner notify-on-leave, recorded as `google_group_leave` request, owner inline disband, Cloud Identity dynamic-group detection → block leave & point to IT, indirect-membership note) + **self-service 申请加入 with in-Helix owner approval** (`email_group_join_requests`, owner/HR/IT-admin decide via directory 待审批 drawer + mobile action inbox, approve → Google `members.insert`; owner member-management drawer view/remove; groups.google.com management-link hint; MCP `request_join_email_group`/`list_pending_group_join_requests`/`decide_group_join_request`) + **HR dynamic email-group management** (2026-07-22, migration 424, `/api/v1/email-groups/dynamic/*`; structured rule builder over synced HR attributes, expected-vs-actual reconcile, `google.dynamic_group_drift` cron flagging dead values, direct-effect HR edits audited + IT-notified, RBAC `can_manage_dynamic_email_groups`; +5 MCP tools; ⚠ needs SA DWD write scope `cloud-identity.groups` before writes work) + **邮件组管理 — 全量后台管理面 + 操作日志** (2026-09-03, 0 迁移, `/admin/email-groups/*` host 页三 tab: 邮件组 / 动态组 / 操作日志; `/api/v1/email-groups/manage/*` 只读估算面; 生产实测 760 在用组 = 40 动态 + 720 静态，此前那 720 个在后台无任何管理面; `google.group_sync` 开始把 Google 侧变化(组被删/负责人被换/改名)写进审计，稳态 tick 写 0 行; RBAC 闸更名 `can_manage_email_groups`; +4 MCP tools → hint 569) + **静态组写面全量** (2026-09-03, 0 迁移, 加/删成员(批量、逐地址结果、允许外部地址)、给/收负责人(实时读 Google 数负责人、拒绝留下无 owner 的组)、改名与描述(走 Directory API,无新 scope 前置)、别名增删、HR 建组(不强制创建者当 owner);自助别名流程补一条 scope 在组上的审计镜像;+5 MCP tools → hint 574) + **空静态组清理工作台** (2026-09-03, 0 迁移, 候选池=静态+0成员(生产 133 个); 逐行信号(被别的组嵌套/真实别名/Helix 配置在用=阻断,探测未运行=indeterminate 且永不降为低风险,名字像基础设施=启发式警告); 响应/横幅/确认弹窗三处自报盲区(云 IAM group 绑定、邮件流量 Helix 都看不到); 执行侧单批≤20、原因≥10字、逐组重校池谓词 + **删前实时复核 Google 成员**、Google 删失败不软删、一次拒绝不中断整批; 每组一条带 batch_id+sequence 的审计,软删让原地址在操作日志里仍可搜; +2 MCP tools → hint 580) + **合并后对抗式复审 28 处修复** (2026-09-03, 0 迁移, 十个复审角度; 最重的四条:add_members 会降权最后一个负责人且审计写成 member_added、管理台建的组 is_dynamic=NULL 导致后续操作全 409、五个 @idempotent 写不带调用者身份使 replay 绕过体内鉴权、audit savepoint 反噬掉它正在记录的那次 reconcile; 另修跨域日志泄漏/别名三态/嵌套探测 404/清理批次清单丢失/前端抽屉过期改名回滚等; +2 MCP tools → hint 582) + **复审第二轮 30 处修复(修复本身的缺陷)** (2026-09-03, 0 迁移, 0 新 MCP → hint 仍 582; 最重的五条:我声明的「Helix 看不到云上 IAM」对爆炸半径最大的那一半是错的 —— `data_access_bindings`/`data_access_compiled_effects` 都带索引外键指到 `email_groups.id`,两句 IN 就能查,现补 `data_access_principal` 阻断信号并把盲区改名 `cloud_iam_outside_helix`;`type:"CUSTOMER"` 成员有 id 没 email,按邮箱归约让「全公司在组里」的组通过了硬删最后一道闸,而镜像计数走同一套归约所以两道闸一致地答错;`already_absent` 在降权/直接移除两个分支含义相反,且 raise 排在镜像清理前留下清不掉的幽灵负责人;唯一负责人退出闸读镜像,镜像滞后会让组落到零负责人;`aliases` 三态被 `real_aliases()` 压平使从未同步的组能进 `risk: low`。另修:`useIntentKey` 返回不稳定对象让建组弹窗每敲一字清表单、幂等 key 被钉住使「只重试失败项」422、抽屉在写操作把行挤出筛选后整块空白、`deleted_record_failed` 三语无文案、owner 授予审计记成 `member_added`、`group_exists` 用写 scope 在只读租户上永久 indeterminate、配置引用正则只锚左边、`sync_alias_changed` 事件补齐、MCP 写工具 `reason` 无上界、`kind=unknown` 有横幅无筛选、批次汇总审计裸奔在 try 外、`management` 被自动提权到整页 403;HR 行内历史从 `/audit/resources/history` 改走本域已按动作过滤的操作日志 tab,`app/audit/router.py` 与 main 的差异只剩注释) + **复审第三轮 19 处修复** (2026-09-03, 0 迁移, +1 只读端点 `GET /manage/groups/{group_id}`, 0 新 MCP → hint 仍 582; 逐行/安全/逻辑三个角度**各自独立**把同一条排在最前:`db.flush()` 紧接 `raise` 等于没写 —— `get_db` 在异常上整体 rollback,所以第二轮那个「409 前先清幽灵负责人行」的修复在生产里从未生效,而 409 正文写着「已同步清除」;这一类本仓功能测试一条都抓不到(conftest 把 get_db 换成裸 yield),补静态闸 `test_no_flush_then_raise`,并把该分支改成 200 + 独立 `already_absent` outcome。另修:抽屉的 `lastResolved` 兜底把第一轮修掉的「旧名字回滚 Google」又装回来了 → 新增按 id 取组的端点,抽屉自己取,父组件筛选在构造上不再相关;实时唯一负责人核验让一次 Google 抖动干掉全平台自助退出 → `None` 退回读镜像且只在安全一侧;通知在 FOR UPDATE 窗口里发真 HTTP → 改 `enqueue`;20 路并发从来没成立(to_thread 默认 executor 只有 6)且 500 行页面能饿死 Day-1 建号 → 并发 6 + 200 行探测预算,超预算标 `nesting_unknown`;别名占用无锚点 LIKE + 无排序 LIMIT 20 会漏掉真占用方;补镜像分支 UI 走不到且没审计;`是`/`否` 混进英日正文;编译效果 `.limit(500)` 会连阻断结论一起截断;`transfer_owner` 不通知被转走的人;`list_members` 仍按邮箱过滤使台账与抽屉互相矛盾;别名三态在管理别名那块屏上仍被断言成「没有别名」;员工端唯一负责人永久禁用退出按钮) + **复审第四轮 14 处修复** (2026-09-03, 0 迁移, 0 新端点, hint 仍 582; 三个角度独立重叠五条,其中三条同一形状 —— **上一轮把后端那一半修绿了,红的搬到读的那一半**:后端刻意返回 `remaining_owner_count=None`,前端 `?? 0` 把它变成绿色成功提示里的「该组还剩 0 名负责人」(虚报 LAST_OWNER 警报,而这次 Helix 什么都没改);`data_access_principal` 的 `live_bindings` / `present_effects` 声明了没人读,而这个信号在普通情形(已批准未下发)下就是 `datasets: []` → 红色阻断标签配空 tooltip;`email_group.owner_mirror_repaired` 注册了但三个 bundle 一个标签都没有,而 i18n parity 闸抓不到(三边缺得一样平)→ 补跨侧测试,后端测试直接读 `web/src/i18n/*.ts` 对齐注册表。另修:无地址成员那一行动作列写着「需在 Google 控制台操作」而旁边勾选框可勾,全选会 POST `emails: [null]` 把整批 422;抽屉墓碑闸读的是自己还没 fetch 回来的 group 所以永不成立,软删行点「管理」必定红 toast;新端点鉴权闸写在路由体里(本仓已付过代价的形状)→ 移进 service 并补 403 与路由顺序两条测试;超出预算未探测的行走的是「探测失败」分支 → 新增 `nesting_not_probed`;导出的审计行记 `nesting_probed: true` 而那张表里可能有 300 行从没探测过;`dynamic_service` 的别名删除在 Google 什么都没删时仍记 `success`,而静态那一侧记 `noop`;两个 MCP owner 工具的注释谎称 `email` 会进 LIKE;`schemas.py` 契约文档与前端注释还写着「UI 禁用退出」) + **复审第五轮 15 处修复** (2026-09-03, 0 迁移, 0 新端点, hint 仍 582; 换成**整功能集成视角**,最重的一条不在任何一次 diff 里 —— 台账每行的「删除」按钮只有角色检查 + 4 字原因就直接 Google 硬删,而同一块屏上的清理工作台为同一操作建了四道「删了会连坏别的东西」的闸(被嵌套 / 真实别名 / BigQuery 授权主体 / Helix 配置引用)⇒ **工作台的每一道闸都能按行逐个绕过**。现在单组删除跑同一套信号,阻断即 409,只有 `can_delete_any_mail_group` 能勾「已确认阻断信号」强制执行,且两种情况都把 risk 与信号写进审计 detail;刻意不纳入「组是空的」——owner 解散自己有成员的组是正当操作。另修:`_probe_nesting` 失败分支按小写做 key 使大小写混排行的真实故障被报成「从没探测」;补镜像对解析不到人的地址记假的「已补齐」审计行;预算说明塞在 `nesting_probe_error` 里让「零失败」页面宣布探测失败;`nesting_not_probed` 的 detail 用了前端不读的 key ⇒ 空 tooltip;`afterChange` 与成员 effect 双发 `members.list` 且无顺序保证 ⇒ 补 seqRef;批量「全部已不在组里」提示没排除失败;`dynamic_service.remove_alias` 记了 noop 却不告诉调用方 ⇒ 绿色「已删除」而别名还列着;删除弹窗用现在时断言镜像成员数;20 个拒绝的清单会撑爆审计 16KB 上限把整个 detail 连批次原因一起丢掉 ⇒ 改成逐组 `cleanup_refused` 行;别名增删是唯一没有原因的写,而它能解除阻断信号;`db.get(..., options=[selectinload])` 对 identity map 里已有的实例不应用 loader ⇒ 不可逆删除的归属检查上会 MissingGreenlet(生产至今没踩到正是因为每请求新 session —— 这才是它危险的地方)。三处「代码对、话错」:删除 payload 的 docstring 承诺了一个**根本不存在**的飞书通知;`sync_renamed` 也含只改描述且指的是显示名不是地址;建组时盖 `last_synced_at` 而 aliases 与成员数都是 NULL)
- Public holidays (法定假期, 2026-08-12, migration 462): the company-wide **multi-country** holiday calendar — Helix had no per-country holiday model at all before this (two source comments said so; the only holiday table was the month-end close engine's China-only workday calendar, deliberately left alone pending its own ADR). `hr_public_holidays` (one row per country/day/holiday **name** — the three-part key is required: HK observes two holidays on 2026-04-04, RO two on 2026-06-01) + `hr_public_holiday_calendars` (per country-year metadata; its existence is the coverage signal). `group_key` folds days back into holidays for the Word export and the notice generator. Seeded with HR's 2026 document: 22 countries, 322 dates, China's 6 调休 workdays, 22 Google Calendar links. HR surface: group-level CRUD, diff-based edit, Excel preview-then-commit import (never reverts an HR edit on re-import), xlsx + **Word export** (rebuilds HR's published document; round-trip-verified), `restore-defaults`. **放假通知生成器** renders the all-hands notice in zh/en/bilingual — text only, Helix never sends it. Employee 公示 at `/public/*` (own country first via `residence_country`, own-timezone "today", auto-widens when the home country is unmaintained). External API `hr.holidays:read` (§B.5) with a `covered`/`no_data`/`unknown_country` tri-state that never collapses "unmaintained" into "no holidays". +3 MCP tools. Employee 公示 at `/company/holidays` (all-employee, own country first via `residence_country`, own-timezone today, auto-widens with an explanation when the home country is unmaintained), a pull-only feed card that renders nothing rather than an error, and a mobile list view at `/m/holidays`. Statutory-holiday **pull** (`/suggest`, offline `holidays` dataset — recovers China's 调休 workdays from the swap references and matched the hand-transcribed 2026 baseline exactly) and a **roll-forward** that opens next year from this year's country roster without copying dates
- Multi-channel localization: `preferred_language` per person; zh-CN + en-US + ja-JP fully translated (three-language parity per the CLAUDE.md i18n hard rule — every new key ships all three)

### Asset (done)
- Hardware: items, categories, brands, models (curated + auto-created), locations, tag sequences
- Lifecycle ops: claim, return, borrow, transfer, disposal — voucher-based with pending/confirmed states
- Asset inventory cycles (盘点) — immutable per-cycle snapshots, response tracking, **IT 帮忙确认** (2026-07-31: asset admin records a response for a non-responding holder or for the holder-less `it_fallback` bucket; mandatory verification note + 「IT 代确认」 tag on every surface so a proxied row never reads as the employee's own answer)
- Procurement orders + suppliers + business payments + legal entities
- Office addresses (capacity, equipment) + company directory
- Employee self-service requests (laptop, borrow, return) — unified `employee_requests` table
- 我的工作 self-service surfaces — 我的申请 (unified request tracker w/ rich `RequestDetailModal` + 催办), 我的待办 (5-domain pending inbox), 我的已办 (**standalone page `/my/done` since 2026-06-15**: 列表 w/ `DoneDetailModal` rich detail + 报表 tab — type/status mix + 12-month activity trend via `GET /me/todos/done/stats`)
- **Financial lifecycle (2026-06-13, migration 283)** — per-category depreciation policies (straight-line / declining-balance, `asset_depreciation_policies`, NULL = platform default), monthly book-value materialisation cron (`asset.book_value_snapshot`), disposal P&L (proceeds vs. frozen net book value), per-asset financial timeline + company-wide financial-stats (NBV/accum-dep/repair-TCO by category/department/office, currency-exact) + hardware-finance insights tab + 3 read MCP tools. **`finance` role now reads the hardware fleet** (was excluded from `can_read_all_assets`), closing the cost-axis parity gap.

### Software seats & API keys (done)
- Software catalog with offboarding policy (`sso_cascade` vs `auto_revoke`)
- License plans (price, billing cycle, max seats, over-quota policy)
- Seat allocations with cancel-at-period-end (cron auto-revokes on month boundary)
- **One-time add-ons (增值项)** (2026-06-27, migration 348) — per-software `software_addon_items` (unit price × quantity envelope: AI credits, vendor person-days) + employee-requested `software_addon_charges`. Self-service via the same request→approval rails as seats (`request_type=software_addon`), but NOT seats (excluded from seat reconciliation / idle-reclaim / offboarding). Approved charges materialise into `software_monthly_bills` as `line_type='addon'` rows, so software + department/BU cost rollups include them with zero query changes. Pages: catalog "申请增值项" modal, `/admin/assets/software-addons`, `/my/software-addons`.
- Monthly bills frozen on close-month; reopen deletes + re-creates (never edits in place)
- **跨月区间汇总** (2026-07-30) — 「这些软件这段时间**分别**一共花了多少」:`GET /assets/software-billing/range-summary` (+ `export.xlsx`, MCP `software_cost_range_summary`) 按 `软件 × 币种` 汇总一个 `YYYY-MM` 区间(≤24 个月),已关账月读冻结快照、未关账月实时算(封顶 3 个月,超出的月份点名进 `excluded_periods` 而不是静默少算),币种分桶不折汇率;前端在软件费用页的「区间汇总」tab。USD 折算后的跨成本类型口径仍在 `/finance/spend/*`(另一个面,刻意不互相校验)
- API key providers + assignments with auto-grant for AWS Bedrock + Aliyun Bailian
- **转手即轮换 + 闲置月检(2026-09-05,迁移 603)** — `apply_transfer` 末尾入队一次轮换(`apikey.rotate_on_transfer`,默认开;不可轮换的提供方发手动轮换卡);`apikey.idle_check` 每月 3 日按上自然月 $0 花费找闲置 Key,台账 `apikey_idle_alerts_dedup`,IT 月报始终发、持有人卡由 `apikey.idle_alert.enabled_for_employees` 控制(默认关),持有人可「保留(写理由)/ 释放 / 找 IT」;管理台「闲置」tab + `/me/api-keys/:id` 横幅。
- IAM-mode binding (per-key dedicated cloud IAM/RAM user, with cloud_scope_id chargeback)
- **Per-Key Bedrock cost attribution** (2026-07-29, migration 435) — when a person holds >1 live Bedrock key they share one AWS `Owner` cost-allocation tag and #1776 pins the whole total to the earliest-created key (the siblings previously rendered a flat `$0.00`, read as "Helix lost my spend"). `cmdb_bedrock_profile_facts` now carries a `profile_tag_key` axis, so the `helix-request-number` IAM tag `bedrock_autogrant` has always written can be pulled as a second CE breakdown and give each key its own number (`cmdb_cost_facts` untouched → every rollup unaffected). Unresolvable spend renders `—`, never `$0`, and links to the key carrying the total. **Live in prod since 2026-07-29**: `helix-request-number` turned out to be ALREADY active as a cost-allocation tag in `841162690310` (the step everyone assumed was pending never was), so enabling it was one setting — `cost_facts.aws_profile_tag.aws-sg = ["UsedBy", "iamPrincipal/helix-request-number"]` — plus a May–July profile-facts backfill (1,281 rows). Result on the reported key: `AK-20260710-B5F7` went `$0.00` → **`$10.67`** (Sonnet 4.6 $9.03 + Opus 4.6 $1.64, reconciles to the cent); `AK-20260430-DA42` moved from its holder's Bedrock total ($51.48) to its own tagged total ($53.04); the transferred `AK-20260428-B9C1` is untouched at $1,645.58. Per-key CE data only goes back to 2026-05 (April and earlier are 100% `(unallocated)` on this axis) — harmless, as every key on the axis was created end-of-April or later. See ops §9a-bedrock.
- Admin **Reports tab** (`GET /apikeys/analytics`, MCP `api_key_analytics`) — fleet distribution (provider / status / cost-attribution / department / owner), committed budget pool + actual MTD spend (per-AK dispatch, unresolvable buckets surfaced honestly), and lifecycle/volume metrics

### Google Workspace lifecycle (done — 2026-05-02, migration 103)
- **Auto-create on hire** — `hr_sync_coordinator.provision_google_account` calls `GoogleWorkspaceDriver.create_user`; `firstname.lastname@<domain>` from `name_pinyin`, FTE vs contractor split via `platform_settings.google.contractor_company_codes`, conflict suffix retry, 16-char password mailed to `personal_email` with `changePasswordAtNextLogin=True`. Master switch: `google.account_provisioning.auto_create_on_hire` (default off so a fresh deploy doesn't race the integration setup).
- **Auto-suspend on termination day** — seeded "Google Workspace" catalog ships with `offboarding_policy=auto_revoke` + `operation=user_suspend` connector binding; existing `hr.process_advance` (every 15 min) handles Drive/Calendar transfer to manager-fallback successor + suspend + flip status to `left`. Since the 2026-07-01 翻案 the offboarding **event path** (`_push_force_logout` → `account_lifecycle_coordinator.suspend_google_account`) account-level-suspends every non-retained leaver at `leaver_date+1` local.
- **Terminal delete — suspend → delete 2 days later** (done — 2026-07-08, migration 395) — new daily cron `hr.offboarding_google_account_delete` hard-DELETEs a leaver's suspended Google account at `leaver_date + delete_lead_days` (config, default 2, employee-local TZ). Only confirmed-suspended, non-retention-exempt accounts; rehired/active skipped; Google's ~20-day undelete window is the mistake safety net; no data transfer. Ledger `session_enforcement.google_account_deletion`. Closes the "suspend was a permanent end-state" gap.
- **Daily seat sync** (`google.seat_sync`, 03:30 UTC) — 3 phases: (a) reconcile base "Google Workspace" seats from tenant user list; (b) for any Google plan whose connector binds `operation=group_member`, mirror the Google Group → seats (Jira-style); (c) discover orphan / shared / service accounts.
- **Shared-account inventory + handover blocker** — `google_orphan_accounts` table; `/admin/google-workspace/orphans` admin page; `RES_GOOGLE_ORPHAN_OWNER` blocker added to offboarding scan; resolved by the same handover endpoint as `sso_grant` / `domain_registration`.

### Feishu seat sync (done — 2026-05-02, migrations 109 + 110)
- **`feishu.seat_sync`** (daily 03:00 UTC) — back-fills `software_seat_allocations` from the Feishu side roster. Two binding modes:
  - `operation=tenant_users` (default, recommended): mirrors every active Feishu account in the tenant onto the bound plan — same shape as `google.seat_sync` Phase A. Driver walks `/contact/v3/departments?parent_department_id=0&fetch_child=true` + `/contact/v3/users/find_by_department(fetch_child=false)` per dept, dedupes by `user_id`, drops `is_resigned` / `is_frozen` accounts. Email-matched against `identity_persons.work_email` + `email_aliases`.
  - `operation=group_member` (niche): mirrors one specific Feishu user group via `/contact/v3/group/{id}/member/simplelist` + batched `/contact/v3/users/batch` for email reverse-lookup.
- **Read-only enforcement on `tenant_users`** — `grant_seat` / `revoke_seat` return an error explaining that Feishu account lifecycle is HRIS-owned, not Helix-owned. `group_member` and `user_suspend` modes still honor the existing grant/revoke paths.
- **Import-only** by deliberate decision (different from Jira / Zoom / Slack which also revoke on drift). Feishu rosters are routinely edited by HR / IT for short-term reasons; auto-revoke would close real seats by mistake. SeatCancel + admin revoke remain the explicit close paths.
- **Plan discovery is connector-binding-driven, not catalog-name-driven** — `feishu_group` integration + binding `operation in {tenant_users, group_member}` + (when group_member) non-empty `group_chat_id`.
- Migration 109 seeds the `scheduled_jobs` row (`enabled=0`); migration 110 seeds the `feishu_group` integration row (also `enabled=0`, awaits admin to fill app_id + app_secret). The catalog / plan / connector binding stay tenant-managed through the UI.
- **Feishu prerequisites**: open-platform → permission management → 通讯录可访问数据范围 = "全部成员" + 发布版本; AND admin console → 应用管理 → Helix → 应用可用范围 = "全部成员". Verified end-to-end against PingCAP's live tenant (478 active users mirrored cleanly, 2026-05-02).

### NexusCRM connector (done — 2026-06-22, migration 315)
- **`nexuscrm` software-provisioning driver** — NexusCRM (`nexuscrm-new-prod.pingcap.com`, Salesforce-compatible REST/SOQL). OAuth `client_credentials` via the IT Connected App; a seat = active `User`, revoke = `IsActive=false`. Driver: `test_connection`, `list_full_members`, `revoke_seat`/`revoke_all_access`. Live-verified: 334 users / 146 active seats / 15 platform accounts auto-excluded.
- **`nexuscrm.seat_sync`** (daily 03:00 UTC) — imports CRM seat-holders into `software_seat_allocations`, drift-reconciles, classifies external/integration holders. Mirrors the slack/jira pattern + shared `_seat_sync_helpers` safety guards.
- **Offboarding = notify-only to jie.ni@pingcap.com** — catalog `offboarding_policy=auto_revoke` + connector binding `execution_mode=manual_notify` + `owner_person_id=jie.ni`; a leaver's seat creates a manual-notify task that DMs jie.ni. No auto-API revoke (decision 2026-06-22).
- **Open ops items**: (1) the IT Connected App run-as user is currently **read-only** (`api.read`) — grant it `api` (write) scope + User-edit permission, then `scripts/nexuscrm_wipe_seats.py --execute` clears the 131 current seats (15 platform accounts excluded); (2) `scripts/seed_nexuscrm_integration.py` fills the Consumer Key/Secret (migration seeds the row disabled, secrets empty) and enable `nexuscrm.seat_sync`.

### Provisioning & lifecycle automation (done)
- Unified `provisioning_tasks` queue across seats / permissions / API keys / hardware
- Three execution modes: `auto_api` (driver-dispatched), `manual_notify` (human task), `sso_auto` (handled by SSO suspension)
- Provision policies: NULL = wildcard, multi-criteria match (department × title × location), evaluated at onboarding + manual reconcile
- Quota enforcement with evict-LRU on max_seats + admin whitelist
- HR sync coordinator + per-product seat coordinators (Jira, Slack, Zoom)

### Offboarding & Leaver Blocked (done)
- Multi-phase offboarding state machine (`scanning → blocked → ready → completed`)
- Per-resource blocker model with explicit resolution (`pending / transferred / revoked_auto / revoked_manual / skipped / sso_cascade`)
- SLA breach levels (`none / warn / urgent / breach`) with manager + IT escalation, daily digest
- Asset transfer requests (peer-to-peer, accept/reject, idempotent)
- **代提回购 (admin on-behalf buyback)** (2026-06-30) — asset-admin starts the normal `offboarding_buyback` approval for a `held_by_leaver` asset on the departed employee's behalf (they can't self-submit; Feishu account frozen). Admin = Feishu applicant, stored requester = leaver (so approve-time auto-dispose attributes correctly); amount/last-working-day server-derived. `POST /approval/asset/requests/on-behalf` + 资产清单 row action + MCP `submit_buyback_on_behalf`.
- **Leaver Blocked guard** — SQLAlchemy event listener intercepts every `employment_status='left'` write at `before_flush`, queries pending blockers, raises if any. Includes background tasks, scripts, sync confirms.
- **Dual-sign override** — IT initiates with ≥30-char reason, named secondary signer (department director chain) reviews within 7 days, IT executes; full audit chain.
- **离职数据交接闸门 + 交接人保障** (2026-08-13, migration 467) — 终删(Google/Zoom 硬删)前必须证明数据交接完成:没有证据就地补做 Drive/Calendar transfer,解析不出交接人就**拒绝删除**并告警 IT(生产上 `alison.u@` / `hice@` 两个号已经被无交接删掉,详见 DECISIONS 2026-08-13)。Zoom 终删补上 `transfer_email` + 会议/网研会/云录制转移,推翻 2026-08-04「不做转移」的决定。交接人解析链:流程默认交接人 → 直属上级 → 上级链(≤5 层)→ 平台兜底人 `offboarding.default_handover_fallback_person_id`(离职控制台上有专用卡片)。新 cron `hr.offboarding_handover_reconcile`(02:40 UTC,排在终删之前)每日回填空的默认交接人,解析不出来的按流程一次性汇总告警 IT。
- **催办节奏 + 员工触达度量** (2026-08-30, migration 576) — 平台上「一直催到你按按钮」的推送(资产盘点人均 16.7 条/30 天、云 Owner 确认人均 7.6 条)改走统一阶梯 `1/1/1/2/3/5` 天、之后每周一次、**永不停止**:开着的事一直开着,第一个月 30 → 8 条。平台规则:**阶梯只能拉长间隔,永远不能收紧已有的下限**(被既有测试当场抓到 —— 叠在 20h 下限之上会丢卡,降噪静默变成改投递)。配套「员工触达」页签按**人均**而非总量报量,让「别给员工发太多」第一次成为可证伪的数。见 DECISIONS 2026-08-30。
- **账号终态保障 (account end-state assurance)** (2026-08-30, migration 575) — 回答与「Helix 撤了什么」不同的第二个问题:**这个离职的人现在还能不能登录**。两张新表(`offboarding_account_assurance` 每人×每系统一行 / `identity_chain_heartbeats` 依赖链心跳),两个新 cron(`jira.idp_sync_heartbeat` 每小时盯 Atlassian 侧 Google 目录同步的活性;`google.leaver_account_audit` 每日拉 Google 全量名册对账),离职撤权尾部由 Helix **自己**停用 Atlassian 登录(`suspend-access`,可 `restore-access` 回滚,**不删账号**、issue 历史不受影响),以及离职管理下的「账号终态」页签 + 两个 MCP 工具。起因:2026-08-13 Atlassian 侧目录同步被一次设置页保存静默关掉,16 天里每日对账天天报「干净」而所有离职者账号全都活着 —— Helix 能证明撤了权限,证明不了有人真的进不去。顺带把「保留豁免」拆出 `exempt_quality`:一条无负责人/无理由/无作者的豁免与「被忘了」在效果上等同,单独 KPI 单独筛选;自动停号**绝不**碰有豁免的账号。
- **离职告知信 · 审批批完即推** (2026-08-31, migration 581) — 通知期(审批完成 → 最后工作日,几周)此前**几乎无触达**(87 条流程里只有 6 位离职者在 LWD 之前收到过任何离职域通知、只有 2 位真的 delivered;HR 这封信本身此前不存在):`OffboardingProcess` 要等 SF 把 `User.status` 翻成 `'f'` 才建(生产 87 条里 80 条建在最后工作日当天或之后),而未来日期那几条的启动卡片被 future-leaver 闸推迟后**从未被任何 T+0 流程重放**(#1739 只补了会话切断那一半)。新 cron `hr.pending_leaver_notice`(工作时段整点)直接读 SF 的「审批已完成」判据(`status='t'` + 未来 `endDate` + 离职原因行,见 PLATFORM 同名小节),把 HR 的《离职流程指引》推给本人。范围:中国 + 正式(1070)/ 实习生(1072),命中 `excluded_termination_reason_codes` 的不推;两份三语模板,北京段落按**工作城市**而非 `office_code` 分流;幂等落在 `identity_persons.offboarding_notice_sent_at` + `offboarding_notice_last_day`(HR 改期才重推)。**未修**:上面那条「defer 后不重放」的缺陷本身仍在,单独一条。
- **Leaver platform-role cleanup** (2026-06-16, migration 290) — completing an offboarding strips a leaver's privileged platform RBAC grants (`global_admin / auditor / cloud_admin / hr / finance / management / asset_global_admin`) via `AuthService.revoke_platform_roles_for_leaver`; `employee` + reconciler-derived `asset_admin` left alone; last global_admin skipped+warned (never wedges). Daily `offboarding.revoke_leaver_platform_roles` cron is the backstop that backfills pre-existing leavers + post-leave drift.

### Cloud lifecycle (CMDB) — done & mature
- 8 cloud providers: AWS, Azure, GCP, Aliyun (RD), Aliyun Intl, Tencent, UCloud, KSyun (KSyun = servers/billing only)
- **2026-05-17 / migration 173** — network assets parity with legacy Go CMDB: per-resource modifier tracking (`last_modified_by_person_id` + `last_modified_at` on servers/domains/dns/lb/cdn, separate from sync-driven `updated_at`); LB `owner_person_id` FK + `app/remark` editable; admin direct DNS enable/disable + DNSPod & Aliyun DNS providers grew `set_dns_record_status`; DNS request approval flow learned `enable` / `disable` actions; legacy data migration script rewritten with manager-chain fallback (departed → first active non-CEO lead, else 孟凡生); AWS Route 53 Domains AccessDenied degrades gracefully; new `POST /cmdb/{servers,load-balancers}/batch-annotate` and `/cmdb/dns-records/batch-owner` admin tools
- Per-capability sync: `cmdb.resource_sync`, `cmdb.scopes_sync`, `cmdb.roles_sync`, `cmdb.grants_sync`, `cmdb.billing_sync`, `cmdb.billing_daily_sync`
- **2026-06-21 / migration 310 — 专线管理 (dedicated/leased line register)**: 网络资产菜单新增「专线管理」(`/admin/network/leased-lines`),`cmdb_dedicated_lines` 人工台账(运营商专线 + VPN 出口:使用位置/签约主体/运营商/付款方式/每月·年度费用/续费日期/用途)+ admin CRUD(写 `can_manage_cloud` / 读 `can_read_cloud`,含成本故 plain employee 不可读)+ 迁移幂等播种 IT 运营驾驶舱 8 行。无上游 provider 同步、无 owner、无 MCP(管理面台账)。
- **2026-06-09 / migration 267 — TLS/SSL certificate inventory + expiry alerts (PR1/3)**: `cmdb_certificates`台账(探测/云/手动三来源)+ 主动 TLS 探测(`cmdb.cert_probe_sweep`,从 DNS+域名凑目标)+ 到期提醒(`cmdb.cert_expiry_alert`,30/7/1/expired bucket)+「网络负责人」兜底(platform setting `network.owner_person_id`)+ `CloudCertificatesPage` + MCP `list_certificates`。**PR2**(云厂商证书同步:AWS ACM / 阿里云 CAS / 腾讯云 SSL,`ProviderCapability.CERTIFICATES`,per-integration opt-in)+ **PR3**(告警跨源指纹去重 + 网络工作台「证书」tab + help)**均已实现**,三段式完成。
- Scope → grant → role catalog model with `source` tracking (helix / discovered / imported)
- Self-service scope requests (`/my/cloud/scope-requests`) with Feishu approval routing
- Per-grant TTL with T-14 / T-7 / T-1 / T+0 notification stages; prod scopes opt out of auto-revoke
- Inactivity sweep (T+60d notify owner, T+74d escalate IT, T+104d disable)
- Idle scope sweep (weekly digest of unused scopes)
- Decommission flow (irreversible, calls provider account-close API)
- Daily spike alerts + monthly budget alerts via Feishu webhook
- AI-resource ledger (Azure Cognitive Services) + AI spending dashboard with chargeback
- **🚧 In-flight（2026-07-27 起）— 云成本双口径改造（6 PR 系列）**: 研发面切「业务折前成本 / list cost」（实际执行价、剥离 SP/RI/CUD/EDP/赠金/调账/税,spot·阶梯节省算部门功劳）,实付+折扣瀑布仅财务/云管理专页;各云白名单规则已用真实账单核验（AWS/GCP 定稿、阿里云半核验、Azure 等 Cost Exports）。PR1 schema（迁移 429）✅;PR2 AWS BQ/GCP 写侧 ✅;PR3 阿里云/AWS CE 写侧 ✅;PR4 读侧分流+财务瀑布专页+出口切换 ✅（部署后须按 ops 手册 §18 执行回填→验收→预算换算）;PR5 云资产总览「成本口径」Tab（方法论说明页 + 保密圈当月对数区）✅;PR6 Azure Cost Exports 白名单（规则/分类器/适配层落地,真实拉取器留 NotImplementedError 等云管理开通导出,见 ops §18.1）✅。**6 PR 代码全部合并**;剩余为运维动作:部署→按 §18 回填/验收/预算换算,Azure Exports 开通后按 §18.1 接拉取器。**收口 PR shipped (2026-07-29, 迁移 432/433/434)**:三轮对抗式复查(6 个独立复查者、127 项发现)把保密红线从「不得同框」改写成更强也更好核查的**「非保密圈用户不得从任何面拿到任何实付数字」**,并落成全量清单 [cost-measure-surface-sweep.md](docs/architecture/cost-measure-surface-sweep.md)——此前两轮各漏一次,根因是每轮只修被点名的端点、无人持有全量清单;共修 5 处(`/billing/daily` 受众含普通员工、`/billing/overview`、以及**本轮把 `/billing/monthly` 翻成折前时新造的** `/cmdb/analytics` 与 `/dashboard/insights/cloud`、扫描时新发现的 `/finance/spend/reconcile/internal`)。告警阈值改**按规则**判定口径(整组闸可被 scope owner 一条未标记规则拖回实付)、跨 run 去重槽位带口径(实付卡曾永久压制同月 list 卡);预算口径升为一等列(迁移 433 从审计回填 #2079 已换算标记,防二次 ×ratio);cost_facts 非 usage 桶行重键(迁移 434,验证式重建 + 前像搜索 + 按批 commit 可续跑)并补上租户级驱动的预窗口 eviction。**运维新增两步**:§18 步骤 4 须先跑 `--detect` 确认无 `converted-but-unmarked` 才可 `--apply`;§18 步骤 5 为迁移 434 的前后核对与失败续跑。**~~仍未收紧~~ 已更正(2026-07-30)**:`/finance/waterfall` 那条「留待决定」是假待决项 —— 它的门禁自 #2065 起就是 `can_view_invoice_measure`(保密圈本身),2026-07-29 那句凭记忆写错了门禁名(见 DECISIONS 2026-07-29 第 4 条的更正与 sweep 表注 1)。**第二批收口 shipped (2026-07-30, 0 迁移)**:通知面按**收件人**分渲染(圈外只拿无量纲比值/序号)+ 审计读侧 `AUDIT_ACTION_COST_BASIS` 分类脱敏(未登记 fail-closed,登记规则=DECISIONS 2026-07-30 ③)+ `/outbox` 圈外读者正文货币脱敏(关掉 #2097 表四留决项,选项 a+d)+ 成本读单飞重抢锁修复 + cockpit FX 披露;与并行会话先行合并的 #2097(审计写侧不落金额)合一。红线的金额层限定见 sweep 顶部不变量(无 list 列的云为已接受缺口,红线保护的是比值)

### Security auditor (done — 10 sub-features)
- MFA enrollment coverage (per provider; the health metric is per-person over
  providers that can actually observe enforcement — fixed 2026-08-18)
- Privileged role quarterly review (auto-opens Q1/Q4 via cron)
- Public exposure scan (GitHub leaked secrets)
- Secret rotation tracking
- Sign-in anomaly detection (**had produced 0 rows in production** until
  2026-08-18: the Google auditor adapter read a config key the
  `google_workspace` driver never stores, so the Reports-API pull raised
  "no active integration" every hour while the `signin_anomaly_monitoring`
  control reported pass. Fixed; first real data expected on the next hourly run)
- PAT audit
- Cert expiry monitoring
- Risk score recompute (hourly)
- Evidence pack generation (zip on demand)
- **安全 FAQ · 员工面 (2026-08-27, migration 560)** — 答案库长出员工那一面:
  `/api/v1/me/security-faq`(全员,自身即授权),搜索 + 分类 + 提问,答案一律取自
  库里原文;零命中的提问进 `auditor_security_faq_gaps` 去重台账,成为下一条该写的
  东西。`employee_visible` 开关(存量默认可见,问卷回填默认待放出)、`is_featured`
  常见问题、每人每条一票的有帮助/没帮助、3 个只读 MCP 工具。菜单 安全合规 →
  安全 FAQ(`/me/security-faq`,员工组第一项);管理端在 安全问卷 页新增
  「员工提问」tab(概览 + 缺口台账),答案库每行带「员工可见 / 常见问题」开关。
  提问按词打分(CJK 二字滑窗),整句提问不再因为不是子串而查不到。
  移动端 H5 `/m/security-faq`(+ 详情页 + 应用宫格入口);桌面「复制链接」
  的深链在飞书里点开直接落到那条答案。管理台单列「已发布给员工、而依据的
  控制项当前不通过」一桶(不自动下架,交人判断),未处理提问数做成 tab 角标。
  - **安全 FAQ · 混合检索 + 有据可查的成文回答 (2026-08-28, migrations 561–563)** —
    员工反馈「这个搜索很蠢」的结构性修复。语料侧:38 条**员工问法**的种子(迁移 562,
    8 条 featured,库从 71 → 109 条),事实只来自既有种子已确立的立场,超出的以
    `last_reviewed_at=NULL` 落库显示「待复核」。检索侧:条目建向量(迁移 561,JSON
    数组 + Python 余弦,同 `support_kb_articles`),词法与语义**两路并行 RRF 融合**
    (不是「词法没命中才走语义」),余弦 < 0.4 不入融合;筛选框仅在零命中时回落语义。
    成文侧:模型第一次被允许写一段引导语,但**只能用召回到的条目**、必须给引用,
    引用对不上召回集合就整段丢弃,条目原文永远原样附在下面;「召回到了但都不对题」
    记进缺口台账。索引回填三条路(写入后台 / 管理台按钮 / cron
    `auditor.security_faq_embed_daily` 05:40,迁移 563),管理台顶部报覆盖率。
    MCP `ask_security_faq`(hint 533,`compose=False` + 不记缺口)。**实测**:10 个真实
    员工问法,纯词法路径下命中正确条目 3/10 → 10/10。检索侧随后补了三件能力
    (2026-08-28 第二轮,0 迁移):**置信度分级**(两路一致 > 余弦分档 > 词法排名)、
    **成文闸**(弱召回不交给模型,页面明说为什么没有引导语)、**模型重排**
    (成文那次调用顺带筛掉不对题的邻居,零额外成本,两道安全绳兜底)。
- **周期性自我检查 (periodic self-check ledger, 2026-06-22, migration 322)** — admin-configurable check categories (monthly/quarterly/semiannual/annual) + monthly opener cron + evidence upload (S3 presign) + xlsx export; on-time completion drives the `periodic_self_check` GRC control. `/auditor/self-check`

### Bug bounty operations (done — 2026-08-26, migrations 533–552)
外部白帽子漏洞报告的闭环:录入(粘贴邮件 + AI 抽取)→ 指派业务负责人确认 → 安全侧定级 →
按级别档位发奖 → 模板回信(**Helix 不代发**,渲染后复制到操作者自己的邮箱线程,发出的原文
逐字存档)→ 付款记账 → 财年预算与归因。域 `bounty`,5 张表,30 个端点,菜单 安全合规 →
漏洞赏金。三条正交状态轴(处置 / 修复 / 赏金)独立推进;赏金档位、回信模板、财年预算、
回信签名全部可配;**不保存银行账号**(只记金额/日期/单号/经办人)。读 `can_view_security`,
写 `can_manage_security`;白帽子身份与金额一律 internal 可见性。
业务确认闭环已上线:指派/催办定向推送给点名的负责人(无 cron、无经理链)、
`/my/bounty/:id` 自助确认页(桌面 + 移动)、`/my/todos` 待办 tab、5 个自助面 MCP 工具;
负责人只看到技术内容(白帽子身份与金额在查询层剥离),`notified` 反映真实投递。
报表与归因已上线(迁移 540):财年改按结束年命名(FY27)、**预算拆产研/IT 两块**各自独立额度与燃烧、
五张报表(等我们回复的 / 预算 / 归因 / 修复 SLA / 白帽子)、逐条台账 xlsx 导出。
每个比率的分母单独定义并在界面上写明(有效率按已定论、按时率按已修复且当时给过时限)。
合作方通讯录已上线(迁移 587):域 `partner_directory`,2 张表,6 个端点,3 个 MCP 工具,
每天 06:40 的只读同步 cron,菜单 后台配置 → **外部人员与账号**(`/admin/external-accounts?tab=partner`)—— 与「外部 / 共用账号」合并为一条菜单两个 tab,因为两者是同一个问题的结论和依据;旧路由 `/admin/partner-directory` 重定向到对应 tab。
回答的是「这个我们认不出人的账号到底是谁的」—— 把 CBG / 平凯星辰的名册镜像过来,
用**邮箱名跨域**(对方 `@pingcap.cn` ↔ 我们 `@pingcap.com`,按完整邮箱串一个都对不上)
和对方通讯录里登记的 GitHub ID,给 Google 孤儿账号 / 厂商 external 坐席 / GitHub 未关联 handle
打上归属标签。**只对无主行生效**,不覆盖管理员显式分类;仅靠姓名对上的进待确认队列。
首次全量:对方 381 人;命中 Google 孤儿 41/60(11 个从未登记过)、厂商坐席邮箱 65/264、
GitHub handle 41/217;命中行里在职员工 0 个。
唯一一个指向别人租户的连接器,接口层没有写方法。
安全员角色已上线(迁移 541):`security_officer` 第 10 个规范角色,安全域内与 auditor 等权,域外不授权。
内部系统台账已上线(迁移 542):域 `system_registry`,2 张表,10 + 1 个端点,4 个 MCP 工具,
菜单 安全合规 → 系统台账(`/admin/systems`)。登记「我们自己建的系统」这一层 —— Owner + 备份 Owner、
对外可达性、所属预算块、是否在赏金范围内;**不重复 CMDB / 软件台账 / SSO 目录**(那些是同步来的机器与买来的坐席)。
漏洞报告可关联台账条目(`bounty_reports.system_id`),定级时自动带出 Owner 与预算块,一键指派;
Owner 变更一律走专门端点并留轨迹;Owner 离职进离职闸门强制交接(`RES_SYSTEM_OWNER`),
T+0+24h 优先交给台账登记的备份 Owner 而不是经理。员工侧在 IT 宇宙图与 `/api/v1/me/systems` 看到自己名下的系统。
审批流程台账已上线(迁移 553,PR1/3):域 `approval_registry`,2 张表,12 + 3 个端点,3 个 MCP 工具,
每晚 03:30 的飞书同步 cron。回答两个此前无处可答的问题 —— **全公司一共有多少个审批流程**、**每个流程谁负责**。
飞书没有「列出全部审批定义」的接口(实测),所以清单靠按人查实例反推;`node_list` 里也没有任何审批人身份。
负责人因此在 Helix 建立,`owner_source` 记录每个名字的来源(待认领 / 推断 / 飞书流程管理员 / IT 指派 / 导入 /
本人确认),覆盖率与「确认率」分开报 —— 只有本人确认过的才算真覆盖。推断只在「占比过半且 365 天内 ≥3 次决策」
时给候选并附证据,人工定过的一律不覆盖。
**这一段此前还写着「审批定义里没有负责人字段(`approval_admin_ids` 返回 null)」—— 那是错的**(迁移 559,
2026-08-27):该字段在不带 `?with_admin_id=true` 时**整个 key 都不出现**,首个版本的探测没带参数,把「字段缺失」
读成了「概念不存在」,并把这句话抄进了代码注释、cron 描述和三语文案,导致 **49 条流程里 32 条长期公示
「还没人认领」,而飞书审批后台每一条都写着流程管理员**。带参数重测:42/49 有管理员、37 个人、33 个能对上在职员工。
现在每晚同步把管理员一并读回来:**只要有在职管理员就认领一位**为「飞书流程管理员·待确认」,全份名单同时公示,
离职的划掉保留。声明压过统计(任务推断不再覆盖飞书声明的管理员),但都压不过人。
选谁**不能取飞书返回的第一个** —— 那个数组每次调用都在轮转(实测名片申请 7 名管理员查 5 次出 4 种顺序),
照 index 0 取会让负责人几乎每晚换人;改为按 user_id 排序 + 认定后钉住(在任者还在名单上且在职就不动)。
生产投影:49 条 = 11 条已有负责人 + 32 条按管理员认领 + 6 条真的没人(飞书侧也没设,或唯一管理员已离职),
**有负责人的从 11/49(22%)升到 43/49(88%)**。公示卡片按「Helix 负责人 → 在职飞书管理员 → 都没有」三段落答
「找谁」,红色的「还没人认领 —— 找 IT」只落在最后那 6 条 —— 一句「找 IT」压着一行管理员名字读起来像页面坏了。
公示面 `/api/v1/approval-flows/published` 全员可读。界面已上线(PR2/3):管理台账 `/admin/approval-flows`
(四个 KPI、按来源筛选、行内补登负责人、归属时间线、批量导入、手动同步)、全员公示 `/approval-flows`
+ 员工手册实时表、`/my/approval-flows`(确认 / 这不该由我负责)、移动端两页、三语。公示页与台账页头部有
「飞书审批后台」入口(不设角色门 —— 飞书自己判权限,按 Helix 角色藏反而会藏掉刚被点名的那批流程管理员);
公示页顶部横幅告诉当事人「你名下有 N 条」并直接跳 `/my/approval-flows`,搜索框可搜飞书管理员的名字。
半年度确认闭环已上线(迁移 556,PR3/3):轮次与逐项快照两张表、两个 cron(边界后 7 天窗口开盘 / 14 天逾期
升级并抄送上级、到期自动关盘冻结完成率)、一人一张的飞书卡片 + 免登录一键确认、逐条转交(必须指定接手人)、
离职闸门 `RES_APPROVAL_FLOW_OWNER` 强制交接、管理端轮次面板 + 3 个自助 MCP 工具。
新建流程申请已上线(迁移 557):员工提申请 → IT 批 → IT 在飞书控制台建 → 回填 code → 台账自动登记、
负责人=申请人 → 通知申请人。「已批准」算未完成(批了没人建,对申请人来说和没批一样);驳回必须写原因;
回填 code 时若扫描已发现同一条流程,会接管那一行而不是撞唯一索引。助手接入已上线:
「我想加入 git 仓库怎么办」直接答出流程名与负责人。
**「转交」到飞书后台之间那一步只能人工**(迁移 564,2026-08-28):审批 v4 的定义面只有创建 / 读取 / 订阅,
**没有 update 动词,流程管理员没有写接口**。所以每一次在 Helix 换负责人(半年确认里点「转交」、离职交接、
IT 重新指派)都必然留下一处不一致 —— 台账记新人、飞书后台仍点着原来那位,而后台那份名单才是决定谁能编辑流程、
看实例数据、收流程管理员通知的东西:新负责人拿到责任、拿不到权限。现在这处不一致是一等读面
(台账页新 tab `?tab=feishu-drift`,不分页,每行印台账负责人 vs 飞书管理员全份名单)+ 每日 09:00 一张汇总卡
给在职 global_admin(不是每条流程一张 —— 离职交接一次搬走一个人名下所有流程)。**漂移每次读时算出来、不落库**,
所以后台改好当晚同步一读就自动消失,不需要「已处理」按钮(那会让人标了却没改)。**员工按下「转交」的当场
也给管理员发一张告警**,并盖戳让次日汇总不重复同一条;钩子只挂在员工自己按按钮那条路
(`announce == HANDOVER_TRANSFER`,两个调用点),离职批量交接仍然只进汇总。**0 条不发**,已有告警期内的也不发。
转交对话框里也对员工说清楚这一步归 IT、已自动进 IT 待办、不用他去催。
台账页同时改成四个 tab(流程台账 / 负责人确认 / 新建流程申请 / 飞书后台待同步,后两个带角标,active tab 存
`?tab=`)—— 顺带修掉一个从上线就存在的死深链:申请队列的通知一直指向 `?tab=requests`,而页面没有 tab。
**全生命周期已闭环**:申请 → 建立 → 负责人 → 半年确认 / 转交 →(人工同步飞书后台管理员)→ 离职交接 →
停用(在飞书操作,每晚同步回来)。
**不做 API 自动建流程**:飞书官方文档明写「通过该 API 创建的审批定义无法从审批管理后台或以 API 方式停用、删除」,
且「不推荐企业自建应用使用」——我们租户已有两个删不掉的僵尸定义为证,与「负责人可下线」的目标直接冲突。

FY27 往来已全量补录(149 条报告 / 56 位研究者,2026-04-01 起):**87 条从未回复**,最长静默 128 天;
产研块已付 \$700 + 已承诺 \$2,300(60% 已烧),IT 块已付 \$100。补录脚本
`server/scripts/backfill_bounty_fy27.py`(台账文件不进仓库,`--ledger` 外部传入)。
上线后整体复审修掉一处真缺陷:离职的人不能当系统 Owner,已离职的 Owner 也不再算「有人负责」。
控制台整体重做已上线(#3231–#3237,迁移 545–548)。原来的扁平表格在生产数据上没有层次可读:149 条里 114 条同一个状态、142 条空级别、84 条同一个主机名,于是 65% 的报告在结构上无法路由。现在默认视图是**归属 × 阶段矩阵** —— 行按预算块分带(产研 / IT / 未归属),列是「还欠什么」(需回复 / 需定级 / 需指派 / 等负责人 / 需修复 / 钱),单元格用最久等待着色;没有 Owner 的系统的「需指派」格画成斜纹的「不可能」而不是 0,因为把它画成 0 会请人以为活干完了。点开一行是 lane 视图(同样的阶段谓词,同一份 `_bucket_for`,所以两个视图不可能各说各话),里面是真实卡片。矩阵上方常驻**数据缺口面板**:没人负责的系统 / 未定级 / 未关联 / 从未回复,每一格都直接跳到能动手的那个页面(不是报表页)。补数据抽屉按缺口成批处理,AI 从标题给建议、人来采纳,AI 看不出来的行给手动档位选择器;回复队列按静默最久排序、答完一条自动开下一条。白帽子可按邮箱前缀搜索,两个地址可合并成一个人(合并是指针,报告仍指向真正发件的那个地址)。回信改成**每个档位一封**并写明金额,少了金额的赏金信直接拒绝渲染而不是渲染成 "USD ."。

security@ 自动收信**已在生产启用**(2026-08-28;迁移 548):轮询只往「待确认收件」队列排队,绝不自动建单;同一 thread 的后续来信挂到已有报告上。开启需要的两件事都已完成:`gmail.readonly` 域级委派(2026-08-26)+ 真实 Workspace 用户 `bounty-intake@pingcap.com` 并订阅 `security@` 组(2026-08-28)—— `security@` 是邮件组,域级委派只能冒充真实用户,指向组地址永远 `unauthorized_client`。可读的邮箱是代码里的白名单(`ALLOWED_MAILBOXES`),不是任务配置里的自由文本。推送卡片列出每封信的发件人 / 主题 / AI 初判 / 开头 / 是否同 thread 重复来信,不是一个计数。

控制台按使用反馈又改了一轮(2026-08-26,迁移 551):点系统名一次点击直达流水线;报告关联到有 Owner 的系统时**自动路由**(不发通知 —— 推送仍是显式动作);报告到达即为最低档级别、并用 `severity_is_default` 记住「没人选过」,于是回信不会误推 $0 赏金信、缺口面板也不会假装 0。报告有了**自己的页面** `/bounty/reports/:code`(用编号做地址),抽屉退居其次,三个入口统一跳转。页面上「报告正文」一节总是在:生产 149 条里 0 条有正文/复现步骤/受影响 URL(补录刻意把研究者往来留在邮箱),所以它直说正文在哪儿并一键跳到原始邮件线程 —— 线程 id 一直写在 `created` 事件上,149 条全有,只是从没被读过。**正文已回填**(2026-08-26):149 条里 **145 条**有了研究者原文 + AI 初筛(结论 / 理由 / 利用前提 / 最坏影响 / 待确认清单 / 待追问),受影响 URL 从 0 补到 148 条;剩下 4 条是邮件确实不在归档邮箱里,如实显示为没有正文。只取白帽子第一封 + 入库前抹掉银行信息。**live 轮询仍缺一步**:`security@` 是邮件组,域级委派只能冒充真实用户,需要在 Workspace 建一个真实账号 `bounty-intake@` 并把组的来信投递一份进去(见 `docs/ops/gmail-readonly-delegation.md`)。

整体复审 + 安全扫描 + 逻辑扫描已完成(2026-08-26,迁移 549):三处批量写的事务毒化、待确认行的 read-then-write 竞态(一封信能变两条报告)、邮箱冒充面、以及「在哪里补数据」的缺口卡链接指向一个不存在的路由(`/systems` → `/admin/systems`,/help 里同一个错)。补了两把关掉整类问题的闸门:前端内部链接对路由表(`pnpm lint`),后端按 router 清扫角色闸(赏金 50+ 条 / 台账 11 条拒绝普通员工,台账每个写入拒绝安全角色)。**当前欠回复 / 欠定级 / 欠 Owner 的数量不再写进文档** —— 控制台的缺口面板就是它的实时来源,把数字冻在这里正是上一版文档过期的原因。仍需人来做的两件事:6 个无 Owner 系统的 Owner 回填(`/admin/systems` → 行 → 「设置负责人」,优先 TiDB Cloud 与官网),以及上面那个 `gmail.readonly` 授权。

### Approval (Feishu — done)
- Webhook-verified approval callback (`POST /webhooks/feishu/approval`)
- Generic approval submission via `ChatApprovalDriver` interface
- Used by: API key requests, asset/seat requests, leaver-blocked overrides, **DNS resolution requests**, **cloud sub-account / subscription requests**, **SSO grants**, **new-domain registrations** (all four extending the same flow_key → external_code dispatch table at `/admin/integrations/<feishu>/flows`)
- Unified cancel: requesters withdraw any pending request (asset / software / email / api-key / DNS / cloud-scope / SSO / domain-registration); the local row flips to `cancelled` AND the parallel Feishu instance is cancelled in the same call

### Insights & dashboards (done)
- Unified `/admin/insights` (**impact (平台成果, default tab, 2026-06-12)** / overview / people / **people-distribution (人员分布 world→China-city map, 2026-06-15)** / assets / software / **software info (catalog governance, 2026-05-02)** / operations / lifecycle / security / cloud tabs) — management role can read every tab
- **Geographic distribution maps (2026-06-15)** — shared `<GeoDistributionMap>` (world choropleth → click China → per-city `effectScatter`) on HR 洞察「人员分布」(`/dashboard/insights/people-geo`) and 资产工作台「资产分布」(`AssetService.get_stats` `by_country`/`by_china_city`); country normalization via `app/core/geo.py`
- **Platform Impact（平台成果，renamed from 平台成绩单 2026-06-12）** (2026-06-12) — leadership-facing cumulative achievements: reclaim (seats/permissions/cloud grants/API keys/SSO, platform-timestamp-only counting; cloud reclaim shares `GRANT_STATUSES_RECLAIMED` + scope-alive with the cloud overview so the two surfaces always agree), estimated annualized savings (flagged estimate, FX via platform rate), managed scale, flows served, security governance, automation; cumulative growth curves; Excel export; chromeless dark **projection board（成果大屏）** at `/admin/insights/board` (60 s auto-refresh) for meeting-room screens
- **HR 洞察「人员洞察」+「入离职洞察」重做 + AI 诊断 (2026-07-08, migration 393)** — both tabs rebuilt to lead with an on-demand **AI diagnosis** (`HrAiPanel`, reuses platform Bailian creds, cached per `(section,range)` in `insights_hr_ai_summaries`; digest is aggregates-only, no PII) over HR-framed KPI strips (net growth / attrition / span of control · onboarding velocity / offboarding SLA / overdue blockers / recovery) + grouped charts; `build_people_payload`/`build_lifecycle_payload` extracted so AI reads exactly what renders; regenerate gated to global_admin **or hr**
- **用户管理 + 入离职管理 opened to HR (read-only, 2026-07-08)** — new `require_lifecycle_read` gate (global_admin+hr+auditor+management) on onboarding/offboarding/provisioning **read** endpoints (writes stay global_admin); `App.tsx` route guards + `MainLayout` `hrItems` gain the 入离职管理 group (overview/onboarding/offboarding; not the cross-domain task queue). 用户管理 was already HR-readable
- Personal `/dashboard` for every authenticated user
- AI spending dashboard with finance + auditor read-only access
- Relationship graph for ownership audit
- Header **security tips ribbon** (2026-05-02, migration 100) — admin-managed bilingual rotating banner shown to every signed-in user; default seeds cover lock-screen / phishing / credentials / IT helpdesk; managed at `/admin/security-tips` (the in-header ribbon now broadcasts via the IT-service-desk mascot bubble; the freed header slot hosts the policy-notices ribbon below)
- Header **制度公告 (policy notices) ribbon** (2026-06-05, migration 254) — admin-managed bilingual title + Feishu doc URL, rotating in the header search-bar slot; clicking opens the doc in a new tab; cycles policy / manual links for every signed-in user; managed at `/admin/policy-notices`
- **IA consolidation (2026-08-17, 0 migration)** — three surfaces collapsed on user feedback: employee 求助 (`/me/support`) now leads with the AI conversation and keeps the ticket list as a side rail (`/me/support/ai` redirects in); 助手对话审计 became the 4th tab of 审计日志 (`/admin/audit?tab=assistant`, gated on `canReadAssistantTranscripts` so auto-promoted `management` never sees a guaranteed-403 tab); 服务台知识库 + 公告与制度 merged into **知识与制度** (`/admin/knowledge`, tabs 知识库 / 制度公告 / 安全提示 — the KB tab rides `require_admin_read`, the other two `require_config_read`, so an auditor sees the KB only). All legacy paths redirect into the matching tab; `/admin/policy-notices` and `/admin/security-tips` now land there

### AI 实验室 (AI Labs) — shipped (2026-09-04, migrations 601 + 602)

- New domain `app/domains/ai_labs` wrapping the org's existing DashScope/百炼 connector as an **employee-facing surface** — the same tenancy that already powers the support copilot, the intelligence briefs and the AI-spending summaries, now open to everyone rather than only to embedded flows. Behind fail-closed `ai_labs.enabled` (default **off**).
- Capabilities, every model and option measured against the live Singapore endpoint (see `scripts/verify_ai_labs_catalog.py`): **streaming chat** (7 models across Qwen / DeepSeek / Zhipu / Moonshot, thinking-mode toggle, image + document attachments), **text-to-image**, **image editing**, **text/image-to-video**, **text-to-speech**, **speech-to-text**, **translation** (14 target languages)
- **Two product promises are structural, not policy**: no admin endpoint in the domain can read a conversation, prompt or artifact (admin surfaces query a counters-only table); nothing is added to the employee's prompt (asserted against the real outgoing HTTP body, including the vendor's own `prompt_extend` rewrite flag being off everywhere)
- Generations are **durable jobs** with cooperative leasing + a 5-minute reaper, so a deploy mid-render is a delay rather than a lost job; artifacts are re-homed into Helix's own bucket because the vendor's result links expire ~24h out
- Per-person daily quotas (tokens / images / video-seconds / speech / transcription) and history retention are all **operator-configurable and default to unlimited / never-expire** — the knobs exist so that "if this ever costs money" is a settings change, not a release
- **Desktop surface shipped (2026-09-03)**: employee top-level menu 「AI 实验室」 with five pages — chat (streaming, model picker, thinking toggle, image + document attachments), image studio, video studio, speech & language, and a gallery of past work. `web/src`'s first SSE consumer; every parameter control is generated from the server catalogue, so an option the vendor stops accepting leaves the UI without a frontend change
- **Mobile + admin shipped (2026-09-03)**: `/m/ai-labs` (chat) and `/m/ai-labs/studio` (image / video), pinned in the apps centre; `/admin/ai-labs` carries the switch, the five daily allowances, retention and aggregate usage plus generation-queue health — and by construction cannot show a prompt, a reply or an artifact
- **Admin drill-down shipped (2026-09-04, migration 602)**: the console could report "8 requests, 2 failures" and stop there. Two different gaps, fixed differently. *Who used it* was never a privacy boundary — `person_id` was always on `ai_labs_usage_daily`, so it took a `GROUP BY` and a directory lookup (`/usage/people`, plus a per-person drill that is the one read in this domain written to the audit log). *What failed* genuinely could not be answered: `failures` is a bare count and the error codes live only on the content rows an admin may not read and an employee can hard-delete — hence `ai_labs_failures_daily`, a second counters-only table keyed by error code. Its one non-numeric column is shape-validated on write, because a vendor's `error_code` is an arbitrary string and the async media family quotes rejected prompts back inside it. Reason coverage starts at this deploy, so the API reports `counted_failures` against `classified_failures` and names the difference rather than passing a partial breakdown off as the whole one. Two silent blind spots closed on the way: a capability pulled from the catalogue under a queued job, and **every translation failure**, had been counted nowhere at all
- **Six review rounds ran over the theme after the four feature commits** (2026-09-03 → 09-04), and every round after the first found its defects *inside the previous round's fixes* rather than in the original feature. The two worst were structural and neither was visible in a diff: every attachment would have been deleted by the platform's own orphan sweep 24 hours after upload (this was the only domain consuming `/uploads/presign-put` that never claimed its grants), and every async generation that changed runners — the documented reaper recovery path after any restart mid-render — reached the employee with no usage row, no cost attribution and no quota consumed. 28 mutation checks back the regression tests. See `docs/CHANGELOG.md` for the per-round digests and `DECISIONS.md` 2026-09-04 for the two corrected ADR sentences
- **The AI 实验室 theme is complete.** Deliberately **no MCP twin** (DECISIONS 2026-09-03) — which is why the three drill-down endpoints ship without MCP tools; that is the decision, not an omission. Before the surface is switched on in production, the four `ai_labs_*` S3 prefixes must be added to the LIVE `helix-prod-ec2-role/HelixProdUploadsAccess` inline policy by hand — the Terraform in this repo is plan-only (see `docs/ops/storage-direct-upload.md`)

---

## Currently in flight

**员工触达的观感与回执(2026-09-07 起,PR1 已交付)。** 起点是用户拿另一个团队的机器人
卡片做对照,并问了一个更要紧的问题:「Helix 的推送没有任何回执能力 —— 这个审批过了,卡片
还是写着『去审批』」。两件事一条链:卡片的**表达力**(v1 schema 只有四种块,没有徽章位、
没有信息盒、按钮不能通栏)和卡片的**生命周期**(发出去就不再管它)。
**PR1 已落地(0 迁移)**:全平台卡片换到 schema 2.0,`card_status` 取代 `header_color` 成为
一等概念(状态一次决定头色 + 徽章 + 图标;ACTION bucket 自动派生 `pending`,46 处 actionable
卡片因此都带上「待处理」徽章),新增事实盒 / 事实行 / 折叠长尾 / 分类徽章四个版式原语,
卡头 Helix 图标按应用运行时解析(`image_key` 跨应用无效,写死 = 生产静默无图标),助手的
确认卡与回执卡并入同一个渲染器。**已实测**:`PATCH /im/v1/messages/{id}` 用现有应用权限
即可原地改写已发出的卡片(含二次改写),不需要新 scope、不需要过飞书版本审核 —— 回执的
地基是通的。
**PR2 已落地(迁移 624)**:回执原语 —— `settlement.settle()` 先在 outbox 行上盖
`settled_*`、再 PATCH 飞书卡片(顺序是不变量:反过来会让卡片说「已批准」而铃铛说
「去审批」)。铃铛的红点排除已结案行;**移动端行动收件箱不需要接** —— 它按设计读每个
域自己的待决状态,本来就是自愈的(原计划「三个面都要接」读代码后改掉了)。首个接线点是
云权限续期的批准/驳回。
**PR3 已落地(迁移 626,+1 cron)**:23 种 actionable resource_type 全部表态(10 条有
状态解析器、13 条写明为什么不能解析),清单由 AST 扫描自动生成 ⇒ 新流程自动进闸;新 cron
`notifications.card_settlement_reconcile`(每 15 分钟)是保证那一条腿,漏掉一处内联调用
只是最多 15 分钟延迟。
**PR4 已落地(迁移 627,+3 API)**:助手聊天改富文本(`post` 的 `md` 元素,保留加粗/
链接/列表);答案卡按钮分两档、按自然宽度排(修掉 PR1 把它们拉成通栏的回归);「转人工」
拆成 `🎫 建工单` / `💬 转人工`,后者 AI 归类 → 按后台可配的路由找负责人 → 建飞书群 +
原样转发那条回答,**并且照旧建工单**;三种降级(未指派/建群失败/开关关闭)都仍建工单并
在台账记原因。配置页 `/admin/assistant-handoff`(写只限 global_admin)。
**PR5 已落地(0 迁移)**:按钮版式按两张真机截图各修一处(手机折行 / 桌面空半屏 →
两行各两颗、都走 flow、动作按钮去 emoji、评价改「赞/踩」);续期审批卡按新版式重写
(一句人话标题 + 事实盒 + 事实行 + 折叠角色清单,邮件主题保留方括号前缀);软件权限
审批卡的 `card_stats` 误用改成事实行。
**本轮收官**:整体 code review → 安全 + 逻辑扫描 → 循环修到干净 → 合并发布生产。
边界:邮件发出去改不了,回执只在卡片 + 铃铛两面成立;刻意不补「已处理」邮件。

**自动化异常闭环(2026-09-06 起,PR1 已交付)。** 起点是 2026-09-05 的全生命周期审计里
剩下的那块板:平台在**发现**问题上很强,但一旦某一步自动化没走通,就退化成五个域各自
持有的一堆错误字符串等人去读。五份失败记录(建号 `failed` / 出箱 `dead` / 定时任务运行
`failed` / 同步条目 `failed`+`blocked` / 集成 `last_test_status`)语义互不相通,没有共同的
根因词表、没有负责人、没有下一步;而**集成凭证根本没有定时体检** —— `last_test_status`
只在管理员手点「测试连接」时才被写过,所以凭证过期永远不在集成这层被发现。
**PR1 已落地(迁移 618,+3 cron,+3 MCP→hint 623)**:新域 `automation_exception`,两张表把
「一次没走通的自动化」做成一等对象(归并后的问题 + 每条失败源行),五个来源采集器 +
恢复判定由采集器逐个表态(完整扫描的才允许用「缺席」证明恢复,历史事件类必须给显式
证据;采集器自身读挂标 `partial`,引擎此时不做任何基于缺席的关闭)、根因→处置闭集全
覆盖、负责人只从既有权威来源解析且解析不到就如实写「无人负责」、新增
`integrations.health_check` 每日体检、`/admin/health` 变成两个 tab(异常工作台 + 原有健康
总览,路由与菜单位不变)。**PR2 已落地(迁移 619,+1 cron,+1 MCP→hint 625)**:签名级 AI 分诊(闭集
根因 + 每个数字必须在事实包里 + **规则永远优先于模型**,所以规则表越长这条任务越便宜)
+ `llm_call_ledger` —— 平台自己也是 AI 消费方,却是唯一没有账本的那个(29 处调用点
只有 2 处记 token)。记 token **不记钱**(本仓没有按模型单价表,不发明一张;厂商发票
仍是钱的真相),**不存**提示词与回复(表里没有那两列),埋点在客户端四个出口且
**失败路径也记** —— 一把过期的 key 会先在这里堆成一面同码的墙,然后才有人来报功能坏了。
**PR3 已落地(0 迁移 —— PR1 已把四列留好)**:处置与重放。「凭证修好了」是**一次点击**,
平台自己把该集成下所有未恢复的行重新驱动一遍,不把 47 行待办转发回给已经做完决定的人。
只驱动既有原语、有界、**成功也不写 `recovered_at`**(恢复由采集器按自己的证据判定)、
失败也留痕。**同一轮拆掉了我自己造的一个缺陷**:初版按「来源」判定能否重放,
与 PR1 已确立的「行上 `replayable`」构成两个互相矛盾的权威 —— 已收敛为一个。
**PR4 已落地(迁移 622,仅播种 cron)**:在等的那个人终于会被告知一声,而一个「又回来」的
问题会去找负责人。**不新增打扰类目**(类目从 template_key 派生,注册进既有 `lifecycle`,
员工已有的静音与免打扰天然管住它);员工那条**不含失败细节**。同轮修掉三个真缺陷:
重放冷却期与回音去重都缺锁与 `populate_existing`(并发下双发),以及 `last_replay_at`
从数据库回来是 naive datetime 导致的 `TypeError`(第一次之后每次运行都会炸)。
**PR5 已落地(0 迁移)**:收口复审 + 安全扫描 + 逻辑扫描,循环四轮到干净。
**六个真缺陷,没有一个在单个 PR 内部** —— 全在 PR 之间的接缝上:回音变成「一辈子一次」、
重放死信通知永远无法恢复而 UI 报成功、被拒绝的重发算成功、分诊队列会被永久饿死、
两个模板没登记进语言表(**每个员工都会收到硬编码中文**)、停用的连接器越停越吵。
守门 `test_every_sent_template_key_is_registered` 的盲区已加宽并做过变异验证。
**自动化异常闭环 5 个 PR 至此全部落地。**


Recent work clusters around **operational maturity & data-trust**, plus one new
domain in flight — `finance` (the unified spend reconciliation centre, below).
For the dated, per-PR history see [docs/CHANGELOG.md](docs/CHANGELOG.md) (newest first).

Active focus themes:

- **安全宣贯运营体系 (security awareness operations) — 5-PR theme, ✅ 全部交付 (2026-09-03)**。安全宣贯此前是「一行纯文本在吉祥物气泡里轮播 + 制度中心挂飞书外链」,**两者都不记录任何曝光** —— 所以「证明这个员工被告知过钓鱼风险」答不出来。这个主题把它做成可持续运营 + 能拿得出合规证据的闭环:素材(漫画/文章/外链,三语)→ 活动(人群 × 渠道 × 时机 × 回执)→ 触达(飞书卡片/邮件/站内铃铛)→ 逐人台账 → 覆盖率 → 合规控制项。两档强度并存:**必修**进 `/my/todos` + 催办 + 算覆盖率 + 走 `CAT_SECURITY`(员工关不掉);**运营**是纯触达、可关、只统计曝光。**PR1 shipped (2026-09-03, 迁移 596, 0 cron, +2 MCP→hint 588)**:`secaw_materials`(托管素材 + 三语 JSON + 版本)+ `secaw_impressions`(**只追加**的曝光台账,记版本与实际渲染语言,30 分钟窗口去重)+ 员工「安全学习」(桌面 `/me/security-learning` + **移动 H5 主场** `/m/security-learning`,apps catalog 默认 pinned)+ 管理端素材库 `/admin/security-awareness`(触达永远是「N 人 / N 次展示」两个数)+ 新上传 purpose `awareness_material`(读全员 / **写限安全台**,`storage_router` 里第一个 per-purpose 写闸)+ 39 条后端测。**PR2 shipped (2026-09-03, 迁移 597, +1 cron, +2 MCP→hint 590)**:`secaw_campaigns` + `secaw_campaign_targets`(受众复用 `app.core.person_filters` 与共享的 `PersonFilterEditor`;两档强度 = 一个布尔列 + 两个 template_key,运营档走**新增的** `CAT_AWARENESS`)+ 分阶段提交的发送(状态翻转即锁)+ **投递真值只从 outbox 读**(管理端印四个数不印完成率)+ 幂等确认 + 催办只补欠账 + 受众实时预览(真名字,不是 confirm 勾选框)+ `/my/todos` 一格 + 素材页上的确认控件 + 26 条后端测;顺带修掉 `PersonFilterEditor` 的 lookup 对 `security_officer` 静默 403 的洞。**PR3 shipped (2026-09-03, 迁移 598, 0 cron, 0 新 MCP)**:环境位 —— 吉祥物气泡接素材(缩略图 + 服务端挑的钩子 + 可点),**四条配给规则**(每天稳定小集合 / 每天新素材上限 1 / 同一素材上限 3 次 / 点开过的出局),**取不记、渲染才记**,三层降级(素材→纯文本提示→问候语)永不空,频控三键播种进 platform_settings 且入口放在素材库页上,顺带补 `platform_security_tips.text_ja`(不回填),15 条后端测。**PR4 shipped (2026-09-03, 迁移 599, +1 cron, +1 MCP→hint 591)**:合规接线 —— 控制项 `security_awareness_training`(取最近一次必修活动,不取历史平均)+ **新增两条此前目录里没有的框架要求** SOC2 CC2.2 / ISO27001 A.6.3 + 覆盖率(只算必修、分母是当时真的发给了谁、按人加权、向下取整、发给 0 人算 fail)+ 逐人证据 CSV(快照列 / 姓名邮箱分列 / BOM / 公式转义)+ **催办阶梯复用 `notifications.cadence` 并只拉长**(7 天才第一次催,之后 7/7/14,三周后给主管一张摘要卡)+ 21 条后端测。顺带修掉:催办的 template_key 差点落进「可关」那一档,员工本可静音掉唯一在告诉他还有必修没做的消息。**PR5 shipped (2026-09-03, 迁移 600, 0 cron, **0 新 MCP**)**:载体联动 —— 素材挂小测(复用既有题库与判分语义,**答对就是确认**,全对才过,重试不限次、答错给解释,**答案永不出服务端**)+ **每次尝试都存**且 `attempt_no` 显式存列(秒级时间戳分不出「答错→马上重来」)+ 第一次答错率读数(改稿输入)+ 积分经 coordinator(每素材一次 / 排在确认之后 / 钱包写入在 SAVEPOINT 里 —— 落地时是裸 `try`,2026-09-03 补隔离)+ 5 枚徽章(按**读了**不按**被展示**计)+ 「阅读情况」独立 Tab(与覆盖率刻意分开,点开率靠去重人数才算得出来)+ 21 条后端测。顺带补窄的只读题库选择器:挂题需要题库,而每日一题的题库闸不含 `security_officer`。**安全宣贯 5-PR 主题全部交付**:素材库与曝光台账 → 活动与逐人回执 → 环境位 → 合规接线 → 载体联动。**复审 shipped (2026-09-03, 0 迁移)**:五个 PR 合完后的整体复审 —— 17 处修复 / 20 条回归测试 / 全部经变异验证。最狠的一处是**小测那道闸只装在前端**(确认端点和 MCP 工具都直通,而 PR5 恰恰以「替员工答题产出假证据」为由拒绝提供答题工具);其余包括**浏览器可以自己上报「我读过了」**(污染已读标记 / 参与看板 / 徽章)、**点开率会超过 100%**、**证据 CSV 点了是 401**(平台第二次踩,已配 lint 闸 `check-api-href.mjs`)、**删素材漏两条 RESTRICT 外键会 500**、**归档素材把活动里的 stragglers 晾在 404 上**、**证据文件的催办次数不数自动阶梯**、主管摘要把两个活动混成一张卡、resend 没有二次确认、题库搜索有看不见的地平线,以及一条顺序敏感却没有 ORDER BY 的测试(单跑绿混跑红)。
  - **语言缺口补齐 shipped (2026-09-05, 迁移 607, +1 cron, 0 新 MCP)**:员工反馈「英文系统右下角气泡是中文的」。排查结论是语言链路没坏(前端传 i18next 当前语言、`pick_block` 按 `LANG_FALLBACK` 解析、`is_fallback_lang` 一直在回报),**缺的是内容** —— 线上 46 条已发布素材里 30 条漫画只有中文、16 条文章没有日文。新增 [`translation.py`](server/app/domains/security_awareness/translation.py) 用平台共享的 AI/LLM 连接器补齐(标题+摘要走 `chat_json`、正文走 `chat_text`),**不改中文原文 / 不改发布状态 / 不译图片**;生成的语言记进新列 `secaw_materials.mt_langs`,四个读取面都带 `machine_translated`,详情页渲染成提示且对漫画明说图片仍是中文。管理端一条按语言分列的缺口横幅 +「一键补齐」+ 逐语言「标为已校对」,编辑器每个非中文 tab 一个 AI 翻译按钮(填表单不落库);cron `security_awareness.translation_backfill` 每小时补 10 条,没配 key 而有缺口时 raise。14 条后端测(含变异验证)。

- **助手需求闭环 (assistant lifecycle closure) — 4-PR theme, **shipped** (2026-08-30)**。助手今天能提交,但答不了「现在到哪一步、最后成没成」:`AssistantActionProposal.status = executed` 只证明**调用返回了**,而 `SupportServiceRequest` 主要靠员工点「有用」判 `self_solved`。**PR1 shipped (2026-08-30, 迁移 572, 0 端点, 0 cron, 0 MCP)**:关联合同与数据模型 —— `outcome_contract.py` 按动作**声明**业务对象位置(不从 JSON 猜 id、不让模型解析结果;平台自己的写工具对同一概念返回五种键名,且策展动作与同名 MCP 工具**返回形状不同**,所以守门走 `get_action()` 同一条解析路径)+ `assistant_object_links`(多态指针,`phase` 只是漏斗缓存)+ 提案四列尝试可见性(生产 14 条提案 0 执行,其中**两条其实按过并失败**,之前在库里数不出来)+ 修掉派生动作把工具返回值 `str()` 掉的两处现场;34 测,两处变异验证。**PR2 shipped (2026-08-30, 0 迁移, +1 端点, +1 MCP → 544)**:八个领域的结果解析器 + `GET /api/v1/assistant/submissions` 与 MCP 孪生;投影 11 格(含 `gone` / `unreadable` / `ended` 三个简报没列但真实会发生的);每个域**自己声明**状态映射(四个生产值落在四格之外,一刀切会全标错)、自己声明归属列(六个不同名字),权限**每次从领域行重新推导**(资产易主后旧链接必须读不到);批次按最坏成员汇报;38 测,两处变异验证分别钉住两层 RBAC。**PR3 shipped (2026-08-30, 迁移 573, +1 端点, +1 cron, 0 MCP)**:「有用」不再单独关单 —— 只有链上对象全部生效(`system_verified`)或压根没产生对象(`self_solved`)才关,rejected/failed 标记 `action_needs_attention` 并保持 active;`needs_attention` 是**派生**的不落库(status 有 21 处消费者);新 cron `assistant.submission_reconcile` 每 30 分钟对账并关掉已落地的,**不发任何通知**、**从不在失败上关单**;追问改成必须调 `list_my_assistant_submissions` 而非复述上文,并补了「提示词点名的工具必须真的注册过」这条守门;新漏斗端点分开报 `closed_verified` / `closed_on_word`(刻意没有 `closed_total`)。19 测,三处变异验证。**PR4 shipped (2026-08-30, 迁移 574, +2 端点, 0 cron, 0 MCP)**:关闭一个知识缺口需要**修复类型(五选一)+ 证据 + 一次通过的重放**,而不是一句没人核过的话;重放拿原问题、以**原提问人**身份、走 **dry-run 模拟**(真实 turn 会给员工发卡、还会写 transcript 污染上一个 PR 刚做诚实的计数);只存那一轮的**形状**不存回复正文;通过的问题进**固定回归集**。14 测,三处变异验证。**PR5 shipped (2026-08-30, 0 迁移, 0 端点, 0 cron, 0 MCP)**:补齐覆盖面与漏斗 —— `request_permission` / `release_my_api_key` 两个动作(**14 动作 / 9 解析器**一一对应),权限域 `pending` 按 `provisioning_task_id` 二分(「等人批」vs「批过了在建号」指向不同的人);漏斗补五个指标(**从需求到最终结果的时间不加列**,由服务请求 `created_at` → 链接 `phase_at` 派生、只量已落地的、能回算历史;卡片回复解决率拆成两个数;没数据一律 `None` 不返回 0%)。10 测,四处变异验证。**助手需求闭环主题至此全部 shipped,无遗留半成品。**

- **下线前置清理 (decommission blocker cleanup) — ✅ shipped (2026-09-07, 迁移 624 / +1 cron / +1 只读端点 / 0 MCP)**。用户点「下线」被 Azure 的 `400 ResourcesExistingOnSubscription` 挡住,而 Helix 把这句话连同一行 `az group delete` 甩回给了刚刚要求下线的人。现在:弹窗先跑只读预检把**会被一并删除的东西**列出来(Azure 资源组 + 内含资源 + 管理锁,GCP liens),管理员确认一次并连同看到的 blocker key 一起提交,之后 Helix 删锁 → 删资源组 → 重试关闭;云上删除是异步的,`cmdb.decommission_cleanup_resume`(`*/5`)推进到云端接受为止再走既有落库路径并给点击的人发一条完成通知。**只删确认过的 key** —— 确认之后新出现的资源不会被静默删掉,而是转 `needs_reconfirm` 并请人重新确认。`status` 全程不动(在飞状态在迁移 624 的 8 个新列上,列表/详情渲染「下线中 · 正在清理资源」)。另四家云(AWS/阿里云/腾讯/UCloud)无预检,弹窗如实说明而不假装空。**未做**:端到端「删完真的能 cancel」需真订阅验证;it-team-us 的实际执行等用户点头。

- **云资源效率洞察 / 成本优化 (cloud efficiency) — 3-PR theme, in flight (2026-07-20 起)**。同事反馈云上机器/集群利用率低、有成本优化空间,但 Helix 只同步了云清单与成本、没有运行时利用率。补上采集层,把「配置 × 利用率 × 成本」拼成每台机器的效率画像 + 优化建议 + 预估月省。**纯只读洞察,不对云做任何写操作**(执行仍走既有 ServerLifecycleActions)。**PR1 shipped (2026-07-20, 迁移 419, +2 cron 类型, 0 MCP)**:后端引擎——`ProviderCapability.METRICS` + 四云 metrics adapter(AWS CloudWatch / 阿里云 CloudMonitor〔含国际站子账号扇出〕/ Azure Monitor / GCP Cloud Monitoring,全部复用现有连接器鉴权、零新增 SDK;CPU+网络 agentless 为闲置主判据,内存/磁盘需 agent 尽力而为、缺失记 NULL);2 张表(`cmdb_server_metrics_daily` 日聚合保留 90 天 + `cmdb_server_efficiency` 每机画像);规则引擎(idle/oversized/busy/healthy/insufficient_data,内存做否决/加强信号,阈值 `cloud_efficiency.rules` 免部署可调);成本关联(`cmdb_cost_facts` 按 resource_id 匹配实例算月成本+预估节省);两 cron(`cmdb.server_metrics_sync` 日采集 + `cmdb.efficiency_compute` 日重算,按 provider 声明 METRICS 自动铺行,METRICS 已排除出 resource_sync);只读 API `/api/v1/cmdb/efficiency`(summary/instances/detail/rules,`can_read_cloud` 管理层读、员工自助只见自有、`can_manage_cloud` 改阈值);30 后端测(规则引擎 22 + API/端到端 8)+ provider/cloud sync 回归 65 过。**PR2 shipped (2026-07-20, 纯前端)**:成本优化洞察大屏(`/admin/cloud/efficiency`,4 等高 KPI 卡 + 判定分布 Donut + 各云可省 BarTrend + 可筛选/排序/分页实例表 + `can_manage_cloud` 阈值编辑弹窗)+ 服务器详情效率卡(判定/建议标签 + 四指标 + 利用率趋势 LineTrend,无画像走平静空态)+ 员工自助面(`/my/cloud/efficiency`,后端 owner 自动收窄)+ 语义色(idle=橙,与资产域 statusColor 区分)+ 导航/路由 + 三语 i18n;tsc/eslint/build/vitest(88)全绿 + 独立端口隔离实例播种四档演示机实机走查(大屏/详情卡实截、成本 API 真实 HTTP 核对)。**PR3 shipped (2026-07-20, 迁移 420, +1 cron〔默认禁用〕, +2 MCP→hint 425)**:百炼 AI 解读(Top-N 可优化机器生成人话解读,缓存到效率行,>7 天才重生,LLM 未配/出错静默降级;作为 efficiency_compute 尾步 + 按需 narrate 端点)+ 组织/部门下钻(owner→department_chain 子树上卷,`/by-department` 端点 + 大屏 Top10 HBar)+ 2 个 MCP 只读工具(get_cloud_efficiency_summary / list_cloud_efficiency_instances,hint 423→425)+ IT-only 周报摘要 cron(单卡扇 IT 管理员,空周静默,seed 禁用/opt-in,遵通知 IT-only 红线 + 上线前评估存量)+ 前端 AI 解读展示/重新生成按钮;6 后端测 + MCP surface 9 + tsc/eslint/build/vitest 全绿。**「云资源效率洞察 / 成本优化」3-PR 主题全部交付。** **PR4 shipped (2026-07-20, 迁移 422, +1 MCP→hint 426, 0 cron/RBAC)**:云订阅内嵌优化建议 + 业主参与——收口「割裂 + 死胡同」。服务器→订阅归属复用既有成本归属链(cost_facts.resource_id→scope_id,不改 sync driver);新端点 `GET /cmdb/efficiency/scopes/{id}/summary|instances`(管理层全量 / 订阅 owner 看本订阅全部 / 其余自有 / 坏 id 404);两处 scope 详情页各加「成本优化」Tab(`ScopeEfficiencyPane`),独立效率页保留互链;新表 `cmdb_server_efficiency_triage` + `POST .../instances/{id}/triage`(keep 须原因 / acknowledge / snooze,非破坏,只登记不动云,audit),**读时保鲜**(verdict 漂移或 snooze 到期→自动回 open,待处理只数 open);MCP `triage_cloud_efficiency_recommendation`(hint 426);7 新后端测 + 15 回归绿,本地实机走查(业主 triage 全链)。**二轮三 PR in flight (2026-07-22 拍板,见 DECISIONS)**:**PR-1 shipped (2026-07-23, 0 迁移/cron/RBAC/MCP-count)**——自助可见性统一为「server owner ∪ 我(+下属)拥有的云订阅归集机器」(新共享原语 `visibility.py`),套用效率页/我的云服务器/服务器详情/analytics-my/4 个 MCP 镜像,效率行带归属订阅 tag,空态解释卡,顺手修 MCP triage 误用读 scope;5 新测 + 95+ 回归绿 + 实机走查。**PR-2 shipped (2026-07-23, 迁移 425)**——已优化成本同步侧检测:被标记机器在控制台被关停/删除/降配也入账(重算前跑检测器,新增 downsize method + source=helix/console 口径 + evidence;baseline 取被标时点成本快照;CFO 页展示来源拆分);6 新测 + 19 回归绿。**PR-3 shipped (2026-07-23, 迁移 426 seed +1 cron)**——Owner 周提醒:待处理建议按 owner 聚合一张卡(站内+飞书),默认关,效率页「触达设置」= 真开关 + 灰度名单 + 收件人预览(dry-run);按人按周去重;6 新测绿。**二轮三 PR 全部交付**;之后:整体 code review + 安全/逻辑扫描循环 → 发布生产(digest 保持关闭待手动开启)。
- **「放松一下」安全游戏厅 (security arcade) — 5-PR theme, ✅ 全部交付 (2026-07-18~19)**。员工安全宣贯游戏化:安全每日一题产出积分(答对 +10 / 答错 +2 / 连答≥7 天 +15,读时懒记账 + 历史补发封顶 100),积分是小游戏入场券(每局 10 分 + **每天 3 局硬闸** = 防沉迷即经济系统);对局全部**服务端出题判分**,排行榜结构性防刷分;游戏本身就是安全学习(题库竞速/钓鱼鉴别/安全找茬)+ 放松类(舒尔特/翻牌/色词);零新增推送,纯 pull。**PR1 shipped (2026-07-18, 迁移 411, 0 cron, +1 MCP→hint 417)**:`arcade` 新域(钱包行锁串行化 + 幂等流水 + 对局框架 GameSpec.build/grade)+ 首游「答题竞速」(60s、排除多选、连击加成、逐题解析)+ 桌面一级菜单「放松一下」门户 + 移动 H5(`/m/arcade` + 补齐 `/m/security-quiz` 闭环)+ 16 后端测 + 双端预览实测全链路。**PR2 shipped (2026-07-19, 迁移 412, 0 cron/RBAC/MCP)**:知识竞速换**独立知识题库**(常识/历史/科学/地理/中华文化/生活百科,精校种子 60 题 + AI 生成 + 管理端 CRUD,不再复用安全题库)+ 开局品类选择(为品类排行榜铺路)+ 四个即开型游戏(弱密码狙击/舒尔特方格/记忆翻牌/色词反应,全服务端判分)+ 首页三区(知识竞技/安全小游戏/轻松一刻)+ 「放松一下」挪至员工菜单**最后一项** + 修 PR1 超时死循环 + 37 后端测。**PR3 shipped (2026-07-19, 迁移 413, 0 cron/RBAC, +1 MCP→hint 418)**:三种排行榜(知识榜/科举、单游戏榜、品类榜,实时聚合 + 我的名次 + 离职排除)+ 科举称号阶梯(白丁→状元)+ 品类达人头衔(各品类 rank-1)+ 首页称号条 + 桌面排行榜弹窗 + 移动 `/m/arcade/leaderboard` + 43 后端测。**PR4 shipped (2026-07-19, 迁移 414, 0 cron/RBAC/MCP)**:钓鱼邮件鉴别(逐封判钓鱼/正常+破绽揭示)+ 安全找茬(办公场景找全隐患,precision/recall 评分)+ 两内容库(钓鱼样本 8 封虚构域名 / 找茬场景 4 个)+ 管理端(概览/list/归档/AI 生成钓鱼样本/新增场景)+ 52 后端测。**PR5 shipped (2026-07-19, 0 迁移, +1 MCP→hint 419)**:成就徽章接入(4 枚 arcade 徽章:初来乍到/游戏常客/进士/状元,自动上既有成就墙)+ 参与看板(采纳率/各游戏/各部门/积分经济,admin+management)+ 运营管理端 `/admin/arcade`(参与看板 + 内容管理:题库/钓鱼/场景 AI 生成)+ 63 后端测。**「放松一下」5-PR 主题全部交付**:积分账本→7 游戏→排行榜/科举称号/品类达人→内容库/AI/管理端→荣誉接入/参与看板。之后:整体 review + 安全/逻辑扫描循环 → 发布生产。**体验整改 shipped (2026-07-19, 0 迁移, +2 MCP→hint 423)**:每局入场费默认 10→3;记忆翻牌难度分级(轻松/经典/进阶/烧脑)+ 卡面图标放大;排行榜重设计为前三名领奖台 + 动效(共享 `LeaderboardView`,桌面/移动一致);对局日志——「我的战绩」(自助) + 管理端「对局审计」台账(`GET /arcade/sessions` + `/arcade/admin/sessions` + 2 MCP);首页紧凑化(游戏墙首屏可见)。
- **API Key AK/SK 自助轮换 (apikey rotation) — 2-PR theme, in flight (2026-07-16 起)**。员工自助「换一把新 AK/SK 而不停机」——标准双活轮换:新凭证即时生效,旧凭证在自选宽限期(立即 / 3 / 7 / 14 天)内仍可用,`apikey.rotation_retire` cron(每小时)到点自动退役。三家云自建号原生支持:Bedrock 同一 IAM 用户加第二把 access key、Bailian 工作区新建 `sk-`、Azure OpenAI 重生成另一个 key 槽。**PR1 shipped (2026-07-16, 迁移 401, +1 cron, +3 MCP→hint 416)**:后端核心——provider-agnostic 状态机 `apikey/rotation.py`(mint/retire/cancel 三家原语,轮换态存 `detail.<provider>.rotation`,含供取消回退的加密旧 `key_value` stash,`_to_response` 全读者剥离)+ `ApiKeyService.{rotate_key,force_complete_rotation,cancel_rotation}`(owner/admin、仅 active、`ROTATION_IN_FLIGHT` 互斥、Bedrock 2-key 预检、不返明文走既有 `/reveal`)+ 退役 cron(到点退役+24h 提醒,对云幂等)+ 驱动新增 `AWSProvider.{create,delete,list}_access_key`/`AzureCloudDriver.regenerate_foundry_account_key` + 3 MCP + `revoke_key` 清态;15 rotation 测 + apikey 全套 206 过、迁移单 head=401、ruff 净。**PR2 shipped (2026-07-16, 纯前端)**:列表页行内「轮换 AK/SK」菜单 + 弹窗宽限期四档位 + 「轮换中·旧key X停用」角标 + 轮换后自动 reveal;轮换中行改显「我已切换,立即停用旧凭证」+「取消轮换」;详情页轮换面板;管理员页角标 + 代操作;抽共享 `ApiKeyRotateModal`;三语齐全;tsc/eslint/build 全绿 + 隔离预览实测。**「API Key AK/SK 自助轮换」2-PR 主题全部交付**;之后:整体 review + 安全/逻辑扫描循环 → 合并发布生产。

- **云权限续期改 Owner 审批 + 「永久授权仍被回收」事故收口 — ✅ shipped (2026-07-29, 迁移 436/437/438 / +1 cron / MCP 435→440 / 0 新角色)**。从用户问「cn68 里明明是永久授权的人，为什么还会回收他们的权限」查起，实测生产后一次性修完云访问生命周期的一整片问题。**事故本体**：「永久授权」不是行上的属性而是各写入点各自推导 —— `grants_sync` 的 discovered 插入完全不判 `cloud_admin`（一律打 90 天），而 `sweep_expiring_grants` 有 owner/prod 豁免却没有 cloud_admin 豁免（兄弟函数 `sweep_inactive_grants` 有 —— 这个不对称让 bug 在历次评审中隐形）；7/24–7/29 已误回收 162 条，另有 150 条排队。现引入唯一判定源 `load_perpetual_person_ids`，写入侧与两个 sweep pass 全部走它，sweep 增归一化 pass 主动清错误 TTL。**2026-08-02 follow-up（迁移 440）**：用户明确全局管理员同样不得被提醒或回收；永久集合扩为 `cloud_admin ∪ global_admin ∪ legacy it_admin`，迁移即时清存量 TTL，前端全云权限面同步显示管理员永久授权。离职与主动撤销不受影响。**第二个独立 bug**：续期 `now+90` 无条件赋值会把有效期改短（4 条 365 天 grant 在 7/15 被砍到 90 天），改为 `max(现有, now+ttl)`；该路径此前零测试覆盖。**第三块**：续期是纯自助（3 个月 763 次全部本人完成），且任何 `expired` 行可被前持有人无限期一键复活（839 条 / 135 人）—— 到期回收只是把「提申请+审批」降级成「点一下」。新增续期申请域 + 表 `cmdb_cloud_grant_renewal_requests`：Owner 审批、>90 天需云管理员二次签名且两签不得同人、禁自批、决策时刻重校离职、14 天未决关单（新 cron `cmdb.renewal_lapse_sweep`）、硬撤销联动关单而到期不联动、复活窗口收敛到 30 天、一次提交覆盖该订阅全部角色。`POST /grants/{id}/renew` 收紧为云管理员 break-glass 并单独审计；MCP 删掉自助续期工具、新增 5 个 1:1 工具。**通知重做**：提醒从逐条（7/15 一次 861 张卡 / 198 人，单人单日 63 张）改为按人聚合；真回收时此前零通知，现补「已回收」卡；审批超期前 3 天提醒 Owner 一次。**治根**：发现即打 TTL 改确定性抖动（治「一个账号接入→全员同一秒到期」）；Owner 交接补 TTL（消除交接当天静默批量回收）；「申请记录」改成真流水（新端点 `access-ledger`，旧 tab 92 次续期零痕迹、`granted_at` 被标成「申请时间」、218/230 行是从云上扫的却冒充申请）；管理后台「460 条过期仍生效」告警实测可处理 0，改为只统计可处理项；未映射云端账号从数字变成点名。**前端**：`renewal_eligibility` 由后端裁定（治「点了才 403」）、`ExpiryChip` 不再丢弃真实到期日、新增申请弹窗与 Owner 审批队列、删除桌面/移动端全部自助续期入口、初次申请 TTL 上限 365→180。35 条新测 + cmdb/mcp/scheduled_jobs/alembic 全量绿。**剩：整体 code review + 安全/逻辑扫描循环 → 合并 → 发布生产（进行中）。** **审批人可达性补丁 — ✅ shipped (2026-07-31，0 迁移 / 0 cron / 0 MCP)**：上线后订阅 Owner 反馈「收到续期推送但页面里找不到这条」。根因是审批入口只做在 global_admin 的 admin 详情页，而 Owner 多数不是 global_admin，卡片按角色分流后落在 `/my/` 页——那里没有队列；`?renewals=1` 桌面端也无人消费；行动中心与 `/my/todos` 都不把续期计入待办（前者因此显示 All clear）。现已：owner 页补「续期审批」分段、卡片 URL 带 `&group=` 落地即定位高亮、行动中心计数、`/my/todos` 增「云权限续期审批」第七类（跨订阅、可内联审批）；并修掉「普通 Owner 的待办里混入只有云管理员能决的 `pending_admin` 行」这条永远做不完的待办。详见 CHANGELOG。

- **安全问卷 (security questionnaire / TPRM) — 4-PR theme, ✅ 全部交付 (2026-07-02~03)**。双向安全问卷能力,落在 auditor 域:**甲方侧**(我们评估云厂商/供应商的安全态势)+ **乙方侧**(客户要求我们填问卷时的答案库/证据库/AI 辅助作答)。已锁:不做任何对外无登录门户,Helix 绝不主动往供应商/客户邮箱发件——甲方侧机制是导出双语 xlsx(带题号列)人工带外发、回收上传解析入库。**PR1 shipped (2026-07-02, 迁移 374, +1 cron, +6 MCP→hint 398)**:甲方侧后端全链路——5 张表(`auditor_sq_templates`/`_template_questions` 双语模板 + `auditor_vendor_assessments`/`_answers`〔创建时按模板物化快照〕/`_attachments` S3 presign);种子两套系统模板(ISO 27001:2022 + 27701,完整版 39 题/精简版 20 题,中英双语);生命周期 draft→sent→answered→completed→archived/cancelled;双语空白问卷导出(数据校验下拉 + 题号列) + openpyxl 回传解析按题号回填 + 加权自评分 + 风险评级(可覆盖) + 逐题复核 + finding 提升(finding_type 按评估 scope,取消标记再完成自动 resolve) + 复评到期分阶段提醒 cron(仅内部,不打扰员工/供应商) + 风险态势总览 + xlsx 结果导出;6 MCP 工具;16 新单测 + auditor 全域 110 过、迁移 up/down/re-up 幂等实测、ruff 净、full app+worker+mcp smoke-import 过。**PR2 shipped (2026-07-02, 纯前端)**:甲方侧前端——「安全问卷」页(风险态势 6 卡 + 供应商评估/问卷模板 两 Tab + 新建评估 + 模板预览/克隆)+ 详情页(逐题作答按类型渲染 + 复核标记 + 逐题/评估级证据上传 + 得分/评级卡〔可覆盖〕+ 下载空白问卷/导入回填/导出结果/完成/重开动作条)+ `api/vendorAssessment.ts` + i18n 三语各 106 键 + PageHelp;双菜单入口;tsc/eslint/build 全过 + 隔离预览真账号建 39 题评估进详情页实截验证。**PR3 shipped (2026-07-02, 迁移 376, +1 cron, +6 MCP→hint 404)**:乙方侧后端——答案库(可复用双语 Q&A + owner/使用计数/复核期/关联证据 + 答过回填)、证据文档库(我们的证书/策略/报告,S3 + 可共享 + 有效期过期高亮)、客户问卷(收件→加题〔手动/粘贴/xlsx 导入表头识别〕→百炼 AI 按答案库匹配起草〔命中回填+置信度、AI 只草人工复核前不算已审、无 key 503 降级〕→逐题复核→导出填好 xlsx 并把发出字节归档为版本〔责任可追溯〕)+ 截止分阶段提醒 cron(仅内部)+ 3 概览端点;6 MCP;8 新单测 + auditor 全域 118 过、迁移幂等实测、ruff 净、smoke-import 过。**PR4 shipped (2026-07-03, 纯前端)**:乙方侧前端——「安全问卷」页扩到 5 Tab(供应商评估/客户问卷/答案库/证据库/问卷模板);客户问卷 Tab(登记+列表进度)、答案库 Tab(新增/编辑双语问答+使用计数+新鲜度+复核)、证据库 Tab(上传 presign+过期高亮+可共享开关)、客户问卷详情页(添加/导入问题+AI 辅助作答+逐题复核+回填入库+导出定稿+版本存档)+ `api/inboundQuestionnaire.ts` + i18n 三语各 93 键;tsc/eslint/build 全过 + 隔离预览真账号建客户问卷进详情页 + 5 Tab 实截验证。**主题收官**:甲方侧(评估→评分→评级→finding→复评)+ 乙方侧(答案库+证据库+AI 作答+回填+定稿存档)双向闭环。见 [docs/architecture/PLATFORM.md](docs/architecture/PLATFORM.md)。

- **系统手册 (System Handbook) — 8-PR theme, shipped (2026-07-02)**。产品内、图为主、全员可见的设计/架构/执行说明中心(`/handbook`),与 `/help`(用户"怎么操作")互补——手册讲"怎么设计/运转/执行",系统全景、不分受众。管线:每域一篇中文 Markdown(`web/src/content/handbook/*.md`,懒加载,内嵌 mermaid + `live-<kind>` 实时表),`.md` 免 i18n 三语税;实时清单表由只读全员可读的 `app/domains/handbook` 端点喂(定时任务=注册表∪DB、连接器、RBAC 角色),永不过期。**PR1 shipped (2026-07-02, 无迁移)**:地基(薄壳 + Markdown/Mermaid 渲染器 + `LiveTable` + `MermaidDiagram` 暗色 theme prop + 一级菜单入口)+ 两篇内容(全景总览、平台底座)。**PR2 shipped (2026-07-02, 纯前端)**:身份与人(HRIS 同步+字段级映射、出站同步中心)+ HR 生命周期(入职状态机、离职+Leaver Blocked+双签、假期健康洞察)共 5 篇、11 张图。**PR3 shipped (2026-07-02, 纯前端)**:账号与连接器——账号生命周期管家(图2 单一入口 + 保留号豁免 + 兜底安全网)+ 连接器全景(× 能力实时矩阵、kind/执行模式/离职策略)共 2 篇。**PR4 shipped (2026-07-02, 纯前端)**:软件与坐席(三层模型、增值项、月账单关账、各 SaaS 坐席同步六桶+漂移两派)+ API Key 与 AI 花费(API Key vs PAT、Bedrock 自动授权、AI 四桶归集)共 2 篇。**PR5 shipped (2026-07-02, 纯前端)**:云与网络(云资源与访问模型 scope→grant→role+四级治理、云访问生命周期 TTL、网络资产、云成本两表、数据仓库 DAX)共 5 篇。**PR6 shipped (2026-07-02, 纯前端)**:权限治理与财务(开通编排、审批统一流、安全审计与合规 GRC、财务对账中心)共 4 篇。**PR7 shipped (2026-07-02, 纯前端)**:员工体验(我的 IT 宇宙、我的工作三件套、求助与 AI 助手、移动端 H5)+ 执行范例集(SOC2 加邮件组等)共 4 篇——**至此 10 大分组 24 篇内容全覆盖**。**PR8 shipped (2026-07-02, 纯前端)**:全量对抗式复审(后端/前端/内容 3 reviewer)+ 安全/逻辑扫描,修 3 处前端(mermaid 主题白名单 / rowKey index 兜底 / 缺失 md 显式 notFound)+ 7 处跨章引用,二次扫描确认无回归。**系统手册 8-PR 主题全部交付并部署生产。****2026-08-18 追加第 25 篇**:`start` 组《AI 大脑》(八域权重与打分口径、`_ROLE_DIMENSIONS` 按角色裁剪表、每日 snapshot→analysis→高危通知链路、建议状态机与写权限边界)。见 [docs/architecture/PLATFORM.md §14.0](docs/architecture/PLATFORM.md)。

- **身份同步中心对标 Authing 升级 (identity-sync Authing parity) — 4-PR theme, in flight (2026-07-01 起)**。对标 Authing「身份自动化 / 同步中心 / 监控看板」给数据同步中心做生产级抬升——**四块功能 Helix 都已有 v1**(flow tab 自研 SVG 流向图、表格式可视化字段映射 `MappingEditor`、`OverviewPanel` cockpit、`presets` 模板选择器),本主题把它们抬到 Authing 级 + 补唯一真缺的**时间序列监控看板**。**不抄**(用户确认)IdP/OAuth server、MFA/会话策略/条件访问——Helix 登录委托 Google/飞书,做 IdP 是架构偏离。**PR1 shipped (2026-07-01, 无迁移, +1 MCP→hint 390)**:同步监控看板——只读聚合端点 `GET /identity-sync/metrics`(today/7d/30d/90d,锚 Asia/Shanghai)在 runs+run_items 上算 KPI 条/按天趋势/按目标明细/触发来源/上下游数据量概览(对标「同步组织目录数据概览」)/最近失败运行+人次;MCP `get_identity_sync_metrics`;前端「监控看板」tab(`MonitorPanel.tsx`,ECharts via reportsShared);3 后端测过、隔离预览实截验证。**PR2 shipped (2026-07-01, 纯前端)**:数据流向图升级到 Authing 同步中心级——复用 `/overview` 富化 hub-and-spoke 节点(HRIS 真实源名+健康点+在册人数 → Helix 通用目录 在职/在册/数据质量 → 各目标 健康点+覆盖率+失败/待处理),边带数据量标签 + 底部图例;共享 `FlowDiagram` 加可选 `metrics/healthDot/edge-label/legend`(SsoApps 复用零回归);隔离预览实截。**PR3 shipped (2026-07-01, 纯前端)**:字段映射编辑器升级——补 value_map 可视化编辑(`ValueMapModal`,源值→下游值 + passthrough 开关,补 FE `value_map_passthrough` 类型)+ 行内校验(空目标/重复目标/未知 token⚠)+ ↑↓ 排序;顺带修 `PersonFilterEditor` 在 filter_config 缺 person_ids 时白屏(`?? []` 守卫);隔离预览实测值映射弹窗/校验/排序。**PR4 shipped (2026-07-01, 迁移 371)**:同步模板矩阵操作维度——`identity_sync_targets.write_mode`(create_update/create_only/update_only,`String(16)` 无 FK,server_default 保持存量历史行为),`_sweep` 按 `state.external_id` 链接态在 runner 调用前中心化门控(create_only 跳已链接、update_only 跳未链接,不写 hash 廉价重判);schemas/validate/preset.write_mode/preset 端点透出;前端编辑抽屉写入模式 Select + 列表紫标签 + 三语;5 测 + 402 套件过、隔离预览实测。**Authing-parity 4-PR 主题全部交付。** 之后:整体 code review + 安全/逻辑扫描循环 → 发布生产(进行中)。

- **Zoom 账号生命周期纳入数据同步 — ✅ shipped (2026-07-24, 迁移 428 / 0 cron / 0 MCP / 0 RBAC);建号链路 2026-07-30 修复后仍待在生产开闸**。新增第 7 个 identity-sync kind `zoom`(runner `runners/zoom.py`,复用现有 `zoom-primary` S2S OAuth 连接器,无新凭据):入职给 in-scope 员工建**基线账号(`no_meetings` / type 4,无 Meetings 许可、无租户名额上限)**、离职**停用**(`PUT /users/{id}/status deactivate`,保数据/释放坐席/managed-domain 可停),`supports_delete=False`。与既有付费坐席流互补——建号走此目标、付费升档走坐席申请流,`upsert` 对已有用户**永不改档位**。取代已下线的 Authing → Zoom 供给链;**Zoom 登录仍走 Google SSO,此目标不碰 SAML/SSO 配置**。migration 428 seed 目标 `enabled=0`(首次全量会给存量员工批量建号,管理员在 UI 确认后开启)。前端补 `zoom`+`netsuite` kind(标签/配色/emoji/三语)。7 新测 + 既有 zoom driver 42 测全绿。见 DECISIONS 2026-07-24 与 [docs/ops/zoom-ops.md](docs/ops/zoom-ops.md) 第九节。
  - **2026-07-30 修复:建号链路上线一个月只建成 1 个账号**。基线档位原为 Basic(`type=1`)—— 名字像免费档但**按租户限量**(20 个,早已坐满),07-24 唯一一次建号全量跑 17 人被拒 16;`3412` 又只在「Licensed→Basic 降级」分支被识别,建号撞上时既无 tag 也被记成可重试的 `failed`;目标随后被调成 `write_mode=update_only`(只维护、不建号),411 个 in-scope 员工全部 skipped —— 直到有人反馈新入职的 bin.ye 没有 Zoom 账号。修复:默认档位改 `no_meetings`(type 4,无上限,本租户 397 个活跃用户已在该档)、3412 按集合识别并归入 `blocked` + 同 run 跳闸、新增 `zoom_max_creates_per_run`(默认 25)给存量补建限速(建号会给本人发 Zoom 邀请邮件)、未知档位不再静默回落到 Basic。**顺带补上一个平台级盲区**:`blocked` 按设计保持 run 绿色且不重试,而此前**任何目标**的 blocked 积压都没有告警(代码注释里承诺的 "alerted once" 从未实现)—— 新增 `maybe_notify_blocked_backlog`(挂 `_finalise_run`,outbox 签名去重,只发目标配置的收件人,自愈型原因不告警)。
  - **生产开闸**:`zoom-account-lifecycle` 生产行原为 `write_mode=update_only` + `zoom_plan_type=basic`(preset 默认只作用于新建目标)。开闸走**锁人灰度**:一次改 `zoom_plan_type=no_meetings` + `filter_config.person_ids=[待验证的人]` + `write_mode=create_update` → 跑一次手动全量(scope 只有 1 人)核对 type=4 → 清空 `person_ids` 放开全员,由 `zoom_max_creates_per_run=25` 按天排空存量。**注意「同步此人」不能用于 `update_only` 下的验证** —— `force_in_scope` 只绕过 filter,不绕过 write_mode(单人路径自 #1736 起与 sweep 对齐),所以灰度只能靠 `person_ids`。步骤见 [docs/ops/zoom-ops.md](docs/ops/zoom-ops.md) 第九节。
  - **2026-08-04:Zoom 账号两段式离职处置(末日次日停用、隔日永久删除)—— ✅ shipped(迁移 451 / +1 cron / 0 MCP / 0 RBAC),**生产已运行**(2026-09-06 核:08-04 起累计删除 7 个离职者账号,08-24~09-05 全绿)**。对标 Google 的 suspend→delete,收归 `account_lifecycle_coordinator`(`suspend_zoom_account` / `unsuspend_zoom_account` / `delete_zoom_account`):`_push_force_logout` 在末日次日停用,新 cron `hr.offboarding_zoom_account_delete`(每日 03:10 UTC)在 `leaver_date + delete_lead_days`(默认 2,员工本地时区)对**已确认停用**且非保留号硬删除(`delete_user_unconditional`)。关键:**按 `work_email` 寻址,不依赖 identity-sync `external_id`**,从根上绕开约 397 个基线 `no_meetings` 账号 external_id 为空导致的「离职停用静默 no-op」盲区(此盲区底层的共享 sweep `skipped` 分支未回填 external_id 的问题**未在本 PR 修**,需单独按全 runner 评估)。⚠️ Zoom 删除永久、无 Google 那种 ~20 天 undelete,停用→删除间隔即唯一纠错窗口。**「无 successor / 无内容转交」的原始决策已于 2026-08-13 推翻** —— 现在删除带 `transfer_email` + meeting/webinar/recording,解析不出交接人就不删;2026-09-06 起厂商拒收件人也按种类分流(无 Webinar 许可 → 不带转移删;其余 → 拒绝删除 + 单次告警,见 DECISIONS)。取消误判离职会经 `unsuspend_zoom_after_cancel` 还原。存量已离职号不追溯清理(从未走过新停用闸)。

- **月结关账编排 (month-end book close) — 6-PR theme, in flight (2026-06-29 起)**。把 PingCAP 财务团队线上自维护的「月结清单」(`finance-close-checklist.vercel.app`:129 条任务、按 owner/工作日截止/状态跟踪、12 流程分组、AI-work 标记、实体适用性)收编进 Helix finance 域,做成生产级关账编排子模块——**与现有 `finance_period_close`(冻结账本数字)正交**,这是运营层的关账清单/日历/周期编排。已锁:owner/commenter=真实 `identity_persons`(FK + 原名兜底)、实体列复用 `asset_legal_entities`、提醒默认纯拉取不推送(仅财务 owner opt-in,守不打扰铁律)、PR5 接 Helix 数据自动信号(把 close-cockpit 5 项检查泛化成 provider-registry 给任务挂绿/黄/红决策灯,不自动完成)。**PR1 shipped (2026-06-29, 迁移 355, 0 cron, 0 MCP)**:关账日历引擎(纯函数、无 DB、完全可单测)——把关账期+日历配置(假期/补班/周末规则/目标工作日)算成 book-close 目标日(次月第 N 工作日,默认 WD5)+ 关账窗口(逐日分类+WD 编号)+ 任意任务锚点(Day-N 日历倒数 / Day+N 第 N 工作日 / On going)的截止日;**周末上班晋升**(窗口跨整周末则配置那天计工作日,单调不动点);逐日对齐两个真实算例(6月关账=Jul 6/Sat Jul 4=WD4、滚动7月=Aug 6/Sat Aug 1=WD1)+ 假期∩补班(9月落十一黄金周 Sat Oct 10 补班=WD3)。4 张表(`finance_close_{calendar_config,holidays,makeup_days,runs}`,无 FK):单例配置 + 假期(day×country)+ 补班 + 每期 run(可编辑 Day±1 锚点 + 手动 override〔强制审计理由〕+ 生命周期 open→in_progress→soft_closed→closed〔可 reopen〕+ 冻结快照〔关账后锁定关账日防回溯〕);迁移幂等 seed 中国假期 2024–2027(120)+ PingCAP 补班(6)+ 配置;129 条任务模板权威数据落 `close_seed/close_checklist_seed.json`(PR2 种子)。HTTP `/api/v1/finance/close/*`(runs/anchors/override/status/config/holidays/makeup/restore-defaults),读=`can_view_finance`、写=`can_operate_finance`+leaver+限流+审计;20 单测过(12 引擎+8 集成)、ruff/mypy 净、迁移真库三验。**PR2 shipped (2026-06-29, 迁移 356)**:任务层——模板库 `finance_close_task_templates`(权威 129 条)+ 每期实例 `finance_close_tasks`(status/截止日/评论/完成)+ 实体目录 `finance_close_entities`→`asset_legal_entities`;模板与实例共用列走 mixin;跨域读经 `finance_close_coordinator`(**负责人仅凭邮箱 hint 解析**,无 hint 绝不按名猜人留 label 兜底;实体码最佳努力映射,不中留 NULL 可改);`ensure_seeded` 幂等(不 NULL 覆盖管理员已设)、`instantiate_from_templates`(常态→in_progress)、`roll_forward`(克隆源期任务带改派/临时任务、状态重置+截止日重算、幂等)、改锚点联动重算、`aggregate`(总/按人/按流程+完成率,逐口径核对);HTTP 任务 CRUD/状态/评论/批量/实例化/滚月/模板/实体;13 单测过、finance shard 149 过。**PR3 shipped (2026-06-29, 纯前端)**:关账控制台 `FinanceClosePage`(`/admin/finance/close`)——月份切换 + 关账目标卡(状态/周末上班/覆盖标记)+ Day±1 锚点编辑 + 关账窗口条(WD1→WD5 CLOSE 配色)+ 进度总览(完成率仪表 + 按负责人卡)+ 多选过滤 + 按流程分组任务表(行内状态 Select/备注/逾期红/编辑删)+ 配置弹窗(假期/补班/周末规则/目标工作日 4 tab)+ 任务增改/手动覆盖/滚月模态 + CSV 导出;新 API 客户端 + 三语 125 键;写操作 PermissionGuard 门控;Preview 实截验证(空态/populated/窗口条/按负责人分布与线上一字不差),tsc/eslint/build 全过。**PR4 shipped (2026-06-29, 无迁移, +3 MCP→hint 380)**:MCP + 员工自助面——`AuthUser`即`IdentityPerson`故 `user.id==owner_person_id` 自助自动作用域;`/api/v1/me/close-tasks`(我的任务/改状态/评论,严格 owner 作用域非本人 403)+ 3 MCP(`finance_close_status` finance-view / `finance_my_close_tasks` self / `finance_close_set_task_status` owner-或-operator mutating)+ 移动动作清单 provider(有未完成任务的 owner 收件箱暖提示,纯拉取深链 `/m/close-tasks`)+ 移动页 `MCloseTasksPage`(期卡+任务列表+点按改状态)。Preview 实截移动自助页 + 待办暖提示;194 后端测过、MCP surface(hint 380)、自助 5 测、tsc/eslint/build 全过。**PR5 shipped (2026-06-29, 迁移 357, +1 MCP→hint 381)**:Helix 原生数据自动信号——把关账驾驶舱 5 项检查泛化成信号注册表 `close_signals.evaluate_signals`(复用 close_cockpit + fx_frozen/cloud_cost/huilianyi 共 8 信号,各带 status+detail+evidence 深链,全 finance 域自有读)+ 任务挂 `signal_key`(迁移 357 加列并回填 15 条精选默认绑定,既有期无需重建即点亮;纯模块 `close_signal_keys`)+ **信号只决策支持绝不自动完成任务** + `GET /runs/{period}/{tasks 含 signals, signals}` + MCP `finance_close_signals` + 前端任务行信号点(Tooltip+evidence)+「数据信号」决策支持卡;Preview 实截信号卡 8 项配色正确,信号 4 测 + shard 167 过、tsc/eslint/build 全过。**PR6 shipped (2026-06-29, 迁移 358, +2 cron 禁用)**:收官——两定时任务默认禁用:`finance.close_auto_roll_forward`(月初克隆上期清单滚月,幂等,中国时区判月界)+ `finance.close_deadline_reminder`(每日为逾期/即将到期任务按负责人汇总提醒,**纯拉取为主+opt-in 推送+仅推送财务负责人不打扰普通员工**,handler 失败必 raise);迁移 358 幂等 seed(Asia/Shanghai,enabled=0);自动化 4 测 + shard 172 过、迁移往返、ruff/mypy 净。**「月结关账编排」6-PR 主题全部交付**(整体 review + 安全/逻辑扫描 → 发布生产 进行中)。

- **半年度全员访问自查·员工自证 (access self-attestation) — 3-PR theme, in flight (2026-06-28 起)**。实地审计:平台对"一个人持有的全部访问"在**离职端**治理彻底(Leaver-Blocked 逐人 22 类),但**在职稳态**的周期复核只覆盖 SaaS 坐席(`access_certification` 仅 `software_seat`/`privileged_role`/`identity_consumer`,且经理/审计员逐项裁决,无持有人自证、无批量、员工无页面),云授权/API Key/SSO grant/软件权限全空白。本主题把周期访问复核补成平台级,做成**员工自证 + 批量 + 半年度安全产出**(6/1 + 12/1),不新建流程、用飞书,完成活动 = SOC2 CC6.3 / ISO A.5.15 证据。**PR1 shipped (2026-06-28, 迁移 352, +2 MCP→hint 376)**:后端引擎——新 `self_attestation` campaign 模式(自证人=持有人)+ 全足迹 snapshot(坐席/云授权/软件权限/SSO/API Key,逐在职人一次性快照)+ 批量自证 `submit_self_attestation`(keep=证据 / remove→各类型现成自助撤销:坐席期末退订·云即时撤·权限异步撤·SSO 转 IT·API Key 仅确认)+ **绝不自动撤未响应者** + 半年 cron(仅边界后 7 天窗口开盘,不追溯历史边界防全员推送风暴;每人单条飞书卡)+ 管理员「立即开盘」+ 自助端点 `/me/access-review` + 2 MCP。**PR2 shipped (2026-06-28, 纯前端)**:员工批量复核页——桌面 `/me/access-review`(KPI + 一屏表 + 默认全保留/勾选「我不需要了」暂存 + 底部汇总一键提交 + 清理确认含原因 + 友好空态)+ 移动 `/m/access-review`(antd-mobile + MActionBar,飞书卡片落地)+ 自隐藏 `AccessReviewBanner`(仅开盘时挂「我的待办」顶,不加常驻菜单)+ 三语 i18n + PageHelp;tsc/eslint/build 全过。**PR3 shipped (2026-06-28, 无迁移, +1 MCP→hint 377)**:治理面 + GRC + 产出——审计员汇总(覆盖率/按决定·类型计数/标记清理处置队列/未响应)`GET /compliance/certifications/{id}/self-attestation-summary`、半年 Word/PDF 导出(复用等保 reportlab CID 字体+python-docx)`/export?fmt=`、GRC 控制项 `access_self_attestation`(CC6.3/A.5.15,crosswalk 32→35)、未响应**按持有人去重**升级飞书+站内信给持有人+经理、经理团队面 `GET /me/team/access-attestation`、管理员「立即开盘」`/self-attestation/open-now`、治理落地页覆盖率 tile;前端 `CertificationDetailPage` 自证概览+处置队列+导出、`CertificationsPage` 立即开盘、`SecurityGovernancePage` tile、`TeamLifecyclePage` 自隐藏团队卡。**「半年度全员访问自查·员工自证」3-PR 主题全部交付**(收官 review + Preview 实截 + 安全/逻辑扫描 → 发布生产 进行中)。

- **财务模块平台级大改造 (finance platform buildout) — 6-PR theme, in flight (2026-06-28 起)** —— 把财务模块从「自下而上的成本聚合器」升级成**对得上 NetSuite 总账的运营子账本 + 各角色驾驶舱**,立起贯穿全公司的脊柱:**预算 → 承诺 → 计提/消耗(Helix 自下而上)→ 实际(NS GL)→ 差异(Flux + BvA)**。已锁决策:NS 拉成入站(GL 总账)为核心、五个角色界面都做、汇联易接通、Flux 收敛到 API 数据源、GL 只拉**汇总**粒度(量级被框死)。**PR1 shipped (2026-06-28, migration 350, +1 cron 禁用, +2 MCP→hint 364)**:NS 入站 GL 基座——`finance_netsuite_coordinator` 复用身份同步的 SuiteQL 传输(同 TBA 凭证,驱动加公共 `build_suiteql_client()`),服务端 `GROUP BY` 拉汇总 GL(期间×主体×科目×部门,绝不拉原始分录)+ 科目/子公司/会计期间主数据,落 4 张表(`finance_gl_actuals` 等,无 FK);已关账期间冻结、当前期间幂等覆盖、FX 月冻结;admin 只读探针 `POST /finance/netsuite/gl-probe`(上线后据真实列结构启用 cron,默认禁用);读面 `/finance/netsuite/{gl/summary,sync-status,periods,subsidiaries}` + 子公司↔法人主体映射 PATCH;MCP `finance_netsuite_gl_summary`/`finance_netsuite_sync_status`;8 单测 + finance/MCP 套件 92 过、migration upgrade+downgrade 真库双验。**PR2 shipped (2026-06-28, migration 351, +2 MCP→hint 366)**:GL 三方对账引擎 + Controller 关账驾驶舱——`finance_ns_accounts.mapped_cost_type`(启发式补 NULL + 可改)、`gl_reconcile`(按 cost_type 比 Helix 自下而上 vs NS 记账 GL + 未分类科目异常)、`close_cockpit`(5 项 go/no-go 清单)、前端两页(关账驾驶舱 / NetSuite 总账接入)+ 菜单。**计划修正**:取消「Flux 收敛到 GL」——与 finance-flux 已验证决策冲突(SuiteQL 无法外算合并抵销/CTA);对账只比费用科目,成立。**Flux 收敛再议(2026-06-28,应用户要求重测)**:发现原结论过强——`consolidatedexchangerate` + 科目类型汇率可外算合并(当初"大 6 倍"是没套合并汇率)。已建 `finance_flux_gl_coordinator` 合并直算 + `tie_out` 对平探针(API vs .xls 逐行 diff)+ `import_from_gl` 收敛(opt-in);端点 `/finance-flux/{gl-tieout,import-from-gl}` + MCP。**活体对平在部署后跑,对得上才切 API 去人工上传**;在此之前 .xls 流程不动。**PR3 shipped (2026-06-28, 无迁移, +2 MCP→hint 369)**:费用智能 + 员工「我的报销」——侦察发现汇联易对账/治理已 ship、cron 激活是外部 开放平台 授权(非代码),故避开投机,只做净新增:OID→person 邮箱解析器(解锁归属)、`/me/expenses` 自助页(空态暖提示)、`expense_analytics`(类别/部门/状态/Top 花费人)。汇联易 cron 仍待用户外部授权;分析 admin UI 待数据流入再建。**PR4 shipped (2026-06-28, 无迁移, +1 MCP→hint 370)**:承诺层——预算/实际/预测已存在,补 `open_commitments`(开口 PO=已批/已收未付清)+ budget_vs_actual 折入 `committed_usd`/`remaining_usd`/承诺感知预测 + FinanceBudgetPage 加列。脊柱(预算→承诺→实际→预测)打通。**PR5 shipped (2026-06-28, 无迁移, +2 MCP→hint 372)**:员工「我的钱」+ BU 负责人自助——`my_attributed_cost`(统一账本切到 owner=我 + 报销 + 12月趋势)、`my_bu_costs`(自助 scope 到本人 BU)、`/me/my-money` 页(本月成本+构成+趋势+我负责的 BU 段+交叉链接)。**PR6 shipped (2026-06-28, 无迁移, +2 MCP→hint 374)**:CFO 总览大屏(`leadership_dashboard` 聚合既有 reads,等高卡/口径平铺/人均成本)+ Vendor 360(`vendor_360` 跨渠道拆分单供应商支出+集中度,FinanceVendorsPage 抽屉)。**六 PR 主题全部交付。** 之后:整体 review + 各新页 Preview 实截(隔离端口+种子数据)+ 安全/逻辑扫描循环 → 发布生产。
- **财务变动分析 Flux 模块 (finance-flux) — 7-PR theme, ✅ 全部交付 (2026-06-27)**。把财务团队现用的外部「PingCAP Financial Flux」портал(Next.js/Vercel,BS & P&L 的环比/同比变动分析 + 双阈值异常旗 + AI 说明 + 行内 commentary/recon,持久化仅 `/api/comments` 存 TiDB 且当前鉴权失败)全量、生产级地复刻进 Helix 财务模块并下线老站。**关键差异**:数据用 **NetSuite 算好的合并报表导出**(实证:SuiteQL 在外部复刻合并不可行——原始 GL 加总比报表大 6 倍,内部抵销/CTA 无法外算;故报表导出=唯一真源);AI 用我们自己的百炼;阈值/科目映射/对比口径/KPI/AI 提示词全部财务自助配置(非 admin)。已锁口径:环比+同比 YoY、KPI 自动算可覆盖、仅已关账期间、合并不拆实体。**PR1 shipped (2026-06-26, 迁移 347)**:新 `finance_flux` 域**完整 10 表 schema** + 文档。**PR2 shipped (2026-06-27, 无迁移)**:NS 报表导入引擎——SpreadsheetML 解析(跳过 GL 明细/总计/Elim 子项,取干净财务行)+ 幂等入库 + `line_defs` 对齐报表(标签匹配复用 key)+ `POST /finance-flux/import`(`can_operate_finance`)/`GET /periods`;真实 5 月合并 BS 离线核验 Cash $32.23M 一字不差、Total assets $187.35M 配平;5 单测过。NS 权威源 cr=472/513/489/322。**PR3 shipped (2026-06-27, 无迁移)**:Flux 计算+只读 API——`flux_service`(Δ/Δ%/双旗[行级阈值覆盖]、KPI 卡[source 行值+覆盖+tone]、变动 Top 排行、环比/同比 prior 解析)+ `GET /finance-flux/{statement,summary,insights}`(`can_view_finance`);9 单测过(含现金 10.8M→32.23M 双旗、缺上期=0)。**PR4 shipped (2026-06-27, 无迁移, +3 MCP→hint 361)**:编辑层+真·AI——`ai_service`(行 AI 叙述走百炼 `chat_text`,无 key 优雅回落确定性模板)+ `editorial_service`(commentary/recon/adopt、KPI 覆盖、洞察卡/跟进项 CRUD,均与 lines 分表 re-import 不冲掉)+ 11 端点(写=`can_operate_finance`)+ 3 只读 MCP 工具;22 单测+MCP surface 过。**PR5 shipped (2026-06-27, 无迁移)**:财务自助配置面——`config_service`+8 端点(config 单行[阈值/合并口径/对比/AI 开关+提示词]、行目录[标签/段/排序/owner/行级阈值/启用]、KPI 目录、NS 科目→行映射[含 unmapped 过滤]),写=`can_operate_finance`(财务即可,非 admin),阈值序列化 2dp 统一;16 单测过。**PR6 shipped (2026-06-27, 纯前端)**:前端——`api/financeFlux.ts` + `pages/finance-flux/`(FinanceFluxPage:期间+环比/同比+导入+四 Tab[摘要 KPI/旗、BS/PL 行内 commentary+AI、洞察排行];FinanceFluxConfigPage:配置/行目录/科目映射 行内编辑)+ 路由 + 财务系统菜单 + 三语 i18n(~70 键/语,无硬编码 CJK);tsc/eslint/`pnpm build` 全过。小尾巴:要点卡/跟进项 authoring widget(后端已支持)下迭代补。**PR7 shipped (2026-06-27, 纯前端/文档)**:页面帮助提示(`pageHelp.finance-flux` 三语)+ [parity 核对文档](docs/architecture/finance-flux-parity.md)(逐条对照 + 数字正确性 + 老站下线步骤)。**七 PR 全部合并到 main,功能端到端可用。** Follow-up:要点卡/跟进项 authoring widget(后端齐);老站 Vercel 下线由财务/运维确认切换后执行(Helix 不删外部基建);全自动投递待邮箱。**剩:整体 code review + 安全/逻辑扫描 + 部署生产(进行中)。****全自动投递(NS 定时报表→邮件→Helix 收件)待用户给邮箱;在此之前财务自助上传即完整可用。**
- **实习生考勤月报 (intern attendance monthly report) — 3-PR theme, ✅ 全部交付 (2026-06-25)**。HR 需求:每个考勤周期按飞书考勤数据为每个实习生生成考勤表 → 推实习生确认 → 确认后推 leader(两段确认),实习生发现缺卡未补可退回、HR 补卡后重发直至确认,并支持实习生在 leader 处理前自撤误确认。设计见 [DECISIONS 2026-06-25](docs/architecture/DECISIONS.md)。**实习生口径 = 飞书「PingCAP Intern」部门成员**(可配,部门派生不加字段);**飞书只读不回写**。**PR-A shipped (#1410, 迁移 339)**:新域 `attendance` 后端基座——飞书考勤 client + 异常明细归一化(`facts.py`)+ 三表(cycle/report 版本化快照/event 审计)+ 周期 21→20 引擎 + 幂等生成(per-person SAVEPOINT)+ HR 端点。**PR-B shipped (迁移 340 / +1 cron / +4 MCP→hint 352)**:两段确认状态机(`lifecycle.py`)+ 飞书确认卡片(`cards.py`)+ 一键免登 `/attendance/confirm`(action-token)+ 自助 H5 `/attendance/me/*`(逻辑收口 `self_service.py` 与 MCP 共用)+ HR send/resend + 提醒 cron `attendance.pending_reminder` + 4 个自助 MCP 工具,详见 CHANGELOG/PLATFORM。**PR-C shipped (纯前端)**:HR 操作台 `/admin/attendance`(周期/生成/逐人状态板/发起·重发·明细/一键发起/明细抽屉)+ 移动 H5 `/m/attendance{,/:id}`(实习生/leader 看明细 + 确认/退回/撤销,飞书卡片落地页)+ `api/attendance.ts` + 三语 i18n。**PR-D shipped (迁移 342)**:生产实测发现「飞书 PingCAP Intern 分组不在 Helix 部门树(Helix org 全 SF 源)」→ roster 改**飞书通讯录部门直读**(`find_by_department`,email 映射 Helix 人)+ 未入库者列**「待补」**(`pending_not_in_helix_json`);快照富化实到/应到时间;**平台级月历** `AttendanceCalendar`(色彩编码 + 点格详情)取代纯异常列表,HR 操作台重做(等高指标卡 + 待补告警 + 月历明细抽屉)+ 新增桌面自助页 `/me/attendance`;HR 菜单入口从「身份源数据与认证」**归位到「HR 系统」组**。口径修正见 DECISIONS 2026-06-25。**PR-E shipped (2026-06-26)**:考勤判定生产级化——缺卡按侧拆分(缺上班/缺下班/全天旷工)+ **请假以飞书假勤审批 `user_approvals` 为权威**(按日期 overlay,含假别;打卡 supplement 仅 fallback)+ 法定并入「休」(不靠星期几猜);月历改按后端权威 `category` 渲染 + 旷工/请假指标卡;**「我的考勤」菜单按需显隐**(`/auth/me.is_attendance_subject`,普通员工不可见)。口径见 DECISIONS 2026-06-26。**收尾修复 (2026-06-26)**:旧快照月历兼容(`resolveCategory` 降级,杜绝 `cat.undefined`)+ 实到时间诚实显示(`user_tasks` 不含实际打卡时刻,仅有则显示,否则显示应到/类别;真实打卡需另接 `user_flows`,待确认有打卡再接)+ **完成/退回通知改发实习生 HRBP**(`_notify_hr`→`resolve_hrbp_for_person`,无 HRBP 跳过、不发 admin)。见 DECISIONS 2026-06-26。**5-PR + 收尾,主题完成。**
- **等保自查与安全自评估体系 (security self-check & self-assessment) — 4-PR theme, ✅ 全部交付 (2026-06-22~23)**。「等保、认证、审计」线下两大支柱做成生产级体系。**PR1 shipped (2026-06-22, migration 322 / +1 monthly cron / +3 MCP→hint 338)**:周期性自我检查——auditor 域 3 表(可配检查类目目录 + (类目,周期) 台账 + S3 证据)+ 每月开单 cron + `periodic_self_check` GRC 控制项(逾期判 fail,映射 SOC2 CC7.1/7.2 + ISO A.8.16/8.8)+ xlsx 导出 + `/auditor/self-check` 前端(类目增删改 + 台账完成/跳过/证据上传),详见 CHANGELOG/PLATFORM。**PR2 shipped (2026-06-23, migration 323 / +1 semiannual cron / +4 MCP→hint 342, backend-only)**:安全自评估数据底座——auditor 域 3 表(cycle/section/measure)+ 21 章节 ISMS 代码模板 + 半年开盘 cron + 13 个真值聚合器(资产/RBAC/MFA/离职/复核/密钥/变更/事件/云/**周期自检打通支柱一**)+ 措施跟踪 + 章节交叉引用 GRC 控制项 + finalize 锁定,详见 CHANGELOG/PLATFORM。**PR3 shipped (2026-06-23, 0 migration / +1 MCP→hint 343 / +dep python-docx)**:AI 起草叙述(百炼 chat_json,无 key 优雅 503)+ Word(python-docx)/PDF(reportlab STSong-Light CID 中文)导出 + 上期对比 + 前端结构化编辑器 `/auditor/self-assessment`(StatCard 概览 + 章节 Collapse 编辑 + 措施 CRUD),详见 CHANGELOG/PLATFORM。**PR4 shipped (2026-06-23, 0 migration / +1 MCP→hint 344)**:安全治理中心统一落地页 `/auditor/governance`(自检健康 + 当期评估 + 在办措施 + 控制状态四合一 StatCard + 深链)+ 只读聚合端点 + 3 个 help 主题 + i18n 收口。**全 4-PR 交付并发布生产。**
- **「有温度·有爱」员工体验系列 — ✅ 全部交付并发布生产 (2026-06-21~22);收官评审(3 路对抗式+安全扫描)已修 P1/P2/P3 并部署;入口按用户反馈重排(足迹/图鉴→头像下拉、安全每日一题→顶部图标且未答显红点、入职欢迎→并入我的入离职、管理层运管驾驶舱→并入运营洞察首个 tab)、员工版 Helix 此刻已下线(只留管理层运管驾驶舱)**。让平台不只有功能,而是有趣、有人情。纯 pull、零打扰、统一审美(复用庆祝/彩蛋视觉语言)的特性:① 我的 IT 足迹(常驻个人回顾) ② 装备图鉴(含历史设备收藏感) ③ 入职同期(同一天来的人) ④ 入职第一天仪式感(新人开箱欢迎) ⑤ Helix 此刻·员工广场视角 ⑥ Helix 此刻·管理层运管驾驶舱 ⑦ 安全每日一题(寓教于乐·连胜·排行榜·合规) ⑧ 节日/节气皮肤(扩展现有彩蛋引擎)。逐个 PR 交付 → 整体 code review → 安全+逻辑扫描循环 → 合并发布。**① 我的 IT 足迹 shipped (2026-06-21, 0 migration / +1 MCP → hint 322)**:`GET /me/footprint`(`me_universe/footprint.py`)+ `FootprintPage`(`/me/footprint`)——入职排位/首台装备/最久相棒/席位/求助/大事记时间线,self-scoped,详见 CHANGELOG。**② 装备图鉴 shipped (2026-06-21, 0 migration / +1 MCP → hint 323)**:`GET /me/device-gallery`(`me_universe/device_gallery.py`)+ `DeviceGalleryPage`(`/me/devices`)——含历史设备的收藏视图,服役时长(ownership 状态机·借用排除)+ 稀有度分档,self-scoped,详见 CHANGELOG。**③ 入职同期 shipped (2026-06-21, 0 migration / +1 MCP → hint 324)**:`GET /me/cohort`(`me_universe/cohort.py`)+ `CohortPage`(`/me/cohort`)——同一天入职的伙伴,周年联动,离职只计数不点名,self-scoped,详见 CHANGELOG。**④ 入职第一天仪式感 shipped (2026-06-21, 0 migration / +1 MCP → hint 325)**:`GET /me/welcome`(`me_universe/welcome.py`)+ `WelcomePage`(`/me/welcome`)——新人欢迎/开箱页,基本信息+manager+装备开箱+第一周清单,新人首登触发庆祝彩蛋,self-scoped,详见 CHANGELOG。**⑤ Helix 此刻·员工广场视角 —— 已于 2026-06-22 删除(用户决定 Helix 此刻只面向管理层)**:原广场氛围脉搏条(`build_employee_pulse` / `GET /dashboard/pulse` / `HelixNow.tsx` / `get_helix_pulse`)整块下线,仅保留 ⑥ 运管驾驶舱,详见 CHANGELOG。**⑥ Helix 此刻·运管驾驶舱 shipped (2026-06-21, 0 migration / +1 MCP → hint 327)**:`GET /dashboard/cockpit`(`build_cockpit`)+ `CockpitPage`——全口径一屏总览(人员/资产/成本/云/运营/合规),等高卡+口径平铺+tone 着色,含成本故 admin/management 门控;**2026-06-22 按用户反馈并入运营洞察首个 tab「Helix 此刻」**(`/admin/insights?tab=cockpit`,`embedded` prop;旧 `/admin/pulse` 重定向),不再单独占二级菜单,详见 CHANGELOG。**⑦ 安全每日一题 shipped (2026-06-21, 迁移 313 / +3 MCP → hint 330; 题型扩展 2026-06-22 迁移 314)**:新域 `security_quiz`(每日题/答题幂等/连胜/全员排行榜/合规覆盖/admin CRUD/AI 出题/cron)+ `SecurityQuizPage`(`/me/security-quiz`)+ `AdminSecurityQuizPage`(`/admin/security-quiz`)。**题库支持单选/多选/判断三种题型**(`qtype`,迁移 314 加列 + 导入《安全考试》31 题),详见 CHANGELOG。**⑧ 节日/节气皮肤 shipped (2026-06-22, 纯前端)**:扩展彩蛋引擎——`festivals.ts` 节日日历(固定公历+农历硬编码 2026–2028)+ `FestiveLayer.tsx`(节日当天飘落氛围 + 一次/会话问候卡,尊重 reduced-motion,localStorage 可关),详见 CHANGELOG。**⑨ 全站暗色模式 + 「外观」设置 shipped (2026-06-22, 0 migration)**:light/dark/system 三态主题(`theme/tokens.ts`→CSS 变量 + `stores/theme.ts` + AntD 双算法),顶栏切换器 + `MySettingsPage`「外观」Tab(主题 + 节日效果开关——把 ⑧ 节日皮肤变成可设置项、修好旧单向 mute bug);偏好走现成 KV 无迁移、跨设备。暗色长尾(深层页面 inline 白底 / 色调-50 强调卡 / `rgba(0,0,0)` 文字)随收官 review 系统化扫除,详见 CHANGELOG / PLATFORM。**⑩ /me 装备墙(RPG 角色卡) shipped (2026-06-22, 0 migration / +1 MCP → hint 332)**:`GET /me/loadout`(`me_universe/loadout.py`)+ `LoadoutView`/`LoadoutPage`——暗色下 `/me` 变身角色卡(装备槽[实时 assigned_to + 稀有度]/属性雷达/背包/成就/安全段位+战力),亮色保持经典 `UserItUniverseTab`;由 device_gallery+footprint+security_quiz 组合,段位阈值与 M3 共用 `_rank_tier`,详见 CHANGELOG / PLATFORM。**⑪ 安全段位擂台 shipped (2026-06-22, 迁移 318 / +1 cron / +1 MCP → hint 333)**:安全每日一题升级段位 + 团队荣誉——`security_quiz/ranks.py`(青铜→王者单一真源,装备墙 chip 委托此)+ `team_battle.py`(部门按主部门聚合参与率/正确率排名)+ `secquiz_team_snapshots`(迁移 318)+ 周一 cron 冻结上周排名;`GET /security-quiz/{rank,team-battle}`,`SecurityQuizPage` 加段位卡 + 部门擂台卡,详见 CHANGELOG / PLATFORM。**⑫ 成就徽章系统 shipped (2026-06-22, 迁移 319 / +1 cron / +1 MCP → hint 334)**:新 `gamification` 域——徽章目录在代码(9 枚:周年/安全连胜/段位/初探云端/收藏家),只持久化解锁 `gamification_unlocks`(迁移 319,幂等);`service` 懒评估(读时即解锁)+ 每日 cron `gamification.scan_unlocks`(逐人 SAVEPOINT);`GET /me/badges`,设置「成就」Tab 徽章墙 + 新解锁撒花(纯 pull 零打扰,不发推送);详见 CHANGELOG / PLATFORM。**全面升级 shipped (2026-08-09, 0 迁移 / 0 新 cron / +1 MCP → hint 451)**:徽章 13→39 枚、5→9 分类(新增软件与工具/合规/自助与审批/社区与互助;含用户点名的资产大亨/云端大佬);新增单枚进度条、4 档稀有度、全站解锁率、「就快到了」推荐、分类分节 + 筛选;从「我的设置」的一个 tab 提为独立页 `/me/achievements` + 移动端 `/m/achievements`;管理侧 `/admin/arcade` 新增「成就分布」tab(聚合口径,无点名排行)。**顺带修掉两个存量 bug**:`cloud_explorer` 因 person_id 类型不匹配全公司零人可解锁(装备墙同源 bug 一并修)、`devices_ever` 漏 `transfer` 拼写;装备墙「成就」区块此前是占位的足迹里程碑,已接真实徽章。每日 cron 改批量算事实(500 人 ~9000 次查询 → 固定十几次)。详见 CHANGELOG / PLATFORM。**⑬ 年度报告 (Helix Wrapped) + 年底护栏推送 shipped (2026-06-22, 迁移 320 仅 seed cron / +1 cron / +1 MCP → hint 335)**:Spotify-Wrapped 式年度回顾——`me_universe/wrapped.py`(按年聚合工龄/安全答题/设备/工单/徽章 + highlights)+ `WrappedPage`(`/me/wrapped/:year`,hero + 统计卡 + 撒花)+ dashboard 限时入口卡(12/20–1/31);**唯一刻意的员工面推送例外**,重护栏 `wrapped_push.py`(一年一次幂等 + opt-out 偏好 + 每轮上限 + 逐人隔离),cron `wrapped.year_end_push`(迁移 320 seed,12/31);设置「外观」加推送 opt-out;详见 CHANGELOG / PLATFORM。**「趣味平台功能」5-PR 收官**(暗色/装备墙/段位擂台/成就/年度报告)。**整改(2026-06-22 用户反馈)**:暗色系统扫全站(普通 css/TSX inline/Markdown 主题化,排除图表文件);「装备图鉴」+「装备墙」并入「我的 IT 足迹」`/me/footprint`(旧路由重定向,撤 /me 暗色绑定);年度报告 `/me/wrapped` 加活动维度(登录/平台操作/发帖/评论/点赞,11 卡),澄清常驻可访问非推送门控。
- **飞书服务台(Helpdesk)接入 + 服务台分析 — ✅ shipped (2026-06-22, 迁移 316 / +1 MCP → hint 330)**。把飞书服务台(Lark Helpdesk)工单拉进 Helix 做运营报表。新连接器 kind `helpdesk`/provider `feishu`(多服务台,双头鉴权 + `helpdesk:all:readonly`)+ 新域 `app/domains/feishu_helpdesk`(`feishu.helpdesk_sync` 每 12h 增量 upsert)+ `/admin/helpdesk-analytics`「服务台分析」(运管驾驶舱,global_admin+management;报表页 KPI/趋势/状态/客服/部门 + 数据页明细+导出)+ MCP `get_feishu_helpdesk_analytics`。链路已用生产凭证实测确认,已部署 + 灌入 IT Help Desk 凭证 + 首同步(2243 真实工单)。**加厚(2026-06-22)**:报表页顶部 **AI 月度运营洞察**(百炼/Qwen,基于近 6 月工单产出趋势/热点/建议,缓存于 platform_settings,无 key 优雅降级)+ MCP `get_feishu_helpdesk_ai_insights`(hint→331);满意度按真实数据校正(score=0=未评价排除、KPI 改满意率、分档 不满意/一般/满意)、状态码 50/51/52、类型 bot/人工。详见 CHANGELOG。
- **申请「结果闭环」(request lifecycle outcome closure) — 4-PR theme, ✅ 全部交付 (2026-06-18)**。实地审计结论:平台在「发现」与「顺利路径」是平台级的(我的申请聚合器/⌘K/action inbox/`current_approvers`),但凡是**不顺利的终态/中间态**(被拒/履行失败/被阻塞/已回收)都是死胡同——告诉你「失败了」却不告诉你**为什么**、也不给**下一步**;而失败原因数据本就躺在库里(provisioning `error_message`、cloud-scope `rejection_reason`、飞书拒绝意见)。本主题把「一笔申请」做成有结果的一等对象:任何对象、任何界面恒定回答 出了什么结果 / 为什么 / 现在能做什么。**PR1 shipped (2026-06-18, 0 migration / 0 cron / 0 new MCP)**:后端统一「结果层」——`build_my_requests`/`build_my_request_detail` 每条终态项带 `outcome_reason`(各源本地列就地解析)/`outcome_reason_code`/`needs_attention`/`recourse[]`(`resolve_recourse`);`_timeline` 把原因挂终态事件 note;飞书拒绝意见经详情 live flow 捞回(`_reject_comment_from_timeline`);self/team/admin 三轴对称;MCP `model_dump()` 透出。详见 CHANGELOG。**PR2 shipped (2026-06-18, 纯前端)**:桌面失败 banner(`RequestDetailModal` 被拒/失败顶部红 Alert:原因+处置)+ 共享 `RequestRecourseActions`(重新申请/前往处理/求助 IT)+「我的申请」列表行内处置 + 状态列失败原因红字 + `contact_it` 跳 `/me/support` 预填工单 + `/dashboard`「我的申请」KPI 加需处理数(rejected+failed)>0 红示。**PR3 shipped (2026-06-18, 1 端点增参)**:provisioning 失败在「我的待办」owner 队列透传——`/provisioning/tasks/my` 增 `statuses` CSV 多值,FE「仅看待办」取 `notified,failed`、failed 行红字展示 `error_message` + 行内「重试」(retryTask)/「已处理」。详见 CHANGELOG。**PR4 shipped (2026-06-18, 纯前端, 主题收官)**:生命周期 blocker/就绪缺口可操作化——`MyLifecyclePage` 离职 blocker 对无自助动作的资源类型(sso/email_group/cloud_scope/domain/google_orphan…)补「求助 IT」预填工单;入职就绪无 CTA 的维度补「求助 IT」;移动端 `MMyLifecyclePage` 未决 blocker 补「求助 IT」→ `/m/help`。**4-PR 主题完成**:任何申请对象、任何界面恒定回答 出了什么结果 / 为什么 / 现在能做什么。**收官评审修复 (2026-06-18)**:整体 code review + 安全扫描(零发现)+ 逻辑扫描后,修 owner 重试 403 死链(retry 端点改 assignee-or-admin)、domain 重新申请死链(deep_link → /my/requests)、列表求助原因诚实(飞书源兜底「见详情」)、自由文本误当枚举码(vpn/sso/domain 去 code)。

- **AI 激活与深化 (Qwen/百炼) — multi-PR theme, ✅ 全部交付 (2026-06-18)**。求助域的 AI 半部分原本只接 Anthropic key,而公司实际有的是百炼(Qwen)key。本主题用既有 Qwen key 激活并深化全部 AI 能力。**PR1 shipped (2026-06-18, 0 migration)**:**copilot LLM 后端可插拔**——给既有 `BailianLLMClient` 加 compatible-mode `chat()`(Qwen function-calling),`copilot.py` provider 可插拔(自动优先连接器已配的 bailian),两套 agent loop 共用工具+RBAC 收口;`ai-suggest`/`draft_kb` 走 provider-agnostic 单轮;管理端 `/support/admin/copilot/config` + IT 支持台一键「AI 助手」开关。连接器配好百炼 sk- key 即一键激活 copilot/ai-suggest/ai-kb-draft,**零新依赖/key,用量进既有百炼成本看板**。**PR2 shipped (2026-06-18, 0 migration)**:**AI 自动分诊**——新工单建好后台 fire-and-forget 调 Qwen 判 `category`/`priority`,经 `apply_ai_triage` **只填默认、绝不覆盖员工显式选择**(分类留 other / 优先级留 normal 才设),system 时间线消息 + 审计;不阻塞建单响应,copilot 未启用 no-op。**PR3 shipped (2026-06-18, migration 306)**:**知识库语义检索**——`BailianLLMClient.embed()`(DashScope `text-embedding-v3`)+ 可移植 JSON 向量列(非 TiDB VECTOR,保 MySQL 测试可跑)+ Python 余弦;`search_published` 语义优先、关键词优雅回退;发布/改文后台 fire-and-forget 向量化 + 管理端批量回填按钮 + 每篇已索引标签;copilot `search_knowledge_base` 自动吃语义结果。**PR4 shipped (2026-06-18, migration 307)**:**提单前置 deflection + 历史工单批量起草 KB + 真·会话级分流度量**——员工提单前先语义检索 KB + AI grounded 作答,「解决了/仍提交」记 `support_deflection_events`;`/admin/analytics` 给真·deflection_rate(deflected/(deflected+escalated));`bulk_draft_from_history` 把已解决工单批量起草成 KB 草稿;KB 关键词回退升级为分词 OR 匹配。**PR5 shipped (2026-06-18, 0 migration)**:**收口评审**——三轮对抗式评审 + 安全/逻辑扫描,修复 4 处(语义阈值空过滤、deflect 同步 60s 阻塞→短超时降级、link_escalation 幂等、畸形 UUID 500、前端再入守卫)。**主题完成。**

- **员工↔IT 双向沟通与求助全闭环 (employee↔IT communication & support) — 5-PR theme, in flight (2026-06-17 起)**。
  实地审计结论:平台在 admin/治理/云/财务上极深,但**员工消费侧**最弱——通知后端企业级却零偏好控制、求助只有死板模板(模板外即死胡同)。本主题把三个对象做成全生命周期闭环且互相咬合:**通知**(IT→员工,被员工偏好/免打扰治理)↔ **求助会话**(员工→AI copilot,答疑/确认后办事/办不了转工单)↔ **工单**(员工→IT 人工,飞书 IM 群接单回复,回写通知走员工偏好,解决后 AI 起草 help 主题沉淀)。
  **PR1 shipped (2026-06-17, migration 300, +2 MCP→hint 311, 0 新 cron)**:**通知治理闭环**——员工对通知的全生命周期掌控。类目登记表(`app/notifications/categories.py`,语义分类 9 类目 × 3 桶,单一真源)+ `notification_preferences`(类目×渠道静音 + 本地时区免打扰窗)+ 送达侧 gate(静音=当禁用渠道跳过→仅站内信不丢;免打扰=非紧急推送停泊到窗口结束,复用既有 outbox 重试 drain 投递、无新 cron)+ critical 模板与站内信永不受影响 + 收件箱分桶/category 标签/`unread_by_bucket` + `/me/notification-preferences` 页 + Bell 底栏入口。详见 CHANGELOG。
  **PR2 shipped (2026-06-17, migration 301, +5 MCP→hint 316, 0 cron)**:**求助域(IT 帮助台)+ 全生命周期工单**——补上「模板外无路可走」的员工死胡同。`support` 域:自由文本提交求助 + 工单全生命周期(open→…→resolved→closed,含 reopen/cancel)+ 消息时间线 + IT 工作台(认领/指派/回复/解决);求助人侧更新走 PR1 被治理通知,新工单扇出 IT(逐人卡片 + 深链);接入「我的申请」聚合器;员工面 + 后台两个菜单入口。单条飞书群卡 + 群内按钮回调列为需联调的后续(逐人卡片路径已可靠交付)。详见 CHANGELOG。
  **PR3 shipped (2026-06-17, 0 migration, +1 dep anthropic)**:**AI IT copilot(接地气答疑 + 转工单,LLM 凭证门控)**——求助入口从「只能开工单」升级为「先问 AI」。手动 agent loop(claude-opus-4-8)+ 两个只读按本人收口工具(get_my_support_tickets / get_my_requests,grounded-or-escalate 不编造)+ 确定性转工单(无需再调 LLM,复用 PR2 建单)。门控 `copilot.enabled`+凭证,未配即优雅停用(200 不 500)。前端 `/me/support/ai` 对话页 + 「先问 AI」入口。**护城河 = 用平台独有 RBAC 工具面+结构化事实接地气,而非通用文档 RAG**。详见 CHANGELOG。
  **PR4 shipped (2026-06-17, migration 302, +1 MCP→hint 317)**:**IT 侧 AI 辅助 + 帮助中心自我生长(DB 知识库)+ deflection 看板**。新 `support_kb_articles`(草稿/发布/下架,manual/ai_ticket 来源);已解决工单一键「起草知识库」(LLM 起草、确定性兜底)→ 管理员发布即沉淀;copilot 加 `search_knowledge_base` 工具优先检索已发布 KB(答得越多库越厚 deflect 越多);工单 `/ai-suggest` 用工单+相似已解决工单+KB 起草建议回复;`/admin/analytics` 看板(工单/分状态/分类/AI 来源/平均解决时长/KB 状态);管理端 KB 页 + 员工只读浏览。详见 CHANGELOG。
  **「员工↔IT 双向沟通与求助」5-PR 主题 PR1-4 全部交付**;**PR5 = 收口评审(三轮对抗式 code review + 安全扫描 + 逻辑扫描循环至干净)→ CI 绿 → 合并 → 发布生产**(进行中)。**诚实记录的 follow-on**:真·deflection rate 需记录 copilot 会话;copilot「办事」更深一层(AI 确认后替你提交笔记本/坐席申请,工具面已预留);单条飞书群 IM 卡片按钮回调(PR2 起,逐人卡片路径已可靠交付);帮助中心 JSX 单体可渐进迁到 KB 库渲染。

- **财务对账中心 (finance reconciliation centre) — 3-PR theme, in flight (2026-06-16 起)** —— **首个跨四线花费的统一账层,新 `finance` 域**。
  财务此前只能开 4 个独立 dashboard(云/软件/AI/采购)各看各的、手动换币种手动加总,无处按
  `时间×部门×成本中心×类型×供应商` 自由切片对账。本主题把「一笔花费」做成全生命周期被治理的对象
  (发生→归属→归一→关账→重述→自检),并升格为真正的 FinOps + 对账 + 治理面。
  **PR1 shipped (2026-06-16, migration 294, +2 cron)**:统一花费总账数据闭环——`finance_spend_facts`
  按月物化(四线归一 USD、AI·云去重不变式、部门+成本中心双轴、未分摊显式桶、basis 分离防双算)、
  月度历史冻结汇率(`finance_fx_monthly` + frankfurter 回填 + admin pin)、关账/重述
  (`finance_period_close`/`finance_restatements`)、内部对账自检(账层 vs 各源域)、运维 HTTP 面 +
  两 cron。跨域聚合走 `app/core/coordinators/finance_spend_coordinator.py`。
  **PR2 shipped (2026-06-16, migration 295, +6 MCP→hint 301)**:看数与对账体验闭环——全读 API
  (summary/pivot/timeseries/detail/movers/completeness/digest/export.xlsx)+ 6 个 finance MCP 读工具 +
  保存视图 + 前端 4 页(对账总览/花费浏览器透视下钻血缘/变动异常/关账完整性,`财务→对账中心` 菜单)。
  digest 为确定性叙述(与大盘同口径,无 LLM 漂移)。合同 vs 实际归入 PR3 的供应商/合同视角。
  **PR3 shipped (2026-06-16, 0 migration, +3 MCP→hint 304) — 主题完成**:成本治理与供应商全景——
  能省的钱清单(残留付费坐席 + 闲置坐席,折每月 USD)+ 供应商跨线汇总 + 续约日历 + 合同 vs 实际履约率;
  跨域聚合走 `finance_governance_coordinator`;前端 `成本治理` + `供应商与合同` 两页。
  **Phase 2(待用户指定外部来源)**:外部账单比对(发票/NetSuite/汇联易/银行)——本轮三 PR 不含。
  **区间化 + 供应商多选 (2026-07-30, 0 迁移, +1 MCP→hint 444)**:`pivot`/`detail`/`export.xlsx` 与新
  `GET /finance/spend/vendors` 都接 `period` 或 `start`+`end`(≤36 个月,共用 `enumerate_periods`);
  `vendor` 由单值精确等值改为可重复参数(`IN`),候选列表来自真实花费(除 vendor 自身外沿用全部
  过滤,封顶 500 且显式 `truncated`);区间响应点名未关账月份 `provisional_periods`;明细/导出补
  `period` 列 + 导出截断响应头。前端花费分析页月份选择器改月区间 + 供应商多选。
  MCP `finance_spend_pivot` 加 `start`/`end`/列表 `vendor`,`finance_spend_vendors` 为其解析器。
  与 `/assets/software-billing/range-summary`(原币、按软件目录)是两个口径,不互相校验。

- **财务全生命周期闭环 (finance closed-loop) — 6-PR theme, SHIPPED (2026-06-18)** —— 把对账中心从"看数"升级成被治理、可执行、有问责的全闭环。**根因实证**:孤儿成本中心满屏是因为权威主数据(`finance_cost_centers`)生产仅 1 行(汇联易开放 API 对本 app 不吐成本中心/报销),而真值早在 `identity_persons.cost_center`(SF 同步,63 编码/497 人)。**PR1**(migration 303)成本中心主数据从 HRIS 真值重建,孤儿归零 + 负责人覆盖率轴;**PR2**(migration 304)预算全生命周期 `finance_budgets`(计划→实际→偏差→run-rate 年底预测→超阈值告警 owner,折进 spend_rollup 不新增 cron);**PR3**(migration 305)`can_operate_finance` 让 finance 能月结 close/reopen + 通用 `finance_finding_acks` 让治理发现可 acknowledge/snooze/resolve 不再刷屏;**PR4** 成本问责下沉(预算负责人 `/finance/budgets/me` 自助,复用 `/me/budget` 受众);**PR5** 总览驾驶舱(预算执行/超支预警/覆盖率/负责人覆盖率健康卡);**PR6** finance 角色默认落地财务系统 + 每页 PageHelp。MCP hint 317→320(+3 read/mutate)。
  - **财年口径修正 (2026-06-25, migration 329)**:预算/实际/run-rate 年底预测/烧钱告警此前把 `fiscal_year` 当**日历年(1–12 月)**算;公司财年**从 4 月起**,已抽出共享 `app/core/fiscal_year.py` 改用真实 Apr→Mar 窗口(可配置 `finance.fiscal_year_start_month`,默认 4)+ admin `GET/PUT /finance/fiscal-year-config`。前端预算页默认定位当前财年并展示 `FY2026 (2026-04 ~ 2027-03)` 标签。
  - **硬件成本轴 (2026-06-25)**:统一花费账本新增 `cost_type='hardware'` 双口径——`consumption`=月度折旧(默认,计入预算+消费总额)/`cash`=采购现金;消费宇宙四轴 = 云·软件·AI·硬件(折旧)。`collect_hardware_rows` 复用资产域折旧策略,归属到持有人 BU。回填脚本 `scripts/backfill_hardware_spend.py`。AI 轴已是一等且带 per-人/部门归属。
  - **BU 财年成本视图 (2026-06-25, +1 MCP→hint 347)**:`财务→部门成本`(`/admin/finance/bu-costs`)——每个一级部门本财年云·软件·AI·硬件花费 vs 预算 vs 年底预测,硬件折旧/现金口径可切,行展开下钻到人/供应商/类型;`read_service.bu_fiscal_year_costs`/`bu_cost_drill` + MCP `finance_bu_costs`;受众 finance/management/auditor/admin。复用 `bu_helpers` 做 BU 归一。

- **身份消费方全生命周期 (identity-consumer lifecycle) — 4-PR theme, in flight (2026-06-15 起)**。
  此前身份消费方(§10.6,2026-06-11 三 PR 栈)只是「管理员 CRUD + 发 token」,缺申请/审批/
  确认/定期复核/owner 离职转移的完整生命周期,且授权范围是写死的 3 个粗 scope、看不见字段、
  不能收窄。本主题把它做成被治理的对象。**PR1 shipped (2026-06-15, migration 285)**:
  Authing 式**字段级授权**——scope 保留为「套餐」一键勾选,新增 `granted_fields` 做最小权限
  投影,字段目录(`field_catalog.py`)带敏感级标签,`/scopes` 暴露完整字段目录让申请人/管理员
  看清每个 scope 吐什么;序列化按授权字段投影(未授权字段整列省略),向后兼容回填。
  **PR2 shipped (2026-06-15, migration 286, +1 MCP→hint 294)**:自助申请闭环——系统 owner 在
  「我的申请 → 软件资产类」的入口卡点开**数据接入申请弹窗**(`ConsumerAccessApplyModal`;原
  `/my/data-access` 独立页 2026-06-15 退役,重定向回 `/my/requests`,记录本就在统一「我的申请」可见)
  填富表单 → Helix 创建飞书审批实例(定义 code `413C2807-…`,单选控件
  做 option 机器键解析)→ 批准回调**自动开通** live 消费方(authoritative 复核 slug/owner,冲突
  置 failed 不留半成品)+ 推一次性令牌领取链接(action-token,明文不入库,replay 守卫)。申请纳入
  统一「我的申请」聚合器;新增 MCP `request_identity_consumer`。
  **PR3 shipped (2026-06-15, migration 287, +2 cron)**:定期复核——复用访问认证引擎
  (campaign_type=identity_consumer),季度自动开盘把所有 active 消费方挂给审计员逐个 keep/revoke
  (item 带近 90 天调用量信号);revoke→停用消费方,keep→盖 last_certified_at(逾期被停的自动重启用);
  新增**逾期自动停用** cron(截止+14 天宽限仍 pending 即 disabled,停掉未复核的 PII 出口)。
  **PR4 shipped (2026-06-15, 0 migration) — 主题完成**:负责人离职处理——离职扫描器对离职者拥有的
  消费方生成 blocker,可手动交接给在职继任者(消费方保活),或 T+0 无人接管时**自动停用**(停掉
  挂在离职者名下的 PII 出口,刻意不静默转给经理)。**「身份消费方全生命周期」4-PR 主题至此全部交付**:
  字段级授权(PR1)→ 自助申请+飞书审批+自动开通(PR2)→ 定期复核+逾期自动停用(PR3)→
  负责人离职交接/停用(PR4)。

- **对外 API 能力 (external API) — phase 1 shipped (2026-06-15)**。**独立功能**(刻意不挂在
  `identity_consumers` 上——那是身份分发,这是业务数据 API,菜单与功能都分开;见 DECISIONS):外部系统
  持自有 `helix_api_` 凭证、按 scope 读写 Helix 资源。独立栈 `app/api_clients`(凭证表/鉴权
  `require_api_client`/scope 注册表/管理端点 `/api/v1/api-clients`/前端页 + 独立菜单「对外 API」),
  低层 token/限流抽到共享 `app/core/service_token.py`。第一个资源 = **云订阅读写**
  (`cloud.subscriptions:read`/`:write`,`/api/v1/open/cloud/subscriptions`;元数据写 owner/环境/名称/
  备注/预算,service 主体审计,护栏继承)。独立参考文档 [`docs/api/external-api.md`](docs/api/external-api.md)
  (英文主 + 中文)。migration 289(3 张表)。第一阶段管理员后台维护凭证。
  **后续**:自助申请、用量看板。

- **对外 API 人员 ↔ GitHub 映射 + 人员范围闸 — shipped (2026-08-18,迁移 513)**。新 scope
  `people.github:read` + `POST /api/v1/open/people/github/lookup`:按邮箱正查、**按 handle 反查**
  (GitHub 组织成员名单给你的正是这个方向)、按部门列人,三个选择器可组合。配套把 `github_id`
  加进目录 Pull API 的字段级授权目录(#2984),两条路各管一半:目录同步整份名册,这个端点回答
  当下的一个问题。**这是对外 API 上第一个能「列人」的面**,因此同时上线了每客户端的
  `person_visibility` 人员范围上限(复用 `FilterConfig` 与共享的 `PersonFilterEditor`,不另造
  词表):请求选择器与上限取交集,范围外的人返回 `unknown_person`,与「查无此人」完全一致。
  口径:三态 `found`/`no_github_id`/`unknown_person`(「有人但没登记 handle」与「查无此人」
  意思相反);默认只返回在职(含待离职),`include_left` 才含已离职,未入职任何情况下不返回;
  命中超 1000 人报 422 而不是静默截断。合并前做了 5 轮对抗式复审,修掉 16 处(其中若干是
  fail-open / 审计扩权记成空记录这类真缺陷)。

- **对外 API 云账单 + 云可见范围 — shipped (2026-08-15,迁移 470/471)**。对外 API 从「只有清单」
  补齐到「有钱数」:新 scope `cloud.billing:read` + 四个只读端点
  (`/api/v1/open/cloud/billing/{daily,monthly,breakdown,changes}`)——日/月时间序列、
  七个维度的窗口聚合(订阅/服务/厂商/环境/部门/owner/资源 Top-N)、以及基于摄取水位的增量拉取
  (`?updated_since=`)。**恒为业务折前(list)口径且不提供切换参数**(实付 ÷ 折前 = 公司折扣率);
  行级优先 + 日汇总按「订阅-月」兜底;折不出汇率的行整行不发并披露;`is_provisional` /
  `data_through` / `provisional_from` / `excludes_unallocatable` 全部写进响应体;拆不开的花费与
  未归属的花费都单独披露而不是消失。同批新增 **`api_clients.cloud_visibility`**(providers +
  subscription_ids 并集白名单,`NULL`=不限制),scope 决定「能读哪类云数据」、白名单决定「能读哪些
  云账号」,范围外统一 404;并作用于既有的 `/open/cloud/subscriptions`。管理台可视化配置 +
  站内 API 文档页 `/api-docs` 同步。**2026-08-15 增补**:`group_by` 支持一到两个维度交叉,新增 `tag:<key>`(云资源标签)维度 + `region`/`resource_type`/`cost_center`,配套 `GET /billing/tag-keys` 发现端点;生产实测 79.1% 的计费额带标签(Azure/UCloud/腾讯为 0%),故 `untagged`、`tag_coverage`、`providers_without_tags` 均为显式披露。详见 [`docs/api/external-api.md`](docs/api/external-api.md) §B.6。

- **移动端一等操作面 (mobile full closed-loop) — multi-PR theme, in flight (2026-06-14 起)**。
  用户实测旧移动端 40 分:能看不能办。废除 2026-05-22「mobile = read + single-record
  only」ADR(见 [DECISIONS.md](docs/architecture/DECISIONS.md) 2026-06-14),把移动端做成
  与桌面对等的全角色、全流程、全生命周期闭环面。**PR1 shipped**(0 migration):
  移动动态申请表单引擎——桌面 widget 逻辑提升到单一真源 `approvalWidgetEngine.ts`,
  新增 `MRequestForm` + `/m/requests/new`,资产模板申请(笔记本更换/借用/归还/离职回购,
  含上传 + 回购报价)手机端全闭环。**PR2 shipped**(0 新端点):全部非模板申请上手机
  ——域名/VPN/云Scope/AI工具/公告/SSO/邮件组/别名/改密(声明式 `MSimpleRequestSheet`)、
  DNS 自助、软件席位目录;我的申请改用统一聚合 `GET /me/requests`(撤回 + 审批进度/时间线),
  DNS 校验器提单一真源。**PR3 shipped**(0 新端点):云授权员工闭环(续期/释放/
  owner 审批)+ provisioning 任务取消上手机;审计确认 legacy 页与待办闭环早已在
  W0-PR3/W1-PR6 完成,故 PR3 收口真实残余缺口。**PR4 shipped**(0 新端点):经理团队
  全量移动面——team tab 改指向 `/m/team` hub,团队申请/离职/入职(只读列表)/资源足迹/
  云/生命周期 6 个面上手机。**PR5 shipped**(0 新端点):管理员离职运维上手机——逐项
  阻塞回收/标记/跳过 + 流程推进/完成/强制跳过宽限 + 复用人员选择器 MPersonPicker。
  **PR6 shipped**:资产管理(分配/转移/退库 + 资产详情页)+ 软件席位回收上手机。
  **PR7 shipped**:云管理(服务器电源 + 云账号同步/转 Owner/下线,全程尊重生产写锁)。
  **PR8 shipped**:HR 管理(触发增量同步 + 标记离职 + 角色变更授权复核);财务规则
  构建器明确留桌面。后续 PR:审计裁决、收口。
- **角色视角的功能对称 (persona-scope parity) — 4-PR theme, in flight (2026-06-13 起)**。
  实地审计发现几处「别的角色/域有、这个没有」的功能对称缺口,逐个补平(都是
  功能层,不碰架构)。**PR1 shipped**(migration 283):**财务视角补硬件**——
  finance 能深度看软件/云/AI/采购四条成本轴,唯独硬件被 `can_read_all_assets`
  排除;补 finance 硬件只读 + 硬件资产财务全生命周期(折旧策略 / 月度账面价值
  物化 cron / 处置损益 / 每台财务时间线 / 按部门-办公室聚合 / insights 硬件财务
  tab / 3 MCP),详见 CHANGELOG。**PR2 shipped**:经理团队人员全生命周期驾驶舱
  ——`GET /me/team/lifecycle`(`build_team_lifecycle`)让经理看到下属在入职(Day-1
  就绪/倒计时)与离职(退场倒计时/blocker/资产收回/SLA)流程里的位置,纯读
  零新推送,深链回既有面;顺手修正「经理云可见性」过期开放项。**PR3 shipped**:
  自助申请对象全生命周期对称化——permission 补撤回 + 进追踪器、sso 进追踪器、
  ai_tool 补 handover/batch-handover/标准 revoke 端点 + 离职 resolver handover
  支持(默认仍 auto-revoke);+3 MCP(hint 292)。**PR4 shipped(主题收官)**:
  云同步 per-capability 可观测+诊断——`GET /cmdb/integrations/{id}/sync-capability-
  status`(每能力最新结果含 never-synced)+ automation-health `cloud_sync` lane +
  集成页「同步能力状态」抽屉(每行重跑)+ MCP `get_cloud_sync_status`(hint 293);
  关闭下方「Cloud sync run history granularity」开放项。**主题完成**:四个角色
  视角对称缺口全部补平。

- **「找得到 → 办得了」(admin operability closure) — 5-PR theme, SHIPPED (2026-06-13)**。
  实地审计结论:平台的检测/可见性已平台级(⌘K、11 类实体搜索、对账四镜头、
  数据质量日扫),但「查到之后」的下半场动作缺失——161 个 Table 页面只有 5 个域
  能导出、批量操作仅 8 处、对账中心 0 写端点(查得出改不了)。**5-PR 栈全部合并**,
  平台收敛为「找得到也办得了」:导出域 5→12+、批量操作 8 处→高频管理页全覆盖、
  对账中心写端点 0→四镜头一键修复闭环;全程零新推送面(管理员主动触发,守
  IT-only 红线)。栈:**PR1 shipped**
  (数据出口闭环):统一导出原语 `app/core/exporting.py` + FE `<ExportButton>` +
  12 个高频列表面 xlsx 导出(镜像列表筛选与 RBAC、复用同一查询路径、全审计、
  截断诚实头)+ /audit/export CSV→xlsx 原地升级(500→5000 行)。**PR2 shipped**
  (检索与深链闭环):公告/角色变更服务端 q + 审批总览暴露既有 q(导出同步)+
  对账中心三镜头客户端检索 + 10 个管理页 Tab 状态进 URL(UserDetailPage 20-tab
  深链、集成页保留 ?kind= 兜底)。**PR3 shipped**(批量操作闭环):5 个幂等批量
  端点(公告上下线/回收站恢复/域名交接+撤销/RBAC 角色/permission grants,
  统一 SAVEPOINT 逐行隔离 + skipped 幂等 + 聚合审计 + 防自锁)+ 域名注册
  管理面补缺(网络工作台「注册申请」tab)+ **顺手修平台级潜伏 bug**:
  core/idempotency 跨请求重放 naive/aware TypeError(所有既有幂等流的重放
  路径在 prod 一直 500)。**PR4 shipped**(对账修复闭环):对账中心四镜头从
  只读长出修复动作——所有权/离职残留 owner 缺失→一键指派/转移(cloud scope
  /dns/domain/cdn/asset/api_key,复用各域既有 owner 端点,api_key 新增
  `transfer-owner` 复用 apply_transfer 全套重写)、leaver grant→一键回收、
  成本漂移 stale/not_materialized→「重算物化」(新端点 `period-rollup/refresh`
  闭月校验+限流)、apikey 成本规则补 `cost-rules/re-enable`(禁用的反向);
  +3 MCP(hint 285)。**PR5 shipped**(收官):对账中心 help 接入 +
  `/help` 对账主题内容纠偏(讲清 PR4 一键修复)+ i18n 三语 parity 核验;
  静默-catch 清扫经核实为误报取消(pages/ 零空 catch、目标页早有完整错误
  反馈),不编造修复。

- **入职面补齐 (onboarding parity) — 5-PR theme, in flight (2026-06-12 起)**。实地
  对账结论:离职面(自动收权/升级链/双签/digest,~9.6k 行)与入职面(~2.9k 行,
  扫描器 3 维 stub、提醒只到经理无兜底、无 digest、合规事件不可见)成熟度严重
  不对称,而 Day-1 就绪是全公司可见的 IT 成绩单。栈:**PR1 shipped**(migration
  278):就绪扫描诚实化——email_group / ai_tool_default 接「策略承诺 ↔ active
  seat」核对(ProvisionPolicy 匹配语义提升为 `app/core/provision_match.py` 单一
  来源;email 组=google_workspace group_member 绑定子集、AI=category='ai' 子集,
  blocker label 点名缺失项,空集 vacuous-true 上线零误伤),training_ack 接新
  「入职须知」原语(`onboarding_ack_items` + `onboarding_acknowledgements`,
  管理 tab `?tab=ack`,员工在 /my/lifecycle 阅读+确认,确认即时解除 blocker;
  有确认记录的条目只可停用不可删);驾驶舱/自助面 7 维 → 10 维;+4 MCP 工具
  (hint 277)。**PR2 shipped**(0 migration):升级链对齐离职侧——经理提醒
  收件人改 `_walk_to_active_manager` 兜底链(链上无在职经理 → IT 兜底接收);
  `day0_it_gaps`(hire_date 已到仍有 BLOCKER 级缺口 → IT 硬缺口工单卡,
  single-shot)+ `sla_urgent`/`sla_breach` 分级升级(breach 加隔级经理);
  lifecycle daily digest 增【入职就绪】段 + 逐行就绪标注。
  **打扰预算红线热修(#1027,已部署)**:PR2 上线后误推 5 名非 IT 员工
  (隔级经理收 breach 卡 + advisory 计入 SLA 把存量翻出来),热修后升级
  链全部 IT-only、SLA 只看 BLOCKER 级、经理提醒=直属经理 only;红线:
  **新增通知面一律 IT-only,advisory 永不分页,上线前必须评估存量触发面**。
  **PR3 shipped**(migration 279,零通知面):备机闭环——`asset_items`
  预留原语(全部授予路径 409 守卫、授予本人自动兑现清除、一人一台)+
  资产工作台「待备机」tab(经理声明×预留×库存×过期预留释放)+ 3 MCP。
  **PR4 shipped**(0 migration,零推送):H5 `/m/lifecycle` 入职态补齐
  Day-1 就绪卡(10 维 + 进度条 + seat/vpn 深链)、入职须知确认卡
  (`?source=mobile` 落库)、经理联系人卡——新人手机上完成
  Day-1 自助闭环。**PR5 shipped(主题收官)**(0 migration,零推送):
  Day-1 就绪率记分卡(按入职月份,`ready_at <= hire_date` 口径,排除
  backfilled/cancelled;驾驶舱月度环图 + 窗口整体率 + 硬缺口债务红标;
  `GET /onboarding/analytics/day1-readiness` + MCP `onboarding_day1_
  readiness_analytics`,hint 281)+ 合规事件页 onboarding_day1_gap 行
  直达流程详情深链。**主题完成**:入职面与离职面成熟度对齐(扫描诚实
  →升级链 IT-only→备机闭环→H5 自助→运营记分卡)。**收官评审闭环**
  (2026-06-12):三轮对抗式 code review + 安全扫描 + 逻辑扫描循环至
  CLEAN——R1 修预留自动化旁路/记分卡 hard_ready_at 口径(migration
  280)/T+7 红线对齐;R2 修日报口径/T+7 取消态;R3 修 blocker 唯一
  约束(migration 281,并发 rescan dup-wedge)/单发重武装/日报过期轴;
  安全扫描无可利用项(跨区清理成文化),逻辑扫描六条旅程全 PASS。

- **身份消费方 — 对外拉取面(SHIPPED,3-PR 栈全部合并,2026-06-11)**。Helix 身份分发补全
  pull 方向:外部系统注册为 consumer、持 `helix_svc_` 服务凭证拉人员/组织数据。
  **PR1 已合并**:凭证底座(migration 277 三表)+ `require_consumer` 认证依赖
  (MCP 同款防御栈:统一 401 / per-token+per-IP 失败窗 / per-consumer 限流 /
  全审计)+ scope 三档(persons:read 脱敏 / org:read / pii:read 明文显式授予)+
  人群 filter 复用(引擎提升为 `app/core/person_filters.py`)+ 治理 console
  `/admin/identity/consumers` + `GET /api/v1/open/directory/me` 冒烟端点 +
  /help 新主题。**PR2 已合并**:REST Directory 数据端点(persons/org,keyset 增量游标,
  PII 分级,工程接入指南 docs/integrations/identity-pull-api.md)。
  **PR3 已合并**:只读 SCIM 2.0 Server(/scim/v2,RFC 7644 格式,写一律
  405,filter 诚实子集,nginx /scim/ 转发)。栈完成 —— pull 方向能力齐备。

- **Authing 退役 — Helix 接管身份分发(数据面能力全部建成, 2026-06-10)**。终态
  (用户钦定):北森与 Authing 全部下线,只剩 SF→Helix→所有下游。实地审计
  结论:Authing 真实在跑的 = 北森上游(已被 SF→Helix 取代)、AWS SSO/AWS AI
  SCIM(实时)、飞书通讯录推送(Helix `feishu_contact` 已建好待启用)、
  汇联易每日全量拉、13 个活跃 SSO 应用(汇联易 SSO 本身 8 周仅 2 次登录)。
  已合并:#949 `huilianyi` kind+driver(token 契约实地核实,companyCode
  value_map 双边枚举预置)、#954 汇联易切换对账 dry-run(工号+邮箱双键、
  邮箱冲突/已离职/缺映射/供应商独有/遗留树仪表)、#956 SCIM 组推送
  (AWS IdC 接管:externalId 收养、分块成员写入、改名 PATCH、出范围组
  永不删除)+ 三轮对抗式复审/安全/逻辑扫描收敛(14 项确认问题全修,含
  1×P1 刹车饱和 + 3×P2 安全)。**全部能力默认 disabled,等待人工逐人
  验证后开闸**。切换运行手册要点:①关汇联易侧 Authing 拉取 ②部门编码
  原地对齐 SF 体系(后台已验证可改)③配凭证+test_connection ④dry-run
  对账清零冲突 ⑤person_ids 小流量试点 ⑥全量。SSO 面:13 个活跃应用转
  飞书直连(OncallSchedule 用户自行联系;PCHub/CMDB 随平台退役),
  汇联易 SSO 8 周仅 2 次登录、切飞书零感知。待外部:汇联易答复(更新
  接口不传字段语义/syncUser 文档)、开放接口权限凭证、AWS IdC SCIM
  token。**2026-07-09:阿里云 CloudSSO SCIM 下游改由 Helix 直推**——
  第三个 `scim` 生产 target `alicloud-sso`(cn-shanghai,552 存量用户按
  邮箱认领,每日 02:00 UTC 全量 reconcile;CloudSSO 建号强制要求
  `externalId`,映射多一条 `externalId={employee_id}`;纯生产配置零代码,
  详见 PLATFORM.md scim runner 节)。

- **自动化执行面真实成功率 (execution-plane honesty) — SHIPPED & DEPLOYED
  (2026-06-10, 9 PRs #924/#929/#930/#932/#933 + 复审 #936/#937/#939/#940,
  prod `sha-4ed6785f…`)**。起点:prod 数据审计发现 outbox 历史送达率 1.4%、
  5 月断权重试风暴 463 次零告警、23 条"预期内做不到"任务烂在 failed。
  落地:通知渠道三态诚实化 + 死信聚类/批量运维/确认关闭;断权风暴上限
  (12 次→升级页+泊车)+ 管理员重试按钮;provisioning 瞬时错误退避重试 +
  capability 降级;关键告警飞书群 webhook 必达兜底;/admin/insights 运营 tab
  「自动化健康」四卡(每个红数字深链修复面)。三轮对抗式复审循环到干净
  (2 P1 语义错 + 1 P1 前端锁旁路 + 10+ P2 全修,含 `IS TRUE` JSON 谓词
  MySQL/TiDB 劈叉这一新 TiDB-parity 类)。**存量清理已执行**(418 partial
  收敛 delivered / 4,866 历史死信基线注记 / 30 failed 任务关闭),清理后
  prod 三指标归零:critical_dead=0、重试池=0、stuck revokes=0。
  详见 [docs/CHANGELOG.md](docs/CHANGELOG.md) 当日九条 digest。

- **申请与审批生命周期可见性 (request/approval lifecycle)** — closing the
  fragmentation where a submitted request's status was scattered across 5
  surfaces (per-domain pages + Feishu inbox) and admin approvals lived on
  3–4 pages. **PR1 shipped** (2026-06-10, 0 migration): the unified
  employee-side **「我的申请」 tracker** — `GET /api/v1/me/requests`
  (`me_universe/my_requests.py::build_my_requests`) aggregates every request
  the caller submitted across 8 source tables into one normalised-lifecycle
  list (the outgoing complement to the mobile action inbox), surfaced as the
  rebuilt `/my/requests` records tab (`MyRequestsTracker`) with inline
  withdraw + MCP `list_my_requests`; also fixed two dead-route onboarding
  CTAs. **PR2 shipped** (2026-06-10, 0 migration): the admin-side
  **待办总览** — `GET /api/v1/approval/admin/overview`
  (`approval/admin_overview.py::build_admin_approval_overview`) aggregates
  every pending approval across 9 sources (employee requests / cloud scope /
  leaver dual-sign / DNS / domain / VPN / AI-tool / announcement / API-key
  release) into one oldest-pending-first board, each tagged with
  `decided_in` (helix/feishu) + a deep-link to its decision surface;
  surfaced as the new default hero tab on `/admin/approvals` + MCP
  `admin_approval_overview`. Decisions still happen on each owning page —
  this unifies discovery. **Generalised (2026-06-25, 0 migration)**: the 9
  providers became 10 declarative `_SourceSpec`s behind one runner (+10th
  source `identity_consumer`, previously missing), and the same registry now
  also powers the **all-status 申请列表** — `GET /approval/admin/all-requests`
  (`build_admin_request_list`) + MCP `admin_request_list` (hint 348) — so the
  申请列表 tab browses every request in any lifecycle state across all 10
  domains (not just `employee_requests`), the cross-domain lifecycle closed
  loop. **PR3 shipped** (2026-06-10, 0 migration): the
  **manager axis** — `GET /api/v1/me/team/requests`
  (`build_team_requests`) lets a manager see every request submitted by
  their direct/transitive reports (read-only). The `my_requests` providers
  were refactored to take a person-id list so the same registry backs self
  + team; new `/my/team/requests` page (`TeamRequestsPage`) under 我的团队 +
  MCP `list_team_requests`. The three visibility axes (self → admin →
  manager) are now complete. **PR4 shipped** (2026-06-10, 0 migration,
  frontend-only): a 首页「我的申请」KPI card on `/dashboard` (open-request
  count → `/my/requests`), reusing `GET /me/requests` — the home-surface
  entry point into the tracker. **申请生命周期可见性 4-PR 收官.** The
  formerly-optional follow-on (search coverage for requests/approvals)
  shipped as 找得到 PR1 below; actionable notification deep-links land in
  找得到 PR3.

- **「找得到」 (findability) — 5-PR theme (SHIPPED, 2026-06-10/11)**。实地
  核查结论:平台本体厚(163 页/60+ 子域)但定位对象的能力落后——全局搜索只盖
  6 类实体、⌘K 是假的、PageHelp 接入率 16%、通知 inbox 不可检索。栈:
  **PR1 shipped**(0 migration):业务单据进全局搜索——`build_my_requests`/
  `build_admin_approval_overview` 增 `q` 过滤 + 三个 HTTP 端点 `q` 参数 +
  三个 MCP 工具同步加参;GlobalSearch 新增 我的申请(全员)/ 待审批
  (admin_read)/ 供给任务(global_admin/management)三组,深链决策面;
  ProvisioningQueuePage 补关键字搜索框 + `?keyword=` 深链。
  **PR2 shipped**(纯前端):真 ⌘K(全局 keydown→聚焦)+「页面」组
  (MainLayout 同源角色化菜单扁平化)+「帮助」组(`helpTopicIndex.ts`
  68 主题轻量索引,HelpCenterPage 改为索引 join PARTS 的编译期单一来源)
  + 最近访问(localStorage per-person, cap 8)。**PR3 shipped**(0 migration):
  通知 inbox 服务端检索(`q`/`read`/`template_prefix`,原筛选只过滤当前页
  20 行)+ 跳转质量(域名命中 `?search=` 预填、我的申请追踪器搜索框 +
  `?q=`/`?focus=` 消费、provisioning 铃铛 url 直达任务队列)。
  **PR4 shipped**(纯前端):PageHelp 接入 26 → 41 页(registry 30 → 47
  条,三语 ~80 键/语言)+ 两个新帮助主题 `compliance-grc` /
  `access-certification`(help 主题 68 → 70,自动进 ⌘K 帮助组)。
  **PR5 shipped**(纯前端):H5 顶栏放大镜 + `/m/search`——同后端同门禁,
  命中深链 `/m/*` 详情页(申请/人员/服务器/域名/供给任务),scope 命中
  仅管理员、nav/help 组不做(H5 形态裁剪,注释成文)。**主题收官**:
  ⌘K 三字直达任何对象,搜索 6 类实体 → 11 类 + 导航 + 帮助 + 最近访问,
  PageHelp 26 → 41 页,通知 inbox 假筛选转真检索,移动端同款。
- **安全合规 (GRC) redesign** — the never-used Security & Compliance menu is
  being rebuilt from scratch into a standard GRC module around a first-class
  **Control** entity (Vanta/Drata/SailPoint model). **PR1 shipped** (migration
  250): control catalog + multi-framework crosswalk (SOC2 + ISO27001) + posture
  dashboard + continuous control monitoring cron, registering the 8 existing
  detective controls. **PR2 shipped** (migration 251): general Access
  Certification engine (periodic access review; software-seat-by-department
  campaigns, reviewer = holder's manager), registered as the
  `access_certification` control (closes the ISO A.5.15 gap). **PR3 shipped**
  (migration 252): privileged-role review folded into the cert engine as
  `campaign_type='privileged_role'` + the old `auditor_privileged_*` tables
  dropped — single-engine end state. **Usability redesign in flight** (the menu
  was still not actually used): collapse 16 menu items → 4 + make every control
  actionable. **PR-A shipped** (migration 255): unified `auditor_findings`
  worklist — one lifecycle (open/acknowledged/accepted_risk/resolved) across all
  controls; public-exposure + sign-in project into it now. **PR-B shipped**: the
  4 native controls (MFA/secret/cert/PAT) emit findings + the 5 binary controls'
  posture pass/fail derives from open findings (so accept_risk/resolve turns a
  control green). **PR-C shipped**: frontend IA — 安全合规 menu collapsed 16→4
  (合规总览 / 控制项目录 / 访问认证 / 证据与事件), the 8 detective pages are
  control-catalog drill-ins, a unified 待办 inbox is the 合规总览 hero tab, and
  ops surfaces (通知队列 / 安全提示 / 制度公告) moved to 后台管理. **Usability
  redesign complete.** **Post-launch fix batch shipped** (migration 257): a
  router-alias collision that 404'd the 合规事件 tab, plus a module-wide
  adversarial review (accepted-incident re-open suppression, CCM upsert race,
  access_certification campaign-type masking, MCP input bounds). **Access-cert
  revoke fulfilment now closed the loop** — a `revoke` decision flips the seat
  + enqueues the real downstream revoke via a coordinator, reconciled by the
  `auditor.access_certification_revoke_fulfil` cron (was attestation-only). All
  cert/incident write flows now have 1:1 MCP twins. SoD + PCI DSS + 等保
  catalogs remain deferred (seed-only, additive to the control catalog when
  picked up). See DECISIONS 2026-06-04.
- **身份中枢 (Identity Hub) — make 「身份源数据与认证」 a platform, not a menu group**
  (4-PR stack, no new domains — surfaces the already-strong identity backend).
  **PR1 shipped** (2026-06-09, 0 migration): the per-person **identity golden
  record** — a reusable `IdentityProfileService` + `GET /identity/persons/{id}/identity-profile`
  joining the upstream HRIS source, login bindings (with admin kill-switch),
  the **downstream projection matrix** (which sync targets the person is
  projected into + live-rendered drift vs last push), signal-derived attribute
  provenance, and the identity change timeline; rendered as a new "身份" tab on
  `UserDetailPage` + MCP `get_person_identity_profile`. **PR2 shipped**
  (2026-06-09, 0 migration): the Identity Sync Center mapping editor is now
  driven by an authoritative backend catalog (`GET /identity-sync/mapping-metadata`
  — source fields grouped by category + transform list, so the FE can't drift
  like the old hard-coded list that dropped `strip_company_prefix`); `transform`
  is a constrained multi-select, `source_template` gets a per-row "insert field"
  picker, and the mapping preview now renders the **unsaved draft rules** against
  a sample person (was saved-config only) + MCP `preview_identity_sync_mapping`.
  **PR3 shipped** (2026-06-09, migration 269 + 1 cron): the identity
  data-quality / reconciliation panel — a daily `identity.data_quality_scan`
  cron upserts findings (deduped, auto-resolved on disappearance) across five
  checks (orphan login identity / stale tombstone / departed manager / missing
  manager / sync drift), surfaced at `/admin/identity/data-quality` with tiles +
  trend + decide actions (each finding links to the golden record) + MCP
  `list_identity_data_quality_findings`. **PR4 shipped** (2026-06-09, 0
  migration): the Identity-Hub landing cockpit — `/admin/identity` (was a
  never-routed "Coming soon" shell) is now the group's navigation root via
  `IdentityHubService` + `GET /identity/hub/overview` (a pure composition:
  headcount by lifecycle state, login bindings by provider, sync-target health,
  recent runs, data-quality posture) + MCP `get_identity_hub_overview`; four
  cockpit tiles deep-link into the spine. **身份中枢 4-PR 栈完成** — inbound HRIS
  → golden record → outbound projection + config builder → data-quality
  reconciliation → cockpit, navigable both ways via person ↔ target ↔ run ↔
  finding. **Follow-on shipped** (2026-06-09): a read-only **字段管理** field
  dictionary at `/admin/identity/fields` (Authing-style) — every user + dept
  attribute's key/type/source/visibility/editability, read straight off the
  schema + MCP `get_identity_field_catalog`.
- **Cloud cost/billing accuracy** — driver-level reconciliation hardening (Tencent
  billing-cycle alignment, `cost_facts` aggregation, month-close re-pull). The
  automated drift alert is **shipped** (2026-06-08): the reconciliation center's 4th
  "cost" lens + the daily `ai_spending.cost_drift_check` cron compare, per closed
  month × bucket, the materialised `ai_spending_period_rollup` against a live recompute
  from the same in-platform sources and flag supra-threshold divergence (likely_cause:
  stale_fingerprint / not_materialized / late_facts_or_drift). Cloud/Bailian **console**
  totals stay out of scope (no API-equivalent口径 — surfaced as a caveat, the console is
  authoritative for absolute spend).
- **AI 大盘折扣前 + 性能重做** — surface pre-discount (原价/list) price alongside the
  post-discount figure, and fix slow loads by materialising the immutable closed-month
  rollup. **PR1 shipped** (migration 259): Bailian gross columns + backfill from BSS
  `PretaxGrossAmount`(already in DB)+ per-bucket discount/coverage on the dashboard
  (2026-05 Bailian = 63.8% off). **PR2 shipped** (migration 260): `cmdb_cost_facts`
  gross columns + AWS Bedrock `pricing_public_on_demand_cost`(列守卫防缺列 400;重跑
  sync 回填);GCP Vertex / Azure Foundry 列就位但 gross=NULL(GCP credits 是全平台
  口径变更、Azure 需独立元价源,均拆 follow-up task,$ 体量极小)。**PR3 shipped**
  (migration 261+262,+1 cron):`ai_spending_period_rollup` 物化闭月 BucketRow 全维度集 +
  `_collect_period_rows` read-through(闭月查表、开月实算、仅 cron 写)+ 内容派生指纹
  (whitelist+FX+语义版本,改白名单/汇率自动失效)+ `ai_spending.period_rollup_refresh` cron(06:30 UTC,
  最近 2 闭月每跳算、更早缺指纹才补)——根治每请求扫 270 万行 `cmdb_cost_facts`。
  **PR4 shipped**:overview 折扣前总额 + 覆盖率 KPI + export 三列 + /help「折扣前 vs 折扣后」段。
  **Azure Foundry gross follow-up shipped**(2026-06-06,0 migration):CM query 增 `MeterId`+`UsageQuantity`
  维 + join 公开 Retail Prices API(`meterId` GUID,tier-0 PAYG 单价)→ `gross=Σ(数量×单价)`,前向;
  增维行折叠回 canonical grain 净 fact 不变、元价失败/400 退 NULL、gross<net 护栏、kill-switch
  `cost_gross_pricing`。**折扣前多桶重做 + 性能 PR1-4 收官**(GCP credits gross 留独立 follow-up)。
  **Bailian 取数纠正 + 三档价格 + 提速 shipped**(2026-06-07,migration 264):财务核账发现"百炼桶"$38,506
  ≠ 官网 $13,900。查清:百炼是**账号级商品账单**计费,QueryAccountBill 给托管整账($38k 垃圾)、
  DescribeInstanceBill 只 $354 子片;正确源是 BSS **`QueryBillOverview`**(月度,`ProductCode='sfm'`)。
  改用它后与官网逐分对齐三档:**原价 $13,900.54 / 优惠后 $4,865.19 / 应付 $4,727.71**,大盘三档都显示。
  月度+账号级(无 per-member;旧逐 member 数本就错)。**提速**:`_load_period_rows` 闭月+开月都 rollup-first
  (开月≤1天陈旧,cron 当前月每跳刷),根治默认当前月每开扫 270 万行 cmdb_cost_facts。历史 = 范围回填(days≤430)。
  **百炼按 API Key / 模型分账 shipped**(2026-06-17,migration 299):实证推翻"DescribeInstanceBill 只 $354"的旧结论——
  其 `InstanceID` 复合字段 `apiKeyId;workspaceId;model;tokenType;channel` 首段就是全局 API Key id,改用
  `DescribeInstanceBill`(DAILY)后能按「日 × 账号 × API Key × 模型」落库并对账到分。归属优先 API Key 负责人、其次账号负责人;
  `usage_summary` 加 `by_api_key`,百炼管理页加「消费排行 · 模型」+ KPI 应付口径 tooltip。**边界**:Helix 未纳管子账号下的 Key 只能归到账号、暂不落人(纳管后自动落人)。
- **Governance guardrails** — prod cloud-write global lock; generalising destructive-op
  confirmation/audit beyond the cloud kill-switch.
- **Recurring-bug class elimination** — structural fixes + CI guards instead of
  point-patches. Done: the no-dash seeded-UUID scheduled-job bug (recurred 3×,
  guard `check_scheduled_jobs_uuid_seed.py`); the **leaver-display bug class**
  (recurred 7× — departed report lingering in 我的团队/团队资源/搜索/图谱)
  collapsed into the canonical `app/core/person_scope.py` resolver + guard
  `check_subordinate_helpers.py` (2026-06-08), which also fixed a mobile-aggregate
  leaver-leak and a provisioning soft-delete leak; the **TiDB-parity lint class**
  (2026-06-08) — `String(36)`-typed FK columns (error 3780) + `.nullslast()/
  .nullsfirst()` (error 1064) + `func.timestampdiff("UNIT", …)` bare-string units
  (error 1064), all of which pass on local CI MySQL but break on prod TiDB —
  guarded by `check_tidb_parity.py` (7 existing String(36)-FK migrations
  grandfathered; `# tidb-parity-exempt:` for justified exceptions). No remaining
  known recurring classes without a guard.
- **HRIS→飞书通讯录同步 (`feishu_contact`) — hardened, pending manual enable** (2026-06-08).
  A prod read-only audit drove three pre-launch fixes so a first sync can't dup or
  pollute the already-populated tenant: **Phase-A dept scoping** (only depts with an
  in-scope member + ancestors → kills 10 empty English shell-depts that clash with
  Chinese-named Feishu depts), **non-employee gate** (`employee_no_pattern=^P\d+$`
  excludes `TimeAdmin`/`LifecyleAdmin`), and a **full read-only "同步前预览"**
  (`dry_run_feishu` + MCP `dry_run_identity_sync_target`) showing exactly which
  users/depts get created/updated/excluded/skipped. Target stays `enabled=false`
  until IT manually flips it after reviewing the preview. Verified: user duplication
  risk ~0 (424/432 clean email match, 0 hard-dups by employee_no).

## What's not yet shipped (genuine open work)

These are **gaps**, not missing features:

### Operability hardening
- **Qwen review automation retired (2026-08-18).** Both workflows and every
  dedicated repository implementation asset are deleted. Do not reactivate the
  runner or credential path from source; a future review programme needs a new
  owner decision and design, not a flag flip. See
  [retired review record](docs/architecture/DEEPSEEK_REVIEW.md).
- **HRIS auto-confirm**: today every batch is admin-reviewed before apply; some shops want a "small-delta auto-confirm" toggle.
- **~~统一资源目录每 15 分钟重写全表~~ —— 这条是错的,已于 2026-09-01 当天实测推翻,保留作为反面教材。**
  原文断言 `cmdb.resource_directory_refresh` 无条件重写全部 104k 行、折算 **133 GB/天**写入。**实测:81 MB/天,差 1600 倍。**
  判据是 `cmdb_cloud_resources.updated_at` 带 `onupdate`,任何真实写入都会刷新它 —— 生产上过去 **24 小时只有 6,086 / 104,239 行**(5.8%)被写过,
  过去 15 分钟 **0 行**。错因:**只读了代码里「逐字段无条件赋值」的写法,就推断出了写入量**,而 SQLAlchemy 在 flush 时会拿新值与 loaded 值逐列比对,
  **净变化为空就一条 UPDATE 都不发**。源表 `cmdb_servers.last_synced_at` 也只在云同步跑时才动(实测 1 小时内 0 行),所以四次目录刷新里有三次无事可做。
  **教训:写入量必须用 `updated_at` 分布量,不能从 ORM 代码的赋值写法推断。**
  仍然成立的只有一条静态事实:`cmdb_cloud_resources.raw_metadata`(1.389 GB)是 `cmdb_servers.raw_data`(1.385 GB)的**副本**,
  约 1.4 GB 重复存储。去重要连带把 `resource_group` 提升为真列(5 处 SQL JSON 路径查询),**收益一般、工作量不小,已判定不做**。
  > 取数教训:枚举「离职域通知」时不要用 `related_resource_type='offboarding_process'` 做 JOIN —— `leaver_reminder` / `leaver_escalation` 的 `related_resource_type` 是 **NULL**,那个口径会静默丢掉 43 行(含全部 25 行已送达的 T-7/T-3/T-1),让人得出「没有触达」的反向结论。按 `template_key LIKE 'offboarding.%' OR template_key LIKE 'leaver%'` 枚举。
- ~~**mypy is not a CI gate.**~~ — closed 2026-07-30: mypy now runs in `Backend Lint` as a **ratchet** (`server/scripts/check_mypy_baseline.py` + `mypy-baseline.json`). Standard-mode mypy reports **1650 pre-existing errors across ~190 files**, so the gate does not require a clean tree — it fails only on a NEW or GROWN `(file, error-code)` pair, and asserts the total never rises. A shrink is reported as `IMPROVED` and still passes (a refactor that incidentally fixes types must not fail its author's CI); refresh with `python scripts/check_mypy_baseline.py --update`. **Remaining work is burning the 1605 down** — the biggest classes are `[attr-defined]` 434, `[arg-type]` 291, `[no-any-return]` 288, `[assignment]` 177, `[union-attr]` ~129. The `union-attr` ones are the crash-adjacent class (attribute access on a possibly-`None` value) and worth going first — 45 of them were already cleared by annotating two always-raising guard helpers `-> NoReturn`; that sub-class is now exhausted (an AST sweep found no other raise-only helper used as a narrowing guard). The next-densest sub-class is the ~41 `datetime | None` → `.isoformat()` sites in `app/domains/auditor/*`, whose single root cause is **nine byte-identical copies of `_to_naive(dt: datetime | None) -> datetime | None`** — one shared, `@overload`-ed helper would fix them all and remove the duplication.
- ~~**Notification card chrome was single-language**~~ — closed 2026-08-09 (0 migration). `_select_template_field` covered `subject` / `body_text` / `body_html` / `card_button_label` and nothing else, so ~179 structured card values across 21 producer modules shipped in whichever language their author wrote in — the monthly attendance card (every employee + intern) had no language branching at all, and the offboarding cards were pure Chinese with the LEAVER as their reader. New `LocalizedText` primitive resolved per recipient; 250 sites converted; `card_subtitle` / `card_footer` / `card_highlights_title` joined `template_catalog._FIELDS` so admins can reword them. Two structural guards now fail the build on a `LocalizedText` missing a language, on Simplified-only characters in a `ja` value, and on a `zh` value pasted verbatim into another language — the backend equivalent of the pair the frontend needed. **Note for future work**: envelope `title` / `body_text` are still plain `str`, so an envelope with NO `template_key` (several offboarding sends) reaches its reader in one language. That is the next layer, not covered here.
- ~~**PageHelp had no integrity gate in either direction**~~ — closed 2026-08-09. Seven pages passed a `helpId` with no registry entry, so their "?" button never rendered at all (`PageHelp` returns `null` for an unknown id — silently); four registry entries were mounted by no page, so their copy could never be shown. Both directions now gated by `web/scripts/check-page-help.mjs` in `pnpm lint`, plus a bullets-vs-copy completeness check. Also fixed here: the notification-preferences page was showing the notification-INBOX help, whose bullet pointed at a location that does not exist, and the settings-page help described 5 tabs when there are 9.
- **Open items left by the 2026-08-09 review cycles** (four adversarial passes over the five-PR sweep; 14 → 6 → 5 → 8 findings, roughly half of each round being damage from the previous round's fixes). All HIGH/MEDIUM findings are fixed; these are the ones deliberately left:
  - **`localized_name`'s Latin-vs-CJK NAME FORM is still on the entity axis** (`hr_lifecycle/hire_day_service.py`). It is shared with the email path, and there is no obvious answer for `ja` — which form does a Japanese reader want for a Chinese colleague's name? A product question, not a mechanical fix.
  - **`{portal_url}` in `card_button_label` still ships literal.** `_resolve_button_label` does not receive `absolute_url`, while the template editor lists `portal_url` as auto-supplied on that field. Low blast radius (an admin has to write it) but the editor invites it.
  - **The footprint page computes the next anniversary CLIENT-SIDE** — a fourth copy of the observed-anniversary rule. Fixed in place (it rolled a Feb-29 hire to Mar 1 where the backend uses Feb 28, and parsed `YYYY-MM-DD` as UTC while reading local components), but it should be served by the API instead. The `grep -rn "years -= 1"` recipe in `core/tenure.py` cannot see frontend copies, and the structural test only scans `server/app/`.
  - **`notifications/service.py` uses a plain `logging.getLogger`**, so anything in `extra` bypasses redaction. The one leak found was fixed by logging no value at all; switching the module to `get_safe_logger` is the class fix and needs its own verification (`SafeLogger._emit` discards the message and args, so existing log lines would change shape).
  - **On no-CSPRNG devices a mobile feed post fails outright.** `useMobileMutation.run()` calls the *throwing* `newIdempotencyKey` before `mutationFn`, so the post never reaches the network and surfaces a generic error toast. Pre-existing; the `retryOnNetworkError: postToken != null` guard added alongside is therefore dead logic with an inverted rationale.
  - **Sweep floor baseline** (`cmdb/sync.py`, from PR #2592, another session): the change from "previous successful run" to a 5-run median means a peak-time run that genuinely lost ~30% of the fleet now passes the drop guard where the old peak baseline refused. Soft-delete + `_audit_resurrections` mitigate. Not this sweep's change — reported, not touched.
- **ja-JP: two separate problems, both ratcheted, both now mostly closed.** (a) **Verbatim-Chinese keys — 284 left** (was 2021). (b) **Mixed-script residue — 0 left** (was 1513 on 2026-08-09, itself discovered only after the parity guard was found blind to it; cleared 2026-08-09): a machine pass substituted a few tokens (`已`→`済み`, `请`→`してください`, `无法`→`なし法`) and left the rest of the Chinese, so the value differs from zh-CN and the parity guard called it translated. Detection is by **Simplified-only characters** in `web/scripts/check-i18n-mixed-script.mjs`. Both guards now accept **`--update`**, which recomputes the baseline from the bundles and **refuses to grow it** — matching `server/scripts/check_mypy_baseline.py --update`, and necessary because one translation pass turns thousands of entries stale and would otherwise fail CI with no supported way to prune. Caveat for anyone widening the character set: `来 / 当 / 点 / 系 / 个` are ordinary Japanese kanji (将来, 該当, 地点, 関係, 箇) — including them rejects correct translations, which is worse than missing residue. **Translating at scale**: split by namespace into disjoint batches, have each worker emit `{key: translation}` JSON, and merge at ONE point — concurrent edits to the 20k-line bundle collide, and a merge that does not re-read afterwards can write nothing while the baseline shrinks, hiding the problem instead of fixing it.
- **Enum-label coverage is contracted for 14 enums; the rest of the ~369 dynamic namespaces are not.** `contracts/i18n-enum-labels.json` + a pair of tests (`server/tests/test_i18n/test_enum_label_contract.py` asserts the live backend constants equal the recorded values; `web/src/i18n/enumLabels.contract.test.ts` asserts every value has a label in all three bundles) make it impossible to grow a contracted enum without deciding on a label. It landed after finding **9 values with no label in any language** — five at render sites that pass no fallback, so the literal i18n key reached the user. An entry may set `namespaces: []` when its values render through hardcoded branches instead of a dynamic lookup; it is then contracted purely for growth detection, and every value must carry an `unlabelled` reason. **Open work: extend the contract to the remaining dynamic namespaces** — the five leads recorded in round 4 were all chased to a verdict in round 5 and **all five were disproved** (prefix mis-pairing in the heuristic, or a frontend-owned hardcoded list), so do not re-chase them; the productive direction is contracting namespaces that render with no fallback.

- **Savepoint ratchet is back to 33 findings / 23 files (2026-09-02) — draining is open work.** The original 99 were drained to 0; then the guard was made **interprocedural** (it previously saw only DB work written literally in the `try` body, which is why it passed the two github_org reconcile loops PR #3394 had to fix) and surfaced a fresh 33. **These are backlog, not false positives** — two clusters dominate and both were verified by hand. (a) **`reconcile_submission_intents` × 10 domains** (apikey, cmdb dns + scope, sso, ai_tool_application, announcement_request, data_access, domain_registration, vpn_application, identity consumers): `submit_with_durable_intent` does `db.add()`, and the broad handler's **first act** is `await defer_submission_intent_retry(self.db, ...)` — a write on the session the failed step just poisoned. The sibling `except AppException` branch already rolls back for the `RESULT_UNKNOWN` code, so the shape is half-fixed; the broad branch is the gap. Fixing these is one shared edit repeated 10× and is the highest-value drain. (b) **`auditor/sync_services.py` × 10** "provider isolation" blocks: `_upsert_secret` / `_upsert_pat` / `_upsert_cert` all `session.add(...)`, so one provider's failed flush kills every provider after it — the exact "isolation that isn't" the guard exists for. Note these evaded the *old* rule only because of a **leading underscore** (`_upsert_cert` does not match the `upsert_` prefix list). Run `uv run python scripts/check_savepoint_isolation.py --list` for the current backlog.

### Self-service expansion
- ~~**Recycled cloud access had no restore entry point**~~ — closed 2026-08-25 (0 migration / 0 API). The *backend* had accepted revival since #2098: `check_renewal_eligibility` returns `eligible + is_revival` for an `expired` grant inside `RENEWAL_REVIVAL_MAX_AGE_DAYS` (30d), `_serialize_grants` shipped that flag on **every row of the `已结束` tab**, and `RenewalRequestModal` already badged revival orange — but `endedGrantColumns` had **no action column**, so the field arrived on the wire and was discarded. A user asked us to build a feature that was already built, because the page that reaches them carried no control. Action column added ("申请恢复访问", reusing the existing modal); ineligible rows now render `—` plus the reason tooltip, because "no button" and "no such feature" look identical. **Open**: 145 active employees / 822 grants were recycled and never restored, 115 of them still inside the 30-day window — that is a data/ops sweep, not code.
- **`source='discovered'` grants get a 90-day TTL stamped with no notice to the holder** (`_apply_discovered_grants`). grants_sync discovers a pre-existing cloud-side assignment and unilaterally gives it an expiry date; the holder's first ever notice is the T-14 reminder. Closing this means a **new employee-facing push**, which per standing policy (new reach is IT-only by default; estimate the deploy-day backlog fire first) needs an explicit decision — 800+ existing rows would qualify on day one. Not built.
- **Cloud scope requests** support routing to scope owner OR admin via Feishu, but **admin-routed approvals don't show up in the unified `/admin/approvals` page yet** — they live on `/admin/cloud/scopes?tab=requests`.
- ~~**Manager-of-subordinate cloud visibility**~~ — closed (was stale): managers already see subordinates' cloud grants/scopes/DNS/CDN/domains via `/my/team/cloud` (`build_team_cloud`). The 2026-06-13 角色对称 PR2 further added `/my/team/lifecycle` so managers also see reports' onboarding-readiness + offboarding status/blockers.
- ~~**Help center (`/help`) content** is sparse~~ — closed: 找得到 PR2/PR4 (2026-06-10/11) rebuilt the topic index (70+ topics across 5 audiences, ⌘K-searchable) and pushed PageHelp to 41 pages / 47 registry entries; the 2026-06-11 doc audit added Identity-Hub / 数据治理 topics for the regrouped 身份源数据与认证 menu. Residual: a handful of admin sub-pages (流程与审批, 公告与制度, notification outbox, platform settings, asset intake/operations sub-tabs) still share parent topics instead of having their own.

### Integration debt
- **GCP: 新账单账号 `0125F7-277DBE-14DC04` 没有 BigQuery 账单导出(2026-09-07 查实)。** `gcp-digital-bi` / `gcp-digital-infra` / `gcp-digital-bi-dev` 在 **2026-08-14** 被挪到这个账单账号,而 GCP 集成的 `config_public.billing_bq_table` 只列了旧账单账号的三张导出表,于是这三个订阅 **8/13 之后零行入库**(折前约 $5.1k/月不可见,GCP 租户总额同额少算)。页面现在会明说这是数据缺口而不是 $0(`coverage.status='gap'`,见 PLATFORM.md `cost_coverage` 段),但**数据本身要三步才回来**:① 在 GCP 侧为该账单账号开启 BQ 导出(建议仍落 `gcp-digital-bi.gcp_billing_v2`,同 dataset ⇒ SA 读权限现成),并给 `pch-resource-manager@gcp-digital-infra` 加 `roles/billing.viewer`(它目前在 `gcloud billing accounts list` 里看不到这个账号);② 把新表追加进 `billing_bq_table`(驱动本就支持多表 UNION ALL);③ 走 `execute_job` + `allow_past_window` 回填。**注意 GCP 的 BQ 导出不回填历史** —— 8/14 → 开启日这一段在 BQ 里将永久缺失,只能从 Cloud Billing 报表 / 经销商发票另补,或认下这个洞;越早开导出洞越小。
- **「一个订阅的账单静默断流」没有告警(2026-09-07)。** 上面那条 25 天无人发现,不是因为没人看,而是三道闸各自都正当地放过了它:日环比尖刺要有「今天」才能比、`cost_chain_crosscheck` 是两条链路互查而这次两条一起断、闲置扫描在「30 天一行都没有」时刻意弃权。`cost_coverage` 已经把判定做成了随手可算的读侧原语(同集成新鲜度 vs 本订阅新鲜度 + 连续计费性),**缺的只是一个按它扫全表的 cron + 通知**(建议:每日扫 `status='gap'` 且 `estimated_missing_usd` 超阈值的 scope,发给 Owner + 云管理,带 `pipeline_stale` 的整云去重)。
- **Feishu create path: no 41004 mobile-invalid retry (regression from `3a0adf7e2`, 2026-08-19).** The retired Contact v3 create dropped an unformattable `mobile` and built the account anyway, recording a `dropped` mobile slot. The Directory v1 create sends `mobile` and has no slot-dropping fallback, so a 41004 now fails a **new hire** outright — an existing person is unaffected (the PATCH path still drops the slot). Pinned honestly as current behaviour in `test_create_mobile_invalid_is_a_hard_failure`; fixing it means adding a drop-and-retry to the Directory create. Note **41010 "no mobile" also lost its `blocked` (vs `failed`) routing** in the same move, and Directory v1's error code for that case has never been probed — do not port the Contact v3 number over blind.
- **AWS IdC roster: `aws-sg-idc` still needs an IAM grant (2026-08-15).** The Identity Store roster channel shipped (`runner_options.roster_source = "aws_identity_store"`) and **`aws-sso` is live on it** — 497 users where SCIM could only ever see 100. `aws-sg-idc` cannot switch yet: its Identity Center instance lives in account `841162690310`, whose Helix credential is denied `sso:ListInstances` / `identitystore:*`. Until that policy is attached, that one target keeps failing `vendor_snapshot_refresh` nightly — **an honest red**: its leaver-assurance vendor signal is capped at 100 accounts and the alert is the only thing saying so. Steps + policy JSON in [identity-sync-scim-token-rotation.md](docs/ops/identity-sync-scim-token-rotation.md).
- **KSyun**: only servers + billing today; no scopes / grants / IAM. Same for **Tencent / UCloud** at scope-grant level (single-tenant `standalone_iam` mode is supported but coarse).
- **Workday / BambooHR HRIS drivers**: framework supports them (`HrisDriver` interface), no driver shipped.
- ~~**Microsoft 365 / Office 365 software provisioning**: no native driver~~ — closed 2026-06-25: native `microsoft_graph` driver (license assign/revoke + Intune device read).
- **Jamf Pro (MDM)**: driver shipped 2026-06-26 (`kind='mdm'`, verified live against `pingcap.jamfcloud.com`, 45 managed Macs). **纳管关系已改挂设备(2026-08-26,迁移 558)** —— MDM 纳管属于设备不属于人:新表 `asset_mdm_devices` 是台账,`jamf.seat_sync` 退役、`jamf.device_sync` 接任,不再写 `software_seat_allocations`,离职扫描按 `kind='mdm'` 跳过坐席 blocker。**离职不再产生任何 MDM 待办**;清理唯一触发点是资产处置(回购/外售/报废/报损),由 `mdm.cleanup_notify` 发卡与催办(首发 → 7 天周催 → 21 天抄送管理员,**每 tick 最多 5 张、最久未处理优先、被压下的条数写进摘要**),结案认「同步发现设备消失」这一硬证据。**开单与撤单双向收敛**:同步阶段 1e 补开「资产已处置而设备仍纳管却无请求」的清理 —— 覆盖上线前的历史处置、`best_effort` 吞掉的钩子错误、以及不走 `AssetService` 的写入方。读面 `/admin/mdm/devices`(桌面台账 + 详情抽屉,移动端 `/m/admin/mdm-devices` 只读)+ MCP `list_mdm_devices` / `list_mdm_cleanup_queue`。对账中心 Jamf 一格保留(45/60 合同用量),`leaver_residual` 保留为数据质量读数而非待办。见 DECISIONS.md 2026-08-26。**仍未做**:(1) Jamf 的写方法(unbind/lock/wipe/set_device_assignment)**从未对生产租户真正触发过** —— 首次执行须 dry-run 预览 + 单台核验 + 确认;(2) 那 4 台离职者名下的 Mac 尚未清理(等首次核验);(3) 4 台 "external" 设备的 @pingcap 邮箱不在 `identity_persons` 里(未走 HRIS 同步),身份待确认;(4) 设备动作的自动化(cron 自动触发)**刻意不做** —— 保持人工发起。

### Observability
- No structured metrics endpoint yet (Prometheus). Logs are stdout-JSON; cloud log collection wired in EC2 prod but not yet aggregated for dashboards.
- No SLO definitions — implicit SLAs live in offboarding `sla_breach_level` enum but aren't lifted to platform-level.

---

## Phase plan reference (historical)

The original CLAUDE.md plan was:

| Phase | Scope | Status |
|-------|-------|--------|
| **M0** | Foundation: auth, identity, RBAC, audit, config | ✅ shipped |
| **M1** | HR domain + frontend | ✅ shipped |
| **M2** | Asset domain + frontend | ✅ shipped |
| **M3** | Events + workflows | ✅ shipped (workflow journal + scheduled_jobs framework instead of Temporal) |
| **M4** | Feishu approval + Leaver Blocked + governance | ✅ shipped (with dual-sign override) |
| **M5** | Self-service portal + i18n | ✅ shipped (`/me`, `/my/*` routes; full zh-CN + en-US, ja-JP added later) |
| **M6** | CI + Kubernetes deployment | ⚠️ CI shipped on GitHub Actions; production runs on AWS EC2 + Docker Compose. K8s manifests not authored — EKS migration is opt-in for later. |

**Phase 2** (Software access) and **Phase 3** (CMDB + Cloud) — both delivered as part of M3–M5.

---

## Working principles (still in force)

These are non-negotiable per [CLAUDE.md](CLAUDE.md):

1. Domain modules don't import from each other — cross-domain via the workflow journal + scheduled jobs.
2. Every state-changing op is idempotent.
3. Every key action is traceable by `trace_id`.
4. Frontend is pure presentation — no business logic.
5. No backwards-compat shims when the code can just be changed.
6. No mocks, no `defaultValue` fallbacks, no empty methods.
7. Pick the most mature option, not the lightweight/phased/test-first version.
8. Single TiDB cluster — no second DB, broker, or state store.
9. Every PR touching arch/migrations/cron/drivers MUST, **in the same PR**, prepend a digest to [docs/CHANGELOG.md](docs/CHANGELOG.md), update [docs/architecture/PLATFORM.md](docs/architecture/PLATFORM.md), refresh this file only if current state changed, and (for cloud changes) update [docs/ops/cloud-lifecycle-ops.md](docs/ops/cloud-lifecycle-ops.md).

---

## Reading order for new contributors

1. [CLAUDE.md](CLAUDE.md) — boot rules + working principles
2. This file — what's shipped, what's not
3. [docs/architecture/PLATFORM.md](docs/architecture/PLATFORM.md) — the authoritative map of every domain, framework, abstraction, table, driver, scheduled job
4. [docs/architecture/DECISIONS.md](docs/architecture/DECISIONS.md) — past architectural decisions, don't re-litigate
5. [docs/ops/cloud-lifecycle-ops.md](docs/ops/cloud-lifecycle-ops.md) — runbook for cloud onboarding / decommission / sync / IAM cleanup
6. [docs/ops/cmdb-ops.md](docs/ops/cmdb-ops.md), [software-billing-ops.md](docs/ops/software-billing-ops.md), [zoom-ops.md](docs/ops/zoom-ops.md), [slack-ops.md](docs/ops/slack-ops.md) — domain-specific runbooks
