Files
xsl 3961b6fbfa feat(admin): minimal console for KB content operations
apps/admin 独立应用:X-Admin-Token 鉴权(空=全部路由不注册=404)、实体/规则
维护与状态机流转、发布编排(publish/archive → 规则缓存 invalidate +
retrieval invalidate_cache 双失效)、素材登记与审核(tryon_ready 门禁透传)、
缺口看板(T-P2-09,FR-ADM-01~05)。V1 共享令牌鉴权,无角色无审计字段,
操作留证靠 structlog request_id。

测试:单测 tests/unit/test_admin_app.py(10 例:空令牌 404、错/缺/非 ASCII
令牌 40100、正确令牌 200 假服务、发布编排接线、幂等重放无副作用);
集成 tests/integration/test_admin_console.py(5 例:顾问全流程不碰客户端
API、双失效副作用可观察、非法跃迁 40900、素材审核门禁、缺口看板排序、
令牌 401)。483 单测 + 43 集成全绿;ruff/mypy/lint-imports 全绿。

偏差说明:
1. packages/retrieval.repo.list_gaps 有缺陷:对 select(KbGap) 的结果调
   .mappings(),行键是实体名而非列名,GapSummary(row) 取 row.gap_id 必然
   AttributeError(该路径无既有测试覆盖,接真库即暴露)。packages/** 本
   任务禁改,故缺口查询在 apps/admin/routers/gaps.py 以等价 SQL 自实现
   (hit_count 降序、同频次按 last_seen_at 降序、limit/offset 语义一致);
   建议合并阶段修复上游后切回 retrieval.list_gaps。
2. 实体 transition 的 target 收全量四态(任务文档示例仅列 reviewing/
   published/archived):按任务测试要求 published→draft 必须返回 40900
   (状态机语义冲突)而非 schema 422;draft 作为目标由服务层跃迁矩阵恒拒。
3. 规则路由以 rule_key 为路径参数(任务文档写 /rules/{id},但 RuleService
   全程以 rule_key 寻址,无按数字 id 的接口,以代码为准)。
4. 发布编排触发面含 archived(撤稿):依据 retrieval.invalidate_cache 的
   自身契约「发布/归档知识后调用」,比文档字面的「发布成功后」多了撤稿
   方向;reviewing 等其余状态不触发。
5. updated_by/author 记 "admin-console":V1 共享令牌无操作者身份,字段
   留渠道来源;运营账号体系属 V2(见包 docstring 的升级路径)。
2026-08-14 21:19:31 +08:00

79 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""admin 域请求/响应模型(FR-ADM-01~05)。
实体与素材的载荷复用 ``packages/kb/schemas.py``(服务层契约同源);本模块只
放后台特有的形状:状态机流转请求、规则对外视图、缺口看板行。雪花 ID 一律
以字符串出现在载荷里(与 kb 契约一致:JS Number 只有 2^53 精度)。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, Field
from packages.kb.schemas import EntityStatus
from packages.rules.dsl import Rule as RuleUpsert
__all__ = [
"GapOut",
"RuleOut",
"RuleTransitionRequest",
"RuleUpsert",
"TransitionRequest",
]
class TransitionRequest(BaseModel):
"""``POST /kb/items/{id}/transition`` 载荷。
``target`` 收全量四态而不是只收三个「前进方向」:published→draft 这类非法
跃迁应该是**状态机语义冲突(40900)**,而不是 schema 层的 422——顾问在
界面上看到的错误要能告诉他「为什么不行」(矩阵见 ``packages.kb.service``),
而不是「这个值不合法」。draft 作为目标在矩阵里永远非法,自然会被拦下。
"""
target: EntityStatus
class RuleTransitionRequest(BaseModel):
"""``POST /rules/{rule_key}/transition`` 载荷。
规则状态机里 draft 永远不是合法目标(发布与下线是仅有的两个前进动作,
与实体不同,规则没有 reviewing 中间态),直接在 schema 层收窄。
"""
target: Literal["published", "archived"]
class RuleOut(BaseModel):
"""规则对外视图。conditions/effects 保持入库的 DSL 原样(决策表是数据,
后台表单按 DSL 渲染,不做二次加工)。"""
rule_id: str
rule_key: str
category: str
version: int = Field(description="内容版本号,每次修改 +1")
status: str
priority: int
conditions: dict[str, Any]
effects: dict[str, Any]
is_hard: bool
confidence: float
evidence: str | None
author: str | None
reviewer: str | None
published_at: datetime | None
created_at: datetime
class GapOut(BaseModel):
"""缺口看板一行(kb_gap,T-P2-06)。hit_count 是排序依据也是优先级信号。"""
gap_id: str
query_text: str = Field(description="用户真实查询原文——内容生产的起点")
filter_ctx: dict[str, Any] = Field(description="缺口出现时的过滤条件快照")
hit_count: int = Field(description="同一缺口累计出现次数")
first_seen_at: datetime
last_seen_at: datetime