Files
group_fqcd_jr/tools
lzf_0626 5da2fb5790 投顾代客三项的权限码缺失:整片 403(与 9057-9059 同一个坑的第二批)
## 现象

投顾工作台(`advisor_t`,9020)一选客户就整片失败,页面只显示
「请求失败,请检查权限或稍后重试」。API 访问日志给出真相:

```
POST /api/v1/advisor/recommendations     403
POST /api/v1/advisor/asset-allocation    403
POST /api/v1/advisor/portfolio-analysis  403
GET  /api/v1/advisor/recommendations/published  200   ← 只有它不需要代客权限
```

## 根因:三个 `*:customer` 权限码在库里根本不存在

三个服务在"代客"(`customer_id != context.user_id`)时要求的是**动态拼出来的
`:customer` 变体**:

- `product_recommendation_service.py:71` → `product-recommendation:generate:customer`
- `asset_allocation_service.py:57`       → `asset-allocation:generate:customer`
- `portfolio_analysis_service.py:46`     → `portfolio-analysis:read:customer`

而 `sys_permission` 里 `:customer` 后缀**只有 4 个**(`memory:read:customer`、
`investment-goal:{read,write,confirm}:customer`)—— 这三个从来没登记过。
`AuthorizationService.require()` 第一步就查不到该码 ⇒ 直接 403。

这与种子里 **9057-9059 的注释是同一个坑**("动态拼出来的权限码,对账工具抓不到字面量,
于是投顾查/建/确认客户目标全部 403"),前人修了 investment-goal 那三个,这三个漏了。

## 修法(按仓库纪律:种子定义权限码 → grant 脚本绑定角色)

1. `tools/seed_test_rbac.py`:新增 **9066-9068** 三个码,`data_scope=own_customers`
   (`require_customer_scope` 只放行 `all`,或 `own_customers` 且客户确在
   `context.customer_ids` = 归属客户内 —— 这正是投顾该有的最小权限),
   并写明三处调用点,避免后人再漏;
2. `tools/grant_advisor_role.py`:把三个码加入 `ADVISOR_GRANTED_CODES`
   (admin 因 `ADMIN_PERMISSIONS = 全部种子权限` 自动获得)。

已执行:`seed_test_rbac.py` → `grant_advisor_role.py`
(advisor 新增 3 项,共 31 项;admin 62 项)。

## 验证(真实 HTTP,9020 身份)

| 客户 | 推荐方案 | 资产配置 | 组合分析 |
|---|---|---|---|
| 9101(演示客户) | 200 `profile_required` | 200 `profile_required` | 200 `no_positions` |
| **9001(真实客户)** | **200 已生成方案**(content_id=5, pending_review) | **200 ready**(含配置比例) | **200 ready**(7 个持仓,市值 10698.60) |

**403 全部消失**;对真实客户三项均返回真实结果。

## 同时补的归属数据

`sys_customer_assignment` 原有 9020→9001 一行;工作台把演示客户的 id
(9101-9104)当真实 `customer_id` 发给后端,而 `own_customers` 要求客户在投顾名下,
因此用 `tools/assign_customer_scope.py` 补了 4 行(9020 → 9101/9102/9103/9104),
回验 `customer_ids = ('9001','9101','9102','9103','9104')`。

## 仍未解决(属**数据**缺口,不是权限)

1. **9101-9104 在库里没有任何数据**:`advisor-config.js:126` 注释指向的
   `_seed_demo_customers.py` 在仓库、git 历史与桌面上**都不存在**(从未提交),
   所以这四位没有账号 / 风险测评 / 投资目标 / 持仓 —— 只能返回 `profile_required`。
2. **推荐候选恒为 0**:`AdvisorProductRepository.authoritative_tradable_products`
   要求**已验证**的适当性证据与合同快照(`review_status='verified'` 且
   `source_url` / `document_sha256` 非空,fail closed),而
   `advisor_product_suitability_reference` / `advisor_product_contract_snapshot`
   均为 0 行。这需要产品治理线提供可核查证据,**不应伪造**。

## 门禁

`tools/check_rbac_seed_consistency.py` 通过(种子内 id 唯一、各 grant 脚本与种子逐条一致);
`pytest tests/unit tests/contract` 全绿。
2026-09-14 22:56:42 +08:00
..
2026-09-11 17:26:18 +08:00