feat: 配置API(分组校验管线/整份版本/回滚/重置出厂/试金石/平台设置)

- config_validation.validate_group: 九组校验管线(类型/键 allowlist/数值
  范围/prompts 大小上限 50KB·100KB/wall 全量交 compile_wall/push.prefix
  跨租户查重 spec E1),错误清单中文带字段路径
- config_routes: GET/PUT config(整组替换原样入版本,MAX+1,热 reload)/
  reset(按 Tenant.profile 取出厂档案组)/versions 列表·详情/rollback
  (旧版复制为新版本,历史不可变)/wall/test 试金石(逐句判定,不落库
  不 bump)/settings 平台设置 CRUD(仅超管,resolver 接线归 Phase 3)
- db.write_config_version: 版本写入公共路径(创建向导与配置 API 单一
  事实源);Tenant 落 profile 列(重置出厂按它取对应档案)
- _profile_snapshot/_blankify 迁 tenant_source(两路由模块共用);
  db_source_app/super_client 夹具收拢 conftest(两测试文件共用)
- 测试: 519 → 537 全绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-13 10:07:40 +08:00
parent ec2e17d5e3
commit 7502176190
10 changed files with 1412 additions and 89 deletions
+29
View File
@@ -26,6 +26,7 @@ PlatformBase.metadata,engine/models.ensure_schema 只跑 Base.metadata——
(Windows 文件锁,同 registry)。
"""
import datetime
import json
import pathlib
from sqlalchemy import (
@@ -38,6 +39,7 @@ from sqlalchemy import (
Text,
UniqueConstraint,
event,
func,
select,
)
from sqlalchemy.exc import IntegrityError
@@ -69,6 +71,10 @@ class Tenant(PlatformBase):
slug: Mapped[str] = mapped_column(String(64), unique=True, index=True)
name: Mapped[str] = mapped_column(String(128))
status: Mapped[str] = mapped_column(String(16), default="active")
# 创建向导选定的出厂档案(Task 6 起):重置出厂按它取对应档案组
# (auto/jiancai 档案 / blank = jiancai 去身份组)。缺省 blank——
# 正常路径一律经创建向导写入,缺省只是手工插行的兜底语义。
profile: Mapped[str] = mapped_column(String(16), default="blank")
created_at: Mapped[datetime.datetime] = mapped_column(default=_utcnow)
@@ -176,6 +182,29 @@ async def ensure_platform_schema(engine: AsyncEngine) -> None:
# ---- 密码哈希:定义在 auth.py(见文件头 re-export)---------------------------
# ---- 配置版本写入(Task 6 抽公共:创建向导与配置 API 的单一版本写路径) --------
async def write_config_version(
session: AsyncSession, tenant_id: int, snapshot: dict, *,
updated_by: str = "", comment: str | None = None) -> int:
"""写新配置版本:MAX(version)+1,整份快照直存(spec §5.2 历史不可变,
当前生效 = MAX)。Task 5 创建向导(版本 1)与 Task 6 配置 API(PUT 分组
编辑/rollback 复制旧版/reset 重置出厂)的公共写入路径——两处各自
MAX+1 会漂移,收敛为单一事实源。调用方负责 commit;返回新版本号。"""
current = (await session.execute(
select(func.max(TenantConfigVersion.version))
.where(TenantConfigVersion.tenant_id == tenant_id))).scalar()
version = (current or 0) + 1
session.add(TenantConfigVersion(
tenant_id=tenant_id,
version=version,
config_json=json.dumps(snapshot, ensure_ascii=False),
updated_by=updated_by,
comment=comment))
return version
# ---- 超管引导 ---------------------------------------------------------------
async def bootstrap_superuser(