实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
339 lines
14 KiB
Python
339 lines
14 KiB
Python
"""真实 PostgreSQL 验证元数据版本与调用方提交点;每例独占可销毁数据库。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import Iterator
|
||
from pathlib import Path
|
||
|
||
import psycopg
|
||
import pytest
|
||
|
||
from muse.元数据.接口 import (
|
||
元数据服务,
|
||
元数据错误,
|
||
导入内置结构,
|
||
读取内置种子,
|
||
)
|
||
from muse.共享.调用身份 import 用途
|
||
from muse.基础设施.数据库.连接 import 连接
|
||
from muse.配置 import 数据库引用
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
|
||
|
||
@pytest.fixture
|
||
def 元数据库(数据库底座, tmp_path: Path) -> Iterator[数据库引用]:
|
||
# 此模板不导入种子;导入幂等、结构发布仍由每个测试实际执行。
|
||
with 数据库底座.借库(tmp_path / "元数据引用", (1, 2)) as 工厂:
|
||
yield 工厂[用途.维护].引用
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-seed-import-pg",
|
||
environment="隔离 PostgreSQL,每例独占新库",
|
||
given="隔离空库",
|
||
when="两次导入种子",
|
||
then=["24 个固定版本可回读且没有隐式绑定。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_内置发布可重复且不自动启用__a42001(元数据库: 数据库引用) -> None:
|
||
"""given 隔离空库;when 两次导入种子;then 24 个固定版本可回读且没有隐式绑定。"""
|
||
with 连接(元数据库, 用途标记=用途.维护) as 连:
|
||
服务 = 元数据服务(连)
|
||
with 连.transaction():
|
||
初次 = 导入内置结构(服务)
|
||
再次 = 导入内置结构(服务)
|
||
assert len(初次) == len(再次) == 24
|
||
assert sum(not f.field_id.startswith("common.") for 项 in 初次 for f in 项.字段) == 216
|
||
assert sum(f.field_id.startswith("common.") for 项 in 初次 for f in 项.字段) == 88
|
||
for 种子 in 读取内置种子():
|
||
assert 服务.读取结构(种子.结构.schema_id, 1) == 种子.结构
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.读取绑定("work-a:character")
|
||
assert 错误.value.错误码 == "SCHEMA_UNKNOWN"
|
||
|
||
|
||
def 安装动态结构(连: psycopg.Connection):
|
||
from muse.元数据.接口 import (
|
||
值规则,
|
||
合成结构,
|
||
启用命令,
|
||
字段定义,
|
||
字段用途,
|
||
类型定义,
|
||
结构定义,
|
||
)
|
||
|
||
服务 = 元数据服务(连)
|
||
类型 = 类型定义("weather_spirit", "天气精灵", "world", "entity", ("entity",))
|
||
基础 = 结构定义(
|
||
"weather_spirit",
|
||
"weather_spirit",
|
||
1,
|
||
(
|
||
字段定义(
|
||
"weather.name",
|
||
"名称",
|
||
"名称",
|
||
值规则("text"),
|
||
必填=True,
|
||
用途=字段用途(aiContext=True),
|
||
),
|
||
),
|
||
)
|
||
服务.登记类型(类型)
|
||
服务.登记结构候选(基础)
|
||
服务.发布结构(基础.schema_id, 1, 基础.内容哈希)
|
||
有效 = 合成结构(基础)
|
||
命令 = 启用命令("activate-initial", 有效.effective_schema_hash, 0, "author-a", "work-a:spirit")
|
||
绑定 = 服务.启用绑定("work-a:spirit", "work-a", 有效, 命令)
|
||
return 服务, 基础, 有效, 绑定, 命令
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-binding-rollback-history",
|
||
environment="隔离 PostgreSQL,每例独占新库",
|
||
given="已启用结构",
|
||
when="发布作品扩展、回滚与重复内置导入",
|
||
then=["版本固定且旧值可回读。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_扩展发布保留历史绑定且回滚不留半成品__a42002(元数据库: 数据库引用) -> None:
|
||
"""given 已启用结构;when 发布作品扩展、回滚与重复内置导入;then 版本固定且旧值可回读。"""
|
||
from muse.元数据.接口 import 作品扩展, 值规则, 启用命令, 字段定义
|
||
|
||
with 连接(元数据库, 用途标记=用途.维护) as 连:
|
||
with 连.transaction():
|
||
服务, 基础, _, 旧绑定, 原命令 = 安装动态结构(连)
|
||
扩展 = 作品扩展(
|
||
"work-a",
|
||
基础.schema_id,
|
||
1,
|
||
1,
|
||
(
|
||
字段定义(
|
||
"work-a.affinity", "天气亲和", "天气亲和", 值规则("enum", 枚举=("雨", "雪"))
|
||
),
|
||
),
|
||
)
|
||
服务.发布扩展(扩展, 扩展.内容哈希)
|
||
服务.发布扩展(扩展, 扩展.内容哈希)
|
||
新结构 = 服务.读取有效结构(基础.schema_id, 1, work_id="work-a", extension_version=1)
|
||
assert 服务.读取绑定("work-a:spirit") == 旧绑定
|
||
with pytest.raises(RuntimeError), 连.transaction():
|
||
服务.启用绑定(
|
||
"work-a:spirit",
|
||
"work-a",
|
||
新结构,
|
||
启用命令(
|
||
"activate-fail", 新结构.effective_schema_hash, 1, "author-a", "work-a:spirit"
|
||
),
|
||
)
|
||
raise RuntimeError("第二参与者失败")
|
||
with 连.transaction():
|
||
assert 服务.读取绑定("work-a:spirit") == 旧绑定
|
||
新绑定 = 服务.启用绑定(
|
||
"work-a:spirit",
|
||
"work-a",
|
||
新结构,
|
||
启用命令(
|
||
"activate-ok", 新结构.effective_schema_hash, 1, "author-a", "work-a:spirit"
|
||
),
|
||
)
|
||
assert 新绑定.extension_version == 1
|
||
导入内置结构(服务)
|
||
assert 服务.读取绑定("work-a:spirit") == 新绑定
|
||
assert 服务.读取结构(基础.schema_id, 1) == 基础
|
||
# 原命令重放返回当时回执,不把当前绑定降回原版本。
|
||
assert (
|
||
服务.启用绑定(
|
||
"work-a:spirit", "work-a", 服务.读取有效结构(基础.schema_id, 1), 原命令
|
||
)
|
||
== 旧绑定
|
||
)
|
||
assert 服务.读取绑定("work-a:spirit") == 新绑定
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-reviewed-version-binding",
|
||
environment="隔离 PostgreSQL,每例独占新库",
|
||
given="已启用结构",
|
||
when="候选未发布或预期版本过期",
|
||
then=["不改变当前绑定。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_候选未发布与过期绑定均不可启用__a42003(元数据库: 数据库引用) -> None:
|
||
"""given 已启用结构;when 候选未发布或预期版本过期;then 不改变当前绑定。"""
|
||
from dataclasses import replace
|
||
|
||
from muse.元数据.接口 import 合成结构, 启用命令
|
||
|
||
with 连接(元数据库, 用途标记=用途.维护) as 连:
|
||
with 连.transaction():
|
||
服务, 基础, _, 绑定, _ = 安装动态结构(连)
|
||
新版 = replace(基础, schema_version=2, 父版本=1)
|
||
服务.登记结构候选(新版)
|
||
新 = 合成结构(新版)
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.启用绑定(
|
||
绑定.target_ref,
|
||
绑定.work_id,
|
||
新,
|
||
启用命令("draft", 新.effective_schema_hash, 1, "author-a", 绑定.target_ref),
|
||
)
|
||
assert 错误.value.错误码 == "SCHEMA_UNKNOWN"
|
||
服务.发布结构(新版.schema_id, 2, 新版.内容哈希)
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.启用绑定(
|
||
绑定.target_ref,
|
||
绑定.work_id,
|
||
新,
|
||
启用命令("stale", 新.effective_schema_hash, 0, "author-a", 绑定.target_ref),
|
||
)
|
||
assert 错误.value.错误码 == "SCHEMA_STALE"
|
||
assert 服务.读取绑定(绑定.target_ref) == 绑定
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-concurrent-submit-guard",
|
||
environment="隔离 PostgreSQL,每例独占新库",
|
||
given="已预览候选",
|
||
when="提交持有结构锁且另连接切换",
|
||
then=["切换等待且旧请求在切换后拒绝。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
@pytest.mark.parametrize(
|
||
"变更类别", ["structure", "policy"], ids=["schema-switch", "policy-revoke"]
|
||
)
|
||
def test_结构策略切换与业务提交共享锁__a42004(元数据库: 数据库引用, 变更类别: str) -> None:
|
||
"""given 已预览候选;when 提交持有结构锁且另连接切换;then 切换等待且旧请求在切换后拒绝。"""
|
||
from concurrent.futures import ThreadPoolExecutor
|
||
from dataclasses import asdict, replace
|
||
|
||
from muse.元数据.接口 import (
|
||
合成结构,
|
||
启用命令,
|
||
字段限制,
|
||
定义哈希,
|
||
投影字段,
|
||
授权摘要,
|
||
策略快照,
|
||
)
|
||
|
||
授权 = 授权摘要("author-v1", "source-v1")
|
||
with 连接(元数据库, 用途标记=用途.维护) as 连:
|
||
with 连.transaction():
|
||
服务, 基础, 原结构, 绑定, _ = 安装动态结构(连)
|
||
新版 = replace(基础, schema_version=2, 父版本=1)
|
||
服务.登记结构候选(新版)
|
||
服务.发布结构(新版.schema_id, 2, 新版.内容哈希)
|
||
新结构 = 合成结构(新版)
|
||
# 仅夹具业务 owner 拥有此表;元数据模块从不访问它。
|
||
连.execute("CREATE TABLE public.meta_owner_probe (id int PRIMARY KEY, body text)")
|
||
预览 = 投影字段(
|
||
原结构,
|
||
服务.当前策略(基础.type_id),
|
||
授权,
|
||
字段用途名="userEditable",
|
||
内容用途="planning",
|
||
运行用途="production",
|
||
)
|
||
|
||
def 切换(限时: bool) -> None:
|
||
with 连接(元数据库, 用途标记=用途.维护) as 第二连, 第二连.transaction():
|
||
if 限时:
|
||
第二连.execute("SET LOCAL lock_timeout='250ms'")
|
||
第二服务 = 元数据服务(第二连)
|
||
if 变更类别 == "structure":
|
||
第二服务.启用绑定(
|
||
绑定.target_ref,
|
||
绑定.work_id,
|
||
新结构,
|
||
启用命令(
|
||
"switch", 新结构.effective_schema_hash, 1, "author-a", 绑定.target_ref
|
||
),
|
||
)
|
||
else:
|
||
新策略 = 策略快照(
|
||
基础.type_id, 1, (字段限制("weather.name", ("userEditable",)),)
|
||
)
|
||
第二服务.更新策略(
|
||
新策略,
|
||
启用命令(
|
||
"revoke",
|
||
定义哈希(asdict(新策略)),
|
||
0,
|
||
"author-a",
|
||
f"type:{基础.type_id}",
|
||
),
|
||
)
|
||
|
||
with 连.transaction():
|
||
服务.保护提交(
|
||
绑定.target_ref,
|
||
预览.effective_schema_hash,
|
||
预览.projection_version,
|
||
授权,
|
||
字段用途名="userEditable",
|
||
内容用途="planning",
|
||
运行用途="production",
|
||
)
|
||
with ThreadPoolExecutor(max_workers=1) as 池:
|
||
with pytest.raises(psycopg.errors.LockNotAvailable):
|
||
池.submit(切换, True).result(timeout=5)
|
||
连.execute("INSERT INTO public.meta_owner_probe VALUES (1, '作者确认的天气精灵')")
|
||
切换(False)
|
||
with 连.transaction():
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.保护提交(
|
||
绑定.target_ref,
|
||
预览.effective_schema_hash,
|
||
预览.projection_version,
|
||
授权,
|
||
字段用途名="userEditable",
|
||
内容用途="planning",
|
||
运行用途="production",
|
||
)
|
||
assert 错误.value.错误码 == (
|
||
"SCHEMA_STALE" if 变更类别 == "structure" else "PROJECTION_STALE"
|
||
)
|
||
assert 连.execute("SELECT id FROM public.meta_owner_probe").fetchall() == [(1,)]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-meta-immutable-extensions",
|
||
environment="隔离 PostgreSQL,每例独占新库",
|
||
given="已发布结构与作品扩展",
|
||
when="覆盖同版本或删除既有扩展",
|
||
then=["历史定义保持不变。"],
|
||
contract="docs/系统架构/新版设计/接口契约/元数据投影与版本.md",
|
||
)
|
||
def test_已发布定义与扩展追加合同不可绕过__a42005(元数据库: 数据库引用) -> None:
|
||
"""given 已发布结构与作品扩展;when 覆盖同版本或删除既有扩展;then 历史定义保持不变。"""
|
||
from dataclasses import replace
|
||
|
||
from muse.元数据.接口 import 作品扩展, 值规则, 字段定义
|
||
|
||
with 连接(元数据库, 用途标记=用途.维护) as 连, 连.transaction():
|
||
服务, 基础, _, _, _ = 安装动态结构(连)
|
||
assert 服务.读取类型(基础.type_id).实例族 == ("entity",)
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.登记结构候选(replace(基础, 字段=()))
|
||
assert 错误.value.错误码 == "SCHEMA_CONFLICT"
|
||
扩展 = 作品扩展(
|
||
"work-a",
|
||
基础.schema_id,
|
||
1,
|
||
1,
|
||
(字段定义("work-a.temp", "体温", "体温", 值规则("number")),),
|
||
)
|
||
服务.发布扩展(扩展, 扩展.内容哈希)
|
||
丢字段 = replace(扩展, extension_version=2, 字段=())
|
||
with pytest.raises(元数据错误) as 错误:
|
||
服务.发布扩展(丢字段, 丢字段.内容哈希)
|
||
assert 错误.value.错误码 == "FIELD_NOT_ALLOWED"
|
||
assert 服务.读取结构(基础.schema_id, 1) == 基础
|
||
assert (
|
||
服务.读取有效结构(基础.schema_id, 1, work_id="work-a", extension_version=1).扩展 == 扩展
|
||
)
|