- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
371 lines
14 KiB
Python
371 lines
14 KiB
Python
"""原S02→合格判据→最小证明→作者启用;仅隔离PG/合成HTTP,不认证外部文学效果。
|
||
|
||
runtime配置验证由测试夹具模拟,用来走有证据的分支;它不是生产配置批准。
|
||
"""
|
||
|
||
import json
|
||
import os
|
||
from dataclasses import replace
|
||
from datetime import UTC, datetime, timedelta
|
||
from uuid import uuid4
|
||
|
||
import psycopg
|
||
import pytest
|
||
import test_上下文快照与索引写入 as 方法测试
|
||
import test_实际效果判据 as 效果测试
|
||
import test_生产评测权限隔离 as 数据测试
|
||
import test_评测执行与失败收敛 as 运行测试
|
||
|
||
from muse.共享.调用身份 import 用途
|
||
from muse.共享.错误 import Muse错误
|
||
from muse.效果评测.接口 import 实验请求, 方法数据集发布, 评测服务, 评测错误, 读取启用凭据
|
||
from muse.知识方法.接口 import 状态命令, 绑定命令, 读取绑定材料
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
执行环境 = 运行测试.执行环境
|
||
方法环境 = 方法测试.方法环境
|
||
|
||
|
||
def _响应工厂():
|
||
config = 效果测试._响应工厂()
|
||
original = config["generator_output"]
|
||
|
||
def generate(request, script):
|
||
data = json.loads(request["input"])
|
||
if data["instruction"].startswith("渡口"):
|
||
text = "他迎着细雨走到门前。" if "方法材料" in data["context"] else "他站在山门旁等候。"
|
||
return {"paragraphs": [{"text": data["instruction"] + "。" + text}]}
|
||
return original(request, script)
|
||
|
||
return {**config, "generator_output": generate}
|
||
|
||
|
||
参数 = {
|
||
**效果测试.参数,
|
||
"output_factory": _响应工厂,
|
||
"runtime_validation_fixture": True,
|
||
}
|
||
|
||
|
||
def _确认方法(env, method_env):
|
||
app, author, methods, source, _, pools = method_env
|
||
req = 方法测试._提案(str(uuid4()), "启用桥接方法", str(source.source_id), owner=author.作者)
|
||
proposed = methods.提出方法(author, "enablement-method", req)
|
||
方法测试._确认(methods, author, proposed["target_ref"])
|
||
version = methods.读取方法详情(author, req.method_id)["versions"][0]
|
||
return {
|
||
**env,
|
||
"actor": replace(env["actor"], 作者=author.作者),
|
||
"business": app,
|
||
"owner": author,
|
||
"methods": methods,
|
||
"method_id": req.method_id,
|
||
"version": version,
|
||
"pools": pools,
|
||
}
|
||
|
||
|
||
def _实际方法留出(env, certificate, 登记=None):
|
||
svc = 评测服务(env["pools"][用途.维护])
|
||
data = svc.发布方法数据集(
|
||
replace(env["actor"], 用途=用途.维护),
|
||
方法数据集发布.model_validate(
|
||
{
|
||
"dataset_id": "enablement-method-holdout",
|
||
"revision": 1,
|
||
"samples": 效果测试._数据样本(10),
|
||
"method_version_id": str(env["version"]["version_id"]),
|
||
"approval_ref": "synthetic:method-material",
|
||
"expires_at": datetime.now(UTC) + timedelta(hours=1),
|
||
}
|
||
),
|
||
)
|
||
req = 实验请求.model_validate(
|
||
{
|
||
**env["request"].model_dump(mode="json"),
|
||
"dataset_version_id": data["version_id"],
|
||
"dataset_hash": data["public_hash"],
|
||
"target": data["method_target"],
|
||
"split": "holdout",
|
||
"max_cost_usd": "20",
|
||
"evaluation_goal": "qualification",
|
||
"effect_policy": "writer-effect-v1",
|
||
"calibration_use": {
|
||
"policy": 效果测试.标定策略,
|
||
"references": [
|
||
{
|
||
"experiment_id": certificate["result"]["experiment_id"],
|
||
"receipt_hash": certificate["receipt_hash"],
|
||
}
|
||
],
|
||
},
|
||
}
|
||
)
|
||
exp = (
|
||
登记(req)
|
||
if 登记
|
||
else env["app"].要求评测().创建实验(env["actor"], "enablement-holdout", req)
|
||
)
|
||
return {**env, "exp": exp, "request": req}
|
||
|
||
|
||
@pytest.mark.parametrize("执行环境", [参数], indirect=True)
|
||
@pytest.mark.timeout(480)
|
||
def test_实际方法凭据作者启用及标定停止阻断新消费__25f901(执行环境, 方法环境, tmp_path):
|
||
from fastapi.testclient import TestClient
|
||
|
||
from muse.接入.http.应用 import 创建应用
|
||
from muse.配置 import 读取配置
|
||
|
||
env = _确认方法(执行环境, 方法环境)
|
||
certificate = 效果测试._校准(env)
|
||
dest = _实际方法留出(env, certificate)
|
||
assert 效果测试._完成(dest)["assessment"]["gate_b"]["status"] == "passed"
|
||
svc, eid = env["app"].要求评测(), dest["exp"]["experiment_id"]
|
||
sealed = svc.封存效果判据(env["actor"], eid)
|
||
assert sealed["assessment"]["validation_modes"] == ["runtime"]
|
||
now = datetime.now(UTC)
|
||
arguments = dict(批准引用="synthetic:export-proof", 有效期=now + timedelta(hours=2))
|
||
|
||
def export():
|
||
return svc.导出启用凭据(
|
||
env["actor"],
|
||
eid,
|
||
env["pools"][用途.维护],
|
||
replace(env["actor"], 用途=用途.维护),
|
||
**arguments,
|
||
)
|
||
|
||
proof = export()
|
||
assert export() == proof
|
||
rid = proof["receipt_id"]
|
||
assert proof["payload"]["target"] == dest["exp"]["conditions"]["target"]
|
||
assert not any(
|
||
word in json.dumps(proof, ensure_ascii=False)
|
||
for word in [
|
||
"paragraphs",
|
||
"candidate_scores",
|
||
"ORACLE-",
|
||
"left",
|
||
"right",
|
||
"source_authorization",
|
||
]
|
||
)
|
||
methods, owner, mid = env["methods"], env["owner"], env["method_id"]
|
||
assert methods.读取方法详情(owner, mid)["state"] == "confirmed"
|
||
assert methods.核对启用(owner, mid, rid)["receipt_hash"] == proof["receipt_hash"]
|
||
for purpose in (用途.生产, 用途.评测):
|
||
with (
|
||
env["pools"][purpose].连接() as conn,
|
||
pytest.raises(psycopg.errors.InsufficientPrivilege),
|
||
):
|
||
conn.execute(
|
||
"INSERT INTO public.muse_enablement_receipt "
|
||
"SELECT * FROM public.muse_enablement_receipt"
|
||
)
|
||
with (
|
||
env["pools"][用途.生产].连接() as conn,
|
||
pytest.raises(psycopg.errors.InsufficientPrivilege),
|
||
):
|
||
conn.execute("SELECT * FROM oracle.muse_dataset_answers")
|
||
with env["pools"][用途.生产].连接(只读=True) as conn:
|
||
assert 读取启用凭据(conn, owner.作者, rid) == proof
|
||
with pytest.raises(Muse错误):
|
||
methods.核对启用(replace(owner, 作者="wrong-owner"), mid, rid)
|
||
|
||
config = 数据测试._配置文件(env["pools"][用途.生产], tmp_path)
|
||
config.write_text(config.read_text().replace("eval-author", owner.作者))
|
||
browser_receipt = None
|
||
if os.environ.get("MUSE_RUN_BROWSER") == "1":
|
||
browser_receipt = _浏览器启用(env, config, rid, tmp_path)
|
||
with TestClient(创建应用(读取配置(config)), headers={"origin": "http://testserver"}) as client:
|
||
assert (
|
||
client.post(
|
||
"/api/v1/session", json={"password": "synthetic-evaluation-only"}
|
||
).status_code
|
||
== 200
|
||
)
|
||
assert client.get(f"/api/v1/methods/{mid}/enablement/{rid}").status_code == 200
|
||
body = {
|
||
"command_id": browser_receipt["command_id"]
|
||
if browser_receipt
|
||
else "enable-tested-method",
|
||
"method_id": mid,
|
||
"action": "enable",
|
||
"启用凭据": rid,
|
||
"expected_state_revision": 1,
|
||
}
|
||
assert client.post(f"/api/v1/methods/{uuid4()}/state", json=body).status_code == 403
|
||
adopted = client.post(f"/api/v1/methods/{mid}/state", json=body)
|
||
assert adopted.status_code == 200, adopted.text
|
||
assert adopted.json()["results"][0]["state"] == "enabled"
|
||
if browser_receipt:
|
||
assert adopted.json() == browser_receipt
|
||
assert client.post(f"/api/v1/methods/{mid}/state", json=body).json() == adopted.json()
|
||
|
||
methods.绑定方法(
|
||
owner,
|
||
"bind-enabled-method",
|
||
绑定命令(
|
||
"method-work",
|
||
"work",
|
||
"work",
|
||
mid,
|
||
str(env["version"]["version_id"]),
|
||
),
|
||
)
|
||
with env["pools"][用途.生产].连接(只读=True) as conn:
|
||
material = 读取绑定材料(conn, "method-work", 内容用途="generation", 运行用途="production")
|
||
assert len(material) == 1
|
||
with pytest.raises(Muse错误, match="当前用途尚未评测"):
|
||
读取绑定材料(conn, "method-work", 内容用途="planning", 运行用途="production")
|
||
usages = methods.列出消费(owner, str(env["version"]["version_id"]))
|
||
assert len(usages) == 1 and usages[0]["kind"] == "enablement"
|
||
assert usages[0]["result_ref"] == "B10.enablement:" + rid
|
||
before = len(env["received"])
|
||
svc.取消实验(env["actor"], certificate["result"]["experiment_id"], "stop-calibration")
|
||
with env["pools"][用途.生产].连接(只读=True) as conn:
|
||
assert 读取启用凭据(conn, owner.作者, rid)["stopped"]
|
||
with pytest.raises(Muse错误, match="停止"):
|
||
读取绑定材料(conn, "method-work", 内容用途="generation", 运行用途="production")
|
||
methods.变更方法状态(
|
||
owner, "disable-tested-method", 状态命令(mid, "disable", expected_state_revision=1)
|
||
)
|
||
with pytest.raises(Muse错误, match="停止"):
|
||
methods.变更方法状态(
|
||
owner,
|
||
"reenable-stopped-method",
|
||
状态命令(mid, "enable", expected_state_revision=1, 启用凭据=rid),
|
||
)
|
||
with pytest.raises(评测错误):
|
||
export()
|
||
assert methods.列出消费(owner, str(env["version"]["version_id"])) == usages
|
||
assert len(env["received"]) == before == 85
|
||
|
||
|
||
def test_未有目标证明不能启用且公开表不可写__25f902(方法环境):
|
||
app, owner, methods, source, _, pools = 方法环境
|
||
request = 方法测试._提案(str(uuid4()), source_id=str(source.source_id), owner=owner.作者)
|
||
proposed = methods.提出方法(owner, "unverified-method", request)
|
||
方法测试._确认(methods, owner, proposed["target_ref"])
|
||
for proof in ("not-a-uuid", str(uuid4())):
|
||
with pytest.raises(Muse错误):
|
||
methods.变更方法状态(
|
||
owner,
|
||
proof,
|
||
状态命令(
|
||
request.method_id,
|
||
"enable",
|
||
expected_state_revision=1,
|
||
启用凭据=proof,
|
||
),
|
||
)
|
||
assert methods.读取方法详情(owner, request.method_id)["state"] == "confirmed"
|
||
with pools[用途.生产].连接() as conn, pytest.raises(psycopg.errors.InsufficientPrivilege):
|
||
conn.execute("SELECT * FROM evaluation.muse_effect_assessment")
|
||
for purpose in (用途.生产, 用途.评测):
|
||
with pools[purpose].连接(只读=True) as conn:
|
||
for privilege in ("INSERT", "UPDATE", "DELETE"):
|
||
assert not conn.execute(
|
||
"SELECT has_table_privilege(current_user,%s,%s)",
|
||
("public.muse_enablement_receipt", privilege),
|
||
).fetchone()[0]
|
||
assert conn.execute(
|
||
"SELECT has_table_privilege(current_user,%s,'SELECT')",
|
||
("public.muse_enablement_status",),
|
||
).fetchone()[0]
|
||
|
||
|
||
def _浏览器启用(env, config, rid, tmp_path):
|
||
import socket
|
||
import subprocess
|
||
from contextlib import asynccontextmanager
|
||
from pathlib import Path
|
||
from threading import Event, Thread
|
||
|
||
import uvicorn
|
||
|
||
from muse.接入.http.应用 import 创建应用
|
||
from muse.配置 import 服务配置, 读取配置
|
||
|
||
password = tmp_path / "method-browser-key"
|
||
password.write_text("synthetic-evaluation-only")
|
||
password.chmod(0o600)
|
||
sock = socket.socket()
|
||
sock.bind(("127.0.0.1", 0))
|
||
port = sock.getsockname()[1]
|
||
base = f"http://127.0.0.1:{port}"
|
||
app = 创建应用(
|
||
replace(
|
||
读取配置(config),
|
||
HTTP=服务配置(
|
||
str(password),
|
||
作者ID=env["owner"].作者,
|
||
端口=port,
|
||
公开地址=base,
|
||
允许来源=(base,),
|
||
),
|
||
)
|
||
)
|
||
previous = app.router.lifespan_context
|
||
ready = Event()
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(instance):
|
||
async with previous(instance):
|
||
ready.set()
|
||
yield
|
||
|
||
app.router.lifespan_context = lifespan
|
||
server = uvicorn.Server(
|
||
uvicorn.Config(
|
||
app,
|
||
log_level="warning",
|
||
access_log=False,
|
||
timeout_graceful_shutdown=3,
|
||
)
|
||
)
|
||
thread = Thread(target=server.run, kwargs={"sockets": [sock]}, daemon=True)
|
||
output = Path(os.environ.get("MUSE_BROWSER_ARTIFACTS", str(tmp_path / "browser"))).resolve()
|
||
output.mkdir(parents=True, exist_ok=True)
|
||
process_env = {
|
||
**os.environ,
|
||
"MUSE_WORKBENCH_URL": base,
|
||
"MUSE_AUTHOR_PASSWORD_FILE": str(password),
|
||
"MUSE_METHOD_ID": env["method_id"],
|
||
"MUSE_ENABLEMENT_RECEIPT": rid,
|
||
}
|
||
chrome = Path("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")
|
||
if chrome.exists():
|
||
process_env.setdefault("MUSE_BROWSER_EXECUTABLE", str(chrome))
|
||
thread.start()
|
||
try:
|
||
assert ready.wait(10)
|
||
with (output / "浏览器.json").open("w") as log:
|
||
result = subprocess.run(
|
||
[
|
||
"pnpm",
|
||
"exec",
|
||
"playwright",
|
||
"test",
|
||
"方法启用.spec.ts",
|
||
"--reporter=json",
|
||
"--output",
|
||
str(output / "产物"),
|
||
],
|
||
cwd=Path(__file__).parents[2] / "web",
|
||
env=process_env,
|
||
stdout=log,
|
||
stderr=subprocess.STDOUT,
|
||
timeout=90,
|
||
)
|
||
assert result.returncode == 0, str(output / "浏览器.json")
|
||
receipts = list(output.rglob("启用回执.json"))
|
||
assert len(receipts) == 1
|
||
return json.loads(receipts[0].read_text())
|
||
finally:
|
||
server.should_exit = True
|
||
thread.join(timeout=10)
|
||
sock.close()
|
||
assert not thread.is_alive()
|