Commit Graph
6 Commits
Author SHA1 Message Date
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 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
ChaiHaoandClaude Opus 5 da8ac8f9bc 模型实例卡重做:名字不截、密钥不撒谎、不可用就别摆统计;只留 flash 与 pro 两条
三条毛病一次改净:

**① 名字被截断**。用途名是活动里声明的那个名,看不全等于没显示 —— 卡面改成
   40px 图标 + **名字独占一行、折行也不截**(overflow-wrap: anywhere),
   厂商与型号退到第二行小字。

**② 「已绑定」是谎**。种子从前塞一个 `sk-placeholder` 冒充密钥,卡上写着「已绑定」,
   一自检就是 `api key ... is invalid`;抽屉里那格也照「是不是新建」写占位,
   于是没钥匙的实例也显示「已绑定 · 留空保持原值」。现在:
     · 种子**读不到环境密钥就不绑**,如实显示未绑定;
     · 抽屉的占位与判词改看 `keyBound` —— 没绑就写「还没有密钥 —— 不填这条实例调不动」。
   **界面撒谎比缺功能更坏**:缺功能人会去补,撒谎的人会照着假状态做决定。

**③ 不可用还摆一排统计**。没密钥连调都调不起来,「系统用途/端点」那些格没有意义 ——
   未绑定时卡上只剩一句判词 +「去配置」;绑好了才谈端点与「测试」。
   系统用途从卡面撤了(用户:不要管是不是系统用途,两条都能用)。

**只留两条实例**:`deepseek-v4-flash` 与 `deepseek-v4-pro`,都不预设系统用途。
配套两件:
  · `pickSystemUse` 加**回落** —— 没人勾就取第一条能用的(启用且已绑密钥);
    从前非勾不可,没勾那天系统叙述整条哑掉,那是配置疏忽不该变成功能缺失(补了三条测试);
  · 六个活动包里声明的用途名 `quote-extractor` 改指 `deepseek-v4-flash`,各升一个补丁版。

顺带补 `DELETE /api/gov/llm/:id`(卡上 ⋯ 菜单):还有活动声明着这个用途名就拒(409)——
删了那些活动一调模型就是「用途名未绑定」,而人当时以为只是清一条没用的记录。
实测:用它把库里遗留的 quote-extractor 删掉,现在库里就是那两条,卡上如实写「未绑定密钥 · 不可用」。
五闸绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-30 01:53:42 +08:00
ChaiHaoandClaude Opus 5 f9318b1ae9 接入模型改成右侧抽屉:先挑供应商、再填连接、当场测连通(且只测不存)
上一版是把字段倒在地上:整页宽的内联卡、输入框拉到 1100px、动作钮掉出屏幕。
照模型接入面的通行做法重排 —— 520px 右侧抽屉,头固定 / 身滚动 / 底动作栏常驻:

  ① **供应商**:一排可点的牌子,挑中即预填它的默认端点(没有默认的标「端点自填」);
  ② **连接**:API 端点 + API Key(密码框带显示切换),下面就是「测试连接」;
  ③ **型号与用途**:模型(厂商侧型号名)+ 用途名(活动里声明的那个)+ 两枚开关。
  遮罩点击与 Esc 都关得掉 —— 抽屉盖住整屏,不给键盘出路是把人关在里面。

**默认端点从服务端取**(新 `GET /api/gov/llm/providers`):前端不自带一份 URL 表 ——
那种表迟早与 provider 档案漂移,而漂移那天没人会发现(端点错了只表现为「连不上」)。

**「测试连接」只测不存**(新 `POST /api/gov/llm/probe` + `llm.probeDraft`):
拿表单上正填的四元真发一次最小调用,一个字节不落库;密钥留空则取该实例已存的密文,
改端点、换型号不必重打钥匙。上一版是「先存再试」—— 人只想知道钥匙对不对,
结果端点被改了、实例被建了,点取消也收不回来。实测:改错端点后测试,
库里 instances 逐字节未变(测过 before/after 全等)。

顺带补 `eye-off` 图标。五闸绿。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-30 01:10:49 +08:00
ChaiHaoandClaude Fable 5 6f36f8340a 三面拆分⑩:工作组管理落员工区 —— ADR-011 欠账清账
建组/改组:逐岗位选人,留空即回落四策略。绑定先于策略,起单时快照进工作流;
实测改绑定后新起单的 work_group 快照即含新成员表,在途不受影响。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 21:28:30 +08:00
JCAI Main AgentandClaude Opus 5 14d4fa046a M1: 底座 —— @contracts 首版 + event / ledger / org / llm
@contracts 首版(§8.1 九个文件):
- id 品牌 ID / terminal 终态五名与归侧四标签 / envelope 信封六字段与四类事件
- card 工单两模式 + 对话卡族(形状对齐 2026-07-26 原型定稿)
- contract7 契约七件 / ledger 三级寻址与写三分 / process-package 流程包 DSL 与触发器三类
- manifest 活动包清单六区(含 CMMN·DMN·步序声明的资产形状)/ org / api 路由契约

模块(L1):
- event   信封存储、归属解析三级短路、投递五诺、监听注册表、三级消费裁定
- ledger  schema 自生长(最大化合并/最宽类型/只增不破坏)、任务视图叠加、写三分、冲突检查
- org     岗位授权(客体三类×动作三种)、停用前置检查、会话、种子播种
- llm     四元绑定、ModelGateway 三模式(live/record/replay)—— 两缝之一

app 装配:Hono 服务 + 鉴权中间件 + 治理台/工作台路由;drizzle 迁移;AES-256-GCM 凭证密封

ADR-007 工具链与门禁 · ADR-008 投递队列 · ADR-009 凭证加密
(ADR 编号自 007 续:编码规范 §14 已用到 006,README 写「从 005 续」是笔误)

门禁五道全绿;96 个测试通过,含对真实 PG 的 ledger 集成测试

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
2026-07-27 00:59:14 +08:00