Skip to content

feat(core): isolate the supplier portal by a contact membership key (v0.6 U2) - #88

Merged
marchtian merged 2 commits into
mainfrom
claude/project-thread-ijzmh8
Nov 2, 2026
Merged

marchtian merged 2 commits into
mainfrom
claude/project-thread-ijzmh8

Conversation

@marchtian

Copy link
Copy Markdown
Collaborator

Requested by 田騏 · project thread

Closes #78.

改了什么

限时验证结论:通过 → 方案 B。 按总监裁定先测 ObjectSchema.membershipKeys(18.1.0),门户用户按组织角色 external、托管同一套 plugin-security、fail-closed、04 §3.7 用例全部通过,所以写 ADR-0010 取代 ADR-0003(总监线程已认可,并提了 4 个条件,下面逐条落实)。

  • 名册:srm_supplier_contact.portal_user(user,字段级只读,只由 flow 写)+ 成员键 portal_supplier_ids(userField: portal_user、valueField: supplier、filter: active == true)。
  • 门户权限集 srm_portal_supplier / srm_portal_admin(src/core/security/portal-permission-sets.ts):每条 RLS 都读 current_user.portal_supplier_ids;合同只放行签署中及以后,考评单只放行已发布 / 申诉中;供应商只能改资料字段,证书只能新建(不能自带已核验),门户管理员可新增联系人与银行账户、看完整账号。都不设 isDefault。
  • 静态岗位 srm_portal_user、srm_portal_admin,不再每家供应商建岗位。
  • private 对象两把锁:实测发现对 external 成员,权限集 RLS 只解除 public_read 对象的「仅本人」下限,解除不了 private(方案 A 同样会碰到)。准入申请、变更单、合同各加一条只授 srm_portal_user 的读共享规则(合同只共享可见状态),再由 RLS 限定本公司。
  • 回收:srm_disposition_apply 在淘汰 / 拉黑时清掉该供应商联系人上的 portal_user,下一个请求即失效(总监条件 4)。
  • portal_position 不加(从未合入),前缀校验和回填 flow 一并取消。
  • 文档:AGENTS.md 红线 2 改写(总监条件 3);02 / 03 / 04 §3 / 06 / 07 与扩展包草案同步;07 §6.2 新增 V6 托管环境复测,列为 v0.6 出口条件,覆盖「键为空」和「运行时不认识这个键」(总监条件 1);V4 记为已通过。

临时做法

  • srm_supplier.risk_level 暂时对门户可读:平台默认搜索会扫描选项字段且不跳过调用者读不到的字段,隐藏它会让门户的供应商搜索整体 403。就是 objectstack#25672(主干已修、18.1.0 未含),没有另提 issue;升级后改回隐藏。

怎么验证的

  • test/portal-isolation.test.ts(26 条):
    • 元数据守卫(总监条件 2):门户能读的每个对象都要有读成员键的 RLS(或是子对象);门户共享规则只能是读、只能给 srm_portal_user,对象上必须有这条 RLS;不设 isDefault、不授删除 / 全量读写 / 导出;每个带供应商引用的对象要么授给门户,要么写明理由;字段权限里的字段名都存在。
    • 运行时:交叉读(全部门户对象,引擎与 REST 按 id 读 = 404,列表 / 搜索 / query 只出本公司,示例种子数据也不出现)、交叉写、冒用名册、自解资质异常、门户新建证书、银行账号脱敏、空名册 / 联系人停用 / 无门户岗位 → 每个对象零行、停用联系人与撤销任命即时生效、淘汰即时断开、用户目录只见自己、导出 403、内部读取不回归。
    • 反向检验:把合同 RLS 里的成员键条件去掉,26 条里 9 条变红。
  • pnpm validate / lint / typecheck / i18n:check / test(276 通过,1 条负载测试照常跳过)/ build 全绿;governance/scripts/build.py --check(含三个扩展包)通过;文档相对链接无新增失效。
  • 未覆盖:开标后改价(寻源 v0.7);AI 助手通道与托管环境在 v0.6 出口按 V6 复测。

Generated by Claude Code

Implements v0.6 U2 (#78). The time-boxed test of ObjectSchema.membershipKeys
(18.1.0) passed, so the portal uses scheme B: ADR-0010 supersedes ADR-0003.

- srm_supplier_contact gains portal_user (flow-only) and declares the
  membership key portal_supplier_ids (portal_user -> supplier, active only).
- Static positions srm_portal_user / srm_portal_admin and permission sets
  srm_portal_supplier / srm_portal_admin; every portal RLS reads the key.
- External members only see private objects through a share, so the
  application, change request and contract get read sharing rules to
  srm_portal_user; the RLS narrows them to the own supplier.
- srm_disposition_apply clears portal_user on elimination and blacklisting.
- test/portal-isolation.test.ts covers 04 §3.7 (except sealed bids, v0.7)
  plus metadata guards that fail if a portal object loses its RLS.
- portal_position, its prefix rule and the backfill flow are dropped from
  the design; AGENTS.md red line 2, 02/03/04/06/07 and the pack drafts follow.

Claude-Session: https://claude.ai/code/session_017ihL2dk71wiRBasBp9drzu
Co-authored-by: Claude <noreply@anthropic.com>
@marchtian marchtian self-assigned this Nov 2, 2026
Resolve 04 §3.3 and 07 §6.2 against the U1 platform checks (#87):
keep the invite-only prerequisite and the org-admin provisioning path,
drop the per-supplier position steps, and record V4 as rechecked on the
formal metadata. V6 stays the v0.6 exit condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2dk71wiRBasBp9drzu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v0.6 U2:门户隔离底座(portal_position、门户权限集与 RLS、隔离测试)

2 participants