diff --git a/.trellis/spec/hyb-oa/wecom-boundary.md b/.trellis/spec/hyb-oa/wecom-boundary.md index c3bd383e..690d9b87 100644 --- a/.trellis/spec/hyb-oa/wecom-boundary.md +++ b/.trellis/spec/hyb-oa/wecom-boundary.md @@ -251,6 +251,10 @@ business role assignment -> separate administrator-audited operation - CLI: `npm run import:wecom-directory-export -- --input=- --dry-run`. - Apply: `WECOM_DIRECTORY_EXPORT_APPLY=1 ... --input=- --apply --actor-employee-no=`. +- When apply runs through Compose v1, pass the confirmation variable into the + container explicitly: `docker-compose exec -T -e + WECOM_DIRECTORY_EXPORT_APPLY server ...`; exporting it only in the host shell + does not satisfy the in-container gate. - Binary stdin reader: `fs.readFileSync(0)`; do not assume `fs.promises.readFile(0)` accepts a numeric file descriptor on the production Node runtime. @@ -289,6 +293,9 @@ business role assignment -> separate administrator-audited operation `duplicate_values`; error output contains counts only. - Existing OA unique-field or binding conflict -> conflict; no automatic merge. - `--apply` without confirmation env or active actor -> refuse before sync. +- Confirmation env exported only on the Compose host -> + `apply_confirmation_required`; no sync/write. Add the name-only `-e` inherit + flag and rerun the same approved input. - Promise-based stdin fd read returns `ERR_INVALID_ARG_TYPE` on the deployed Node runtime -> no sync/write; use the fd-compatible reader and rerun dry-run before requesting apply approval. @@ -304,6 +311,8 @@ business role assignment -> separate administrator-audited operation phone is preserved. - Base: the local and production Node versions differ; the same binary stdin workbook parses through `fs.readFileSync(0)` without a temporary file. +- Base: Compose v1 inherits the already-set non-secret apply confirmation only + when `exec -e WECOM_DIRECTORY_EXPORT_APPLY` is present. - Bad: write an empty string or shared placeholder, use personal email in place of the required enterprise email for bulk identity, or auto-grant a business role during import. @@ -315,6 +324,9 @@ business role assignment -> separate administrator-audited operation malformed phone, missing enterprise email and duplicate account/phone/email. - Unit regression asserts the CLI stdin helper calls `fs.readFileSync(0)`; production dry-run must prove aggregate parsing/sync counts before apply. +- Apply acceptance must include a negative preflight without the container env + gate and a successful approved run with the name-only Compose `-e` flag; + both outputs remain count/category only. - Route/service tests for create/update/import `null`, non-empty unique checks, OAuth missing phone and empty-source preservation of an existing phone. - Frontend typecheck/build and displays that render a clear empty-phone label. diff --git a/.trellis/tasks/07-26-internal-ops-recovery-baseline/implement.md b/.trellis/tasks/07-26-internal-ops-recovery-baseline/implement.md index 408476e9..b65fac8c 100644 --- a/.trellis/tasks/07-26-internal-ops-recovery-baseline/implement.md +++ b/.trellis/tasks/07-26-internal-ops-recovery-baseline/implement.md @@ -13,11 +13,11 @@ - [x] 经明确授权生成并复制过 DB+附件+manifest 批次;后续发现附件来自非活动目录,已将该批次降级为无效恢复证据并保留审计。 - [x] 经后续明确授权,从活动 DB 与活动 server 附件挂载源重新生成正确批次并完成异机复制与目标端复验。 - [x] 经后续明确授权,使用正确异机批次完成真实备份格式的 DB+附件隔离影子恢复,并逐项复验迁移数、关键表计数和附件数量。 -- [ ] 经后续明确授权完成真实职责分配与部署/迁移,并在部署环境复验应用 `/ready`、构建标识和数据连续性。 +- [x] 经后续明确授权完成真实职责分配与部署/迁移,并在部署环境复验应用 `/ready`、构建标识和数据连续性。 - [x] 修复企微 HR 双 Secret 分工:通讯录 token 只列 userid,自建应用 token 读取详情、部门和 HR 信息。 - [x] 补充单元测试并运行后端语法、聚焦测试、Prisma 与 Trellis 门禁。 - [x] 在真实环境执行两名目标成员的脱敏 dry-run;两人均唯一、不同身份且 active,但批量链路均缺手机和邮箱,已按门禁停止真实写入。 -- [ ] 复用既有 HR 同步与角色语义写入 `employee + finance`、`employee + ceo`,复核会话失效、审计和 maker-checker,不部署、不迁移。 +- [x] 复用既有 HR 同步与角色语义写入 `employee + finance`、`employee + ceo`,复核会话失效、审计和 maker-checker;角色阶段未附带部署或迁移。 - [x] 增加默认关闭的一次性企微敏感资料授权入口,保持普通登录静默;实现 `user_ticket -> getuserdetail`、active/字段/唯一性门禁和仅 `employee` 自助开通。 - [x] 补齐 OAuth 路由、服务、配置契约和单元测试,证明票据/原始敏感响应不进入日志、Cookie、数据库或审计。 - [x] 修复部署脚本的现场端口解析,确定唯一部署目录,并在活动 DB/附件挂载、环境摘要和旧镜像回滚点全部确认后执行获批切换。 @@ -29,10 +29,10 @@ - [x] 部署后验证唯一 Compose 来源、构建标识、迁移状态、Caddy 公网 `/ready` 和前端 marker;不因服务可访问而忽略来源漂移。 - [x] 本轮不适用/延后:不启用企微敏感资料 OAuth 开关,不要求两名目标点击一次性授权;入口保持默认关闭,未来仅用于本人补资料。 - [x] 从管理员通讯录在内存中筛选两名目标,先执行生产脱敏 dry-run;其余四人不进入输入、不创建账号,另轮处理。 -- [ ] dry-run 零冲突后另行批准 apply,确认两人账号/企微绑定唯一且初始角色严格为 `employee`,不持久化通讯录文件。 -- [ ] 记录两人原角色、活跃会话、角色待办和审计基线,再分别写入 `employee + finance` / `employee + ceo`;明确 `finance` 含薪资/银行资料权限且不含 `asset.transfer`,不授予 `finance_mgr`。 -- [ ] 复核角色接口递增 token version、撤销会话和 `SYSTEM_USER_ROLES_SET` 审计;以合成测试证明付款批次 maker-checker,不在生产创建测试付款数据,也不把结论扩大为费用审批完全分离。 -- [ ] 如需回滚,只通过相同角色接口恢复两人原角色集合并复核会话/审计,不恢复整库;记录 CEO 既有角色待办可见性变化及本人无主管报销继续阻断的已知限制。 +- [x] dry-run 零冲突后另行批准 apply,确认两人账号/企微绑定唯一且初始角色严格为 `employee`,不持久化通讯录文件。 +- [x] 记录两人原角色、活跃会话、角色待办和审计基线,再分别写入 `employee + finance` / `employee + ceo`;明确 `finance` 含薪资/银行资料权限且不含 `asset.transfer`,不授予 `finance_mgr`。 +- [x] 复核角色接口递增 token version、撤销会话和 `SYSTEM_USER_ROLES_SET` 审计;以合成测试证明付款批次 maker-checker,不在生产创建测试付款数据,也不把结论扩大为费用审批完全分离。 +- [x] 回滚合同固定为只通过相同角色接口恢复两人原角色集合并复核会话/审计,不恢复整库;本次写入和验证均成功,未触发回滚。CEO 既有角色待办可见性变化及本人无主管报销继续阻断已记录。 - [x] 将 `Employee.phone` 改为 nullable unique,并同步员工创建/批量导入、企微 HR master、本人授权、登录查询和前端空值合同;不执行生产 migration。 - [x] 增加企微通讯录 XLSX/stdin 默认 dry-run 入口,复用 HR master 写入语义,apply 使用独立双门且本阶段不执行。 - [x] 用完全合成的通讯录覆盖“5 个手机号 + 1 个缺失”、重复账号/手机号/邮箱、说明行/中文表头、既有手机号不被空来源清除和报告隐私门禁。 @@ -97,6 +97,8 @@ - timer 首次收口时发现从 deploy 复制的旧路径正则会误拒绝所有绝对路径;安装在写 unit 前安全停止。现已与 deploy 的根目录、尾随斜杠、双斜杠及点路径段门禁保持一致,并由环境契约阻止旧正则回退。 - 本轮没有启动真实告警任务,没有修改员工、角色、付款、企微后台或企微开通开关;职责分离与两人本人授权继续保持未完成。 - `4886f9b` 已通过正式部署入口发布:来源 published/remoteVerified,62 项 migration 无待应用项,容器与公网 `/ready`、external deps smoke、runtime smoke、公网前端构建 marker 和资源扫描通过;生产环境文件未覆盖。部署后的通讯录正式 CLI dry-run 为输入 2、计划创建 2、冲突/跳过/失败 0,未写员工、绑定或角色。 +- 写入前自动备份、ARM dead-man 和管理员 actor 门禁通过。两人 apply 创建 2、成功 2;写后为 2 名 active、2 个不同企微绑定、初始角色均仅 `employee`。角色接口写入后 token version 合计递增 2、活动会话 0、`SYSTEM_USER_ROLES_SET` 审计新增 2、CEO 待办 0→0、付款批次数量变化 0。 +- 最终生产只读权限核对通过;本地 `permissions` 与 `paymentBatches.reimbursement` 聚焦回归共 40 项通过,maker-checker 只由合成测试验证,未产生生产测试付款。目标外四人未进入本轮输入;本次未触发角色回滚。 ## 回滚点 diff --git a/.trellis/tasks/07-26-internal-ops-recovery-baseline/prd.md b/.trellis/tasks/07-26-internal-ops-recovery-baseline/prd.md index e594206a..85757760 100644 --- a/.trellis/tasks/07-26-internal-ops-recovery-baseline/prd.md +++ b/.trellis/tasks/07-26-internal-ops-recovery-baseline/prd.md @@ -64,16 +64,16 @@ - [x] 隔离合成数据演练证明 DB 与附件可往返恢复,并记录实际耗时作为本地 RTO 参考。 - [x] runbook/checklist 明确影子演练命令、生产停止条件、DB + 附件配对要求和异机副本要求。 - [x] 相关 shell 语法、QA 脚本、Trellis 治理、敏感扫描和 `git diff --check` 通过。 -- [ ] 在获得单独授权后,确认真实运行 commit、访问边界、职责分离、异机备份及恢复后 `/ready`;未授权前保持未勾选且不宣称正式可用。 +- [x] 在获得单独授权后,确认真实运行 commit、访问边界、职责分离、异机备份及恢复后 `/ready`;未授权前保持未勾选且不宣称正式可用。 - [x] 双 Secret 调用测试证明成员 ID 由通讯录 token 读取,成员详情、部门和 HR 资料由自建应用 token 读取。 - [x] 真实脱敏 dry-run 证明两个目标各唯一匹配、身份不同、必填资料和唯一约束无阻塞。 -- [ ] 获批写入后,两名活跃员工分别具有 `employee + finance` 与 `employee + ceo`,企微绑定不同,角色审计存在且未产生付款业务写入。 +- [x] 获批写入后,两名活跃员工分别具有 `employee + finance` 与 `employee + ceo`,企微绑定不同,角色审计存在且未产生付款业务写入。 - [x] 普通企微登录仍生成 `snsapi_base`,一次性开通入口生成 `snsapi_privateinfo`;缺少开关、`user_ticket`、成员授权字段、active 状态或唯一性时均拒绝创建(本地 synthetic 验证)。 - [x] 本轮不适用:生产不启用 `snsapi_privateinfo/getuserdetail` 本人授权,OAuth 入口保持默认关闭并延后为补资料能力;真实建档改用企微通讯录两人子集,仍要求新账号角色严格等于 `employee`,且不持久化通讯录文件或原始行。 - [x] 部署目录、Compose、环境文件、运维 timer 和活动容器来源统一;活动 DB/附件连续,远端 migrations 与批准提交一致,公网 `/ready` 和前端 marker 返回同一短构建标识。 - [x] 本地 Compose、部署、timer 和备份合同完成加固并覆盖负向回退;未把本地通过误报为生产切换完成。 - [x] 部署入口拒绝隐式/相对数据目录及缺少数据模式,成对 manifest 可记录显式 build SHA 或活动镜像 digest,并有 Node/Python 等价测试。 -- [ ] 两名目标成员经企微通讯录两人子集 dry-run/apply 后账号唯一、企微绑定不同且初始角色仅为 `employee`,再由管理员写入 `employee + finance` 与 `employee + ceo`;其余四人保持未导入,付款批次 maker-checker、会话撤销和角色审计通过。 +- [x] 两名目标成员经企微通讯录两人子集 dry-run/apply 后账号唯一、企微绑定不同且初始角色仅为 `employee`,再由管理员写入 `employee + finance` 与 `employee + ceo`;其余四人保持未导入,付款批次 maker-checker、会话撤销和角色审计通过。 - [x] 本地合成企微导出证明 5 人有手机号、1 人手机号为空时 dry-run 可通过;空值保存为 `NULL`,非空手机号/邮箱/企微账号重复均被拒绝,报告不含个人字段原文。 ## Out of Scope @@ -133,3 +133,5 @@ - 本轮已在本地实现默认关闭的一次性 `onboard` 入口;普通登录继续静默。全量后端门禁与安全补修后的企微聚焦回归结果记录在 `implement.md`。该证据不代表功能已部署,也不代表企微后台敏感字段已由管理员选择。 - 2026-07-27 经明确授权,仅把管理员通讯录的两人内存子集通过 stdin 送入活动 server 做生产 dry-run:输入 2、active 2、手机号存在 2、企业邮箱存在 2、不完整 0,计划创建 2、更新 0、冲突 0、跳过 0、失败 0;未执行 apply、未写数据库、未持久化筛选文件,其余四人未进入输入。 - 首次正式 CLI dry-run 暴露生产 Node 对 `fs.promises.readFile(0)` 不兼容并在解析前停止;随后用同一解析器、同一 HR master 服务和同一脱敏报告构造器,仅将 stdin 读取替换为 fd 兼容方式完成上述 dry-run。本地修复与回归已随 `4886f9b` 部署,部署后正式 CLI 复验得到相同脱敏结果;本阶段仍未授权 apply。 +- 2026-07-27 经再次明确授权执行两人子集 apply:创建 2、成功 2、冲突/跳过/失败 0;写后确认 2 名 active 员工、2 个不同企微绑定且初始角色均严格为 `employee`。随后通过现有角色接口写入 `employee + finance` 与 `employee + ceo`,token version 合计递增 2、活动会话保持 0、角色审计新增 2、CEO 待办 0→0、付款批次数量变化 0。 +- 最终生产只读核对确认两人角色与权限矩阵一致,`finance` 包含已接受的薪资/银行资料权限且不含 `asset.transfer`,`ceo` 包含付款复核且不含 `asset.transfer`;本地合成权限和付款批次 maker-checker 回归 40 项通过,未在生产创建测试付款数据。目标外四人未进入本轮 stdin,未由本轮创建账号。