 xcsandClaude Fable 5
|
d64f0b5ffd
|
修复轮 1(评审):mail 用例测试补钉三处伪覆盖 —— 附件不重存/中途失败/分发不因单个失败断链
评审判 Needs fixes:三条断言此前恒真,改坏实现照样绿。①重复拉到里撞 providerRef 的
那封改带附件,才验得出「守卫在存附件之前生效」;②拉取抛错改为两轮 pollNow ——
第一轮先用 afterCalls 让 pull() 真正成功推进游标,第二轮才炸,不再是「零封被处理,
游标没动只因为它压根没机会动」;③分发失败的订阅方 boom 从最后一个挪到中间(ok →
boom → skip),证「继续往下投」而不是恰好排在末尾。send 测试补 references 断言。
fakes.ts 的 failPullWith 加 afterCalls 支持第 N 次调用起报错。逐条用临时注入的
实现缺陷验证测试确实会红(验证后已还原,mail-service.ts 净差异为零)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 02:06:42 +08:00 |
|
 xcsandClaude Fable 5
|
0c6e8e2c1b
|
app 装配 mail:channel 作首个订阅方、出站经 mail.send、mail.send 工具真发信(dryRun 只校验)、轮询工作者;上传件存取抽出共用
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 02:05:50 +08:00 |
|
 xcsandClaude Fable 5
|
58358210e2
|
计划再修:游标推进与 more 必须同一个判据 —— 「页翻完」不等于「新信吃完」
上一次修的是「多页时截断不许推进游标」,但漏了一形:最后一页可能同时 has_more:0
且新信多过本轮预算(LIST_PAGE 是 100,与 perTick 的 50 无关)—— 那时 exhausted 为真、
游标照推,而 wanted 截掉的那几封从此永久丢(重叠窗只兜 10 分钟)。
评审的话说得准:more 的判据是对的,cursor 的不是,两者本该是同一个。
改成 `const more = !exhausted || fresh.length > wanted.length; cursor: more ? cursor : 推进`。
补两条测试:单页 has_more:0 但新信超预算(游标不许动)、-1 连击到边界不再重试。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 02:05:39 +08:00 |
|
 xcsandClaude Fable 5
|
5bd92f2e93
|
mail adapter:企业微信邮件 API provider(token 缓存与刷新 / 翻页 / 已知 mail_id 不再读 / EML 编码兜底 / compose_send)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:50:11 +08:00 |
|
 xcsandClaude Fable 5
|
7afc40987b
|
mail adapter:IMAP/SMTP provider(只读打开 / UID 游标 / n:* 过滤 / UIDVALIDITY 重锚 / nodemailer 发信)+ env 门控真信箱 IT
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:47:24 +08:00 |
|
 xcsandClaude Fable 5
|
f04c64926b
|
计划修死企业微信那条路的游标丢信:截断时不许把游标推到窗口上沿,且滤重要在截断之前
Task 9 的评审点过「批末无条件覆盖游标、没有单调性护栏」,当时两个适配器还没落地、判不了实情。
照原计划的 wecom pull 读下来是两个真缺陷,都只在积压超过一轮预算(50 封)时现形:
① 被 limit 截断时仍把游标推到窗口上沿 —— 没读到的那几封从此跳过去了;
重叠窗只兜 10 分钟,兜不住一次积压(停机重启、首次回填)。
② 滤重(known)发生在截断**之后** —— 一个窗口里若前 50 条恰好都已入库,这一轮交出 0 封,
而引擎见空批即收工、游标又没动,下一轮还是这 0 封,永远吃不到后面的新信。
改法:滤重提到分页循环里(每页先滤再数够不够),游标只在把窗口翻完时才推进,
截断时原样退回 —— 已入库的下一轮被 known 滤掉,每轮照旧往前吃一批,不卡也不丢。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:42:10 +08:00 |
|
 xcsandClaude Fable 5
|
40e592ae94
|
mail 用例测试:发信留痕 / 厂商失败不抛 / 幂等键 / 附件来源 / from 对账号
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:39:06 +08:00 |
|
 xcsandClaude Fable 5
|
fba82c71dc
|
mail 用例测试:拉取 / 去重 / 逐封游标 / 失败停位 / 分发三态 / 重新投递 / 附件原件
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:38:01 +08:00 |
|
 xcsandClaude Fable 5
|
c5cb779390
|
修复轮 1:邮件 GET 改归 ops,不撑大两端共用读路由名单
全仓测试 route-access.test.ts 钉着一份有意枚举、随 Keycloak spec 审过的名单:哪些读路由被两端共用。
之前把邮件 7 条 GET 标成 design|ops,名单从八条长到十四条。裁决改路由、不改名单:邮箱那一屏落在
运营中心,设计端没有消费方;入站正文装的是客户来往内容,ADR-012 已把「设计端能读客户主数据」记为
一处有意承担的代价,不再无故加宽。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:36:12 +08:00 |
|
 xcsandClaude Fable 5
|
43bacffd90
|
计划裁决:邮件路由的读也归运营中心,不去撑大「两端共用读路由」那份名单
全仓测试 route-access.test.ts 钉着一份有意枚举、随 Keycloak spec 审过的名单:哪些读路由被两个端共用。
计划原把邮件的 7 条 GET 标成 design|ops,名单就从八条长到十四条。裁决改路由、不改名单:
①邮箱那一屏按本计划自己的前端说明落在运营中心,设计端没有消费方(测试台读的是通道,不是邮箱账号);
②入站正文装的是客户来往内容 —— ADR-012 已把「设计端能读客户主数据」记为一处有意承担的代价,不再无故加宽;
③要往那份名单里加,得有真实消费方撑着。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:34:24 +08:00 |
|
 xcsandClaude Fable 5
|
78e36ab73c
|
修复轮1:凭证解不开时明文经 SyntaxError 泄进 last_poll_error;同键在飞的那封会被当失败行重发;发成功不清上一次的错误话;卡死的 dispatching 给人手一条路
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:29:24 +08:00 |
|
 xcsandClaude Fable 5
|
79fd25e1c1
|
mail 测试夹具:内存 store 排序与真 store 严格同序,补两处差异注释
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:11:48 +08:00 |
|
 xcsandClaude Fable 5
|
1328aeb142
|
mail 用例:账号建改删与探测(草稿探测可借库里密文;wecom 探到地址回填);收发用例同份落地待测
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 01:08:48 +08:00 |
|
 xcsandClaude Fable 5
|
b9a4af9a2a
|
mail adapter:postal-mime 解析器(GBK 字头 / 群组地址 / References 拆串 / 附件字节)+ 测试夹具
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:59:40 +08:00 |
|
 xcsandClaude Fable 5
|
d65579c0d8
|
修复轮1:mail 账号列表排序键还原为 name(与 channel/llm 惯例一致),测试改断言集合而非顺序
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:53:28 +08:00 |
|
 xcsandClaude Fable 5
|
5f1e64b4d4
|
迁移 0033:邮箱账号 / 入站 / 投递 / 出站四张表;mail store 与跨进程租约(两连接对抢 20 轮每轮一个赢)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:49:24 +08:00 |
|
 xcsandClaude Fable 5
|
8d7e56aa92
|
mail domain:发信校验与选账号(from 不静默回落)、分发状态机、账号记录与视图(密文进不了视图)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:28:02 +08:00 |
|
 xcsandClaude Fable 5
|
6748ef01b7
|
mail domain:解析结果 → 入站记录 / 视图,附件与原件按上限分存留注,厂商原话脱敏三形
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:26:08 +08:00 |
|
 xcsandClaude Fable 5
|
58d6d778ea
|
计划勘误:cursor 测试的 wecom epoch 字面量错了整一年(1756116000 是 2025-08-25)
实现 Task 4 时逼出来的:那条断言期望 2026-08-25T10:00:00Z,而字面量落在 2025 —— 整套夹具都锚在
2026-08-25T10:00:00Z,改输入而不是改期望,才不会留下唯一一个锚在别的年份的夹具。
代码侧已在 83f65b2 修正,这里把计划文本对齐。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:14:53 +08:00 |
|
 xcsandClaude Fable 5
|
83f65b2785
|
mail 模块骨架:契约 / 端口 / 领域错误 / 游标两形 / 到期与租约(ADR-013)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-26 00:11:14 +08:00 |
|
 xcsandClaude Fable 5
|
7c6e399b57
|
邮件路由契约 15 条:账号 CRUD / 探测 / 立即拉取 / 试发 / 入站出站列表 / 附件与原件 / 重新投递
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 23:59:57 +08:00 |
|
 xcsandClaude Fable 5
|
411fecc8c6
|
邮件契约修复:地址校验保持严格 z.email(),改测试夹具而非放宽生产 schema
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 23:51:57 +08:00 |
|
 xcsandClaude Fable 5
|
404a32fa0a
|
邮件契约:账号入参按 kind 分两形、视图只回 secretBound、入站 / 出站 / 探测 schema
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 23:45:37 +08:00 |
|
 xcsandClaude Fable 5
|
26f07f3487
|
mail 模块登记为 L1;批准 imapflow / nodemailer / postal-mime 三个依赖(设计稿 §2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 23:34:17 +08:00 |
|
 xcsandClaude Fable 5
|
649cec1197
|
邮件收发模块实施计划:17 个任务(契约 / 迁移与 store / domain / 用例 / 两条 provider / app 装配 / 路由 / 工具契约 2.0.0 / 文档)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 23:21:52 +08:00 |
|
 xcsandClaude Fable 5
|
ebc6f778df
|
邮件收发模块设计稿:两条接入路(IMAP/SMTP · 企业微信邮件 API)、订阅分发、出站留痕、跨进程租约
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
|
2026-08-25 22:36:58 +08:00 |
|
 xcsandClaude Opus 5
|
bb22545bdf
|
路由表字面重复也掀桌子;死信没收件人时出声;票据横向范围与「权限只到端」写进 ADR
**I1:字面路由重复注册仍是静默覆盖 —— 与参数侧的掀桌子不对称。**
两条**字面**契约同方法同路径撞上时,后写覆盖先写,命中谁全看 `Object.values(contracts)`
的顺序。参数模版撞了会抛(理由是「命中谁只看声明顺序 = 静默判错权限」),而字面这一半
没设防:同一个病、同一个后果。把守卫从 59 处收成 1 处之后,注册表是**唯一**的授权
事实源,它的自检不该有半边缺口。`size` 一并从 `routes.length` 改成
`literal.size + patterns.length`(重复被拒之后两者相等,但语义上它说的是「表里有几条」)。
`lookup` 上补一句:它只认 HTTP_METHODS 那五个,**归一是调用方的职责**;测里钉了
`lookup('HEAD', …)` 仍是 undefined —— 将来有人把中间件那行归一挪走,这条分工还在。
**顺手两条:**
- 死信通知的收件人为空时不出声(`deadLetters.report`),而 container 的 governanceStaff
对同一件事会往 stderr 喊一句 —— 后者的理由(把「出事了」无声丢掉最糟)在前者同样成立。
- ADR-010 状态行补「已被 ADR-012 取代」:ADR-012 说了取代,但先翻到 ADR-010 的人看不到。
**两条不修,写进文档:**
- 票据的**横向范围**进 ADR-012 与 debt:票据里没有用途,60 秒内它等于持票人的
**全部读权限** —— 凭一枚为下载附件取的票据,同一分钟里读得了整张客户主数据。
不是提权(那些读权限他本来就有),风险在留痕面(URL 走浏览器历史 / 反代日志 / Referer)。
收敛做法记进 debt:签发时把用途绑进票据(scope ∈ {stream, file}),或中间件对认票据的
路径设白名单。
- 「权限只到端」的影响面进 ADR-012 的「后果」:design 现在同时意味着装活动、跑测试台、
传文件、探大模型;design|ops 意味着 `GET /api/gov/data/:domain/:object` 能翻整张客户
主数据(**从前要 data:read 才行**)。半年后有人问「为什么设计的人看得到客户手机号」,
ADR 里得有这句话在。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 19:42:48 +08:00 |
|
 xcsandClaude Opus 5
|
5facf6b745
|
名册同步补三道下限保护;同名让位;停用判定走在 JIT 写之前
**C2:一次同步能把所有人停用,而停用之后没有界面能救回来。**
「本地有、远端无 → 停」是有意的,但没有下限:①`listUsers()` 因任何原因回了空数组
(realm 配错但存在、代理 200 空体、view-users 被改窄)→ 全表 disabled;②更可能的是
**Admin 的 `GET /users` 不返回服务账号用户**(手册 §14.5 为此单列一条接口)——
e2e 与试跑的 AI 账号是首登 JIT 建的,本地在册、远端查无此人,**第一次点「同步名册」
就把它停了,再同步多少次都回不来**。而恢复动作 `POST /api/gov/staff/sync` 要 ops,
ops 要一个 active 的 principal:全员停用之后这条路自己把自己锁死,只剩去库里手改 state。
三道闸(两个触发面分别被后两条挡住,第一条是最后的网):
- `diffRoster`:`remote` 为空时不产出「缺席即停」—— 远端一个人都没有,更像取数出了问题;
- `diffRoster`:`testAccounts` 豁免「缺席即停」(新入参,必填 —— 只有装配层知道哪几个是
服务账号),判据用既有的 `isTestAccount`(不分大小写、忽略空白);
- `syncRoster`:落 setState 之前用**同步后的活跃员工角色集**(新纯函数 `activeRolesAfter`)
跑一遍 `assertNotLockedOut`,不过就整体拒绝、一条都不写;路由映射 409(与
setRoleAccess 那两条同款,`syncStaffRoute` 从前没有这个 catch)。
`AccessLockoutError` / `assertNotLockedOut` 收可选 message —— 两个来路拒绝的理由不同,
给人的下一步也不同(这条指向「先核对 provisioning 指的是不是同一个 realm」)。
**I3:JIT 与同步撞 `account` 唯一索引 → 500,且此人从此登不进。**
Keycloak 里删了人再建一个同名的(或用户名被让渡),新 sub 走 insert 就撞
`staff_account_key`,`server.ts` 的 onError 把 PG 原文原样回给调用方。
处置选的是**先查后让位**(不是捕异常):走到 insert 就说明按 sub 找不到他,本地那条同名的
必然是另一个 sub 的 stale 行 —— 把它的账号让位成 `账号#retired-<旧 sub 前 8 位>` 再插,
留一条 stderr;不删那一行(它身上可能还挂着在途工作)。`StaffRepository.findByAccount`
为此回到端口上(退役的是 OrgService 上的那个同名出口,不是仓储这一口)。
同步逐条落库改为**单条失败不停在半路**:失败的人点名进 `RosterSyncOutcome.failed`
(契约变更,api-report 已刷新),三个计数改成**真落下的条数** —— 半途失败的同步
不许报成一排绿数字。
顺手两条:
- `authenticate` 的 JIT 写挪到停用判定之后 —— 一个已停用的账号只要还攥着有效 token,
从前每 60 秒就能驱动一次 updateIdentity 写库;「第一次见到的人要建档」原样保留。
- `keycloak-admin-roster` 自己又写了一遍 dedup+sort+滤噪声 → 改用 `normalizeRoles`。
两处对「同一次归一」的理解一分叉,`identityChanged` 的逐字比对就会天天判「变了」。
测:roster-diff +8(两道下限保护 + activeRolesAfter 四种局面)、org-service +5
(整体拒绝且一条 setState 都没落 · 远端空着不停人 · 服务账号缺席不停 · 单条失败点名且
不停在半路 · 停用账号驱动不了 JIT 写)、同名让位 1(内存仓储照搬了唯一索引这条约束)。
文档:org/CONTRACT.md 不变量与影响半径、API 文档 §0(HEAD / 409)· §3(三道保护 + failed)· §7。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 19:42:19 +08:00 |
|
 xcsandClaude Opus 5
|
5964a80b28
|
鉴权中间件:HEAD 按 GET 判 —— 不归一等于「只要登录就能跑任何 GET 处理器」
Hono 在 #dispatch 里把 HEAD 改写成 GET 再分发,而 `c.req.method` 读的是请求行上的
原始方法,仍是 'HEAD';路由注册表只装 HTTP_METHODS 那五个,于是 lookup('HEAD', …)
**必然 miss** → 落进 `?? 'any'` 兜底 → 一个 `ends: []` 的人(Keycloak 里还没配角色的
新员工)照样能执行三个端的全部 GET 处理器。不绕过认证(无凭据仍 401),够不到
POST/PUT/DELETE,响应体被框架丢掉 —— 但它绕过**端授权**:响应头照抄过去
(desk 那两处 content-disposition 带着资料文件名),而 GET 处理器不是都没副作用
(`GET /api/reports/:period` 一路走到 report-service 的 store.save)。
改:查表之前先归一方法;**票据那一路的 GET 判断用同一个归一后的 method**,
否则 HEAD 拿不到票据那条路,两条路行为不一致。
测(auth-middleware.test.ts +3):HEAD 到 ops 路由持只有 workbench 的 token → 403
(与 GET 同款)· HEAD 到自己端内的路由 → 200 · HEAD 上的票据与 GET 同款。
反向验证:撤掉归一那一行,前两条当场红(200 / 401),装回即绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 19:41:43 +08:00 |
|
xcs
|
253c6c3afa
|
文档:补 412 precondition-failed 状态码说明,修正 Keycloak 前置编号
评审逐个核六条会返 412 的路由(流程包写回/启用、活动包安装两审、决议登记、
汇报提交、入站通道),发现 §0「状态码怎么读」表漏了 412 —— 这六条路由全在本文
自己的 §6 总表里,漏了会让照文档接的人撞上一个查不到含义的状态码。补一行说清
412 不是权限/参数问题,是「此刻办不了」。
另修一处编号错误:示例来源注明里把 e2e 服务账号 client(bpos-e2e)前置写成了
「前置 1」,按 spec 编号应为「前置 4」(前置 1 是本服务 client api-gateway)——
前端对接说明那份原本就写对,这次只改 API 文档这一处。
|
2026-08-25 19:01:27 +08:00 |
|
xcs
|
a4716c8632
|
文档:前端对接说明与 API 文档按真实契约核对(路由归属总表与 schema 均已核对一致,补记示例来源)
用契约(packages/contracts/src/api/*.ts + org.ts)重新生成路由归属总表,与 API 文档 §6 逐行比对
(方法/路径/access 三者):零差异,95 条契约路由与文档现状完全一致,不改路由表。
§7 的六个 schema(Staff/Position/Principal/RoleAccess/RosterSyncOutcome/InFlightWork/WorkGroup/
ApiError)逐个核对字段名与可选性,与 org.ts 一致,不改。前端对接说明 §2 的十个文件指针全部存在。
Keycloak 侧前置(serviceAccountsEnabled 的 client、audience mapper)本轮未就位,示例响应仍按契约
构造 —— 两份文档末尾补记这一状态,留给前置就位后换真实响应。
|
2026-08-25 18:51:13 +08:00 |
|
xcs
|
7978c6faca
|
文档修订:评审四处 —— 「约100」改实数59(governance-ops 45 + governance 14)、ADR 引用改实际文件名、CONTRACT.md depcruise 措辞改准
评审指出「约 100 处守卫」是把行数(每处两行)误当处数,实测切换前 governance-ops.ts 45 处
+ governance.ts 14 处 = 59,其余路由文件 0 处;ADR-012 与 CLAUDE.md 各改一处。
ADR-012「后果」段引用的《前端接入手册》仓内不存在,改成实际路径
docs/前端对接说明-统一鉴权中心.md。org/CONTRACT.md「depcruise 已反查确认」改成
「depcruise 全量校验通过」——这一轮跑的是全量校验,不是针对性反查,原措辞强于事实。
|
2026-08-25 18:39:45 +08:00 |
|
xcs
|
aa18701130
|
文档:ADR-012 统一鉴权中心;编码规范鉴权定调改为资源服务器 + 权限只到端;org CONTRACT.md 重写;debt 鉴权段;CLAUDE.md 铁规矩 5/6
|
2026-08-25 18:32:23 +08:00 |
|
xcs
|
9848e2f735
|
欠账:CD 接上了,五处「信号全绿而事情没办成」各配了一道机检
入口守卫的字符串比较、install-activities 的 continue、savePackage 的 onConflictDoNothing、
drizzle 只认 max(created_at) 的水位线、/api/health 不传 status —— 五个会静默成功的机制各配机检。
另记六条还欠(无版本可见性 / 无 graceful shutdown / 换版空窗 / activities:install 的样例副作用 /
reset-runs 被打进生产 dist / 每版一份 node_modules)。
|
2026-08-25 18:23:13 +08:00 |
|
xcs
|
4dabac2f7d
|
e2e:改用 Keycloak 服务账号 client_credentials 拿 token,4 分钟一换;断言进得了设计端与工作台
|
2026-08-25 18:20:44 +08:00 |
|
xcs
|
fc5afa9600
|
route-coverage 补下界断言防「数不到路由也绿」;跳过告警点名哪道保险没跑
评审 Important:missing 为空有两种可能——真的不漏,或者根本没数到路由
(app.routes 形状变了、createServer 早退)。空集是任何集合的子集,不设
下界这条 fail-closed 测试会从「会红」退化成「永远绿」。补
registered.length > 50 的下界,反向验证过(临时调到 1000 确认真的报红)。
无库跳过时的 stderr 告警原来只说连不上库,没说清「这道保险这次没跑、
漏的是哪一类」——按评审裁决不改 skipIf 行为(仓库级惯例),只改措辞
点名到位。
|
2026-08-25 18:17:03 +08:00 |
|
xcs
|
61622f94d5
|
启动自检 issuer 不一致拒绝启动;路由覆盖 fail-closed;Keycloak 配置解析测试(ADR-012)
上一个任务把统一鉴权中心接上了,但缺配置照常起、issuer 配错也照常起 ——
每个请求都 401 还查不出原因。boot() 开头补两道保险:缺
KEYCLOAK_URL/REALM/CLIENT_ID 直接拒绝启动;配了就拉一次
.well-known/openid-configuration 核对 issuer,不一致同样拒绝启动
(话里带期望/实际/KC_HOSTNAME,照后端接入手册第 5 步)。
另加路由覆盖 it 测试:Hono 里每条注册路由都必须能在 appRouteRegistry()
里查到归属,查不到就是一个没人管的权限口子。真库跑通,96 条路由全部
有归属、0 条查不到——把上一个任务里人工核对的结论固化成测试。
|
2026-08-25 18:04:21 +08:00 |
|
 xcsandClaude Opus 5
|
8dc71db9d8
|
评审两条 Important:迁移连带清掉指向身份的两张配置表;治理台收件人为空时出声
节点负责人(排在轮值之前)与触发器确认人都是无 FK 的裸 staff id,而 reset-runs 按设计
不碰配置表 —— 切换后留着就是「把单派给不存在的人」与「命中的单没人点头全进死信」。
governanceStaff 在全新安装(还没人以 admin 登录过)时是空集,而调用方是死信通知:
收件人为空原本会静默丢掉「出事了」这件事,现在留一条说清怎么办的话。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 17:57:21 +08:00 |
|
 xcsandClaude Opus 5
|
dd31b91829
|
统一鉴权中心接入(ADR-012):org 重写为 Keycloak 身份 + 角色→端;中间件按路由归属统一判;口令/会话/岗位授权退役;迁移 0032
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 17:40:55 +08:00 |
|
xcs
|
81a185cd61
|
app:鉴权中间件 —— Bearer / 票据 → Principal → 按路由归属判 401/403(ADR-012)
鉴权链的核心件:查路由归属表拿 access(public 直接放行),取凭据(Authorization:
Bearer;GET 且没带头时认 ?ticket=),Bearer 走 org.authenticate、票据走
org.principalOf,按端判定后把 principal 放进上下文。401(没凭据/凭据坏了,该
重新登录)与 403(端不够/账号停用,重新登录没用)分得清楚,前端靠这个分辨。
http-env.ts 只加不删:account/session 是 ADR-010 旧物,现役 server.ts 与十来个
路由文件还在读,退役是下一个任务的事。这一轮也不接线到 server.ts —— 接上会让
现役 cookie 会话链断掉。
测试文件按简报逐字照用,唯一偏差是 get/post 两个测试辅助函数包了一层
Promise.resolve:本仓 Hono 4.12 的 app.request() 类型签名是
Response | Promise<Response>,直接赋给 Promise<Response> 类型标注过不了
typecheck;包一层不改变行为(await 对两者结果一致)。
|
2026-08-25 17:14:30 +08:00 |
|
xcs
|
d66f7fad3a
|
app:60 秒 HMAC 票据 —— 给 EventSource 与 <a href> 下载这两条带不了头的路(ADR-012)
|
2026-08-25 17:02:08 +08:00 |
|
 xcsandClaude Opus 5
|
3a34362eec
|
app:路由注册表 —— 评审反馈修复(三个 Important findings)
补充了三处测试密度不足的地方:
① has/lookup 区分(Finding 1):
参数名换一个时,lookup 仍能匹配(参数段认任意值),has 说没有。
两个接口的语义差异这次被真的逼出来。
② 字面优先级压力测试(Finding 2):
注册 /api/gov/:zone 和 /api/gov/staff,两条都能匹配同一个路径时,
验证字面优先于参数段这个优先级是否真的生效。
③ 建表自检防卫(Finding 3):
两条参数段模版互撞时,命中谁只看声明顺序 —— 静默判错权限。
添加 canCollide() 函数与入口自检,撞了就 build fail loud。
验证:真实 95 条路由建得起来(no collision)。
测试统计:9 → 18 条(+9),全量 1757 → 1766(+9),门禁全绿。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 16:56:15 +08:00 |
|
 xcsandClaude Opus 5
|
2eef8b4544
|
app:路由注册表 —— 契约自带 access,一处 lookup 给中间件(ADR-012)
每条 RouteContract 都声明了自己的 access 权限;此处扫一遍契约命名空间,
构建一个 lookup(method, path) → access 的注册表。新路由无需另加守卫,
只需在契约里声明,就自动生效——遗漏即无门禁,一处事实源确保完整。
- 字面路径优先于参数段(:name 匹配任意非空段)
- 方法维度单独追踪
- 识别 RouteContract:method/path/access/output 都具备
- routesOf() 抽出命名空间内的路由
- buildRouteRegistry() 与 lookup/has API 给中间件与测试用
扫结果:95 条路由已覆盖
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 16:42:11 +08:00 |
|
xcs
|
4d55ac465d
|
org adapter:Keycloak Admin API 拉名册(provisioning 服务账号,分页 100,只读 view-users)
用 master realm 的 provisioning 服务账号(client_credentials)取管理 token(闭包缓存、
提前 60 秒过期),分页拉全 realm 用户(默认 max=100,必须分页),逐人查
role-mappings/realm/composite 取实际生效的 realm 角色并滤噪声(复用 isNoiseRole)。
非 2xx 一律抛带状态码的错,不吞;secret 不落任何留痕。
|
2026-08-25 16:32:47 +08:00 |
|
xcs
|
43917b559a
|
org adapter:改正 explainJoseError 注释里的继承关系描述(评审 Important)
「JWTExpired 是 JWTClaimValidationFailed 的子类」是错的 —— jose 5.10.0 里两者都
直接继承 JOSEError,implements 只是 TS 结构化类型声明,运行时不建立继承。
判在前的真实理由是让「已过期」这条更具体的话先命中,不是靠父子关系兜底 ——
错误的继承说法会给日后「简化掉」判断顺序的人一个不存在的依据。仅改注释,
两条 instanceof 分支的顺序与内容一字未动,10/10 测试原样通过。
|
2026-08-25 16:28:18 +08:00 |
|
xcs
|
cb143d6413
|
org adapter:jose 验签 —— RS256 写死、校 iss/aud、30s 容差、错误译人话(ADR-012;新依赖 jose,用户已批)
四项校验顺序即安全要点:algorithms 写死 RS256(绝不读 token header 的 alg,否则
alg:none 可绕过)、issuer、audience、clockTolerance 30 秒;jose 的错误经
explainJoseError 翻成人话后包成 TokenRejectedError,排错省时间。测试用
createLocalJWKSet 造本地 JWKS 当 Keycloak 替身,不联网,验签逻辑本身走同一个
jwtVerify。zod 的 .optional() 与本仓 exactOptionalPropertyTypes 有摩擦,
按 claims.ts::identityOf 已有的手法(undefined 就不带键)加了 toVerifiedClaims
做收窄,详见 task-3-report.md。
|
2026-08-25 16:21:17 +08:00 |
|
xcs
|
5108d7a580
|
org domain:claims 归一、角色→端与锁死不变量、名册差异、测试账号策略(ADR-012,纯函数)
为统一鉴权中心(Keycloak)落地铺地基:五个零 IO 的纯函数文件,后续 org 用例
重写(Task 8)直接复用。本任务只加不删 —— 旧的岗位授权 / 口令会话 / 停用检查
原样保留,现役 org-service 仍在用它们。
- claims.ts:token claims → Identity,滤掉三种 Keycloak 家具角色
- ends.ts:角色 → 端的并集,并守住「改完之后至少一人能进运营中心」这条锁死不变量
- roster-diff.ts:与 Keycloak 名册的插/改/停/复活差异(远端说了算)
- test-account.ts:测试账号判定与独立工作组构造(ADR-011 语义原样搬)
- in-flight.ts:停用前置检查要看的在途工作(与旧 disable-check.ts 并存一轮)
contract.ts / port.ts / index.ts / seed-data.ts 均为文件末尾追加,契约变更已
经 pnpm api:check --update 刷新快照。
|
2026-08-25 16:09:10 +08:00 |
|
 xcsandClaude Opus 5
|
a5c8d4786d
|
契约:三个端与路由归属(access 必填)、Principal / 角色→端 / 名册同步 schema、退役路由标 @deprecated(ADR-012)
- packages/contracts/src/api/http.ts:新增 SURFACES / surfaceSchema / Surface / SURFACE_LABEL /
RouteAccess;RouteContract.access 必填;API_ERROR_CODES 增 'gone'。
- packages/contracts/src/org.ts:staffSchema 补 email? / roles(realm 角色快照);新增
principalSchema/Principal、roleAccessSchema/RoleAccess、rosterSyncOutcomeSchema/RosterSyncOutcome、
inFlightWorkSchema/InFlightWork;旧岗位授权(grant* / governanceFunction*)与 sessionSchema/Session
全部标 @deprecated 留一轮(web 编译不受影响,下个迭代删)。
- workbench.ts / governance.ts / ingest.ts:每条既有路由补 access(按所属端/两端共用/退役 public);
新增 meRoute、authTicketRoute(access:'any')与 syncStaffRoute、setStaffPositionRoute、
staffInFlightRoute、listRoleAccessRoute、setRoleAccessRoute、deleteRoleAccessRoute(access:'ops')。
- apps/api 的 org-service.ts 两处 staff.insert 补 roles: [](staffSchema 新必填字段,该文件下个
迭代会整份替换,这里只是让本次 typecheck 过)。
- 新增 packages/contracts/src/__tests__/route-access.test.ts:95 条路由的 access 归属断言。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015kAzS35kMnZNt4YGgJXtfw
|
2026-08-25 15:54:20 +08:00 |
|
xcs
|
61f96d5ab2
|
gitignore:SDD 工作区不入库
|
2026-08-25 15:40:18 +08:00 |
|
xcs
|
d788140548
|
工作成果里摆着别单的资料:「这一单产出的」认本单碰过的行
放行存档(archiveAssetFiles)与在办列单(assetFilesOf)取行都用 taskView(where:{}) ——
那是「正式版全表 + 本单草稿」的合并视图,而资料常落在主数据上,整表里躺着别单放行过的行。
抽 assetRowsOf 两处同用,touched 为必填入参;e2e 补份数断言。
顺手清两笔:wake.test.ts 的 as unknown as(lint 红)、wake 契约的 api-report 快照(api:check 红)。
|
2026-08-25 15:38:10 +08:00 |
|