实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
442 lines
17 KiB
Python
442 lines
17 KiB
Python
"""同一结构的五类用途、授权与来源版本分别生效。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from dataclasses import replace
|
||
|
||
import pytest
|
||
|
||
from muse.元数据.接口 import (
|
||
值规则,
|
||
元数据错误,
|
||
合成结构,
|
||
字段定义,
|
||
字段用途,
|
||
字段限制,
|
||
投影值,
|
||
投影字段,
|
||
授权摘要,
|
||
策略快照,
|
||
结构定义,
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-purpose-projection",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="作者备忘仅可看可编辑",
|
||
when="选择五类用途",
|
||
then=["不扩大 AI、检索或导出权限。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
@pytest.mark.parametrize(
|
||
"用途",
|
||
["uiVisible", "userEditable", "userSearchable", "exportable", "aiContext"],
|
||
ids=["display", "edit", "search", "export", "generation"],
|
||
)
|
||
def test_五类用途分别投影并解释剔除__a41001(用途: str) -> None:
|
||
"""given 作者备忘仅可看可编辑;when 选择五类用途;then 不扩大 AI、检索或导出权限。"""
|
||
结构 = 合成结构(
|
||
结构定义(
|
||
"character",
|
||
"character",
|
||
1,
|
||
(
|
||
字段定义("name", "姓名", "姓名", 值规则("text"), 用途=字段用途(aiContext=True)),
|
||
字段定义(
|
||
"memo",
|
||
"创作备忘",
|
||
"创作备忘",
|
||
值规则("text"),
|
||
用途=字段用途(userSearchable=False, exportable=False),
|
||
),
|
||
),
|
||
)
|
||
)
|
||
投影 = 投影字段(
|
||
结构,
|
||
策略快照("character", 1),
|
||
授权摘要("a1", "s1"),
|
||
字段用途名=用途,
|
||
内容用途="generation",
|
||
运行用途="production",
|
||
)
|
||
assert 投影值(投影, {"姓名": "青棠", "创作备忘": "作者私记"}) == (
|
||
{"姓名": "青棠", "创作备忘": "作者私记"}
|
||
if 用途 in {"uiVisible", "userEditable"}
|
||
else {"姓名": "青棠"}
|
||
)
|
||
if 用途 not in {"uiVisible", "userEditable"}:
|
||
assert 投影.剔除原因["创作备忘"] == "FIELD_NOT_ALLOWED"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-projection-fingerprint",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="历史结构",
|
||
when="撤回用途或改变授权来源",
|
||
then=["投影版本变化且旧值不泄露。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_投影身份包含授权来源及当前策略__a41002() -> None:
|
||
"""given 历史结构;when 撤回用途或改变授权来源;then 投影版本变化且旧值不泄露。"""
|
||
结构 = 合成结构(
|
||
结构定义(
|
||
"character",
|
||
"character",
|
||
1,
|
||
(字段定义("secret", "秘密", "秘密", 值规则("text"), 用途=字段用途(aiContext=True)),),
|
||
)
|
||
)
|
||
策略 = 策略快照("character", 1)
|
||
授权 = 授权摘要("author-a1", "source-s1")
|
||
|
||
def 生成(策略=策略, 授权=授权):
|
||
return 投影字段(
|
||
结构, 策略, 授权, 字段用途名="aiContext", 内容用途="generation", 运行用途="production"
|
||
)
|
||
|
||
旧 = 生成()
|
||
assert (
|
||
旧.projection_version != 生成(授权=replace(授权, 授权指纹="author-a2")).projection_version
|
||
)
|
||
assert (
|
||
旧.projection_version != 生成(授权=replace(授权, 来源指纹="source-s2")).projection_version
|
||
)
|
||
新 = 生成(策略=策略快照("character", 2, (字段限制("secret", ("aiContext",)),)))
|
||
assert 新.projection_version != 旧.projection_version
|
||
assert 投影值(新, {"秘密": "暗门"}) == {}
|
||
with pytest.raises(元数据错误):
|
||
投影字段(
|
||
结构, 策略, 授权, 字段用途名="aiContext", 内容用途="unknown", 运行用途="production"
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-seed-semantics",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="冻结原定义",
|
||
when="检查发布种子",
|
||
then=["逐型身份、别名和 AI 用途原样承接。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_原结构字段语义用途别名逐项承接__a41003() -> None:
|
||
"""given 冻结原定义;when 检查发布种子;then 逐型身份、别名和 AI 用途原样承接。"""
|
||
import hashlib
|
||
from pathlib import Path
|
||
|
||
import yaml
|
||
|
||
from muse.元数据.接口 import 读取内置种子
|
||
|
||
根 = Path(__file__).resolve().parents[2]
|
||
源 = 根 / "docs/系统架构/新版设计/研究依据/原结构快照"
|
||
import json
|
||
|
||
字段合同 = json.loads((根 / "tests/夹具/内置结构字段合同.json").read_text())["schemas"]
|
||
assert {v.结构.schema_id for v in 读取内置种子()} == set(字段合同)
|
||
for 种子 in 读取内置种子():
|
||
文件 = 源 / f"{种子.类型.type_id}.yaml"
|
||
原 = yaml.safe_load(文件.read_text(encoding="utf-8"))
|
||
assert 种子.原定义哈希 == hashlib.sha256(文件.read_bytes()).hexdigest()
|
||
按键 = {项.key: 项 for 项 in 种子.结构.字段 if not 项.field_id.startswith("common.")}
|
||
assert set(按键) == {项["key"] for 项 in 原.get("特有字段", [])}
|
||
for 项 in 原.get("特有字段", []):
|
||
新 = 按键[项["key"]]
|
||
ai = 项.get("aiContext", False)
|
||
assert 新.用途.aiContext == (tuple(ai) if isinstance(ai, list) else ai)
|
||
assert not 项.get("alias") or 项["alias"] in 新.别名
|
||
assert 项.get("说明", "") in 新.说明
|
||
_核对结构字段合同(种子.结构, 字段合同[种子.结构.schema_id])
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-common-fields",
|
||
environment="离线纯规则",
|
||
given="内置人物结构和公共名称标签",
|
||
when="合成、校验并按 AI 用途投影",
|
||
then=[
|
||
"公共字段完整往返;错误列表类型拒绝",
|
||
"来源和状态仍受业务骨架保护;运行外壳不合入公共字段",
|
||
],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_公共内容字段参加同源校验投影且不混入运行外壳__a41009() -> None:
|
||
"""名称等公共内容与逐型字段合成;来源和状态仍由业务骨架维护。"""
|
||
from muse.元数据.接口 import 校验值, 读取内置结构
|
||
|
||
结构 = 合成结构(读取内置结构("character"))
|
||
值 = {"名称": "青棠", "别名": ["阿棠"], "一句话摘要": "守住旧约的人", "标签": ["守信"]}
|
||
assert 校验值(结构, 值).值 == 值
|
||
投影 = 投影字段(
|
||
结构,
|
||
策略快照("character", 1),
|
||
授权摘要("a1", "s1"),
|
||
字段用途名="aiContext",
|
||
内容用途="generation",
|
||
运行用途="production",
|
||
)
|
||
assert 投影值(投影, 值) == 值
|
||
with pytest.raises(元数据错误):
|
||
校验值(结构, {"标签": "不能把列表写成字符串"})
|
||
with pytest.raises(元数据错误):
|
||
校验值(结构, {"状态": "已确认"})
|
||
for schema_id in ("generation_context", "fine_outline"):
|
||
assert not any(f.field_id.startswith("common.") for f in 读取内置结构(schema_id).字段)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-union-projection",
|
||
environment="离线纯规则",
|
||
given="文本列表与对象列表联合结构,合法值含私记",
|
||
when="校验后投影至模型",
|
||
then=["按原结构真实分支裁剪,私记不进入输出"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_联合类型按已验证分支裁剪隐藏值__a41010() -> None:
|
||
"""两个分支都是列表时,不能因外形相同把私记按文本列表原样送出。"""
|
||
from muse.元数据.接口 import 校验值
|
||
|
||
分支 = 值规则(
|
||
"object",
|
||
字段=(
|
||
字段定义("public", "公开", "公开", 值规则("text"), 用途=字段用途(aiContext=True)),
|
||
字段定义("private", "私记", "私记", 值规则("text")),
|
||
),
|
||
)
|
||
规则 = 值规则(
|
||
"union",
|
||
分支=(
|
||
值规则("list", 元素=值规则("text")),
|
||
值规则("list", 元素=分支),
|
||
),
|
||
)
|
||
结构 = 合成结构(
|
||
结构定义(
|
||
"t", "s", 1, (字段定义("data", "资料", "资料", 规则, 用途=字段用途(aiContext=True)),)
|
||
)
|
||
)
|
||
值 = 校验值(结构, {"资料": [{"公开": "公开信息", "私记": "私人想法"}]}).值
|
||
投影 = 投影字段(
|
||
结构,
|
||
策略快照("t", 1),
|
||
授权摘要("a1", "s1"),
|
||
字段用途名="aiContext",
|
||
内容用途="generation",
|
||
运行用途="production",
|
||
)
|
||
assert 投影值(投影, 值) == {"资料": [{"公开": "公开信息"}]}
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-nested-projection",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="嵌套动态对象含私人备注",
|
||
when="模型投影",
|
||
then=["输出合同和值均删除隐藏项。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_嵌套隐藏字段不会流入模型合同或值__a41004() -> None:
|
||
"""given 嵌套动态对象含私人备注;when 模型投影;then 输出合同和值均删除隐藏项。"""
|
||
from muse.元数据.接口 import 生成字段合同
|
||
|
||
对象 = 值规则(
|
||
"object",
|
||
字段=(
|
||
字段定义(
|
||
"nested.public", "公开", "公开", 值规则("text"), 用途=字段用途(aiContext=True)
|
||
),
|
||
字段定义("nested.private", "私记", "私记", 值规则("text")),
|
||
),
|
||
)
|
||
结构 = 合成结构(
|
||
结构定义(
|
||
"new_type",
|
||
"new_schema",
|
||
1,
|
||
(
|
||
字段定义(
|
||
"outer",
|
||
"观察",
|
||
"观察",
|
||
值规则("list", 元素=对象),
|
||
用途=字段用途(aiContext=True),
|
||
),
|
||
),
|
||
)
|
||
)
|
||
投影 = 投影字段(
|
||
结构,
|
||
策略快照("new_type", 1),
|
||
授权摘要("a1", "s1"),
|
||
字段用途名="aiContext",
|
||
内容用途="generation",
|
||
运行用途="production",
|
||
)
|
||
assert 投影值(投影, {"观察": [{"公开": "桥下有人", "私记": "后续身份计划"}]}) == {
|
||
"观察": [{"公开": "桥下有人"}]
|
||
}
|
||
合同 = 生成字段合同(投影.允许字段)
|
||
assert set(合同["properties"]["观察"]["items"]["properties"]) == {"公开"}
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-open-type-owner",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="新题材类型",
|
||
when="选择实体生命周期",
|
||
then=["可归 B02,不能声明表名或脚本。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_开放类型只选择已登记实例维护者__a41005() -> None:
|
||
"""given 新题材类型;when 选择实体生命周期;then 可归 B02,不能声明表名或脚本。"""
|
||
from muse.元数据.接口 import 类型定义, 选择实例维护者
|
||
|
||
类型 = 类型定义("weather_spirit", "天气精灵", "world", "entity", ("entity",))
|
||
assert 选择实例维护者(类型, "entity") == "B02"
|
||
with pytest.raises(元数据错误):
|
||
选择实例维护者(replace(类型, 实例族=("public.character",)), "public.character")
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-change-impact",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="原枚举与可选字段",
|
||
when="缩窄枚举新增必填",
|
||
then=["明确转换对象并保留旧解释。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_结构升级要求转换且旧结构可解释__a41006() -> None:
|
||
"""given 原枚举与可选字段;when 缩窄枚举新增必填;then 明确转换对象并保留旧解释。"""
|
||
from muse.元数据.接口 import 分析变更, 校验值
|
||
|
||
字段 = 字段定义("mood", "情绪", "情绪", 值规则("enum", 枚举=("平静", "焦虑")))
|
||
旧 = 合成结构(结构定义("character", "character", 1, (字段,)))
|
||
新 = 合成结构(
|
||
结构定义(
|
||
"character",
|
||
"character",
|
||
2,
|
||
(replace(字段, 值=值规则("enum", 枚举=("平静",)), 必填=True),),
|
||
父版本=1,
|
||
)
|
||
)
|
||
影响 = 分析变更(旧, 新, ("entity-a",))
|
||
assert 影响.分类 == "incompatible"
|
||
assert 影响.转换字段 == ("mood",)
|
||
assert 影响.影响对象 == ("entity-a",)
|
||
assert 校验值(旧, {"情绪": "焦虑"}).值 == {"情绪": "焦虑"}
|
||
with pytest.raises(元数据错误):
|
||
校验值(新, {"情绪": "焦虑"})
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-editable-contract",
|
||
environment="离线,固定结构及合成候选值",
|
||
given="作品动态表单",
|
||
when="重发计算字数",
|
||
then=["只允许编辑字段且条件来自同源合同。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_表单修改不能重发只读字段__a41007() -> None:
|
||
"""given 作品动态表单;when 重发计算字数;then 只允许编辑字段且条件来自同源合同。"""
|
||
from muse.元数据.接口 import 校验字段修改, 生成结构合同, 读取内置结构
|
||
|
||
作品 = 合成结构(读取内置结构("novel_work"))
|
||
投影 = 投影字段(
|
||
作品,
|
||
策略快照("novel_work", 1),
|
||
授权摘要("author-v1", "source-v1"),
|
||
字段用途名="userEditable",
|
||
内容用途="planning",
|
||
运行用途="production",
|
||
)
|
||
assert 校验字段修改(投影, {"书名": "候雨"}).值 == {"书名": "候雨"}
|
||
with pytest.raises(元数据错误):
|
||
校验字段修改(投影, {"总字数": 999999})
|
||
合同 = 生成结构合同(合成结构(读取内置结构("craft")))
|
||
条件 = 合同["allOf"]
|
||
assert 条件[0]["then"]["required"] == ["埋设手法", "回收点", "记忆维持", "间隔纪律"]
|
||
assert 条件[1]["then"]["properties"] == {"间隔纪律": False}
|
||
assert 合同["properties"]["读者收益"]["maxItems"] == 2
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-field-edit-operations",
|
||
environment="离线纯合同",
|
||
given="结构与投影限制不同,已有实例含不展示的子字段",
|
||
when="提交可编辑叶子的修改",
|
||
then=["只改指定叶子,隐藏子值保留,越界修改被拒绝"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_逐字段修改保留未展示子字段__aa1201():
|
||
from muse.元数据.接口 import 字段变更, 校验字段变更
|
||
|
||
结构 = 合成结构(
|
||
结构定义(
|
||
"profile",
|
||
"profile",
|
||
1,
|
||
(
|
||
字段定义(
|
||
"p.info",
|
||
"资料",
|
||
"资料",
|
||
值规则(
|
||
"object",
|
||
字段=(
|
||
字段定义("p.public", "公开", "公开", 值规则("text")),
|
||
字段定义(
|
||
"p.private",
|
||
"私记",
|
||
"私记",
|
||
值规则("text"),
|
||
用途=字段用途(userEditable=False),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
)
|
||
)
|
||
投影 = 投影字段(
|
||
结构,
|
||
策略快照("profile", 0),
|
||
授权摘要("author", "source"),
|
||
字段用途名="userEditable",
|
||
内容用途="planning",
|
||
运行用途="production",
|
||
)
|
||
原 = {"资料": {"公开": "甲", "私记": "不改这里"}}
|
||
结果 = 校验字段变更(结构, 投影, 原, (字段变更(("资料", "公开"), "set", "乙"),))
|
||
assert 结果.值 == {"资料": {"公开": "乙", "私记": "不改这里"}}
|
||
assert 原["资料"]["公开"] == "甲"
|
||
with pytest.raises(元数据错误):
|
||
校验字段变更(结构, 投影, 原, (字段变更(("资料",), "set", {"公开": "乙"}),))
|
||
|
||
|
||
def _核对结构字段合同(结构, 预期):
|
||
assert 结构.schema_version == 预期["schema_version"]
|
||
实际 = {f.field_id: {"key": f.key, "type": f.值.类型, "required": f.必填} for f in 结构.字段}
|
||
assert 实际 == 预期["fields"], f"{结构.schema_id} 字段身份、类型或必填合同漂移"
|
||
|
||
|
||
@pytest.mark.case_id("NC-O09-FIELD-SWAP")
|
||
def test_字段总数相同不能掩盖字段替换():
|
||
import json
|
||
from pathlib import Path
|
||
|
||
from muse.元数据.接口 import 读取内置结构
|
||
|
||
结构 = 读取内置结构("character")
|
||
预期 = json.loads((Path(__file__).parents[1] / "夹具/内置结构字段合同.json").read_text())[
|
||
"schemas"
|
||
]["character"]
|
||
更换 = replace(
|
||
结构,
|
||
字段=(*结构.字段[:-1], replace(结构.字段[-1], field_id="unapproved", key="未批准字段")),
|
||
)
|
||
assert len(更换.字段) == len(结构.字段)
|
||
with pytest.raises(AssertionError, match="字段身份、类型或必填合同漂移"):
|
||
_核对结构字段合同(更换, 预期)
|