"""Skill 表权限沙箱（ARCH §5.6 / PRODUCT_SPEC §5.5）——纯函数，无 IO。

这**不是进程级沙箱**：声明式 Skill 不执行代码。强制点在编排/写库层（"声明式权限 +
apply 层白名单"）：
- `filter_reads`：注入时只把 skill 声明 `reads` 的表数据喂给它（未声明的表丢弃，不喂）。
- `partition_writes`：写库时只应用 skill 声明 `writes` 的产出（越权字段丢弃 + 审计），
  返回 `(allowed, rejected)`——调用方落 `allowed`、log `rejected`。不变量 #3：
  allowed 仍须经验收 gate 才真入库，本层只裁剪白名单，不开写后门。
- `validate_declaration`：注册/加载 skill 时校验 `reads/writes` 全在已知创作表白名单内
  （杜绝声明指向不存在/系统表）；越界 → AppError(VALIDATION)。

不可变：所有函数返回新 dict/list，从不原地改入参（全局 immutability 约定）。
"""

from __future__ import annotations

from typing import Any

from ww_agents import AgentSpec
from ww_shared import AppError, ErrorCode

# 声明式 Skill 允许声明读/写的创作表白名单（ARCH §3.1 创作表 + rules；
# 系统/运营表 users/jobs/usage_ledger/provider_credentials/tier_routing/skills 不在此列）。
KNOWN_TABLES: frozenset[str] = frozenset(
    {
        "projects",
        "characters",
        "world_entities",
        "outline",
        "chapter_digests",
        "foreshadow",
        "style_fingerprint",
        "rules",
        "chapters",
        "chapter_reviews",
    }
)


def filter_reads(spec: AgentSpec, available: dict[str, Any]) -> dict[str, Any]:
    """注入白名单：只保留 skill 声明 `reads` 且 `available` 里确有的表（新 dict）。"""
    declared = set(spec.reads)
    return {table: data for table, data in available.items() if table in declared}


def partition_writes(spec: AgentSpec, produced: dict[str, Any]) -> tuple[dict[str, Any], list[str]]:
    """写库白名单：把产出分成 (allowed, rejected)。

    `allowed`：产出表 ∈ skill 声明 `writes`（新 dict，调用方落库）。
    `rejected`：越权产出表名（排序，调用方审计 log + 丢弃）。
    """
    declared = set(spec.writes)
    allowed: dict[str, Any] = {}
    rejected: list[str] = []
    for table, data in produced.items():
        if table in declared:
            allowed[table] = data
        else:
            rejected.append(table)
    return allowed, sorted(rejected)


def validate_declaration(spec: AgentSpec) -> None:
    """校验 skill 的 `reads/writes` 全在 `KNOWN_TABLES`；越界 → AppError(VALIDATION)。"""
    unknown = sorted((set(spec.reads) | set(spec.writes)) - KNOWN_TABLES)
    if unknown:
        raise AppError(
            ErrorCode.VALIDATION,
            f"Skill「{spec.name}」声明了未知表：{unknown}",
            {"skill": spec.name, "unknown_tables": unknown},
        )
