# 口径登记:全局配置键的「维度字典」(账套维度 / 非账套维度 / 集团共享) - **立档**: 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** | 账套维度 | 值描述**某个账套**的状态,或**按账套业务**取值(余额、方向、期间窗口…) | **必须拆**:键名带 `.`(零 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.`;本账套未配置 → `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 指回本档),否则下一轮审计重复报缺口。