ci/woodpecker/push/main Pipeline was canceled
任务 dama-entity-isolation-sweep-20260923(DAMA 独立回查漏项 / 任总 2026-09-23「按照建议执行」)
① 补漏同源全局键 kpi_alert_higher_better(system_configs,零 DDL)
- deviation_engine: 新增 higher_better_key(entity_id);get_higher_better_codes(db, entity_id)
改为只读本账套键、不回退旧全局键;未配置 → DEFAULT_HIGHER_BETTER(兜底,与原行为逐字一致)
- budget.py: GET/PUT /alert-direction 挂 get_entity_id,只读写会话账套键;body entity_id 不信
- scripts/migrate_higher_better_key_entity_20260923.py: --check/--apply/--down + 库名保护 + 幂等
- 生产实况:库内该键 0 行 ⇒ 无原值可搬运(--apply 走 skip 零写入),e1 行为不变
② kpi_visibility 口径裁定:角色级天然全局 = 非账套维度,不拆(关闭本项)
- auth_middleware 注释 + docstring 登记「有意不做账套后缀」;口径字典
docs/global-config-keys-caliber-20260923.md(含 budget_mode/budget_driver_mode 评估结论)
③ tenant/switch 审计收口
- detail 显式 from_entity_id → to_entity_id + source + operator{id,username,name,role}
- 审计失败不静默:同事务回滚 + AUDIT_WRITE_FAILED ERROR + HTTP 500(不留「改了但没审计」)
- GET /current 无写路径不落审计;历史缺口不补写
证据:新增/改写用例 17 条;全量 1397 passed(去新增文件 1380 + 17);RED 态 8 failed
107 lines
8.1 KiB
Markdown
107 lines
8.1 KiB
Markdown
# 口径登记:全局配置键的「维度字典」(账套维度 / 非账套维度 / 集团共享)
|
||
|
||
- **立档**: wecom-fullstack,2026-09-23
|
||
- **来源**: `dama-entity-isolation-sweep-20260923` §一(DAMA 独立回查新增漏项,任总 2026-09-23
|
||
「按照建议执行」);父单 `dama-workbench-entity-isolation-fullstack-20260922`
|
||
- **性质**: 口径登记 + 代码收口说明。**不含**任何数据订正(数据订正另立单 + 审批)
|
||
- **配套**: 审计字面/通知日志口径见 `audit-and-notification-caliber-20260922.md`
|
||
|
||
---
|
||
|
||
## 一、判据(把「全局唯一键」分三档,别再一刀切)
|
||
|
||
`system_configs.config_key` 与 `role_permissions.key` 都是 **unique 单行键** ⇒ 一次写入全平台生效。
|
||
并非所有全局键都是缺陷,必须按键的**语义维度**分档:
|
||
|
||
| 档 | 名称 | 判据(问:这个值描述的是谁的什么?) | 处置 |
|
||
|---|---|---|---|
|
||
| **A** | 账套维度 | 值描述**某个账套**的状态,或**按账套业务**取值(余额、方向、期间窗口…) | **必须拆**:键名带 `.<entity_id>`(零 DDL);读**不回退**旧全局键;写侧 `entity_id` 必传;配 `--check/--apply/--down` 搬运脚本 |
|
||
| **B** | 非账套维度 | 值按**角色**(或其它平台级维度)组织,与账套无关 | **保持全局**,但必须在代码里写明「**有意不做账套后缀,这是集团共享口径,不是漏隔离**」并在本档登记 |
|
||
| **C** | 集团共享(配置/模板) | 对齐模式模板、通知渠道、OKR 模板、路由/动作权限 | 保持全局 + docstring 声明口径 |
|
||
|
||
**不写声明的代价**:下一轮隔离审计会把 B/C 档重复报成缺口,来回返工(本仓已发生两轮)。
|
||
|
||
---
|
||
|
||
## 二、逐键裁定表(2026-09-23 现况)
|
||
|
||
| 键 | 载体 | 消费点 | 档 | 处置 | 状态 |
|
||
|---|---|---|---|---|---|
|
||
| `kpi_alert_higher_better` | `system_configs` | `utils/deviation_engine.py::get_higher_better_codes`(L35-66)、`build_deviation_alert`(L305);`api/budget.py` GET/PUT `/alert-direction` | **A** | 拆为 `kpi_alert_higher_better.<entity_id>`;本账套未配置 → `DEFAULT_HIGHER_BETTER`(内置兜底) | ✅ 2026-09-23 本单 |
|
||
| `cash.current_balance` | `system_configs` | `utils/cash_forecast_engine.py`、`api/cash.py` | A | 同上(B1) | ✅ 2026-09-22 |
|
||
| `alignment_config` | `role_permissions` | `api/alignment.py`(GET/POST `/config`、GET `/tree`) | A | 同上(B3a) | ✅ 2026-09-22 |
|
||
| `current_tenant` | `system_configs` | `api/tenant.py` | A(特殊) | **语义收敛**:非数据维度,仅项目Bot A2A 状态记录;GET 限 it/ceo;POST 落审计(含 from→to) | ✅ 2026-09-22 / 审计收口 2026-09-23 |
|
||
| `kpi_visibility` | `role_permissions` | `auth_middleware.py::_load_kpi_visibility`(L248-286) | **B** | **不拆**;代码注释登记 | ✅ 2026-09-23 关闭 |
|
||
| `route_permissions` / `action_permissions` | `role_permissions` | `auth_middleware.py`(路由/动作权限) | C | 集团共享 | ✅ |
|
||
| `notification_channels` / `okr_templates` | 各自表 | 通知渠道 / OKR 模板 | C | 集团共享(父单 docstring 已声明) | ✅ |
|
||
| `budget_mode` | `system_configs` | `api/budget.py` L509(GET `/config`)、L535(POST `/config`)、L568(`/roll-forward`)、L677(预测期间窗口) | **A(待拆)** | 见 §三 | ⏳ 排期 |
|
||
| `budget_driver_mode` | `system_configs` | `api/driver_budget.py` L114(GET `/mode`)、L135(POST `/mode`) | **A(待拆)** | 见 §三 | ⏳ 排期 |
|
||
|
||
---
|
||
|
||
## 三、`kpi_visibility` 裁定(本单 §一②,**关闭本项**)
|
||
|
||
**结论:属「非账套维度」的全局配置,不拆账套;按 B 档登记后关闭。**
|
||
|
||
理由(三条,均以库内/代码实况为据):
|
||
|
||
1. **值本身是角色维度字典**:`{role: [维度规则]}`(默认 `DEFAULT_KPI_VISIBILITY`:
|
||
`ceo/it → ["*"]`、`finance → ["finance_*"]`、`business → ["customer_*","process_*","learning_*"]`),
|
||
条目的键是**角色**,没有任何账套语义。
|
||
2. **载体天然无账套列**:`role_permissions` 只有 `id/key/value/updated_at`,`key` unique
|
||
⇒ 表结构上不存在「按账套存一份」的位置(要按账套分必须 DDL,与「零 DDL 优先」相悖)。
|
||
3. **同表同族一致性**:`route_permissions` / `action_permissions` 同为角色级权限面,都是全局;
|
||
只拆 `kpi_visibility` 会造成同表三种维度口径分裂,反而更难维护。
|
||
|
||
**口径**:同表同族的键按同一维度处理。本键**不消费任何账套状态**,也不被账套写侧改写
|
||
(现库该键 0 行 ⇒ 走内置默认表,与任何账套无关)。
|
||
|
||
**落地**:代码注释 + `_load_kpi_visibility` docstring 已写明「**有意不做账套后缀**」并指向本档;
|
||
用例锁「结构面(无 `entity_id` 列)+ 行为面(裸键读值)+ 登记面(口径写在代码里)」。
|
||
|
||
> 现网实况(2026-09-23 只读核对):`role_permissions` 4 行 = `route_permissions` /
|
||
> `action_permissions` / `alignment_config` / `alignment_config.1`;**`kpi_visibility` 无行**。
|
||
|
||
---
|
||
|
||
## 四、`budget_mode` / `budget_driver_mode` 评估结论(本单「顺带」项,**不并入本单**)
|
||
|
||
**现况(只读核对)**:库内各 1 行 ——
|
||
`budget_mode = {"mode":"rolling","rolling_months":12}`(2026-09-08 更新)、
|
||
`budget_driver_mode = {"mode":"driver","label":"驱动因子模式"}`(2026-08-26 更新)。
|
||
|
||
**为什么属 A 档(不是集团共享)**:
|
||
|
||
- `budget_mode` 决定**本账套**预算预测的**期间窗口**(`budget.py:677-702`:`rolling` = 当月起 12 个月、
|
||
`fixed` = 当年 1-12 月),而其数据查询本身**按会话 entity 过滤**(同函数 L710-713)
|
||
⇒ 全局键在决定「本账套的取数窗口」,属跨账套污染同源。
|
||
- `budget_driver_mode` 决定**本账套**预算编制模式(`subject` 科目模式 / `driver` 驱动因子模式)。
|
||
- 任一账套调 `POST /budget/config`(或 `POST /driver-budget/mode`)即**改全平台模式**。
|
||
|
||
**结论:应拆(B1 同款,零 DDL),但不并入本单**,理由三条:
|
||
|
||
1. **库内确有旧值,且两个 active 账套都在用预算**(`budget_plans` 逐账套:e1 = 276 行、
|
||
e2 = 25 行)⇒ 必须裁定「原值落哪家」,或「是否双落」。若按单账套搬运(只落 e1),
|
||
e2 会由 `rolling` 退化为默认 `fixed`(期间窗口从「当月+12 月」变「全年 1-12 月」)——
|
||
这是**跨账套行为变更**,须任总拍板,不能由执行方顺手决定。
|
||
2. **牵动既有用例**:`tests/test_budget.py`(8 处)、`tests/test_cross_entity_isolation_batch2_20260918.py`(2 处)、
|
||
`tests/test_budget_tech_improve.py` 均直接读写该全局键字面 ⇒ 应作为独立单元出 RED→GREEN + 全量回归。
|
||
3. **又是一次生产写**(`system_configs` 新键)⇒ 按「**同一生产写目标只开一张单**」的约定不并入本单。
|
||
|
||
**建议排期**:P2 单「`budget_mode` / `budget_driver_mode` 拆账套(含原值落账裁定)」。
|
||
**建议口径(供拍板)**:**原集团值落全部 active 账套(e1 + e2)、demo 账套留空走默认** ——
|
||
即「原值 = 拆键前的事实生效面」(该键拆键前对两个 active 账套同时生效),
|
||
这样拆键**零行为变更**,只是把「谁改谁生效」的写入面收窄到本账套;
|
||
比「只落 e1、e2 退化」更保守,也比「读侧回退旧键」更干净(回退仍是借用通路)。
|
||
|
||
---
|
||
|
||
## 五、消费者自查法(防下一次漏扫)
|
||
|
||
1. **枚举**:`grep -rn 'SystemConfig' backend/app`+
|
||
`grep -rhoP 'config_key == "\K[a-zA-Z0-9_.]+' backend/app`(`role_permissions` 同法 grep `RolePermission`);
|
||
2. **分档**:对每个键问「这个值描述的是**谁的**什么」→ A / B / C;
|
||
3. **A 档改完必须四件套**:读**不回退**旧键 + 写侧 `entity_id` 必传(不给默认值,漏传即 `TypeError`)
|
||
+ 本档登记 + 搬运脚本 `--check/--apply/--down`(库名保护 + 幂等 + 目标账套存在性校验);
|
||
4. **B/C 档必须留声明**(代码注释/docstring 指回本档),否则下一轮审计重复报缺口。
|