kb_rule DSL 解析 + 纯域求值(优先级分组/硬约束/用户禁忌压过专业推荐) + 400 条基准 + 黄金集雏形(T-P2-02,TD-10)。 实现:dsl.py 封闭词汇表 pydantic 校验(未识别键拒绝)+ match 纯逻辑 (等值/include/exclude/min-max,缺字段不命中);engine.py 纯域求值 (priority 降序、confidence 次序、is_hard avoid → hard_blocks、高优先级 avoid 剔除冲突 recommend);service.py+repo.py published 整表载入进程内 缓存 + CRUD + draft→published→archived 状态机(同 T6 语义)。 测试:DSL 解析与 match 矩阵、排序/硬约束/禁忌优先、状态机矩阵、 黄金集 11 组(含用户禁忌压过专业推荐与多规则叠加)、400 条 × 1000 次 基准(实测均值 0.26ms/次、p95 0.41ms,预算 50ms);集成 3 用例 (published 载入命中拦截、draft 不载入、DSL 写入拒绝与状态机守卫)。 单测 310 通过(+72),集成 19 通过(+3)。 偏差说明: 1. 冲突仲裁按通用优先级语义实现(高优先级 avoid 剔除"严格更低"优先级的 冲突 recommend),任务文档的"用户禁忌(≥95)永远压过专业推荐(≤90)" 是该规则的特例,黄金集已含该场景用例。 2. include 子句按字段类型取语义:标量字段=命中列表任一值;集合字段= 列表全部在场(子集,精确命中);文档未区分,此实现两类字段均可用。 3. 单测引擎文件命名为 tests/unit/test_rules_evaluation.py(而非 test_rules_engine.py):与契约指定的集成文件 tests/integration/ test_rules_engine.py 同名会导致 pytest 收集冲突(tests 目录非包)。 4. 集成测试每用例独立 engine 并 dispose:pytest-asyncio 每用例一个事件 循环,直接用全局 session 工厂会让连接跨 loop 污染其它集成用例。 5. 规则不提供物理删除(命中链需可追溯,下线走 archived);kb_rule 契约 无 reviewing 状态,状态机为 draft→published→archived(draft 可撤稿)。
packages/ — 业务域与能力包
架构 §3.2:单体不等于混乱。这里的每个子目录都是一个边界,越界会被 CI 拦下。
目录含义
| 包 | 类型 | 内容 |
|---|---|---|
domain/ |
纯逻辑层 | 领域模型与业务规则。无 IO、无框架、可纯单测 |
shared/ |
基础设施 | 配置、日志、错误码、统一响应、DB、Redis、可观测 |
llm/ |
AI 能力 | LLM 网关(已实现) |
retrieval/ rules/ memory/ |
AI 能力 | 混合检索 / 规则引擎 / 记忆系统(占位) |
compliance/ |
业务域 | 统一合规出口(占位,CR-05/CR-06) |
identity/ billing/ task/ agent/ |
业务域 | 已有实现 |
profile/ report/ tryon/ kb/ notify/ wardrobe/ |
业务域 | 占位 |
每个业务域包的标准结构:
packages/<域>/
├─ __init__.py
├─ README.md 这个域负责什么、不负责什么
├─ service.py 对外唯一入口。其他域只能调这里
└─ repo.py SQL。只有本域可以 import
硬性规则(由 make importlint 机器校验)
- 禁止跨域直接查表。要用别的域的数据,调它的
service,不要 import 它的repo,更不要在自己的 SQL 里 JOIN 别人的表。表名的域前缀(id_/bl_/tk_/ …)就是为将来拆库预留的边界。 packages/domain/必须保持纯净。不许 importfastapi、sqlalchemy、httpx、redis、celery,也不许 importpackages/shared。放不进去说明 它不是领域逻辑,是应用逻辑,应该待在service.py。compliance不得依赖任何业务域。它必须能被任何模块安全调用。identity不得依赖任何其他业务域。登录链路不能被业务拖累。- AI 能力包(
llm/retrieval/rules)不得依赖业务域,也不得 importapps。它们要能在 V2 原样拆成独立服务。
加一个新业务域时
- 建目录,写
README.md说明职责与不负责的事。 - 在
pyproject.toml的[tool.importlinter]契约里把它加进相应的layers/forbidden_modules列表 —— 新域默认没有约束,忘了加就等于没有边界。 - 领域规则先写进
packages/domain/<域>/并配单测,再写service.py。 - 表加域前缀,走 Alembic,迁移必须向后兼容(NFR-MAINT-06)。