deliver: 交付收口(引擎代为提交)

This commit is contained in:
agent.develop 2026-09-18 16:17:15 +08:00
parent 80ff678332
commit 6178449816
13 changed files with 440 additions and 23 deletions

View File

@ -3,17 +3,36 @@
"""pbl_compiler —— PBL Compiler v1确定性编译 + Game DefinitionM3a/M3b
注册三处同步之 必须导出 init.py 里的全部契约函数漏一行 .dspy 调用即 NameError
QC #2 整改本轮api.py __all__ 声明的 15 个 Web 契约全部在此导出,
此前漏接线的 8 task_get / task_list / game_definition_get /
game_definition_get_by_blueprint / verify_determinism / version_register /
version_get / version_diff已补齐其中 verify_determinism
US-11 / F-CP-03同输入重编译指纹相等的验收入口属核心需求
"""
from pbl_compiler.init import load_pbl_compiler
from pbl_compiler.api import (
# —— 编译主流程 / 预览 / 对比 ——
pbl_compiler_compile,
pbl_compiler_preview,
pbl_compiler_compare,
# —— 编译任务QC #2 补task_get / task_list——
pbl_compiler_task_get,
pbl_compiler_task_list,
# —— Game Definition 产物QC #2 补get / get_by_blueprint——
pbl_game_definition_get,
pbl_game_definition_get_by_blueprint,
# —— 确定性验证QC #2 补US-11/F-CP-03 验收入口)——
pbl_compiler_verify_determinism,
# —— 编译器版本管理QC #2 补register / get / diff——
pbl_compiler_version_register,
pbl_compiler_version_get,
pbl_compiler_version_list,
pbl_compiler_version_diff,
pbl_compiler_version_save,
# —— 能力注册表 ——
pbl_capability_list,
pbl_capability_register,
)
__all__ = [
@ -21,9 +40,16 @@ __all__ = [
'pbl_compiler_compile',
'pbl_compiler_preview',
'pbl_compiler_compare',
'pbl_compiler_task_get',
'pbl_compiler_task_list',
'pbl_game_definition_get',
'pbl_game_definition_get_by_blueprint',
'pbl_compiler_verify_determinism',
'pbl_compiler_version_register',
'pbl_compiler_version_get',
'pbl_compiler_version_list',
'pbl_compiler_version_diff',
'pbl_compiler_version_save',
'pbl_capability_list',
'pbl_capability_register',
]

View File

@ -26,11 +26,13 @@ from __future__ import annotations
import hashlib
import inspect
import json
import os
import time
from pbl_common.api import (
PblError,
actor_id,
get_module_dbname,
json_dump,
now_str,
sql_exec,
@ -80,7 +82,34 @@ COMPILE_GATE_MIN_STATE = 'pbl_ready'
TASK_PENDING, TASK_RUNNING, TASK_SUCCESS, TASK_FAILED = (
'pending', 'running', 'success', 'failed')
DB = 'pbl'
#: 库名解析QC #4 整改)——**禁止硬编码 DB 名**。
#: 统一走 pbl_common.get_module_dbname('pbl_compiler')
#: 优先级 ServerEnv.get_module_dbname 映射 > 环境变量 PBL_DBNAME_PBL_COMPILER
#: > PBL_DBNAME_DEFAULT > 派生 pbls_pbl_compiler。
#: 换库/多租户部署由宿主应用 app/{app}.py 的 get_module_dbname 决定,模块零感知。
MODULE_NAME = 'pbl_compiler'
_dbname_cache = {}
def _dbname():
"""取本模块库名(每次调用解析,宿主 init() 后映射即生效;带进程内缓存兜底)。"""
name = _dbname_cache.get('db')
if name:
return name
try:
name = get_module_dbname(MODULE_NAME)
except Exception: # noqa: BLE001 - 解析失败不静默走错库
name = None
if not name:
name = os.environ.get('PBL_DBNAME_PBL_COMPILER') or 'pbls_pbl_compiler'
_dbname_cache['db'] = str(name)
return _dbname_cache['db']
def set_dbname(name):
"""测试/宿主注入库名(显式覆盖,避免任何硬编码)。"""
_dbname_cache['db'] = str(name) if name else None
return _dbname_cache['db']
_T_GD = 'pbl_game_definition'
_T_TASK = 'pbl_compile_task'
_T_CV = 'pbl_compiler_version'
@ -137,11 +166,11 @@ def _actor():
def _crud(table):
return tenant_crud(table, module='pbl_compiler', db=DB)
return tenant_crud(table, module=MODULE_NAME, db=_dbname())
async def _rows(sql, params):
return list(await _await_maybe(sql_rows(sql, params, DB)) or [])
return list(await _await_maybe(sql_rows(sql, params, _dbname())) or [])
async def _one(sql, params):
@ -150,7 +179,7 @@ async def _one(sql, params):
async def _exec(sql, params):
return await _await_maybe(sql_exec(sql, params, DB))
return await _await_maybe(sql_exec(sql, params, _dbname()))
async def _audit(action, table, row_id, detail=None):

View File

@ -3,6 +3,9 @@
"""`load_pbl_compiler()` —— pbl_compiler 模块唯一挂载入口。
注册三处同步之 env.<契约名> = <契约名> 定义在 api.py 导出在 __init__.py
QC #2 整改本轮15 个 Web 契约全部 env 注册,此前漏注册的 8 个已补齐,
.dspy 路由wwwroot/api/*.dspy 直接调用同名全局全部可达
"""
from ahserver.serverenv import ServerEnv
@ -10,22 +13,42 @@ from pbl_compiler.api import (
pbl_compiler_compile,
pbl_compiler_preview,
pbl_compiler_compare,
pbl_compiler_task_get,
pbl_compiler_task_list,
pbl_game_definition_get,
pbl_game_definition_get_by_blueprint,
pbl_compiler_verify_determinism,
pbl_compiler_version_register,
pbl_compiler_version_get,
pbl_compiler_version_list,
pbl_compiler_version_diff,
pbl_compiler_version_save,
pbl_capability_list,
pbl_capability_register,
)
#: 契约名 → 实现单一事实源__init__.py 导出、env 注册、load_path.py RBAC 三处都从这里派生)
CONTRACTS = {
'pbl_compiler_compile': pbl_compiler_compile,
'pbl_compiler_preview': pbl_compiler_preview,
'pbl_compiler_compare': pbl_compiler_compare,
'pbl_compiler_task_get': pbl_compiler_task_get,
'pbl_compiler_task_list': pbl_compiler_task_list,
'pbl_game_definition_get': pbl_game_definition_get,
'pbl_game_definition_get_by_blueprint': pbl_game_definition_get_by_blueprint,
'pbl_compiler_verify_determinism': pbl_compiler_verify_determinism,
'pbl_compiler_version_register': pbl_compiler_version_register,
'pbl_compiler_version_get': pbl_compiler_version_get,
'pbl_compiler_version_list': pbl_compiler_version_list,
'pbl_compiler_version_diff': pbl_compiler_version_diff,
'pbl_compiler_version_save': pbl_compiler_version_save,
'pbl_capability_list': pbl_capability_list,
'pbl_capability_register': pbl_capability_register,
}
def load_pbl_compiler():
env = ServerEnv()
env.pbl_compiler_compile = pbl_compiler_compile
env.pbl_compiler_preview = pbl_compiler_preview
env.pbl_compiler_compare = pbl_compiler_compare
env.pbl_compiler_version_list = pbl_compiler_version_list
env.pbl_compiler_version_save = pbl_compiler_version_save
env.pbl_capability_list = pbl_capability_list
env.pbl_capability_register = pbl_capability_register
for name, fn in CONTRACTS.items():
setattr(env, name, fn)
return 'pbl_compiler'

View File

@ -4,8 +4,20 @@
约定
- 路径 = 模块自动路由 `/pbl_compiler/api/<契约>.dspy`不带端口不带 /wss 前缀
- 角色 `logined` = 登录即可访问的读接口写接口按角色分级teacher/admin
- 角色 `logined` = 登录即可访问写接口compile/register/save按角色分级
- apps/pbls/build.sh 8 步调用 `register()`rbac CLI 不在位时打印清单不静默跳过
QC #2 整改本轮PATHS 补齐 8 个此前漏注册的契约端点task_get/task_list/
game_definition_get/game_definition_get_by_blueprint/verify_determinism/
version_register/version_get/version_diff init.py CONTRACTS__init__.py
__all__ 三处同步 15 .dspy 端点
QC #3 整改本轮register() 格式串占位符与实参数量错位导致
`TypeError: not enough arguments for format string`
39 '[%s] rbac paths: total=%d ok=%d pending=%d' 4 个占位符只传 3 个参数
44 '%%-12s %s' 转义错位
现改为 % (MODULE, len(PATHS), done, len(missing)) % (role, path)
python3 -c 实测 register() 可跑通 scripts/test_m3a_selfcheck.py 4 组断言
"""
import os
import subprocess
@ -13,32 +25,57 @@ import sys
MODULE = 'pbl_compiler'
# (path, role)
# (path, role) —— 与 init.py CONTRACTS 一一对应15 个端点,无通配符)
PATHS = [
# 编译主流程 / 预览 / 对比
('/pbl_compiler/api/pbl_compiler_compile.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_preview.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_compare.dspy', 'logined'),
# 编译任务QC #2 补)
('/pbl_compiler/api/pbl_compiler_task_get.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_task_list.dspy', 'logined'),
# Game Definition 产物QC #2 补)
('/pbl_compiler/api/pbl_game_definition_get.dspy', 'logined'),
('/pbl_compiler/api/pbl_game_definition_get_by_blueprint.dspy', 'logined'),
# 确定性验证QC #2 补US-11/F-CP-03 验收入口)
('/pbl_compiler/api/pbl_compiler_verify_determinism.dspy', 'logined'),
# 编译器版本管理QC #2 补register/get/diff
('/pbl_compiler/api/pbl_compiler_version_register.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_version_get.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_version_list.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_version_diff.dspy', 'logined'),
('/pbl_compiler/api/pbl_compiler_version_save.dspy', 'logined'),
# 能力注册表
('/pbl_compiler/api/pbl_capability_list.dspy', 'logined'),
('/pbl_compiler/api/pbl_capability_register.dspy', 'logined'),
]
def register():
"""逐条注册 RBAC 路径。rbac CLI 不在位时收集为 pending 并打印(不静默跳过)。
:return: True 全部注册成功False 存在 pending调用方据此决定退出码
"""
tool = os.environ.get('RBAC_SET_PERM', 'set_role_perm.py')
py = sys.executable if os.environ.get('PY') else 'python3'
done, missing = 0, []
for path, role in PATHS:
if subprocess.call([sys.executable if os.environ.get('PY') else 'python3',
tool, role, path],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0:
try:
rc = subprocess.call([py, tool, role, path],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except Exception: # noqa: BLE001 - CLI 缺失不崩,记 pending
rc = 1
if rc == 0:
done += 1
else:
missing.append((path, role))
print('[%s] rbac paths: total=%d ok=%d pending=%d' %(len(PATHS), done, len(missing)))
# QC #3 修复4 个占位符 ↔ 4 个实参
print('[%s] rbac paths: total=%d ok=%d pending=%d'
% (MODULE, len(PATHS), done, len(missing)))
for path, role in missing:
print(' PENDING %%-12s %s' %(role, path))
# QC #3 修复:去掉多余的 % 转义2 个占位符 ↔ 2 个实参
print(' PENDING %-12s %s' % (role, path))
return len(missing) == 0

View File

@ -0,0 +1,270 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbl_compiler M3a 自检脚本QC #1 整改:真实落盘,可执行)。
运行
cd modules/pbl_compiler && python3 scripts/test_m3a_selfcheck.py
python3 -m pytest scripts/test_m3a_selfcheck.py -q
覆盖 QC 退回意见要求的断言组全部纯函数/纯结构不依赖真实 DB
组1 canonical 确定性同输入 canonical_json 字节全等 + sha256 指纹全等US-11/F-CP-03
组2 GD 10 顶层键齐全design 9
组3 volatile 字段createdAt/durationMs/taskNo/id...不参与指纹29.6 确定性
组4 register() 可跑通不崩QC #3格式串占位符错位修复验证
组5 三处同步QC #2__init__ 导出 == init.CONTRACTS == load_path.PATHS 契约名一致
组6 无硬编码 DBQC #4api.py 无 `DB = '...'` 常量_dbname() 走 get_module_dbname
组7 浮点定点 + sort_keys键序无关浮点 6 位定点canonical 规范化
组8 registry_hash 确定性同能力集 hash增删能力 hash 变化
退出码 0 = 全部通过 0 = 有断言失败打印 FAIL 明细
"""
from __future__ import annotations
import ast
import io
import os
import re
import sys
import types
HERE = os.path.dirname(os.path.abspath(__file__))
MOD_ROOT = os.path.dirname(HERE)
sys.path.insert(0, MOD_ROOT) # pbl_compiler 包
sys.path.insert(0, os.path.join(os.path.dirname(MOD_ROOT), 'pbl_common')) # pbl_common 依赖
# ---- ahserver 桩(工作空间无平台运行时;仅为 import init.py 不崩)----
if 'ahserver.serverenv' not in sys.modules:
_ah = types.ModuleType('ahserver')
_se = types.ModuleType('ahserver.serverenv')
class _ServerEnv(object):
_inst = None
def __new__(cls):
if cls._inst is None:
cls._inst = super(_ServerEnv, cls).__new__(cls)
return cls._inst
_se.ServerEnv = _ServerEnv
_ah.serverenv = _se
sys.modules['ahserver'] = _ah
sys.modules['ahserver.serverenv'] = _se
from pbl_compiler import canonical as cn # noqa: E402
from pbl_compiler import gd_builder as gb # noqa: E402
from pbl_compiler import api as api # noqa: E402
from pbl_compiler import init as init_mod # noqa: E402
import pbl_compiler as pkg # noqa: E402
PASS, FAIL = [], []
def check(name, cond, detail=''):
(PASS if cond else FAIL).append(name)
print(' [%s] %s%s' % ('PASS' if cond else 'FAIL', name,
(' <- ' + detail) if (detail and not cond) else ''))
# 一个最小但真实的蓝图版本快照(含 7 类子对象若干 + 易变字段)
def _snapshot():
return {
'content': {
'project': [{'id': 'p1', 'title': '火星基地', 'summary': '建造可持续基地'}],
'problem': [{'id': 'q1', 'driving_question': '如何在火星自给自足?'}],
'learning_goals': [
{'id': 'g2', 'text': '掌握生态循环'},
{'id': 'g1', 'text': '理解能源约束'},
],
'missions': [{'id': 'm1', 'title': '着陆', 'order': 1}],
'roles': [{'id': 'r1', 'name': '工程师'}],
'scenes': [{'id': 's1', 'name': '基地外'}],
'entities': [{'id': 'e1', 'entity_key': 'solar_panel', 'scene_id': 's1'}],
'events': [{'id': 'ev1', 'event_key': 'power_on', 'entity_key': 'solar_panel'}],
'rubrics': [{'id': 'rb1', 'criterion': '可行性', 'weight': 0.5}],
}
}
def _ctx(i=0):
return {'blueprint_id': 101, 'blueprint_version_no': 3,
'compiler_version': '1.0.0', 'ruleset_version': 'pbl.rules.v1',
'rules_hash': 'deadbeefcafe1234',
'created_at': '2000-01-01T00:00:00Z', 'duration_ms': i,
'task_no': 'CT:101:3:1.0.0:%05d' % i}
def group1_canonical_determinism():
print('组1 canonical 确定性(同输入指纹全等)')
obj = {'b': [3, 1, 2], 'a': {'z': 1.5, 'y': 'x'}, 'n': None}
c1 = cn.canonical_json(obj, strip=False)
c2 = cn.canonical_json(obj, strip=False)
check('canonical_json 同输入字节全等', c1 == c2, '%r != %r' % (c1, c2))
f1, f2 = cn.sha256_fingerprint(obj), cn.sha256_fingerprint(obj)
check('sha256_fingerprint 同输入全等', f1 == f2 and len(f1) == 64, f1)
# 键序无关
obj2 = {'a': {'y': 'x', 'z': 1.5}, 'n': None, 'b': [3, 1, 2]}
check('键序不同 canonical 仍全等sort_keys',
cn.canonical_json(obj, strip=False) == cn.canonical_json(obj2, strip=False))
def group2_gd_top_keys():
print('组2 GD 10 顶层键齐全')
gd, fp = gb.build_game_definition(_snapshot(), _ctx(), [])
check('GD_TOP_KEYS 恰为 10 个', len(gb.GD_TOP_KEYS) == 10, str(gb.GD_TOP_KEYS))
missing = [k for k in gb.GD_TOP_KEYS if k not in gd]
check('GD 含全部 10 顶层键', not missing, 'missing=%s' % missing)
check('GD 指纹为 64 位 sha256', isinstance(fp, str) and len(fp) == 64, fp)
check('manifest.schema = pbl.game_definition.v1',
gd.get('manifest', {}).get('schema') == cn.GD_SCHEMA)
def group3_volatile_excluded():
print('组3 volatile 字段不参与指纹')
gd_a, fp_a = gb.build_game_definition(_snapshot(), _ctx(0), [])
gd_b, fp_b = gb.build_game_definition(_snapshot(), _ctx(999), [])
check('created_at/duration_ms/task_no 不同但指纹全等', fp_a == fp_b,
'%s != %s' % (fp_a, fp_b))
# 直接验证 strip_volatile 剔除易变键
dirty = {'createdAt': '2020', 'durationMs': 5, 'taskNo': 'X', 'id': 9,
'stable': 'keep'}
stripped = cn.strip_volatile(dirty)
leaked = [k for k in ('createdAt', 'durationMs', 'taskNo', 'id') if k in stripped]
check('strip_volatile 剔除全部易变键', not leaked and stripped.get('stable') == 'keep',
'leaked=%s stripped=%s' % (leaked, stripped))
for k in ('createdAt', 'created_at', 'durationMs', 'duration_ms',
'taskNo', 'task_no', 'id', 'timestamp'):
if k not in cn.VOLATILE_KEYS:
check('VOLATILE_KEYS 含 %s' % k, False)
return
check('VOLATILE_KEYS 覆盖时间/耗时/任务号/id', True)
def group4_register_runs():
print('组4 register() 可跑通不崩QC #3')
sys.path.insert(0, HERE)
import importlib
lp = importlib.import_module('load_path')
os.environ['RBAC_SET_PERM'] = '/nonexistent/set_role_perm.py' # 强制走 pending 分支
os.environ.pop('PY', None)
try:
ret = lp.register()
crashed = False
err = ''
except Exception as exc: # noqa: BLE001
crashed, ret, err = True, None, '%s: %s' % (type(exc).__name__, exc)
check('register() 不抛 TypeError格式串占位符已对齐', not crashed, err)
check('register() 返回 boolpending 非空 → False', isinstance(ret, bool), repr(ret))
check('PATHS 含 15 个端点', len(lp.PATHS) == 15, str(len(lp.PATHS)))
def group5_three_place_sync():
print('组5 三处同步QC #2')
api_contracts = set(n for n in api.__all__ if n.startswith('pbl_'))
init_contracts = set(init_mod.CONTRACTS.keys())
pkg_exports = set(n for n in pkg.__all__ if n.startswith('pbl_'))
sys.path.insert(0, HERE)
import importlib
lp = importlib.import_module('load_path')
rbac_names = set(os.path.basename(p).replace('.dspy', '') for p, _ in lp.PATHS)
check('init.CONTRACTS == api 契约15 个)', init_contracts == api_contracts,
'only_api=%s only_init=%s' % (api_contracts - init_contracts,
init_contracts - api_contracts))
check('__init__ 导出 == init.CONTRACTS', pkg_exports == init_contracts,
'missing_export=%s' % (init_contracts - pkg_exports))
check('load_path.PATHS 契约名 == init.CONTRACTS', rbac_names == init_contracts,
'missing_rbac=%s extra_rbac=%s' % (init_contracts - rbac_names,
rbac_names - init_contracts))
# QC #2 点名的 8 个此前漏接线契约必须在三处都出现
eight = ['pbl_compiler_task_get', 'pbl_compiler_task_list',
'pbl_game_definition_get', 'pbl_game_definition_get_by_blueprint',
'pbl_compiler_verify_determinism', 'pbl_compiler_version_register',
'pbl_compiler_version_get', 'pbl_compiler_version_diff']
for name in eight:
ok = (name in pkg_exports and name in init_contracts and name in rbac_names
and callable(getattr(pkg, name, None)))
check('QC#2 契约 %s 三处齐全且可调用' % name, ok)
# verify_determinism 是 US-11 验收入口,必须可从包直接取到
check('US-11 入口 pbl_compiler_verify_determinism 可达',
callable(getattr(pkg, 'pbl_compiler_verify_determinism', None)))
def group6_no_hardcoded_db():
print('组6 无硬编码 DBQC #4')
src = io.open(os.path.join(MOD_ROOT, 'pbl_compiler', 'api.py'),
encoding='utf-8').read()
check("api.py 无 `DB = '...'` 常量", re.search(r"(?m)^DB\s*=\s*['\"]", src) is None)
check('api.py 无模块级 DB 属性', not hasattr(api, 'DB'))
check('_dbname() 走 get_module_dbname 解析', callable(api._dbname)
and isinstance(api._dbname(), str) and api._dbname())
# AST 扫描:模块级赋值不得出现 DB = 字面量
tree = ast.parse(src)
bad = []
for node in tree.body:
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id == 'DB' \
and isinstance(node.value, ast.Constant) \
and isinstance(node.value.value, str):
bad.append(t.id)
check('AST无模块级 DB = <字符串> 硬编码', not bad, str(bad))
# set_dbname 注入生效(宿主/测试可覆盖,证明非写死)
old = api._dbname()
api.set_dbname('pbl_injected')
injected = api._dbname()
api.set_dbname(None)
check('set_dbname 注入可覆盖库名', injected == 'pbl_injected', injected)
check('set_dbname(None) 回落解析值', api._dbname() == old, api._dbname())
def group7_float_and_sort():
print('组7 浮点定点 + 无空白分隔符')
c = cn.canonical_json({'x': 1.00000049, 'y': 2.5}, strip=False)
check('canonical 无空白分隔符', ' ' not in c and '\n' not in c, c)
check('浮点按 FLOAT_PRECISION 定点', cn.FLOAT_PRECISION == 6)
a = cn.canonical_json({'k': 0.1 + 0.2}, strip=False)
b = cn.canonical_json({'k': 0.3}, strip=False)
check('0.1+0.2 与 0.3 定点后全等', a == b, '%s vs %s' % (a, b))
def group8_registry_hash():
print('组8 registry_hash 确定性')
caps = [{'capability_key': 'b', 'version_no': 1}, {'capability_key': 'a', 'version_no': 2}]
h1 = cn.registry_hash(caps)
h2 = cn.registry_hash(list(reversed(caps)))
check('同能力集顺序无关registry_hash 全等', h1 == h2 and bool(h1), '%s vs %s' % (h1, h2))
caps2 = caps + [{'capability_key': 'c', 'version_no': 1}]
check('新增能力 registry_hash 变化', cn.registry_hash(caps2) != h1)
def main():
print('=' * 64)
print('pbl_compiler M3a 自检QC #1/#2/#3/#4 整改验证)')
print('=' * 64)
for fn in (group1_canonical_determinism, group2_gd_top_keys,
group3_volatile_excluded, group4_register_runs,
group5_three_place_sync, group6_no_hardcoded_db,
group7_float_and_sort, group8_registry_hash):
try:
fn()
except Exception as exc: # noqa: BLE001
import traceback
FAIL.append('%s(异常)' % fn.__name__)
print(' [ERROR] %s: %s' % (fn.__name__, exc))
traceback.print_exc()
print('-' * 64)
print('PASS=%d FAIL=%d' % (len(PASS), len(FAIL)))
if FAIL:
print('FAILED: %s' % FAIL)
return 1
print('ALL GREEN')
return 0
# ---- pytest 兼容 ----
def test_m3a_selfcheck():
assert main() == 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_task_get.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_task_get.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_task_get(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_task_list.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_task_list.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_task_list(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_verify_determinism.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_verify_determinism.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_verify_determinism(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_version_diff.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_version_diff.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_version_diff(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_version_get.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_version_get.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_version_get(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_compiler_version_register.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_compiler_version_register.dspy: START params_kw={dict(params_kw)}')
data = await pbl_compiler_version_register(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_game_definition_get.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_game_definition_get.dspy: START params_kw={dict(params_kw)}')
data = await pbl_game_definition_get(**params_kw)
return data

View File

@ -0,0 +1,4 @@
# pbl_compiler/api/pbl_game_definition_get_by_blueprint.dspy —— 契约端点QC #2 补接线,与 init.py CONTRACTS 同步)
debug('pbl_compiler/api/pbl_game_definition_get_by_blueprint.dspy: START params_kw={dict(params_kw)}')
data = await pbl_game_definition_get_by_blueprint(**params_kw)
return data