feat: M4 文风 + M5 生成/多provider/Skill + Kimi Code 订阅接入 + 本地联调修复

M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。
M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变);
  网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则;
  前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。
K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径;
  coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。
本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400);
  GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化;
  字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。
门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yaojia Wang
2026-06-20 10:39:58 +02:00
parent 5fb7bfb1de
commit 765dbdfbd4
161 changed files with 17330 additions and 208 deletions

View File

@@ -0,0 +1,130 @@
"""T5.5 表权限沙箱单测ARCH §5.6:声明式权限 + apply 层白名单,非进程沙箱)。
纯函数、无 IO
- `filter_reads`:注入时只把 skill 声明 `reads` 的表数据喂给它(越权读 → 丢弃,不喂)。
- `partition_writes`:写库时只应用 skill 声明 `writes` 的产出字段(越权写 → 丢弃 + 审计),
返回 `(allowed, rejected)` 两份,调用方据此落库 allowed、log rejected。
- `validate_declaration`:加载/注册 skill 时校验其声明的 `reads/writes` 全在已知表白名单内
(杜绝声明指向不存在的表);越界 → AppError(VALIDATION)。
不变量 #3自定义 skill 产出仍过验收 gate——本层只做白名单裁剪不开写后门。
"""
from __future__ import annotations
import pytest
from ww_agents import AgentSpec
from ww_shared import AppError, ErrorCode
from ww_skills import (
KNOWN_TABLES,
filter_reads,
partition_writes,
validate_declaration,
)
def _spec(*, reads: list[str], writes: list[str], scope: str = "custom") -> AgentSpec:
return AgentSpec(
name="custom_skill",
tier="writer",
system_prompt="x",
reads=reads,
writes=writes,
scope=scope,
)
# ---- filter_reads ----
def test_filter_reads_keeps_only_declared_tables() -> None:
spec = _spec(reads=["world_entities"], writes=[])
available = {
"world_entities": [{"name": "灵根"}],
"characters": [{"name": "主角"}], # 未声明 → 必须丢弃
}
fed = filter_reads(spec, available)
assert fed == {"world_entities": [{"name": "灵根"}]}
def test_filter_reads_ignores_declared_table_absent_from_available() -> None:
spec = _spec(reads=["world_entities", "characters"], writes=[])
available = {"world_entities": [{"name": "灵根"}]}
fed = filter_reads(spec, available)
assert fed == {"world_entities": [{"name": "灵根"}]}
def test_filter_reads_empty_declaration_feeds_nothing() -> None:
spec = _spec(reads=[], writes=[])
available = {"world_entities": [{"name": "灵根"}]}
assert filter_reads(spec, available) == {}
# ---- partition_writes ----
def test_partition_writes_drops_over_permission_fields() -> None:
spec = _spec(reads=[], writes=["world_entities"])
produced = {
"world_entities": [{"name": "新势力"}],
"characters": [{"name": "越权角色"}], # 越权 → 丢弃 + 审计
}
allowed, rejected = partition_writes(spec, produced)
assert allowed == {"world_entities": [{"name": "新势力"}]}
assert rejected == ["characters"]
def test_partition_writes_all_allowed_yields_empty_rejected() -> None:
spec = _spec(reads=[], writes=["world_entities", "characters"])
produced: dict[str, object] = {"world_entities": [], "characters": []}
allowed, rejected = partition_writes(spec, produced)
assert set(allowed) == {"world_entities", "characters"}
assert rejected == []
def test_partition_writes_no_declared_writes_rejects_all() -> None:
spec = _spec(reads=[], writes=[])
produced = {"world_entities": [{"name": "x"}]}
allowed, rejected = partition_writes(spec, produced)
assert allowed == {}
assert rejected == ["world_entities"]
# ---- validate_declaration ----
def test_validate_declaration_passes_for_known_tables() -> None:
spec = _spec(reads=["world_entities"], writes=["world_entities"])
# 不抛即通过
validate_declaration(spec)
def test_validate_declaration_rejects_unknown_read_table() -> None:
spec = _spec(reads=["secret_table"], writes=[])
with pytest.raises(AppError) as exc:
validate_declaration(spec)
assert exc.value.code is ErrorCode.VALIDATION
assert "secret_table" in str(exc.value.details)
def test_validate_declaration_rejects_unknown_write_table() -> None:
# 系统表 users 不在创作表白名单 → 越权写声明被拒。
assert "users" not in KNOWN_TABLES
spec = _spec(reads=[], writes=["users"])
with pytest.raises(AppError) as exc:
validate_declaration(spec)
assert exc.value.code is ErrorCode.VALIDATION