Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/design/02-domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ SQE、质量经理经共享规则 `srm_supplier_audit_edit_*` 改所有审核(
可空:存量供应商批量邀请时填)、`company_name`、`contact_name`、`contact_email`、
`token`(text,只读,flow 生成)、`expires_at`、`status`(`sent` / `accepted` / `expired` /
`revoked`)、`application`(lookup,供应商接受邀请并提交申请后回填)、`invited_by`(user)。
邀请注册是 v0.6 的默认注册方式(01 FR-202);实现方式按 07 §6 V5 的结论。
邀请注册是 v0.6 的默认注册方式(01 FR-202);组织邀请本身由组织管理员在成员页发(flow 发不了,07 §6 V3),本对象记录邀请的业务信息和进度(04 §3.3)。

### 4.3 寻源(v0.7)

Expand Down
37 changes: 22 additions & 15 deletions docs/design/04-security-and-portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> 所属:[SRM 设计总览](./README.md) · 上一篇:[03 流程与自动化](./03-processes-and-automation.md) · 下一篇:[05 界面与分析](./05-experience-and-analytics.md)

> 版本:内部岗位与权限集随 v0.1–v0.5 逐步建立;供应商门户在 v0.6,前提是门槛 1 通过、
> objectstack#25233(门户用户可列出全组织用户)修复,并完成 [07 §6](./07-delivery-plan.md#6-平台依赖与验证结果) 的 V1–V5 实测;
> objectstack#25233(门户用户可列出全组织用户)修复(18.1.0),并完成 [07 §6](./07-delivery-plan.md#6-平台依赖与验证结果) 的 V1–V5 实测(2026-11-02 完成,结论见 07 §6.2);
> 封标在 v0.7。订单、送货、对账、发票相关的权限为 ERP 集成后的预留([ADR-0008](../adr/0008-order-to-pay-stays-in-erp.md))。

## 1. 安全目标与主要威胁
Expand Down Expand Up @@ -200,23 +200,29 @@ flowchart LR
`controlled_by_parent` 子对象跟随主记录。

所以门户用户能看到的数据只来自 §3.1 的门户岗位和门户权限集,内部权限集即使误设了 `isDefault` 也不会漏给他们
(内部权限集仍一律不设 `isDefault`,R2 照常审查)。`org_external` 是平台保留名,岗位不能用它命名。
(内部权限集仍一律不设 `isDefault`,R2 照常审查;07 §6 V5 已实测)。`org_external` 是平台保留名,岗位不能用它命名。

**开通**(由 `srm_supplier_admin` 操作):
**环境前提**:开门户的环境,成员策略(Setup → 认证 → 成员,平台设置 `auth.membership_policy`,或环境变量
`OS_AUTH_MEMBERSHIP_POLICY`)必须是 `invite-only`。单组织部署默认是 `auto`:任何人注册即以普通成员入组织,被邀请的
供应商在接受邀请前同样是普通成员,能读全组织用户目录、拿到所有 `isDefault` 权限集。`invite-only` 下注册不入组织,
接受邀请才以邀请上的角色(`external`)入组织(07 §6 V1 实测)。

1. 在组织成员页按联系人邮箱发出邀请,角色选 `external`;
**开通**:由组织管理员(组织角色 `owner` / `admin`,通常由 SRM 系统管理员兼任)操作;品类经理、采购主管
(`srm_supplier_admin`)提出开通需求,自己做不了:平台只让组织管理员发邀请、写岗位任命和权限集授予,委托管理
(`adminScope`)又只能在业务单元内任命,`external` 成员不进业务单元(07 §6 V2、V3 实测)。

1. 在组织成员页按联系人邮箱发出邀请,角色选 `external`(flow 发不了邀请:平台规定邀请只经组织接口,07 §6 V3);
2. **该供应商第一次开通门户时**,由管理员在 Setup 新建岗位 `srm_vendor_<编号>`,并在
岗位定义里挂上门户权限集。平台把岗位视为环境级的**定义**(元数据),不是数据行,
所以这一步不能由 flow 完成,需要持有元数据编辑权限的管理员操作(已向平台提出
「按外部方在运行时建岗位并任命」,objectstack#25443;存量供应商批量开通时管理员
按清单批量建);
3. 运行「开通门户」screen flow:选择联系人、已加入的用户和刚建的岗位 → 写入供应商的
`portal_position`,并把它回填到该供应商已有的单据上 → 把用户任命到该岗位(任命是
数据行,但 18.1.0 起 `runAs: 'system'` 的 flow 不得写岗位任命、权限集分配和邀请
(objectstack#25251),`runAs: 'user'` 能否写按 07 §6 V2 实测;不放行时由管理员在成员页任命,
flow 只做回填)→ 在联系人上写 `portal_user`;联系人
`contact_function` 含 `admin` 时,再把门户管理员权限集**直接授予该用户**(同一家供应商
的用户共用一个岗位,管理员能力只能按人授予);
3. 运行「开通门户」screen flow(`runAs: 'user'`,以操作的组织管理员身份执行):选择联系人、已加入的用户和
刚建的岗位 → 写入供应商的 `portal_position`,并把它回填到该供应商已有的单据上 → 把用户任命到该岗位 →
在联系人上写 `portal_user`;联系人 `contact_function` 含 `admin` 时,再把门户管理员权限集**直接授予该用户**
(同一家供应商的用户共用一个岗位,管理员能力只能按人授予)。任命和授权是数据行,18.1.0 起 `runAs: 'system'`
的 flow 不得写(objectstack#25251),`runAs: 'user'` 由组织管理员启动时能写,其他人启动被拒(07 §6 V2);
flow 进门先检查操作人是组织管理员,不是就直接结束并说明;
4. 同一供应商以后再开通账号,只需第 1、3 步。

岗位定义随开通门户的供应商数量增长(几百到上千个),设置界面的岗位列表会变长。
Expand All @@ -227,9 +233,9 @@ flowchart LR

| 场景 | 动作 |
|---|---|
| 联系人离职 / 停用 | 撤销岗位任命,移出组织;联系人 `active = false` |
| 联系人离职 / 停用 | 组织管理员撤销岗位任命并移出组织;联系人 `active = false` |
| 供应商冻结 | 账号与门户功能保持不变:整改单仍要回复,资料仍要维护;冻结只阻止新的询价邀请(BR-03),新订单由 ERP 侧冻结标志拦截 |
| 供应商淘汰 / 黑名单 | 处置 flow 撤销该岗位的全部任命,账号移出组织 |
| 供应商淘汰 / 黑名单 | 处置 flow 通知组织管理员撤销该岗位的全部任命、把账号移出组织(处置 flow 以系统身份运行,审批后以审批人身份运行,都写不了任命,07 §6 V2);撤销前门户用户仍能看到本公司数据,季度复核兜底 |
| 季度复核 | 报表列出每家供应商的门户账号与在职联系人,差异由采购主管确认 |

### 3.4 RLS 示例
Expand Down Expand Up @@ -326,8 +332,9 @@ export const PortalSupplier = definePermissionSet({

1. 18.0.0 实测:普通成员身份的门户用户在界面和 REST 上能列出全组织用户(objectstack#25233);
2. 18.1.0 以组织角色 `external` 修复:`external` 成员只读自己的身份行,团队、邀请列表也只返回自己相关的。
门户用户因此一律按 `external` 邀请(§3.3),07 §5 D8 不再需要;v0.6 U1 复测(07 §6 V1),§3.7 的「用户目录」
用例照常回归。
门户用户因此一律按 `external` 邀请(§3.3),07 §5 D8 不再需要;2026-11-02 实测通过(07 §6 V1),§3.7 的「用户目录」
用例照常回归。同一家供应商的其他门户用户也看不到(按 id 读同样是「不存在」),门户里要列本公司账号时读联系人的
`portal_user`,不读用户目录。

### 3.7 隔离验收测试

Expand Down
23 changes: 14 additions & 9 deletions docs/design/07-delivery-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| v0.3 合同与处置 | 合同与合同行、处置单(暂停 / 冻结 / 解除)、供应商自助重新申请 | +3 = 13:合同、合同行、处置单 | 合同到期前提醒;冻结后不能被邀请 | 已交付(PR #29–#33、#41) |
| v0.4 质量 | 交付记录导入、质量问题与整改单、供应商审核与年度审核计划 | +4 = 17:交付记录、质量问题、供应商审核、审核问题项 | 不合格交付能开整改单并闭环;OTD、合格率、PPM 出数 | 已交付(PR #40、#42、#44–#46、#56、#57) |
| v0.5 绩效与风险 | 指标库、按周期考评、风险事件与扫描、黑名单 | +4 = 21:绩效指标、考评单、考评明细、风险事件 | 第一次考评自动生成并发布;D 级进入处置 | 已交付(PR #58、#59、#62–#64、#66–#70);门槛 1 验收已跑通(2026-10-26,§2.3),待田騏确认 |
| v0.6 供应商门户 | 邀请注册、门户补资料与证书、整改回复、通知 | +1 = 22:供应商邀请 | 供应商自己维护资料并回复整改 | objectstack#25233 已随 18.1.0 修复(2026-10-26 升级,#76);V1–V5 实测通过(§6) |
| v0.6 供应商门户 | 邀请注册、门户补资料与证书、整改回复、通知 | +1 = 22:供应商邀请 | 供应商自己维护资料并回复整改 | objectstack#25233 已随 18.1.0 修复(2026-10-26 升级,#76);V1–V5 已实测(2026-11-02,§6.2):V1、V4、V5 通过,V2 限组织管理员,V3 改由管理员在成员页邀请 |
| v0.7 寻源 | 询价 / 竞价、封标、比价、定标审批、价格记录 | +6 = 28:寻源单、寻源明细、寻源邀请、报价单、报价明细、价格记录 | 封标询价到定标,价格记录生效 | 门槛 2:核心 28 个对象冻结(ADR-0009 第 5 条) |
| 之后(客户驱动) | ERP 连接器与门户镜像(订单确认、送货通知、付款进度)、电子签、工商数据、扩展包 | 镜像对象另计 | — | 客户明确型号或需求 |

Expand Down Expand Up @@ -132,6 +132,7 @@ src/core/
test/ # 仓库根目录
├── import-idempotency.test.ts # 样例表格跑两遍(v0.2 起)
├── approval-matrix.test.ts # 03 §12 每一行(v0.1 起)
├── portal-platform.test.ts # 门户平台实测 V1–V5(§6.2,v0.6 U1)
├── portal-isolation.test.ts # 04 §3.7 的交叉越权用例(v0.6)
├── sealed-bid.test.ts # 封标与截止锁价(v0.7)
└── flows/*.test.ts # 每条 flow 的正向与异常路径
Expand All @@ -152,10 +153,10 @@ test/ # 仓库根目录

| # | 风险 | 可能性 | 影响 | 应对 |
|---|---|---|---|---|
| R1 | 门户用户可见的用户目录范围不满足隔离要求(04 §3.6) | 低:18.1.0 的组织角色 `external` 已修复(objectstack#25233) | 中:暴露竞争对手与员工联系方式 | 门户用户一律按 `external` 角色邀请(04 §3.3);U1 复测 V1,隔离测试覆盖用户目录 |
| R2 | 同一环境其他应用的默认权限集把数据开放给供应商用户 | 低:`external` 成员不持 `everyone` 锚点,默认权限集不作用到他们(18.1.0) | 高 | v0.6 上线前仍逐个审查环境内所有 `isDefault` 权限集(V5 实测上面这条);新应用上线纳入同一审查 |
| R1 | 门户用户可见的用户目录范围不满足隔离要求(04 §3.6) | 低:18.1.0 的组织角色 `external` 已修复(objectstack#25233) | 中:暴露竞争对手与员工联系方式 | 门户用户一律按 `external` 角色邀请,环境成员策略设 `invite-only`(04 §3.3);V1 已通过(2026-11-02),隔离测试覆盖用户目录 |
| R2 | 同一环境其他应用的默认权限集把数据开放给供应商用户 | 低:`external` 成员不持 `everyone` 锚点,默认权限集不作用到他们(18.1.0) | 高 | V5 已实测通过(2026-11-02);v0.6 上线前仍逐个审查环境内所有 `isDefault` 权限集,新应用上线纳入同一审查 |
| R3 | 定时任务延迟或漏跑(平台不提供保证,PF1) | 中 | 中:提醒延迟 | 追赶式扫描(03 §2.3);强制规则不依赖计时器 |
| R4 | 每家供应商首次开通门户都要管理员新建岗位定义;`runAs: 'system'` 的 flow 不得写任命和邀请(objectstack#25251) | 高(建岗位必然手工) | 低:效率 | 开通流程写成管理员操作清单;U1 实测 V2/V3;18.1.0 已有声明式成员键(`ObjectSchema.membershipKeys`,PF2),U2 评估改用方案 B(04 §3.5) |
| R4 | 每家供应商首次开通门户都要管理员新建岗位定义;`runAs: 'system'` 的 flow 不得写任命和邀请(objectstack#25251) | 高(建岗位必然手工) | 低:效率 | 开通由组织管理员操作(04 §3.3):邀请在成员页发,任命与门户管理员授权由 `runAs: 'user'` 的「开通门户」flow 写(V2、V3,2026-11-02);18.1.0 已有声明式成员键(`ObjectSchema.membershipKeys`,PF2),U2 评估改用方案 B(04 §3.5) |
| R5 | 供应商不愿用门户,继续用邮件和微信 | 中 | 高:协同价值落空 | 试点选配合度高的供应商;邮件只给链接不给内容;在采购合同中约定门户为正式渠道 |
| R6 | 存量供应商数据质量差(重复、缺信用代码) | 高 | 中 | v0.2 导入前专项清洗;导入时按信用代码(境外按国家 / 地区 + 注册号)匹配,可重复执行;标记「存量导入」进「存量待复核」视图([导入说明](../implementation/legacy-supplier-import.md)) |
| R7 | ERP 集成推迟后,交付数据靠每月导入,客户不按时导出则绩效断档 | 中 | 中 | 导入模板与操作清单写进实施手册;考评 flow 发现本期无交付记录时标记「数据不全」而不是给零分 |
Expand Down Expand Up @@ -233,11 +234,14 @@ test/ # 仓库根目录

| # | 项 | 用法 | 不满足时 | 结果 |
|---|---|---|---|---|
| V1 | 门户用户的用户目录可见范围 | 门户隔离(04 §3.6) | 见 D8 | ⏳ 18.1.0 以组织角色 `external` 修复(PF6);U1 复测 |
| V2 | flow 以系统身份写岗位任命(`sys_user_position`) | 门户开通自动化 | 管理员手工任命,flow 只做回填 | ⏳ 18.1.0 起 `runAs: 'system'` 的 flow 不得写岗位任命、权限集分配、邀请与成员(objectstack#25251 已关),按「不放行」设计;U1 实测 `runAs: 'user'` 能否写 |
| V3 | flow 发起组织邀请 | 门户开通自动化 | 管理员在成员页邀请 | ⏳ 同 V2:系统身份的 flow 不得写邀请;U1 实测 |
| V4 | 门户用户对 `controlled_by_parent` 子记录的写入 | 证书、整改回复 | 父记录授编辑权 + 字段级只读 | ❓ |
| V5 | 环境内其他应用的 `isDefault` 权限集 | R2 | 逐个审查 | ⏳ 18.1.0 起 `external` 成员不受默认权限集作用;U1 实测并照常审查 |
| V1 | 门户用户的用户目录可见范围 | 门户隔离(04 §3.6) | 见 D8 | ✅ 2026-11-02 通过:`external` 成员读 `sys_user`、`sys_member` 只有自己,按 id 读员工、同公司同事、别家门户用户都是「不存在」;`list-members`、`get-full-organization`、`list-invitations` 只返回自己。前提是环境成员策略为 `invite-only`:`auto` 时注册即以普通成员入组织,接受邀请前能读全组织目录(04 §3.3) |
| V2 | flow 以系统身份写岗位任命(`sys_user_position`) | 门户开通自动化 | 管理员手工任命,flow 只做回填 | ⚠️ 2026-11-02 部分通过:`runAs: 'system'` 拒绝(objectstack#25251);`runAs: 'user'` 由组织管理员(`owner` / `admin`)启动时能写任命和直接授权(`sys_user_permission_set`);品类经理等业务岗位启动被拒(ADR-0090 D12)。委托管理也不行:委托任命必须挂业务单元,`external` 成员不进业务单元。所以「开通门户」flow 用 `runAs: 'user'`、由组织管理员操作(04 §3.3) |
| V3 | flow 发起组织邀请 | 门户开通自动化 | 管理员在成员页邀请 | ❌ 2026-11-02 不通过:`runAs: 'system'` 拒绝(objectstack#25251);`runAs: 'user'` 连组织管理员也被拒,邀请只能经组织接口发(平台裁定,ADR-0092)。邀请接口只认组织管理员,品类经理调用返回 403。按「不满足时」一列:管理员在成员页按 `external` 邀请(04 §3.3 第 1 步);登记 PF17 |
| V4 | 门户用户对 `controlled_by_parent` 子记录的写入 | 证书、整改回复 | 父记录授编辑权 + 字段级只读 | ✅ 2026-11-02 通过:父记录授编辑 + RLS 限本公司后,门户用户能给本公司增改证书(含员工录的证书,同公司另一位门户用户也能改),不能给别家建、不能把证书挪到别家、不能改 `status` / `verified`、不能删;父记录只授读时子记录增改都被拒,所以父记录必须授编辑。父记录上设为只读的字段(名称、状态、`portal_position`)一经提交整单拒绝(403),不是静默丢弃,门户表单不要提交只读字段。门户管理员加银行账户还要 `srm_view_bank_account_no` 能力(BR-20) |
| V5 | 环境内其他应用的 `isDefault` 权限集 | R2 | 逐个审查 | ✅ 2026-11-02 通过:另一个应用的 `isDefault` 权限集作用到每位员工,不作用到任何 `external` 成员;`external` 也不持 `member_default`,只有 `external_default` 和岗位、直接授予的权限集 |

复现:`test/portal-platform.test.ts`(18.1.0,进程内启动整个应用,门户用户走真实的邀请 → 注册 → 接受邀请)。其中的门户岗位、
门户权限集和供应商的 `portal_position` 是实测夹具,U2 落地正式元数据后,04 §3.7 的隔离测试改用正式的。

### 6.3 已提交的平台 issue

Expand All @@ -262,6 +266,7 @@ test/ # 仓库根目录
| PF14 | objectstack#25683 i18n 检查不把字段的 `inlineHelpText` 当作默认语言词条 | 带帮助文字的字段(绩效指标的满分值、零分值) | zh-CN 翻译包只放这两条帮助文字,引用源码同一份常量;修复后删除 | 主干已修,18.1.0 未带上 |
| PF15 | objectstack#14500 flow 没有汇总能力:CEL 不能对列表求和,`get_record` 只返回行(已关的「汇总 / 分组节点」卡,hotsrm 的用法作为数据点记在评论里) | 考评生成的 Σ不良数、Σ实收数、启用指标权重合计 | `loop` + `assignment` 逐行累加;200 家 × 30 行约 8.6 秒,性能可接受 | 平台不纳入计划 |
| PF16 | objectstack#26133 导入 upsert 时同一文件里匹配键相同的两行都新建,之后再导报 `AMBIGUOUS_MATCH` | 证书等没有唯一约束的对象重复建档 | 清洗时同一张证书只留一行([导入说明 §6.2](../implementation/legacy-supplier-import.md#62-清洗清单)) | 2026-10-26 新提 |
| PF17 | objectstack#25251 自动化不得以系统身份写岗位任命、权限集授予、邀请和成员(裁定 B);邀请只经组织接口(V2、V3) | 门户开通、回收 | 组织管理员在成员页邀请、移出组织;任命与门户管理员授权由 `runAs: 'user'` 的「开通门户」flow 在组织管理员操作时写;处置 flow 不撤任命,改为通知组织管理员(04 §3.3) | 平台裁定,长期做法(2026-11-02 实测登记,不另提 issue) |

其他代码和文档里指向平台的临时做法,2026-10-26 核对结果:

Expand Down
Loading
Loading