Archived
文档:ADR-012 统一鉴权中心;编码规范鉴权定调改为资源服务器 + 权限只到端;org CONTRACT.md 重写;debt 鉴权段;CLAUDE.md 铁规矩 5/6
This commit is contained in:
@@ -1,51 +1,46 @@
|
||||
# module: org
|
||||
|
||||
职责: 员工与岗位:账号、口令与登录会话(ADR-010)、岗位授权(客体三类 × 动作三种)、
|
||||
停用前置检查、岗位在岗人员查询(负责人四策略解析的候选集来源)、
|
||||
工作组(ADR-011:起单时的岗位绑定)、种子数据播种
|
||||
不负责: 负责人解析算法本身(见 process)、任务与卡的在途判定(经 InFlightWorkProbe 端口由 app 注入)、
|
||||
鉴权中间件与 cookie 的读写(app 层:§4「中间件在 app 层,身份判定读 org 出口」)
|
||||
职责: 员工与岗位(ADR-012):验签后的人是谁(名册即时更新)、他能进哪几个端(角色→端)、
|
||||
岗位指派与在岗人员查询(负责人四策略解析的候选集来源)、名册同步(Admin API)、
|
||||
在途工作(只读)、工作组(ADR-011)、种子(岗位 + 缺省角色→端)
|
||||
不负责: 验签之外的 HTTP 语义(401/403 由 app 层中间件按路由归属判)、
|
||||
负责人解析算法本身(见 process)、任务与卡的在途判定(经 InFlightWorkProbe 由 app 注入)
|
||||
|
||||
公开出口: index.ts
|
||||
- 类型:`OrgService` `StaffWithPosition` `CreateStaffInput` `IssuedSession` 与 @contracts 的
|
||||
`Staff` / `Position` / `Grant` / `GrantObject` / `GrantAction` / `DisableCheck` / `Session` 转出
|
||||
- 错误:`StaffNotFoundError` `PositionNotFoundError` `AccountTakenError` `DisableBlockedError`
|
||||
- 端口类型:`PositionRepository` `StaffRepository` `GrantRepository` `SessionRepository`
|
||||
`WorkGroupRepository` `PasswordHasher` `InFlightWorkProbe` `Clock` `IdGenerator`
|
||||
- 类型:`OrgService` `StaffWithPosition` `Identity` 与 @contracts 的 `Staff` / `Position` / `Principal` /
|
||||
`RoleAccess` / `RosterSyncOutcome` / `InFlightWork` / `Surface` / `WorkGroup` / `WorkGroupBinding` 转出
|
||||
- 错误:`TokenRejectedError`(→401)`AccountDisabledError`(→403)`AccessLockoutError`(→409)
|
||||
`StaffNotFoundError` `PositionNotFoundError`
|
||||
- 端口类型:`TokenVerifier` `VerifiedClaims` `RosterSource` `RosterUser` `IdentityPatch`
|
||||
`PositionRepository` `StaffRepository` `StaffRecord` `RoleAccessRepository`
|
||||
`WorkGroupRepository` `InFlightWorkProbe` `Clock` `IdGenerator`
|
||||
- 用例工厂:`createOrgService(deps: OrgServiceDeps): OrgService`
|
||||
- 纯函数:`isAuthorized` `actionCovers` `objectCovers`
|
||||
- 常量:`POSITION_SEED` `STAFF_SEED` `DEFAULT_LOGIN_ACCOUNT`
|
||||
- adapter 工厂(供 app 装配):`createPositionRepository` `createStaffRepository`
|
||||
`createGrantRepository` `createSessionRepository` `createWorkGroupRepository` `objectKeyOf`
|
||||
- 口令散列(供 app 装 `PasswordHasher`):`hashPassword` `verifyPassword` `newSessionToken`
|
||||
- Drizzle 表(供 app 迁移与 join):`positionTable` `staffTable` `grantTable` `sessionTable`
|
||||
`workGroupTable`
|
||||
- 纯函数:`identityOf` `isNoiseRole` `normalizeRoles` `endsOf` `assertNotLockedOut` `normalizeEnds` `buildInFlightWork`
|
||||
- 常量:`POSITION_SEED` `NONE_POSITION_CODE` `TEST_POSITION_CODE` `DEFAULT_ROLE_ACCESS`
|
||||
- adapter 工厂(供 app 装配):`createKeycloakTokenVerifier`(jose,配置型 `KeycloakVerifierOptions`)
|
||||
`createKeycloakRoster`(Admin API,配置型 `KeycloakRosterOptions`)
|
||||
`createPositionRepository` `createStaffRepository` `createRoleAccessRepository` `createWorkGroupRepository`
|
||||
- 错误译文(供 app 拼 401 message):`explainJoseError`(jose 错误 → 人话,手册 4.4)
|
||||
- Drizzle 表:`positionTable` `staffTable` `roleAccessTable` `workGroupTable`
|
||||
|
||||
层级: L1(编码规范 §4)
|
||||
依赖: @contracts;@shared/db(Drizzle 句柄与 sys schema)
|
||||
port:`InFlightWorkProbe`(app 注入 task / human-tool 的只读用例)、`IdGenerator`
|
||||
被依赖: process(负责人解析的候选集)、message、interaction、human-tool、report、
|
||||
app(鉴权中间件与 /api/gov/staff 路由组装)
|
||||
依赖: @contracts;@shared/db(Drizzle 句柄与 sys schema);jose(仅 adapter)
|
||||
port:`InFlightWorkProbe`(app 注入 task / human-tool 的只读用例)、`IdGenerator`、
|
||||
`TokenVerifier`(app 给 jose 远程 JWKS;测试给本地密钥对)、`RosterSource`(可缺省)
|
||||
被依赖: process(负责人解析的候选集)、message(治理台收件人 = staffWithEnd('ops'))、
|
||||
app(鉴权中间件与 /api/gov/staff、/api/gov/access 路由组装)
|
||||
|
||||
不变量:
|
||||
- 动作蕴含:治理 ⊃ 写 ⊃ 读;数据客体三级覆盖(域 ⊃ 对象 ⊃ 字段)
|
||||
- 停用是全有全无:前置检查未过即抛 `DisableBlockedError`,不做部分执行
|
||||
- `authorize` 对停用员工恒 false
|
||||
- `ensureSeeded` 幂等:按 code / account 判存在,已存在则跳过,不覆盖人工改动;
|
||||
唯一例外是**补种口令** —— 已存在但 `password_hash` 为空的员工会被补上初始口令,
|
||||
否则 ADR-010 之前建的账号永远登不进(这不算覆盖人工改动:空不是改动)
|
||||
- **登录失败不区分原因**:账号不存在 / 已停用 / 未设口令 / 口令错,一律 `undefined`;
|
||||
且无论账号是否存在都走一遍散列比对,免得响应时间泄漏账号是否存在
|
||||
- **明文永不落库**:口令只在 `login` / `setPassword` 的入参上停留一瞬,落库前即散列
|
||||
- **会话可即刻作废**:服务端表而非签名 cookie —— `logout` 立刻生效;
|
||||
`disableStaff` 连带 `revokeAllOf`,被停用的人拿旧 cookie 也办不了事
|
||||
- **工作组是绑定不是策略**(ADR-011):它给出岗位的人选,四策略于是无从发问;
|
||||
`bindingOf` 只出成员表快照,在岗与否由调用方(process)核 —— 岗位候选集核不了跨岗位的绑定
|
||||
- `Staff.id` 就是 Keycloak `sub`;`account` 只作显示,永不当键
|
||||
- `state` 只由名册同步置 `disabled`;`authenticate` / `principalOf` 对 disabled 一律抛 AccountDisabledError,
|
||||
有效 token 不能把它翻回 active(撤权在同步之后即时生效)
|
||||
- 名册即时更新只写变了的列;`last_seen_at` 60 秒内不重写
|
||||
- 角色→端:`setRoleAccess` / `deleteRoleAccess` 先算新表再验锁死不变量(名册为空不算锁死)
|
||||
- `staffOfPosition` 只回 active;哨兵岗 NONE 恒回 []
|
||||
- `ensureSeeded` 幂等:岗位按 code 判存在;缺省角色→端只在表空时播;测试账号策略只补不覆盖
|
||||
- 工作组是绑定不是策略(ADR-011):`bindingOf` 只出成员表快照,在岗与否由调用方核
|
||||
|
||||
影响半径: contract 增 `login` / `logout` / `resolveSession` / `setPassword` 与 `IssuedSession`;
|
||||
port 增 `SessionRepository` / `PasswordHasher`,`StaffRepository` 增两个口令读写方法;
|
||||
下游只有 app(装配 + 鉴权中间件),depcruise 已反查确认
|
||||
|
||||
近变(2026-07-27):模块面**未变**。app 层新增工作组读写路由
|
||||
(`GET|PUT /api/gov/work-groups`),组装既有的 `workGroupOf` / `saveWorkGroup`
|
||||
—— ADR-011 落地时欠的那个治理界面就此清账,一人一组的口径由 app 层按 ownerId 保证。
|
||||
影响半径(2026-08-25,ADR-012):contract / port / index 全面重写 —— 退役 `login` `logout` `resolveSession`
|
||||
`setPassword` `authorize` `loadSession` `listGrants` `setGrants` `createStaff` `checkDisable`
|
||||
`disableStaff` `findByAccount` 与 `SessionRepository` `PasswordHasher` `GrantRepository`;
|
||||
下游 app(中间件 / 装配 / 路由)与 message 装配同批改,depcruise 已反查确认。
|
||||
|
||||
Reference in New Issue
Block a user