fix(cma): 工作台跨实体污染治本 — 全局键拆账套 + 缺列收口 + 预警归属兜底

任务 dama-workbench-entity-isolation-fullstack-20260922(DAMA 诊断 / 项目Bot batch5 §B / 任总 2026-09-22「都批」)

B1 现金基线拆账套(零 DDL)
- cash_forecast_engine: 键 cash.current_balance → cash.current_balance.<entity_id>;
  读只读本账套键、不做旧键回退;set_* 签名 entity_id 必传(漏传 TypeError)
- cash.py: POST /balance 挂 get_entity_id;网银流水联动写回带账套
B3(a) alignment_config 拆账套(零 DDL)
- alignment.py: GET/POST /config、GET /tree 只读 alignment_config.<entity_id>;未配置=configured:false
B3(b) 预警 KPI 归属兜底
- bot_bridge_v2: 去掉 kpi_id or 1 兜底;本账套无 KPI 定义 → 跳过建预警 + OperationLog 留痕
  (kpi_alerts.kpi_id NOT NULL ⇒ 方案A 置 NULL 不可行,走方案B 跳过+审计)
B2 缺列五表分表处置
- notification_logs: 经 alert_id → kpi_alerts.entity_id join 归属(无父键行不返回)
- notification_channels / okr_templates: 集团共享口径写入 docstring(有意不过滤)
- system_configs: 由 B1/B3(a) 拆键覆盖;kpi_data_quality_logs 沿用 batch5 的 join 收口
P0 语义收敛
- tenant.py: current_tenant 收敛为项目Bot A2A 状态记录,GET 仅 it/ceo;POST 补 operation_logs 审计
证据:新增/改写用例 22 条;全量 1370 passed(基线 1348)
This commit is contained in:
wecom-fullstack
2026-09-22 11:06:02 +08:00
parent e89629228e
commit 0c2e57a4cf
11 changed files with 1243 additions and 57 deletions
+49 -11
View File
@@ -20,7 +20,30 @@ router = APIRouter(prefix="/api/cma/alignment", tags=["KPI目标对齐"],
dependencies=[Depends(require_auth_401)],
)
# 三种对齐模式定义
# ── B3(a) 对齐模式拆账套(2026-09-22 dama-workbench-entity-isolation-fullstack-20260922)──
# 背景:`role_permissions.key` 为 unique 单行键 → `alignment_config` 全库一行,
# 任一账套的 ceo/it 改对齐模式 → **全账套生效**(跨账套配置污染)。
# 治本(方案 A,零 DDL):键名带账套后缀 → `alignment_config.<entity_id>`。
# · 读:只读本账套键;未配置 → configured=false(/tree 提示先初始化),
# **不回退旧全局键**(回退即恢复跨账套生效)。
# · 写:只写本账套键(entity 来自会话 token)。
# · 旧值搬运:脚本 scripts/migrate_cash_balance_key_entity_20260922.py --check/--apply
# (同一 500 口径:原全局值落到「当前生产账套」名下;--down 删新键并写回旧键)。
ALIGNMENT_KEY_PREFIX = "alignment_config"
def alignment_key(entity_id: int) -> str:
"""账套维度的对齐模式键名:alignment_config.<entity_id>"""
return f"{ALIGNMENT_KEY_PREFIX}.{int(entity_id)}"
def _get_alignment_perm(db: Session, entity_id: int):
"""读本账套的对齐模式配置行(不存在 → None)"""
return db.query(RolePermission).filter(
RolePermission.key == alignment_key(entity_id)
).first()
ALIGNMENT_MODES = [
{
"key": "vertical_decomposition",
@@ -50,18 +73,25 @@ def list_modes():
@router.get("/config")
def get_alignment_config(db: Session = Depends(get_db)):
"""获取当前系统对齐模式配置(公开接口,无需认证)"""
perm = db.query(RolePermission).filter(RolePermission.key == "alignment_config").first()
def get_alignment_config(entity_id: int = Depends(get_entity_id),
db: Session = Depends(get_db)):
"""获取**当前账套**的对齐模式配置(B3(a) 拆账套,2026-09-22)
修复前:读全局唯一键 `alignment_config` → 任一账套改模式全平台生效。
修复后:只读 `alignment_config.<entity_id>`;未配置 → configured=false。
"""
perm = _get_alignment_perm(db, entity_id)
if not perm:
return {
"mode": None,
"configured": False,
"entity_id": entity_id,
"modes": ALIGNMENT_MODES,
}
return {
"mode": perm.value,
"configured": True,
"entity_id": entity_id,
"modes": ALIGNMENT_MODES,
}
@@ -69,23 +99,31 @@ def get_alignment_config(db: Session = Depends(get_db)):
@router.post("/config")
def set_alignment_config(
data: dict,
entity_id: int = Depends(get_entity_id),
db: Session = Depends(get_db),
user = Depends(require_role("ceo", "it")),
):
"""初始化/修改系统对齐模式(CEO/IT权限)"""
"""初始化/修改**当前账套**的对齐模式(CEO/IT权限)
B3(a)(2026-09-22):只写 `alignment_config.<entity_id>`,不覆盖他账套;
entity 来自会话 token,显式传他账套 entity_id → get_entity_id 403。
"""
mode_key = data.get("mode")
if mode_key not in [m["key"] for m in ALIGNMENT_MODES]:
raise HTTPException(400, f"无效的对齐模式: {mode_key}")
perm = db.query(RolePermission).filter(RolePermission.key == "alignment_config").first()
perm = _get_alignment_perm(db, entity_id)
if perm:
perm.value = {"mode": mode_key, "set_at": datetime.now().isoformat()}
perm.value = {"mode": mode_key, "set_at": datetime.now().isoformat(),
"entity_id": entity_id}
else:
perm = RolePermission(key="alignment_config", value={"mode": mode_key, "set_at": datetime.now().isoformat()})
perm = RolePermission(key=alignment_key(entity_id),
value={"mode": mode_key, "set_at": datetime.now().isoformat(),
"entity_id": entity_id})
db.add(perm)
db.commit()
return {"message": f"对齐模式已设置为: {mode_key}", "mode": mode_key}
return {"message": f"对齐模式已设置为: {mode_key}", "mode": mode_key, "entity_id": entity_id}
@router.get("/tree")
@@ -106,8 +144,8 @@ def get_alignment_tree(
growth-quality / predict.accuracy)。三种模式共用本列表 → 三模式同时隔离。
跨实体显式传参(?entity_id=<他人账套>)由 get_entity_id 返回 403。
"""
# 获取当前模式
config_perm = db.query(RolePermission).filter(RolePermission.key == "alignment_config").first()
# 获取当前账套的对齐模式(B3(a) 拆账套 2026-09-22:只读 alignment_config.<entity_id>)
config_perm = _get_alignment_perm(db, entity_id)
mode = config_perm.value.get("mode") if config_perm else None
if not mode:
raise HTTPException(400, "系统未配置对齐模式,请先在系统设置中初始化")