fix(pbls): 修复 Bug Yr4Gv908T4B6yhA7DtE_0(第9轮 3/5 绿)种子注入段 async 调用缺陷 + QC 退回 #1/#2 收口
【1】Bug Yr4Gv908T4B6yhA7DtE_0 根因(a):build.sh 第 12 步种子注入段以同步方式调用 async def seed_all(modules/pbl_appcodes/pbl_appcodes/seed.py:460)→ RuntimeWarning: coroutine 'seed_all' was never awaited + TypeError: Object of type coroutine is not JSON serializable,r12 BUILD_RC=1,种子未注入 → 应用未起 → 9380 无 LISTEN → healthz=000(本轮红项①②)。修法:asyncio.run() 驱动协程,两轮各自独立事件 循环,幂等对比口径不变。 【2】连带缺陷:原断言用 groups_inserted/items_inserted/idempotent 三个键,而 seed_all 真实返回契约(seed.py:494-499)是 ok/groups/items/inserted/updated/skipped/per_group → 协程修好后必然 KeyError。改按真实返回键断言(第1轮 8组74项、第2轮 inserted==0 且 updated==0),幂等语义不变。 【3】QC 退回 #1:build.sh.r11bak 系误提交(149dd5c 中为 A 状态,实测 4 files changed / 879 insertions,此前摘要声称「未纳入 / 3 files 451 insertions」与实测矛盾)。处置: git rm --cached 移出仓库(本地文件保留供排障),.gitignore 新增 *.bak / *.r[0-9]*bak / *.orig / *.rej / *.old / *.tmp 六条模式,一并清出历史遗留的 build.sh.r4bak、 build.sh.r8bak、requirements.txt.r4bak、requirements.txt.r6bak、 scripts/ddl/pbls_tables.sql.bak、scripts/ddl/pbls_tables.sql.r11bak(共 7 个)。 【4】QC 退回 #2:收口 build.sh 工作区未提交变更(种子段 asyncio.run 补丁),交付摘要 按实测字节/行数如实填写,不再声称「全部已收口」。 【5】同类问题全仓扫描(任务要求「不能只修报出来的那一个」):AST 提取 490 个 async def 函数名,扫全部 apps/*/build.sh、*.sh、modules/*/*.sh 内嵌 python heredoc 的裸同步调用 → 命中 modules/pbls/build.sh:377,379 同型缺陷(同一仓库的第二工作副本),已同步修复; apps/scense/build.sh:129 与 scripts/fix_scense_access.sh:53 的 main() 是 shell 函数定义 非 python 调用,误报排除。修复后两副本去注释代码行 diff 为空(CODE_IDENTICAL_OK)。 【自检】bash -n apps/pbls/build.sh OK;bash -n modules/pbls/build.sh OK;两副本内嵌 PYEOF 块(各 2 块)ast.parse 全 OK;硬门禁 6.6 PASS(2 个 requirements 文件 / 20 项声明 / 基座 7 包与 wheelhouse 逐字一致 / 40 个模块 pyproject 零幻影依赖,rc=0);DDL 表数 grep -c 'CREATE TABLE IF NOT EXISTS' = 37(期望 37)。 【责任边界·不造假声明】未声称已部署、未声称 healthz=200、未声称 37 表已落库、未声称 测试通过。红项①②的执行落地(以 pbl 身份跑 build.sh 建 venv → DDL → 种子 → 起进程监听 9380 → 自证 healthz=200)属 agent.deploy_test 职责,develop 无部署机执行权限。
This commit is contained in:
parent
149dd5c811
commit
d2cf64dd7f
9
.gitignore
vendored
9
.gitignore
vendored
@ -15,3 +15,12 @@ venv/
|
|||||||
# 应用仓自带的 wwwroot/pbl_overlay/(pbl_overlay.js 为跟踪文件)仍保持跟踪,故显式反排除。
|
# 应用仓自带的 wwwroot/pbl_overlay/(pbl_overlay.js 为跟踪文件)仍保持跟踪,故显式反排除。
|
||||||
wwwroot/pbl_*
|
wwwroot/pbl_*
|
||||||
!wwwroot/pbl_overlay/
|
!wwwroot/pbl_overlay/
|
||||||
|
|
||||||
|
# 备份/临时副本不入库(QC 退回 #1:build.sh.r11bak 曾被误提交进 149dd5c)。
|
||||||
|
# 备份是本地排障工作文件,非交付产物;入库会污染仓库并让 diff stat 失真。
|
||||||
|
*.bak
|
||||||
|
*.r[0-9]*bak
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
*.old
|
||||||
|
*.tmp
|
||||||
|
|||||||
19
build.sh
19
build.sh
@ -372,15 +372,22 @@ log "枚举数量契约自检通过: 74 项 / 自检全绿(无 FAIL)"
|
|||||||
# ---- 12. 9 项种子数据幂等注入(重复执行零差异)----
|
# ---- 12. 9 项种子数据幂等注入(重复执行零差异)----
|
||||||
log "种子注入(第 1 轮)"
|
log "种子注入(第 1 轮)"
|
||||||
"${PY}" - <<'PYEOF'
|
"${PY}" - <<'PYEOF'
|
||||||
import json
|
import json, asyncio
|
||||||
from pbl_appcodes.seed import seed_all
|
from pbl_appcodes.seed import seed_all
|
||||||
s1 = seed_all()
|
# [deploy_test R12 定点补丁] seed_all 是 async def(modules/pbl_appcodes/pbl_appcodes/seed.py:460),
|
||||||
|
# 原脚本同步调用 → TypeError: Object of type coroutine is not JSON serializable
|
||||||
|
# + RuntimeWarning: coroutine 'seed_all' was never awaited(r12d 实测,建表 37/37 后种子段中断)。
|
||||||
|
# 修法:asyncio.run 驱动协程,两轮各自独立事件循环(幂等对比口径不变)。
|
||||||
|
s1 = asyncio.run(seed_all())
|
||||||
print('[seed round1]', json.dumps(s1, ensure_ascii=False, sort_keys=True))
|
print('[seed round1]', json.dumps(s1, ensure_ascii=False, sort_keys=True))
|
||||||
s2 = seed_all()
|
s2 = asyncio.run(seed_all())
|
||||||
print('[seed round2]', json.dumps(s2, ensure_ascii=False, sort_keys=True))
|
print('[seed round2]', json.dumps(s2, ensure_ascii=False, sort_keys=True))
|
||||||
assert s2['groups_inserted'] == 0 and s2['groups_updated'] == 0, '重复执行组有差异'
|
# [deploy_test R12 定点补丁 #2] 原断言用 groups_inserted/items_inserted/idempotent 三个键,
|
||||||
assert s2['items_inserted'] == 0 and s2['items_updated'] == 0, '重复执行项有差异'
|
# 但 seed_all 真实返回契约(seed.py:494-499)是 ok/groups/items/inserted/updated/skipped/per_group,
|
||||||
assert s2['idempotent'] is True, '幂等校验失败'
|
# 无上述键 → 同步调用修好后必然 KeyError。改按真实返回键断言,幂等口径不变(第二轮零插入零更新)。
|
||||||
|
assert s1['ok'] is True and s1['groups'] == 8 and s1['items'] == 74, '第 1 轮种子契约不满足(8 组 74 项)'
|
||||||
|
assert s2['ok'] is True and s2['inserted'] == 0 and s2['updated'] == 0, '重复执行有差异(幂等失败)'
|
||||||
|
assert s2['groups'] == 8 and s2['items'] == 74, '第 2 轮种子契约不满足'
|
||||||
print('[seed] 幂等零差异校验通过: 8 组 74 项')
|
print('[seed] 幂等零差异校验通过: 8 组 74 项')
|
||||||
PYEOF
|
PYEOF
|
||||||
|
|
||||||
|
|||||||
428
build.sh.r11bak
428
build.sh.r11bak
@ -1,428 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# =====================================================================
|
|
||||||
# PBL Agent OS 验证原型(Phase 0/1)—— 一键部署脚本(test 环境)
|
|
||||||
# 端口唯一事实源:projects/pbls/env/test.json(9380);本脚本不硬编码端口,从 env 读取
|
|
||||||
# 门禁:建表 37 张 + 9 项种子全绿 + self_check PASS 74/74 + py_compile 全通过
|
|
||||||
# =====================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
APP_NAME="pbls"
|
|
||||||
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
WS_DIR="$(cd "${APP_DIR}/../.." && pwd)"
|
|
||||||
ENV_FILE="${WS_DIR}/projects/pbls/env/test.json"
|
|
||||||
DDL_FILE="${APP_DIR}/scripts/ddl/pbls_tables.sql"
|
|
||||||
VENV_DIR="${APP_DIR}/venv"
|
|
||||||
PY="${VENV_DIR}/bin/python"
|
|
||||||
|
|
||||||
log() { echo "[build.sh][$(date '+%F %T')] $*"; }
|
|
||||||
die() { echo "[build.sh][FATAL] $*" >&2; exit 1; }
|
|
||||||
|
|
||||||
# ---- 0. 前置检查:env 唯一事实源必须存在且无占位 ----
|
|
||||||
[ -f "${ENV_FILE}" ] || die "缺少环境事实源 ${ENV_FILE}"
|
|
||||||
if grep -Eq '待明确|TBD|占位|CHANGE_ME|xxx' "${ENV_FILE}"; then
|
|
||||||
die "${ENV_FILE} 含占位/待明确字段,禁止部署"
|
|
||||||
fi
|
|
||||||
|
|
||||||
read_env() { python3 -c "import json,sys;print(json.load(open('${ENV_FILE}'))$1)"; }
|
|
||||||
PORT="$(read_env "['port']")"
|
|
||||||
DB_HOST="$(read_env "['db']['host']")"
|
|
||||||
DB_PORT="$(read_env "['db']['port']")"
|
|
||||||
DB_USER="$(read_env "['db']['user']")"
|
|
||||||
DB_PASS="$(read_env "['db']['password']")"
|
|
||||||
DB_NAME="$(read_env "['db']['dbname']")"
|
|
||||||
DEPLOY_PATH="$(read_env "['deploy']['path']")"
|
|
||||||
# 外网口自检用(复测门禁 ① 同时 curl 127.0.0.1 与 192.168.16.3 两个地址);
|
|
||||||
# env 无 ssh.host 时回退 127.0.0.1,不让 read_env 抛 KeyError 中断部署。
|
|
||||||
SSH_HOST="$(python3 -c "import json;print(json.load(open('${ENV_FILE}')).get('ssh',{}).get('host') or '127.0.0.1')")"
|
|
||||||
log "env 事实源: port=${PORT} db=${DB_NAME}@${DB_HOST}:${DB_PORT} deploy=${DEPLOY_PATH}"
|
|
||||||
|
|
||||||
# ---- 1. 端口一致性核验(conf/config.json 必须与 env 同值)----
|
|
||||||
CONF_PORT="$(python3 -c "import json;print(json.load(open('${APP_DIR}/conf/config.json'))['port'])")"
|
|
||||||
[ "${CONF_PORT}" = "${PORT}" ] || die "端口不一致: conf=${CONF_PORT} env=${PORT}"
|
|
||||||
log "端口一致性核验通过: ${PORT}"
|
|
||||||
|
|
||||||
# ---- 2. 应用仓库不得另存 env/(端口唯一事实源在项目侧)----
|
|
||||||
[ ! -d "${APP_DIR}/env" ] || die "应用仓库存在 env/ 目录,违反端口唯一事实源规则"
|
|
||||||
log "应用仓库无 env/ 目录,核验通过"
|
|
||||||
|
|
||||||
# ---- 3. 入口硬门禁核验(6.1 / 6.2)----
|
|
||||||
ENTRY="${APP_DIR}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${ENTRY}" ] || die "缺少入口 ${ENTRY}"
|
|
||||||
grep -q 'from ahserver.webapp import webapp' "${ENTRY}" || die "入口未使用 ahserver.webapp.webapp"
|
|
||||||
grep -q 'webapp(init)' "${ENTRY}" || die "入口未调用 webapp(init)"
|
|
||||||
if grep -Eq 'Flask|FastAPI|Django|http\.server|jsonify|@app\.route' "${ENTRY}"; then
|
|
||||||
die "入口含禁用框架关键字(Flask/FastAPI/Django/http.server/jsonify/@app.route)"
|
|
||||||
fi
|
|
||||||
log "硬门禁 6.1 通过: ahserver.webapp + webapp(init),无禁用框架"
|
|
||||||
|
|
||||||
# ServerEnv() 行号必须早于 load_rbac() 行号
|
|
||||||
ENV_LINE="$(grep -n 'ServerEnv()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
RBAC_LINE="$(grep -n 'load_rbac()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
[ -n "${ENV_LINE}" ] && [ -n "${RBAC_LINE}" ] || die "入口缺 ServerEnv() 或 load_rbac() 显式调用"
|
|
||||||
[ "${ENV_LINE}" -lt "${RBAC_LINE}" ] || die "ServerEnv() 必须早于 load_rbac()(env 行 ${ENV_LINE} >= rbac 行 ${RBAC_LINE})"
|
|
||||||
grep -q 'load_appbase()' "${ENTRY}" || die "入口缺 load_appbase() 显式调用"
|
|
||||||
grep -q 'load_pybricks()' "${ENTRY}" || die "入口缺 load_pybricks() 显式调用"
|
|
||||||
log "硬门禁 6.2 通过: ServerEnv()(L${ENV_LINE}) < load_rbac()(L${RBAC_LINE}),load_appbase/load_rbac/load_pybricks 均显式调用"
|
|
||||||
|
|
||||||
# ---- 4. DDL 方言核验(6.4:mariadb,BIGINT AUTO_INCREMENT,零 SERIAL/FK/ENUM/TIMESTAMP)----
|
|
||||||
# 【Q4 修复 / deploy_test 第 3 轮实测】旧版直接对 DDL **原文** grep,存在两类假阳性:
|
|
||||||
# (1) DDL 文末「契约自检」注释块为说明"零命中项",逐字写出了
|
|
||||||
# FOREIGN KEY / REFERENCES / ENUM( / SERIAL / BIGSERIAL / nextval / TIMESTAMP
|
|
||||||
# (实测原文命中 3 行:L6 头部说明、L220 COMMENT 串内的 references、L891 契约自检块),
|
|
||||||
# 旧 `grep -Eiq` 一律触发 die —— 注释/COMMENT 串被当成真实 DDL,门禁必然误杀;
|
|
||||||
# (2) `CREATE TABLE` / `AUTO_INCREMENT` 计数同理会把注释行与 COMMENT '...' 串内字样算进去,
|
|
||||||
# 表数不可信(且旧断言 36 与 DDL 实际 37 张不符,新增 pbl_compile_task 后未同步)。
|
|
||||||
# 修复:改为「先剥 SQL 注释 + 字符串字面量(含 COMMENT 串),再在纯 DDL 正文上扫描」,
|
|
||||||
# 由 scripts/ddl_gate_check.py 单遍字符扫描实现(非逐行正则,串内 -- / # 不会误判为注释),
|
|
||||||
# 期望值与 DDL 实际表数对齐为 37(表数 / BIGINT AUTO_INCREMENT 主键数同为 37)。
|
|
||||||
[ -f "${DDL_FILE}" ] || die "缺少 DDL ${DDL_FILE}"
|
|
||||||
DDL_GATE="${APP_DIR}/scripts/ddl_gate_check.py"
|
|
||||||
[ -f "${DDL_GATE}" ] || die "缺少 DDL 门禁扫描器 ${DDL_GATE}(6.4 依赖,禁止回退到原文 grep)"
|
|
||||||
|
|
||||||
# 4a. 扫描器自身可信度:先跑内置最小单测(剥注释/剥串/幂等/真实违规不漏报)
|
|
||||||
SELFTEST_RC=0
|
|
||||||
python3 "${DDL_GATE}" --selftest || SELFTEST_RC=$?
|
|
||||||
[ "${SELFTEST_RC}" = "0" ] || die "DDL 门禁扫描器自检未通过(--selftest rc=${SELFTEST_RC}),扫描结果不可信,终止部署"
|
|
||||||
|
|
||||||
# 4b. 在降噪后的纯 DDL 正文上核验:方言零命中 + 表数 37 + BIGINT AUTO_INCREMENT 37
|
|
||||||
GATE_RC=0
|
|
||||||
python3 "${DDL_GATE}" --ddl "${DDL_FILE}" --expect-tables 37 --expect-autoinc 37 || GATE_RC=$?
|
|
||||||
[ "${GATE_RC}" = "0" ] || die "硬门禁 6.4 未通过(ddl_gate_check.py rc=${GATE_RC}):非 mariadb 方言关键字命中 / 表数≠37 / BIGINT AUTO_INCREMENT≠37,详见上方回显"
|
|
||||||
log "硬门禁 6.4 通过: mariadb 方言(剥注释+COMMENT 串后零命中),37 表 / 37 处 BIGINT AUTO_INCREMENT"
|
|
||||||
|
|
||||||
# ---- 5. 依赖声明核验(6.5)----
|
|
||||||
for dep in ahserver sqlor apppublic appbase rbac PyMySQL; do
|
|
||||||
grep -qi "^${dep}" "${APP_DIR}/requirements.txt" || die "requirements.txt 缺依赖 ${dep}"
|
|
||||||
done
|
|
||||||
log "硬门禁 6.5 通过: ahserver/sqlor/apppublic/appbase/rbac/PyMySQL 均已声明"
|
|
||||||
|
|
||||||
# ---- 6. website.processors / indexes 核验(缺一会导致 / 返回 500)----
|
|
||||||
python3 - <<'PYEOF'
|
|
||||||
import json, sys
|
|
||||||
conf = json.load(open('conf/config.json'))
|
|
||||||
procs = dict((p[0], p[1]) for p in conf['website']['processors'])
|
|
||||||
for ext, name in (('.tmpl', 'tmpl'), ('.ui', 'bui'), ('.dspy', 'dspy')):
|
|
||||||
assert procs.get(ext) == name, 'processors 缺 %s->%s,实际 %r' % (ext, name, procs.get(ext))
|
|
||||||
assert 'index.ui' in conf['website']['indexes'], 'indexes 缺 index.ui'
|
|
||||||
print('[build.sh] website 核验通过: processors=%s indexes=%s' % (procs, conf['website']['indexes']))
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 7. venv + 依赖安装 ----
|
|
||||||
if [ ! -x "${PY}" ]; then
|
|
||||||
log "创建 venv: ${VENV_DIR}"
|
|
||||||
python3 -m venv "${VENV_DIR}"
|
|
||||||
fi
|
|
||||||
"${PY}" -m pip install --upgrade pip wheel setuptools
|
|
||||||
|
|
||||||
# [deploy_test R5 修复 #1] 平台基础包(ahserver/sqlor/apppublic/appbase/rbac/bricks_for_python/accounting)
|
|
||||||
# 原走内网私有源 http://10.20.30.41:8081 —— 目标机 192.168.16.3 实测 connect timeout(5 次重试全失败),
|
|
||||||
# build.sh 在此处 BUILD_RC=1 终止(R4 部署失败根因,见 evidence/r4_build.log)。
|
|
||||||
# 改为:机构 wheelhouse 离线供给(--find-links,绝对路径,不依赖 cwd)+ 公网 pypi.org 取第三方包
|
|
||||||
# (目标机实测 https://pypi.org/simple/pymysql/ HTTP=200)。
|
|
||||||
WHEELHOUSE="${WS_DIR}/wheelhouse"
|
|
||||||
[ -d "${WHEELHOUSE}" ] || die "缺少机构 wheelhouse ${WHEELHOUSE}(平台基础包离线源,内网 pypi 在目标机不可达)"
|
|
||||||
for _w in ahserver sqlor apppublic appbase rbac bricks_for_python accounting; do
|
|
||||||
ls "${WHEELHOUSE}"/${_w}-*.whl >/dev/null 2>&1 || die "wheelhouse 缺 ${_w} 的 whl(${WHEELHOUSE})"
|
|
||||||
done
|
|
||||||
log "wheelhouse 就位: $(ls "${WHEELHOUSE}"/*.whl | wc -l) 个 whl @ ${WHEELHOUSE}"
|
|
||||||
"${PY}" -m pip install --find-links "${WHEELHOUSE}" -r "${APP_DIR}/requirements.txt"
|
|
||||||
|
|
||||||
# ---- 7b. 测试前置依赖(DISC.2)----
|
|
||||||
# 运行时依赖(requirements.txt)刻意不含 pytest;测试依赖单列 requirements-test.txt,
|
|
||||||
# 由本步装进同一个 venv,使
|
|
||||||
# source venv/bin/activate && python3 -m pytest tests/test_m5a_idempotency.py -v
|
|
||||||
# 在**不手工注入 PYTHONPATH** 的前提下可直接执行(此前靠 PYTHONPATH=<ws>/.pylibs
|
|
||||||
# 临时注入跑通,换机器/换角色即失效,test 角色会误判为环境不可用)。
|
|
||||||
# 只要运行时依赖的场景(如生产镜像)用 PBL_SKIP_TEST_DEPS=1 显式跳过。
|
|
||||||
REQ_TEST="${APP_DIR}/requirements-test.txt"
|
|
||||||
if [ "${PBL_SKIP_TEST_DEPS:-0}" = "1" ]; then
|
|
||||||
log "PBL_SKIP_TEST_DEPS=1:跳过测试前置依赖(venv 内将没有 pytest,测试角色需自行引导)"
|
|
||||||
elif [ ! -f "${REQ_TEST}" ]; then
|
|
||||||
die "缺测试依赖清单 ${REQ_TEST}(DISC.2 要求显式声明;确要跳过请设 PBL_SKIP_TEST_DEPS=1)"
|
|
||||||
else
|
|
||||||
log "安装测试前置依赖: ${REQ_TEST}"
|
|
||||||
"${PY}" -m pip install -r "${REQ_TEST}"
|
|
||||||
# 自证:装完必须真能 import + 跑 --version,否则本步等于没做(不让注释与事实矛盾)
|
|
||||||
"${PY}" -c 'import pytest' >/dev/null 2>&1 \
|
|
||||||
|| die "安装后 venv 内仍无法 import pytest(${PY})"
|
|
||||||
log "7b 自证通过: $("${PY}" -m pytest --version 2>&1 | head -1) @ ${PY}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)----
|
|
||||||
# [deploy_test R5 修复 #2] 原循环只匹配 modules/pbl_*,但入口 app/pbls.py 还显式 import
|
|
||||||
# world / scene / entity / script_engine / scense_runtime / scense / scense_game
|
|
||||||
# 这 7 个复用域模块(pbls_spec.json referenced_modules)。漏装 → 入口 ImportError →
|
|
||||||
# 进程起不来(端口无 LISTEN、healthz=000)。此处按 spec.json referenced_modules 补装,
|
|
||||||
# 并做「入口 import 全部可解析」自证门禁,装不上就 die,不留到启动阶段才炸。
|
|
||||||
REUSE_MODULES="$(python3 -c "import json;print(' '.join(json.load(open('${WS_DIR}/projects/pbls/pbls_spec.json'))['referenced_modules']))")"
|
|
||||||
# [deploy_test R8 修复] 安装顺序改为拓扑序:pbl_common 必须最先装。
|
|
||||||
# 原实现用 glob `modules/pbl_*` 按字母序安装,pbl_agent_runtime 排在 pbl_common 之前,
|
|
||||||
# 而 pbl_agent_runtime 的 pyproject 依赖 pbl_common(本地模块,任何索引都没有)→
|
|
||||||
# pip 报 "ERROR: No matching distribution found for pbl_common",BUILD_RC=1
|
|
||||||
# (实测 /d/pbl/pbls_ws/build_r8.log L170-176)。故先装 pbl_common,再装其余 pbl_*。
|
|
||||||
INSTALL_DIRS=""
|
|
||||||
if [ -d "${WS_DIR}/modules/pbl_common" ]; then
|
|
||||||
INSTALL_DIRS="${WS_DIR}/modules/pbl_common"
|
|
||||||
fi
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}" ] || continue
|
|
||||||
[ "$(basename "${mod_dir}")" = "pbl_common" ] && continue
|
|
||||||
INSTALL_DIRS="${INSTALL_DIRS} ${mod_dir}"
|
|
||||||
done
|
|
||||||
for rm in ${REUSE_MODULES}; do
|
|
||||||
# 平台基础包(apppublic/sqlor/ahserver/appbase/rbac/accounting)已由 wheelhouse 装好,跳过
|
|
||||||
case " ${rm} " in *" apppublic "*|*" sqlor "*|*" ahserver "*|*" appbase "*|*" rbac "*|*" accounting "*) continue;; esac
|
|
||||||
[ -d "${WS_DIR}/modules/${rm}" ] || die "spec.json referenced_modules 声明 ${rm},但 ${WS_DIR}/modules/${rm} 不存在"
|
|
||||||
INSTALL_DIRS="${INSTALL_DIRS} ${WS_DIR}/modules/${rm}"
|
|
||||||
done
|
|
||||||
for mod_dir in ${INSTALL_DIRS}; do
|
|
||||||
if [ -f "${mod_dir}/pyproject.toml" ] || [ -f "${mod_dir}/setup.py" ]; then
|
|
||||||
log "安装模块 $(basename "${mod_dir}")"
|
|
||||||
"${PY}" -m pip install --find-links "${WHEELHOUSE}" -e "${mod_dir}"
|
|
||||||
else
|
|
||||||
export PYTHONPATH="${mod_dir}:${PYTHONPATH:-}"
|
|
||||||
log "模块 $(basename "${mod_dir}") 无打包描述,加入 PYTHONPATH"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ---- 8a. 入口 import 可解析性自证(部署前拦截 ImportError,不留到启动阶段)----
|
|
||||||
log "入口 import 自证:$(basename "${ENTRY}") 全部 import 必须可解析"
|
|
||||||
IMPORT_RC=0
|
|
||||||
"${PY}" - <<'PYIMP' || IMPORT_RC=$?
|
|
||||||
import ast, importlib.util, sys
|
|
||||||
src = open('app/pbls.py', encoding='utf-8').read()
|
|
||||||
mods = set()
|
|
||||||
for node in ast.walk(ast.parse(src)):
|
|
||||||
if isinstance(node, ast.ImportFrom) and node.module:
|
|
||||||
mods.add(node.module.split('.')[0])
|
|
||||||
elif isinstance(node, ast.Import):
|
|
||||||
for a in node.names:
|
|
||||||
mods.add(a.name.split('.')[0])
|
|
||||||
mods -= set(sys.stdlib_module_names)
|
|
||||||
mods -= {'json', 'os', 'sys', 'ast'}
|
|
||||||
missing = sorted(m for m in mods if importlib.util.find_spec(m) is None)
|
|
||||||
print('[import-check] 需解析顶层模块 %d 个: %s' % (len(mods), ' '.join(sorted(mods))))
|
|
||||||
if missing:
|
|
||||||
print('[import-check][FAIL] 无法解析: %s' % ' '.join(missing))
|
|
||||||
sys.exit(1)
|
|
||||||
print('[import-check] PASS 全部可解析')
|
|
||||||
PYIMP
|
|
||||||
[ "${IMPORT_RC}" = "0" ] || die "入口 import 自证失败(rc=${IMPORT_RC}):上列模块在 venv 内不可解析,应用必然启动失败,终止部署"
|
|
||||||
|
|
||||||
# ---- 8b. M9 覆盖层页面壳生成(构建产物,不入库)+ 一致性门禁 ----
|
|
||||||
# 页面壳由 pbl_scense_ext 的生成器产出;产物目录 wwwroot/pbl_overlay/ 在模块仓
|
|
||||||
# .gitignore 中被排除,因此必须在构建期生成,否则新克隆/新部署环境该目录为空、
|
|
||||||
# /pbl_overlay/<key>.ui 导航全量 404。位置:模块安装之后、模块 wwwroot 软链(紧随其后的
|
|
||||||
# 「8c. 模块 wwwroot 挂载」段)之前——生成物落在模块仓,必须再经 8c 挂载到应用 wwwroot
|
|
||||||
# 才能在应用侧 URL /pbl_scense_ext/pbl_overlay/<key>.ui 上取到。
|
|
||||||
OVERLAY_GEN="${WS_DIR}/modules/pbl_scense_ext/scripts/gen_overlay_pages.py"
|
|
||||||
[ -f "${OVERLAY_GEN}" ] || die "缺少覆盖层页面壳生成器: ${OVERLAY_GEN}(modules/pbl_scense_ext 未就位)"
|
|
||||||
log "生成 PBL 覆盖层页面壳: ${OVERLAY_GEN}"
|
|
||||||
# 注意:不能用 `if ! cmd; then die "... rc=$?"` —— 进入分支时 $? 已被 if 取反为 0,
|
|
||||||
# 打印出的 rc 恒为 0 会误导排障;改为显式捕获真实返回码。
|
|
||||||
GEN_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" || GEN_RC=$?
|
|
||||||
if [ "${GEN_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳生成失败(gen_overlay_pages.py rc=${GEN_RC}):wwwroot/pbl_overlay/ 将为空,/pbl_overlay/<key>.ui 全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳门禁校验(gen_overlay_pages.py --check)"
|
|
||||||
CHK_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" --check || CHK_RC=$?
|
|
||||||
if [ "${CHK_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳门禁校验未通过(--check rc=${CHK_RC}):i18n zh/en key 不一致,或页面壳与 PAGES 注册表不一致,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳生成 + 门禁校验通过(12 个页面壳 / i18n key 一致)"
|
|
||||||
|
|
||||||
# ---- 8c. 模块 wwwroot 挂载(软链)+ 应用侧可达性门禁(QC 退回意见 #1/#2)----
|
|
||||||
# 上一版只做了 8b「生成」,生成物落在模块仓 modules/<m>/wwwroot/ 下,但本脚本从未把
|
|
||||||
# 模块 wwwroot 挂进应用 wwwroot —— 应用 website.root=wwwroot 只指向应用自身目录,
|
|
||||||
# 因此 build.sh 跑完后应用侧依然取不到 /pbl_scense_ext/pbl_overlay/<key>.ui(可达链路
|
|
||||||
# 断在挂载环节,全量 404 并未消除)。此处按 web-application-spec「挂载模块 9 步流程」
|
|
||||||
# 第 5 步(wwwroot 软链,非 cp,保持与模块仓单一副本同步)补齐,并立即以应用侧路径
|
|
||||||
# 实测 12 个页面壳数量作为门禁,rc≠0 终止部署。
|
|
||||||
LINKED_MODULES=""
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}/wwwroot" ] || continue
|
|
||||||
mod_name="$(basename "${mod_dir}")"
|
|
||||||
www_link="${APP_DIR}/wwwroot/${mod_name}"
|
|
||||||
# 已存在但不是软链(应用仓自带同名真实目录)时不覆盖,直接终止让人工处置,
|
|
||||||
# 避免 ln -sfn 把软链建进既有目录里、或静默删掉应用仓跟踪文件。
|
|
||||||
if [ -e "${www_link}" ] && [ ! -L "${www_link}" ]; then
|
|
||||||
die "wwwroot 挂载冲突:${www_link} 已存在且不是软链(应用仓自带目录?),请人工处置后重试"
|
|
||||||
fi
|
|
||||||
ln -sfn "${mod_dir}/wwwroot" "${www_link}"
|
|
||||||
LINKED_MODULES="${LINKED_MODULES} ${mod_name}"
|
|
||||||
done
|
|
||||||
[ -n "${LINKED_MODULES}" ] || die "未发现任何含 wwwroot/ 的 pbl_* 模块,模块 wwwroot 挂载为空"
|
|
||||||
log "模块 wwwroot 软链挂载完成:${LINKED_MODULES}"
|
|
||||||
|
|
||||||
# 挂载后应用侧可达性门禁:覆盖层页面壳必须能通过应用 wwwroot 路径取到
|
|
||||||
APP_OVERLAY_DIR="${APP_DIR}/wwwroot/pbl_scense_ext/pbl_overlay"
|
|
||||||
APP_UI_COUNT=0
|
|
||||||
if [ -d "${APP_OVERLAY_DIR}" ]; then
|
|
||||||
APP_UI_COUNT="$(find -L "${APP_OVERLAY_DIR}" -maxdepth 1 -name '*.ui' | wc -l | tr -d '[:space:]')"
|
|
||||||
fi
|
|
||||||
if [ "${APP_UI_COUNT}" != "12" ]; then
|
|
||||||
die "应用侧覆盖层页面壳应为 12 个,实际 ${APP_UI_COUNT}(${APP_OVERLAY_DIR}):模块 wwwroot 未挂载成功或生成器未产出,/pbl_scense_ext/pbl_overlay/<key>.ui 将全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "应用侧可达性核验通过: ${APP_OVERLAY_DIR}/*.ui = ${APP_UI_COUNT}"
|
|
||||||
|
|
||||||
# ---- 9. 语法验证:全部 .py 跑 py_compile(QC 退回意见 #16)----
|
|
||||||
log "py_compile 全量语法验证"
|
|
||||||
FAIL_SYNTAX=0
|
|
||||||
while IFS= read -r pyfile; do
|
|
||||||
if ! "${PY}" -m py_compile "${pyfile}"; then
|
|
||||||
echo "[build.sh][SYNTAX FAIL] ${pyfile}"
|
|
||||||
FAIL_SYNTAX=1
|
|
||||||
fi
|
|
||||||
done < <(find "${APP_DIR}/app" "${WS_DIR}/modules/pbl_common" "${WS_DIR}/modules/pbl_appcodes" -name '*.py' -not -path '*/venv/*' 2>/dev/null)
|
|
||||||
[ "${FAIL_SYNTAX}" = "0" ] || die "存在语法错误文件,终止部署"
|
|
||||||
log "py_compile 全部通过"
|
|
||||||
|
|
||||||
# ---- 10. 建表(mariadb,显式 TCP,禁 socket)----
|
|
||||||
log "执行 DDL: ${DDL_FILE}"
|
|
||||||
mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
--default-character-set=utf8mb4 "${DB_NAME}" < "${DDL_FILE}"
|
|
||||||
ACTUAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
# Q4 修复:与 DDL 实际表数对齐(36 → 37,新增 pbl_compile_task)
|
|
||||||
[ "${ACTUAL_TABLES}" = "37" ] || die "建表数应为 37,实际 ${ACTUAL_TABLES}"
|
|
||||||
log "建表完成: pbl_* 共 ${ACTUAL_TABLES} 张"
|
|
||||||
|
|
||||||
# ---- 10b. 运行目录前置创建(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本到步骤 13 才 mkdir logs/run,而步骤 11 已经 `tee ${APP_DIR}/logs/self_check.log`。
|
|
||||||
# 全新克隆(仓库不含 logs/,.gitignore 也排除 *.log)时 tee 打不开文件 →
|
|
||||||
# set -euo pipefail 下整条管道非零 → die,部署在「建表已成功、自检未跑」处中断,
|
|
||||||
# 表现为门禁 ③ 表数 0/37(DDL 事务已提交但脚本没走到断言)与门禁 ②③ 全红。
|
|
||||||
mkdir -p "${APP_DIR}/logs" "${APP_DIR}/run"
|
|
||||||
log "运行目录就位: ${APP_DIR}/logs ${APP_DIR}/run"
|
|
||||||
|
|
||||||
# ---- 11. 枚举数量契约自检(QC 退回意见 #15:必须真实可运行输出)----
|
|
||||||
"${PY}" -m pbl_appcodes.self_check | tee "${APP_DIR}/logs/self_check.log"
|
|
||||||
grep -q 'SELF_CHECK pbl_appcodes: PASS 74/74' "${APP_DIR}/logs/self_check.log" \
|
|
||||||
|| die "pbl_appcodes 数量契约自检未通过"
|
|
||||||
|
|
||||||
# ---- 12. 9 项种子数据幂等注入(重复执行零差异)----
|
|
||||||
log "种子注入(第 1 轮)"
|
|
||||||
"${PY}" - <<'PYEOF'
|
|
||||||
import json
|
|
||||||
from pbl_appcodes.seed import seed_all
|
|
||||||
s1 = seed_all()
|
|
||||||
print('[seed round1]', json.dumps(s1, ensure_ascii=False, sort_keys=True))
|
|
||||||
s2 = seed_all()
|
|
||||||
print('[seed round2]', json.dumps(s2, ensure_ascii=False, sort_keys=True))
|
|
||||||
assert s2['groups_inserted'] == 0 and s2['groups_updated'] == 0, '重复执行组有差异'
|
|
||||||
assert s2['items_inserted'] == 0 and s2['items_updated'] == 0, '重复执行项有差异'
|
|
||||||
assert s2['idempotent'] is True, '幂等校验失败'
|
|
||||||
print('[seed] 幂等零差异校验通过: 8 组 74 项')
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 12.5 RBAC 路径注册(Bug WIu0Rkn0KwB0XTjYZz0qk 修复:原脚本从不注册)----
|
|
||||||
# 中央聚合器 apps/pbls/scripts/load_path.py 把各模块 + 宿主自有端点(含本次新增的
|
|
||||||
# /healthz、/healthz.dspy、/api/public/health)逐条写入 permission/rolepermission。
|
|
||||||
# 不注册的后果:进程起来了、端口监听了,但 curl 一律 401/403 → 冒烟门禁 ① HTTP!=200。
|
|
||||||
# 先跑 --check 做通配符 + 聚合完整性门禁(rc!=0 即终止,禁止带缺口部署)。
|
|
||||||
LOAD_PATH="${APP_DIR}/scripts/load_path.py"
|
|
||||||
[ -f "${LOAD_PATH}" ] || die "缺少 RBAC 注册入口 ${LOAD_PATH}"
|
|
||||||
log "RBAC 一致性门禁: load_path.py --check"
|
|
||||||
CHK_RBAC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" --check || CHK_RBAC=$?
|
|
||||||
[ "${CHK_RBAC}" = "0" ] || die "RBAC 聚合门禁未通过(rc=${CHK_RBAC}):存在通配符或整模块聚合失败,终止部署"
|
|
||||||
log "RBAC 注册: load_path.py(逐条显式,无通配符)"
|
|
||||||
REG_RC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" || REG_RC=$?
|
|
||||||
if [ "${REG_RC}" != "0" ]; then
|
|
||||||
# set_role_perm.py 不在本机时 register() 返回 PENDING 清单且 rc=1;
|
|
||||||
# 这不是代码缺陷而是部署机缺工具,明确回显后终止(不静默放过 → 否则又是 403)。
|
|
||||||
die "RBAC 注册未成功(rc=${REG_RC}):set_role_perm.py 不可用或写库失败,端点将 401/403,终止部署"
|
|
||||||
fi
|
|
||||||
log "RBAC 注册完成"
|
|
||||||
|
|
||||||
# ---- 12.8 落地部署目录 DEPLOY_PATH(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本把 DEPLOY_PATH 读出来只用于最后一行日志 echo,从未创建/同步任何内容,
|
|
||||||
# 于是复测门禁 ④「ls -la /d/pbl/pbls」恒为 No such file or directory,
|
|
||||||
# 且 env/test.json 的 start_cmd(${DEPLOY_PATH}/venv/bin/python ${DEPLOY_PATH}/app/pbls.py)
|
|
||||||
# 指向的解释器与入口都不存在 → 门禁 ②③(进程数 0 / 端口未监听)不可能转绿。
|
|
||||||
# 处置:把应用树同步到 DEPLOY_PATH,venv 以软链复用(venv 内 shebang/符号链接是
|
|
||||||
# 绝对路径,复制换目录必坏,软链才安全),随后从 DEPLOY_PATH 启动。
|
|
||||||
if [ "${DEPLOY_PATH}" = "${APP_DIR}" ]; then
|
|
||||||
log "DEPLOY_PATH 与仓库目录同址(${DEPLOY_PATH}),跳过同步"
|
|
||||||
RUN_DIR="${APP_DIR}"
|
|
||||||
RUN_PY="${PY}"
|
|
||||||
RUN_ENTRY="${ENTRY}"
|
|
||||||
else
|
|
||||||
log "同步应用树到部署目录: ${DEPLOY_PATH}"
|
|
||||||
mkdir -p "${DEPLOY_PATH}"
|
|
||||||
# -a 保留权限/时间并**保留软链本身**(8c 建的模块 wwwroot 软链是绝对路径,换机仍可解析);
|
|
||||||
# 排除 venv/logs/run/.git:venv 不可搬移,logs/run 由目标侧自建,.git 不属于运行态。
|
|
||||||
rsync -a --delete \
|
|
||||||
--exclude 'venv/' --exclude '.venv/' --exclude 'logs/' --exclude 'run/' \
|
|
||||||
--exclude '.git/' --exclude '__pycache__/' --exclude '*.pyc' \
|
|
||||||
"${APP_DIR}/" "${DEPLOY_PATH}/"
|
|
||||||
mkdir -p "${DEPLOY_PATH}/logs" "${DEPLOY_PATH}/run"
|
|
||||||
# venv 软链复用(目标侧 start_cmd 要求 ${DEPLOY_PATH}/venv/bin/python 可用)
|
|
||||||
if [ ! -e "${DEPLOY_PATH}/venv" ]; then
|
|
||||||
ln -sfn "${VENV_DIR}" "${DEPLOY_PATH}/venv"
|
|
||||||
fi
|
|
||||||
[ -x "${DEPLOY_PATH}/venv/bin/python" ] || die "部署目录 venv 不可用: ${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
[ -f "${DEPLOY_PATH}/app/${APP_NAME}.py" ] || die "部署目录缺入口: ${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${DEPLOY_PATH}/conf/config.json" ] || die "部署目录缺配置: ${DEPLOY_PATH}/conf/config.json"
|
|
||||||
log "部署目录就位: ${DEPLOY_PATH}(app/conf/wwwroot/scripts + venv 软链 + logs/run)"
|
|
||||||
RUN_DIR="${DEPLOY_PATH}"
|
|
||||||
RUN_PY="${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
RUN_ENTRY="${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 13. 启动应用(唯一入口 + 唯一端口,从部署目录启动)----
|
|
||||||
# 先停旧进程(幂等重跑不撞端口):按 pid 文件 + 入口路径双口径清理。
|
|
||||||
if [ -f "${RUN_DIR}/run/${APP_NAME}.pid" ]; then
|
|
||||||
OLD_PID="$(cat "${RUN_DIR}/run/${APP_NAME}.pid" 2>/dev/null || true)"
|
|
||||||
if [ -n "${OLD_PID:-}" ] && kill -0 "${OLD_PID}" 2>/dev/null; then
|
|
||||||
log "停止旧进程 pid=${OLD_PID}"
|
|
||||||
kill "${OLD_PID}" 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
pkill -f "${RUN_ENTRY}" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
log "启动 ${APP_NAME} 于端口 ${PORT}(cwd=${RUN_DIR})"
|
|
||||||
# cwd 必须是 RUN_DIR:conf/config.json 的 website.root="wwwroot" 是相对路径,
|
|
||||||
# 从别处启动会解析不到 wwwroot → 所有 .ui/.dspy 404(含 /healthz)。
|
|
||||||
( cd "${RUN_DIR}" && nohup "${RUN_PY}" "${RUN_ENTRY}" \
|
|
||||||
> "${RUN_DIR}/logs/${APP_NAME}.out" 2>&1 & echo $! > "${RUN_DIR}/run/${APP_NAME}.pid" )
|
|
||||||
sleep 6
|
|
||||||
|
|
||||||
# 进程 + 端口双自证(对应复测门禁 ②):起不来就把日志尾部打出来再 die,
|
|
||||||
# 避免「HTTP=000 但没人知道为什么」的四轮同构复测。
|
|
||||||
PROC_N="$(ps -ef | grep -F "${APP_NAME}.py" | grep -v grep | wc -l | tr -d '[:space:]')"
|
|
||||||
log "进程数=${PROC_N}"
|
|
||||||
if [ "${PROC_N}" = "0" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "应用进程未存活(ps 计数 0),门禁 ② 不成立,详见上方日志尾部"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 14. 健康检查(两条口径都验:/healthz = 复测门禁 ①,/api/public/health = 原自检口径)----
|
|
||||||
check_http() {
|
|
||||||
curl -s -m 8 -o /dev/null -w '%{http_code}' "$1" 2>/dev/null || echo 000
|
|
||||||
}
|
|
||||||
HZ_CODE="$(check_http "http://127.0.0.1:${PORT}/healthz")"
|
|
||||||
AH_CODE="$(check_http "http://127.0.0.1:${PORT}/api/public/health")"
|
|
||||||
IP_CODE="$(check_http "http://${SSH_HOST:-127.0.0.1}:${PORT}/healthz")"
|
|
||||||
log "健康检查: /healthz=${HZ_CODE} /api/public/health=${AH_CODE} 外网口 /healthz=${IP_CODE}"
|
|
||||||
if [ "${HZ_CODE}" != "200" ] || [ "${AH_CODE}" != "200" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "健康检查失败(/healthz=${HZ_CODE}, /api/public/health=${AH_CODE}),门禁 ① 不成立"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 表数终态复述(对应复测门禁 ③):部署完成后 pbl 库必须有 37 张 pbl_* 表。
|
|
||||||
FINAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
log "终态表数: ${DB_NAME}.pbl_* = ${FINAL_TABLES}/37"
|
|
||||||
[ "${FINAL_TABLES}" = "37" ] || die "终态表数 ${FINAL_TABLES} != 37,门禁 ③ 不成立"
|
|
||||||
|
|
||||||
log "BUILD SUCCESS: ${APP_NAME} 已运行于 http://127.0.0.1:${PORT}/ (部署路径 ${DEPLOY_PATH},/healthz=200,表数 ${FINAL_TABLES}/37)"
|
|
||||||
367
build.sh.r4bak
367
build.sh.r4bak
@ -1,367 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# =====================================================================
|
|
||||||
# PBL Agent OS 验证原型(Phase 0/1)—— 一键部署脚本(test 环境)
|
|
||||||
# 端口唯一事实源:projects/pbls/env/test.json(9380);本脚本不硬编码端口,从 env 读取
|
|
||||||
# 门禁:建表 37 张 + 9 项种子全绿 + self_check PASS 74/74 + py_compile 全通过
|
|
||||||
# =====================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
APP_NAME="pbls"
|
|
||||||
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
WS_DIR="$(cd "${APP_DIR}/../.." && pwd)"
|
|
||||||
ENV_FILE="${WS_DIR}/projects/pbls/env/test.json"
|
|
||||||
DDL_FILE="${APP_DIR}/scripts/ddl/pbls_tables.sql"
|
|
||||||
VENV_DIR="${APP_DIR}/venv"
|
|
||||||
PY="${VENV_DIR}/bin/python"
|
|
||||||
|
|
||||||
log() { echo "[build.sh][$(date '+%F %T')] $*"; }
|
|
||||||
die() { echo "[build.sh][FATAL] $*" >&2; exit 1; }
|
|
||||||
|
|
||||||
# ---- 0. 前置检查:env 唯一事实源必须存在且无占位 ----
|
|
||||||
[ -f "${ENV_FILE}" ] || die "缺少环境事实源 ${ENV_FILE}"
|
|
||||||
if grep -Eq '待明确|TBD|占位|CHANGE_ME|xxx' "${ENV_FILE}"; then
|
|
||||||
die "${ENV_FILE} 含占位/待明确字段,禁止部署"
|
|
||||||
fi
|
|
||||||
|
|
||||||
read_env() { python3 -c "import json,sys;print(json.load(open('${ENV_FILE}'))$1)"; }
|
|
||||||
PORT="$(read_env "['port']")"
|
|
||||||
DB_HOST="$(read_env "['db']['host']")"
|
|
||||||
DB_PORT="$(read_env "['db']['port']")"
|
|
||||||
DB_USER="$(read_env "['db']['user']")"
|
|
||||||
DB_PASS="$(read_env "['db']['password']")"
|
|
||||||
DB_NAME="$(read_env "['db']['dbname']")"
|
|
||||||
DEPLOY_PATH="$(read_env "['deploy']['path']")"
|
|
||||||
# 外网口自检用(复测门禁 ① 同时 curl 127.0.0.1 与 192.168.16.3 两个地址);
|
|
||||||
# env 无 ssh.host 时回退 127.0.0.1,不让 read_env 抛 KeyError 中断部署。
|
|
||||||
SSH_HOST="$(python3 -c "import json;print(json.load(open('${ENV_FILE}')).get('ssh',{}).get('host') or '127.0.0.1')")"
|
|
||||||
log "env 事实源: port=${PORT} db=${DB_NAME}@${DB_HOST}:${DB_PORT} deploy=${DEPLOY_PATH}"
|
|
||||||
|
|
||||||
# ---- 1. 端口一致性核验(conf/config.json 必须与 env 同值)----
|
|
||||||
CONF_PORT="$(python3 -c "import json;print(json.load(open('${APP_DIR}/conf/config.json'))['port'])")"
|
|
||||||
[ "${CONF_PORT}" = "${PORT}" ] || die "端口不一致: conf=${CONF_PORT} env=${PORT}"
|
|
||||||
log "端口一致性核验通过: ${PORT}"
|
|
||||||
|
|
||||||
# ---- 2. 应用仓库不得另存 env/(端口唯一事实源在项目侧)----
|
|
||||||
[ ! -d "${APP_DIR}/env" ] || die "应用仓库存在 env/ 目录,违反端口唯一事实源规则"
|
|
||||||
log "应用仓库无 env/ 目录,核验通过"
|
|
||||||
|
|
||||||
# ---- 3. 入口硬门禁核验(6.1 / 6.2)----
|
|
||||||
ENTRY="${APP_DIR}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${ENTRY}" ] || die "缺少入口 ${ENTRY}"
|
|
||||||
grep -q 'from ahserver.webapp import webapp' "${ENTRY}" || die "入口未使用 ahserver.webapp.webapp"
|
|
||||||
grep -q 'webapp(init)' "${ENTRY}" || die "入口未调用 webapp(init)"
|
|
||||||
if grep -Eq 'Flask|FastAPI|Django|http\.server|jsonify|@app\.route' "${ENTRY}"; then
|
|
||||||
die "入口含禁用框架关键字(Flask/FastAPI/Django/http.server/jsonify/@app.route)"
|
|
||||||
fi
|
|
||||||
log "硬门禁 6.1 通过: ahserver.webapp + webapp(init),无禁用框架"
|
|
||||||
|
|
||||||
# ServerEnv() 行号必须早于 load_rbac() 行号
|
|
||||||
ENV_LINE="$(grep -n 'ServerEnv()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
RBAC_LINE="$(grep -n 'load_rbac()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
[ -n "${ENV_LINE}" ] && [ -n "${RBAC_LINE}" ] || die "入口缺 ServerEnv() 或 load_rbac() 显式调用"
|
|
||||||
[ "${ENV_LINE}" -lt "${RBAC_LINE}" ] || die "ServerEnv() 必须早于 load_rbac()(env 行 ${ENV_LINE} >= rbac 行 ${RBAC_LINE})"
|
|
||||||
grep -q 'load_appbase()' "${ENTRY}" || die "入口缺 load_appbase() 显式调用"
|
|
||||||
grep -q 'load_pybricks()' "${ENTRY}" || die "入口缺 load_pybricks() 显式调用"
|
|
||||||
log "硬门禁 6.2 通过: ServerEnv()(L${ENV_LINE}) < load_rbac()(L${RBAC_LINE}),load_appbase/load_rbac/load_pybricks 均显式调用"
|
|
||||||
|
|
||||||
# ---- 4. DDL 方言核验(6.4:mariadb,BIGINT AUTO_INCREMENT,零 SERIAL/FK/ENUM/TIMESTAMP)----
|
|
||||||
# 【Q4 修复 / deploy_test 第 3 轮实测】旧版直接对 DDL **原文** grep,存在两类假阳性:
|
|
||||||
# (1) DDL 文末「契约自检」注释块为说明"零命中项",逐字写出了
|
|
||||||
# FOREIGN KEY / REFERENCES / ENUM( / SERIAL / BIGSERIAL / nextval / TIMESTAMP
|
|
||||||
# (实测原文命中 3 行:L6 头部说明、L220 COMMENT 串内的 references、L891 契约自检块),
|
|
||||||
# 旧 `grep -Eiq` 一律触发 die —— 注释/COMMENT 串被当成真实 DDL,门禁必然误杀;
|
|
||||||
# (2) `CREATE TABLE` / `AUTO_INCREMENT` 计数同理会把注释行与 COMMENT '...' 串内字样算进去,
|
|
||||||
# 表数不可信(且旧断言 36 与 DDL 实际 37 张不符,新增 pbl_compile_task 后未同步)。
|
|
||||||
# 修复:改为「先剥 SQL 注释 + 字符串字面量(含 COMMENT 串),再在纯 DDL 正文上扫描」,
|
|
||||||
# 由 scripts/ddl_gate_check.py 单遍字符扫描实现(非逐行正则,串内 -- / # 不会误判为注释),
|
|
||||||
# 期望值与 DDL 实际表数对齐为 37(表数 / BIGINT AUTO_INCREMENT 主键数同为 37)。
|
|
||||||
[ -f "${DDL_FILE}" ] || die "缺少 DDL ${DDL_FILE}"
|
|
||||||
DDL_GATE="${APP_DIR}/scripts/ddl_gate_check.py"
|
|
||||||
[ -f "${DDL_GATE}" ] || die "缺少 DDL 门禁扫描器 ${DDL_GATE}(6.4 依赖,禁止回退到原文 grep)"
|
|
||||||
|
|
||||||
# 4a. 扫描器自身可信度:先跑内置最小单测(剥注释/剥串/幂等/真实违规不漏报)
|
|
||||||
SELFTEST_RC=0
|
|
||||||
python3 "${DDL_GATE}" --selftest || SELFTEST_RC=$?
|
|
||||||
[ "${SELFTEST_RC}" = "0" ] || die "DDL 门禁扫描器自检未通过(--selftest rc=${SELFTEST_RC}),扫描结果不可信,终止部署"
|
|
||||||
|
|
||||||
# 4b. 在降噪后的纯 DDL 正文上核验:方言零命中 + 表数 37 + BIGINT AUTO_INCREMENT 37
|
|
||||||
GATE_RC=0
|
|
||||||
python3 "${DDL_GATE}" --ddl "${DDL_FILE}" --expect-tables 37 --expect-autoinc 37 || GATE_RC=$?
|
|
||||||
[ "${GATE_RC}" = "0" ] || die "硬门禁 6.4 未通过(ddl_gate_check.py rc=${GATE_RC}):非 mariadb 方言关键字命中 / 表数≠37 / BIGINT AUTO_INCREMENT≠37,详见上方回显"
|
|
||||||
log "硬门禁 6.4 通过: mariadb 方言(剥注释+COMMENT 串后零命中),37 表 / 37 处 BIGINT AUTO_INCREMENT"
|
|
||||||
|
|
||||||
# ---- 5. 依赖声明核验(6.5)----
|
|
||||||
for dep in ahserver sqlor apppublic appbase rbac PyMySQL; do
|
|
||||||
grep -qi "^${dep}" "${APP_DIR}/requirements.txt" || die "requirements.txt 缺依赖 ${dep}"
|
|
||||||
done
|
|
||||||
log "硬门禁 6.5 通过: ahserver/sqlor/apppublic/appbase/rbac/PyMySQL 均已声明"
|
|
||||||
|
|
||||||
# ---- 6. website.processors / indexes 核验(缺一会导致 / 返回 500)----
|
|
||||||
python3 - <<'PYEOF'
|
|
||||||
import json, sys
|
|
||||||
conf = json.load(open('conf/config.json'))
|
|
||||||
procs = dict((p[0], p[1]) for p in conf['website']['processors'])
|
|
||||||
for ext, name in (('.tmpl', 'tmpl'), ('.ui', 'bui'), ('.dspy', 'dspy')):
|
|
||||||
assert procs.get(ext) == name, 'processors 缺 %s->%s,实际 %r' % (ext, name, procs.get(ext))
|
|
||||||
assert 'index.ui' in conf['website']['indexes'], 'indexes 缺 index.ui'
|
|
||||||
print('[build.sh] website 核验通过: processors=%s indexes=%s' % (procs, conf['website']['indexes']))
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 7. venv + 依赖安装 ----
|
|
||||||
if [ ! -x "${PY}" ]; then
|
|
||||||
log "创建 venv: ${VENV_DIR}"
|
|
||||||
python3 -m venv "${VENV_DIR}"
|
|
||||||
fi
|
|
||||||
"${PY}" -m pip install --upgrade pip wheel setuptools
|
|
||||||
"${PY}" -m pip install -r "${APP_DIR}/requirements.txt"
|
|
||||||
|
|
||||||
# ---- 7b. 测试前置依赖(DISC.2)----
|
|
||||||
# 运行时依赖(requirements.txt)刻意不含 pytest;测试依赖单列 requirements-test.txt,
|
|
||||||
# 由本步装进同一个 venv,使
|
|
||||||
# source venv/bin/activate && python3 -m pytest tests/test_m5a_idempotency.py -v
|
|
||||||
# 在**不手工注入 PYTHONPATH** 的前提下可直接执行(此前靠 PYTHONPATH=<ws>/.pylibs
|
|
||||||
# 临时注入跑通,换机器/换角色即失效,test 角色会误判为环境不可用)。
|
|
||||||
# 只要运行时依赖的场景(如生产镜像)用 PBL_SKIP_TEST_DEPS=1 显式跳过。
|
|
||||||
REQ_TEST="${APP_DIR}/requirements-test.txt"
|
|
||||||
if [ "${PBL_SKIP_TEST_DEPS:-0}" = "1" ]; then
|
|
||||||
log "PBL_SKIP_TEST_DEPS=1:跳过测试前置依赖(venv 内将没有 pytest,测试角色需自行引导)"
|
|
||||||
elif [ ! -f "${REQ_TEST}" ]; then
|
|
||||||
die "缺测试依赖清单 ${REQ_TEST}(DISC.2 要求显式声明;确要跳过请设 PBL_SKIP_TEST_DEPS=1)"
|
|
||||||
else
|
|
||||||
log "安装测试前置依赖: ${REQ_TEST}"
|
|
||||||
"${PY}" -m pip install -r "${REQ_TEST}"
|
|
||||||
# 自证:装完必须真能 import + 跑 --version,否则本步等于没做(不让注释与事实矛盾)
|
|
||||||
"${PY}" -c 'import pytest' >/dev/null 2>&1 \
|
|
||||||
|| die "安装后 venv 内仍无法 import pytest(${PY})"
|
|
||||||
log "7b 自证通过: $("${PY}" -m pytest --version 2>&1 | head -1) @ ${PY}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)----
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}" ] || continue
|
|
||||||
if [ -f "${mod_dir}/pyproject.toml" ] || [ -f "${mod_dir}/setup.py" ]; then
|
|
||||||
log "安装模块 $(basename "${mod_dir}")"
|
|
||||||
"${PY}" -m pip install -e "${mod_dir}"
|
|
||||||
else
|
|
||||||
export PYTHONPATH="${mod_dir}:${PYTHONPATH:-}"
|
|
||||||
log "模块 $(basename "${mod_dir}") 无打包描述,加入 PYTHONPATH"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ---- 8b. M9 覆盖层页面壳生成(构建产物,不入库)+ 一致性门禁 ----
|
|
||||||
# 页面壳由 pbl_scense_ext 的生成器产出;产物目录 wwwroot/pbl_overlay/ 在模块仓
|
|
||||||
# .gitignore 中被排除,因此必须在构建期生成,否则新克隆/新部署环境该目录为空、
|
|
||||||
# /pbl_overlay/<key>.ui 导航全量 404。位置:模块安装之后、模块 wwwroot 软链(紧随其后的
|
|
||||||
# 「8c. 模块 wwwroot 挂载」段)之前——生成物落在模块仓,必须再经 8c 挂载到应用 wwwroot
|
|
||||||
# 才能在应用侧 URL /pbl_scense_ext/pbl_overlay/<key>.ui 上取到。
|
|
||||||
OVERLAY_GEN="${WS_DIR}/modules/pbl_scense_ext/scripts/gen_overlay_pages.py"
|
|
||||||
[ -f "${OVERLAY_GEN}" ] || die "缺少覆盖层页面壳生成器: ${OVERLAY_GEN}(modules/pbl_scense_ext 未就位)"
|
|
||||||
log "生成 PBL 覆盖层页面壳: ${OVERLAY_GEN}"
|
|
||||||
# 注意:不能用 `if ! cmd; then die "... rc=$?"` —— 进入分支时 $? 已被 if 取反为 0,
|
|
||||||
# 打印出的 rc 恒为 0 会误导排障;改为显式捕获真实返回码。
|
|
||||||
GEN_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" || GEN_RC=$?
|
|
||||||
if [ "${GEN_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳生成失败(gen_overlay_pages.py rc=${GEN_RC}):wwwroot/pbl_overlay/ 将为空,/pbl_overlay/<key>.ui 全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳门禁校验(gen_overlay_pages.py --check)"
|
|
||||||
CHK_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" --check || CHK_RC=$?
|
|
||||||
if [ "${CHK_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳门禁校验未通过(--check rc=${CHK_RC}):i18n zh/en key 不一致,或页面壳与 PAGES 注册表不一致,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳生成 + 门禁校验通过(12 个页面壳 / i18n key 一致)"
|
|
||||||
|
|
||||||
# ---- 8c. 模块 wwwroot 挂载(软链)+ 应用侧可达性门禁(QC 退回意见 #1/#2)----
|
|
||||||
# 上一版只做了 8b「生成」,生成物落在模块仓 modules/<m>/wwwroot/ 下,但本脚本从未把
|
|
||||||
# 模块 wwwroot 挂进应用 wwwroot —— 应用 website.root=wwwroot 只指向应用自身目录,
|
|
||||||
# 因此 build.sh 跑完后应用侧依然取不到 /pbl_scense_ext/pbl_overlay/<key>.ui(可达链路
|
|
||||||
# 断在挂载环节,全量 404 并未消除)。此处按 web-application-spec「挂载模块 9 步流程」
|
|
||||||
# 第 5 步(wwwroot 软链,非 cp,保持与模块仓单一副本同步)补齐,并立即以应用侧路径
|
|
||||||
# 实测 12 个页面壳数量作为门禁,rc≠0 终止部署。
|
|
||||||
LINKED_MODULES=""
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}/wwwroot" ] || continue
|
|
||||||
mod_name="$(basename "${mod_dir}")"
|
|
||||||
www_link="${APP_DIR}/wwwroot/${mod_name}"
|
|
||||||
# 已存在但不是软链(应用仓自带同名真实目录)时不覆盖,直接终止让人工处置,
|
|
||||||
# 避免 ln -sfn 把软链建进既有目录里、或静默删掉应用仓跟踪文件。
|
|
||||||
if [ -e "${www_link}" ] && [ ! -L "${www_link}" ]; then
|
|
||||||
die "wwwroot 挂载冲突:${www_link} 已存在且不是软链(应用仓自带目录?),请人工处置后重试"
|
|
||||||
fi
|
|
||||||
ln -sfn "${mod_dir}/wwwroot" "${www_link}"
|
|
||||||
LINKED_MODULES="${LINKED_MODULES} ${mod_name}"
|
|
||||||
done
|
|
||||||
[ -n "${LINKED_MODULES}" ] || die "未发现任何含 wwwroot/ 的 pbl_* 模块,模块 wwwroot 挂载为空"
|
|
||||||
log "模块 wwwroot 软链挂载完成:${LINKED_MODULES}"
|
|
||||||
|
|
||||||
# 挂载后应用侧可达性门禁:覆盖层页面壳必须能通过应用 wwwroot 路径取到
|
|
||||||
APP_OVERLAY_DIR="${APP_DIR}/wwwroot/pbl_scense_ext/pbl_overlay"
|
|
||||||
APP_UI_COUNT=0
|
|
||||||
if [ -d "${APP_OVERLAY_DIR}" ]; then
|
|
||||||
APP_UI_COUNT="$(find -L "${APP_OVERLAY_DIR}" -maxdepth 1 -name '*.ui' | wc -l | tr -d '[:space:]')"
|
|
||||||
fi
|
|
||||||
if [ "${APP_UI_COUNT}" != "12" ]; then
|
|
||||||
die "应用侧覆盖层页面壳应为 12 个,实际 ${APP_UI_COUNT}(${APP_OVERLAY_DIR}):模块 wwwroot 未挂载成功或生成器未产出,/pbl_scense_ext/pbl_overlay/<key>.ui 将全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "应用侧可达性核验通过: ${APP_OVERLAY_DIR}/*.ui = ${APP_UI_COUNT}"
|
|
||||||
|
|
||||||
# ---- 9. 语法验证:全部 .py 跑 py_compile(QC 退回意见 #16)----
|
|
||||||
log "py_compile 全量语法验证"
|
|
||||||
FAIL_SYNTAX=0
|
|
||||||
while IFS= read -r pyfile; do
|
|
||||||
if ! "${PY}" -m py_compile "${pyfile}"; then
|
|
||||||
echo "[build.sh][SYNTAX FAIL] ${pyfile}"
|
|
||||||
FAIL_SYNTAX=1
|
|
||||||
fi
|
|
||||||
done < <(find "${APP_DIR}/app" "${WS_DIR}/modules/pbl_common" "${WS_DIR}/modules/pbl_appcodes" -name '*.py' -not -path '*/venv/*' 2>/dev/null)
|
|
||||||
[ "${FAIL_SYNTAX}" = "0" ] || die "存在语法错误文件,终止部署"
|
|
||||||
log "py_compile 全部通过"
|
|
||||||
|
|
||||||
# ---- 10. 建表(mariadb,显式 TCP,禁 socket)----
|
|
||||||
log "执行 DDL: ${DDL_FILE}"
|
|
||||||
mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
--default-character-set=utf8mb4 "${DB_NAME}" < "${DDL_FILE}"
|
|
||||||
ACTUAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
# Q4 修复:与 DDL 实际表数对齐(36 → 37,新增 pbl_compile_task)
|
|
||||||
[ "${ACTUAL_TABLES}" = "37" ] || die "建表数应为 37,实际 ${ACTUAL_TABLES}"
|
|
||||||
log "建表完成: pbl_* 共 ${ACTUAL_TABLES} 张"
|
|
||||||
|
|
||||||
# ---- 10b. 运行目录前置创建(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本到步骤 13 才 mkdir logs/run,而步骤 11 已经 `tee ${APP_DIR}/logs/self_check.log`。
|
|
||||||
# 全新克隆(仓库不含 logs/,.gitignore 也排除 *.log)时 tee 打不开文件 →
|
|
||||||
# set -euo pipefail 下整条管道非零 → die,部署在「建表已成功、自检未跑」处中断,
|
|
||||||
# 表现为门禁 ③ 表数 0/37(DDL 事务已提交但脚本没走到断言)与门禁 ②③ 全红。
|
|
||||||
mkdir -p "${APP_DIR}/logs" "${APP_DIR}/run"
|
|
||||||
log "运行目录就位: ${APP_DIR}/logs ${APP_DIR}/run"
|
|
||||||
|
|
||||||
# ---- 11. 枚举数量契约自检(QC 退回意见 #15:必须真实可运行输出)----
|
|
||||||
"${PY}" -m pbl_appcodes.self_check | tee "${APP_DIR}/logs/self_check.log"
|
|
||||||
grep -q 'SELF_CHECK pbl_appcodes: PASS 74/74' "${APP_DIR}/logs/self_check.log" \
|
|
||||||
|| die "pbl_appcodes 数量契约自检未通过"
|
|
||||||
|
|
||||||
# ---- 12. 9 项种子数据幂等注入(重复执行零差异)----
|
|
||||||
log "种子注入(第 1 轮)"
|
|
||||||
"${PY}" - <<'PYEOF'
|
|
||||||
import json
|
|
||||||
from pbl_appcodes.seed import seed_all
|
|
||||||
s1 = seed_all()
|
|
||||||
print('[seed round1]', json.dumps(s1, ensure_ascii=False, sort_keys=True))
|
|
||||||
s2 = seed_all()
|
|
||||||
print('[seed round2]', json.dumps(s2, ensure_ascii=False, sort_keys=True))
|
|
||||||
assert s2['groups_inserted'] == 0 and s2['groups_updated'] == 0, '重复执行组有差异'
|
|
||||||
assert s2['items_inserted'] == 0 and s2['items_updated'] == 0, '重复执行项有差异'
|
|
||||||
assert s2['idempotent'] is True, '幂等校验失败'
|
|
||||||
print('[seed] 幂等零差异校验通过: 8 组 74 项')
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 12.5 RBAC 路径注册(Bug WIu0Rkn0KwB0XTjYZz0qk 修复:原脚本从不注册)----
|
|
||||||
# 中央聚合器 apps/pbls/scripts/load_path.py 把各模块 + 宿主自有端点(含本次新增的
|
|
||||||
# /healthz、/healthz.dspy、/api/public/health)逐条写入 permission/rolepermission。
|
|
||||||
# 不注册的后果:进程起来了、端口监听了,但 curl 一律 401/403 → 冒烟门禁 ① HTTP!=200。
|
|
||||||
# 先跑 --check 做通配符 + 聚合完整性门禁(rc!=0 即终止,禁止带缺口部署)。
|
|
||||||
LOAD_PATH="${APP_DIR}/scripts/load_path.py"
|
|
||||||
[ -f "${LOAD_PATH}" ] || die "缺少 RBAC 注册入口 ${LOAD_PATH}"
|
|
||||||
log "RBAC 一致性门禁: load_path.py --check"
|
|
||||||
CHK_RBAC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" --check || CHK_RBAC=$?
|
|
||||||
[ "${CHK_RBAC}" = "0" ] || die "RBAC 聚合门禁未通过(rc=${CHK_RBAC}):存在通配符或整模块聚合失败,终止部署"
|
|
||||||
log "RBAC 注册: load_path.py(逐条显式,无通配符)"
|
|
||||||
REG_RC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" || REG_RC=$?
|
|
||||||
if [ "${REG_RC}" != "0" ]; then
|
|
||||||
# set_role_perm.py 不在本机时 register() 返回 PENDING 清单且 rc=1;
|
|
||||||
# 这不是代码缺陷而是部署机缺工具,明确回显后终止(不静默放过 → 否则又是 403)。
|
|
||||||
die "RBAC 注册未成功(rc=${REG_RC}):set_role_perm.py 不可用或写库失败,端点将 401/403,终止部署"
|
|
||||||
fi
|
|
||||||
log "RBAC 注册完成"
|
|
||||||
|
|
||||||
# ---- 12.8 落地部署目录 DEPLOY_PATH(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本把 DEPLOY_PATH 读出来只用于最后一行日志 echo,从未创建/同步任何内容,
|
|
||||||
# 于是复测门禁 ④「ls -la /d/pbl/pbls」恒为 No such file or directory,
|
|
||||||
# 且 env/test.json 的 start_cmd(${DEPLOY_PATH}/venv/bin/python ${DEPLOY_PATH}/app/pbls.py)
|
|
||||||
# 指向的解释器与入口都不存在 → 门禁 ②③(进程数 0 / 端口未监听)不可能转绿。
|
|
||||||
# 处置:把应用树同步到 DEPLOY_PATH,venv 以软链复用(venv 内 shebang/符号链接是
|
|
||||||
# 绝对路径,复制换目录必坏,软链才安全),随后从 DEPLOY_PATH 启动。
|
|
||||||
if [ "${DEPLOY_PATH}" = "${APP_DIR}" ]; then
|
|
||||||
log "DEPLOY_PATH 与仓库目录同址(${DEPLOY_PATH}),跳过同步"
|
|
||||||
RUN_DIR="${APP_DIR}"
|
|
||||||
RUN_PY="${PY}"
|
|
||||||
RUN_ENTRY="${ENTRY}"
|
|
||||||
else
|
|
||||||
log "同步应用树到部署目录: ${DEPLOY_PATH}"
|
|
||||||
mkdir -p "${DEPLOY_PATH}"
|
|
||||||
# -a 保留权限/时间并**保留软链本身**(8c 建的模块 wwwroot 软链是绝对路径,换机仍可解析);
|
|
||||||
# 排除 venv/logs/run/.git:venv 不可搬移,logs/run 由目标侧自建,.git 不属于运行态。
|
|
||||||
rsync -a --delete \
|
|
||||||
--exclude 'venv/' --exclude '.venv/' --exclude 'logs/' --exclude 'run/' \
|
|
||||||
--exclude '.git/' --exclude '__pycache__/' --exclude '*.pyc' \
|
|
||||||
"${APP_DIR}/" "${DEPLOY_PATH}/"
|
|
||||||
mkdir -p "${DEPLOY_PATH}/logs" "${DEPLOY_PATH}/run"
|
|
||||||
# venv 软链复用(目标侧 start_cmd 要求 ${DEPLOY_PATH}/venv/bin/python 可用)
|
|
||||||
if [ ! -e "${DEPLOY_PATH}/venv" ]; then
|
|
||||||
ln -sfn "${VENV_DIR}" "${DEPLOY_PATH}/venv"
|
|
||||||
fi
|
|
||||||
[ -x "${DEPLOY_PATH}/venv/bin/python" ] || die "部署目录 venv 不可用: ${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
[ -f "${DEPLOY_PATH}/app/${APP_NAME}.py" ] || die "部署目录缺入口: ${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${DEPLOY_PATH}/conf/config.json" ] || die "部署目录缺配置: ${DEPLOY_PATH}/conf/config.json"
|
|
||||||
log "部署目录就位: ${DEPLOY_PATH}(app/conf/wwwroot/scripts + venv 软链 + logs/run)"
|
|
||||||
RUN_DIR="${DEPLOY_PATH}"
|
|
||||||
RUN_PY="${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
RUN_ENTRY="${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 13. 启动应用(唯一入口 + 唯一端口,从部署目录启动)----
|
|
||||||
# 先停旧进程(幂等重跑不撞端口):按 pid 文件 + 入口路径双口径清理。
|
|
||||||
if [ -f "${RUN_DIR}/run/${APP_NAME}.pid" ]; then
|
|
||||||
OLD_PID="$(cat "${RUN_DIR}/run/${APP_NAME}.pid" 2>/dev/null || true)"
|
|
||||||
if [ -n "${OLD_PID:-}" ] && kill -0 "${OLD_PID}" 2>/dev/null; then
|
|
||||||
log "停止旧进程 pid=${OLD_PID}"
|
|
||||||
kill "${OLD_PID}" 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
pkill -f "${RUN_ENTRY}" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
log "启动 ${APP_NAME} 于端口 ${PORT}(cwd=${RUN_DIR})"
|
|
||||||
# cwd 必须是 RUN_DIR:conf/config.json 的 website.root="wwwroot" 是相对路径,
|
|
||||||
# 从别处启动会解析不到 wwwroot → 所有 .ui/.dspy 404(含 /healthz)。
|
|
||||||
( cd "${RUN_DIR}" && nohup "${RUN_PY}" "${RUN_ENTRY}" \
|
|
||||||
> "${RUN_DIR}/logs/${APP_NAME}.out" 2>&1 & echo $! > "${RUN_DIR}/run/${APP_NAME}.pid" )
|
|
||||||
sleep 6
|
|
||||||
|
|
||||||
# 进程 + 端口双自证(对应复测门禁 ②):起不来就把日志尾部打出来再 die,
|
|
||||||
# 避免「HTTP=000 但没人知道为什么」的四轮同构复测。
|
|
||||||
PROC_N="$(ps -ef | grep -F "${APP_NAME}.py" | grep -v grep | wc -l | tr -d '[:space:]')"
|
|
||||||
log "进程数=${PROC_N}"
|
|
||||||
if [ "${PROC_N}" = "0" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "应用进程未存活(ps 计数 0),门禁 ② 不成立,详见上方日志尾部"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 14. 健康检查(两条口径都验:/healthz = 复测门禁 ①,/api/public/health = 原自检口径)----
|
|
||||||
check_http() {
|
|
||||||
curl -s -m 8 -o /dev/null -w '%{http_code}' "$1" 2>/dev/null || echo 000
|
|
||||||
}
|
|
||||||
HZ_CODE="$(check_http "http://127.0.0.1:${PORT}/healthz")"
|
|
||||||
AH_CODE="$(check_http "http://127.0.0.1:${PORT}/api/public/health")"
|
|
||||||
IP_CODE="$(check_http "http://${SSH_HOST:-127.0.0.1}:${PORT}/healthz")"
|
|
||||||
log "健康检查: /healthz=${HZ_CODE} /api/public/health=${AH_CODE} 外网口 /healthz=${IP_CODE}"
|
|
||||||
if [ "${HZ_CODE}" != "200" ] || [ "${AH_CODE}" != "200" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "健康检查失败(/healthz=${HZ_CODE}, /api/public/health=${AH_CODE}),门禁 ① 不成立"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 表数终态复述(对应复测门禁 ③):部署完成后 pbl 库必须有 37 张 pbl_* 表。
|
|
||||||
FINAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
log "终态表数: ${DB_NAME}.pbl_* = ${FINAL_TABLES}/37"
|
|
||||||
[ "${FINAL_TABLES}" = "37" ] || die "终态表数 ${FINAL_TABLES} != 37,门禁 ③ 不成立"
|
|
||||||
|
|
||||||
log "BUILD SUCCESS: ${APP_NAME} 已运行于 http://127.0.0.1:${PORT}/ (部署路径 ${DEPLOY_PATH},/healthz=200,表数 ${FINAL_TABLES}/37)"
|
|
||||||
419
build.sh.r8bak
419
build.sh.r8bak
@ -1,419 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# =====================================================================
|
|
||||||
# PBL Agent OS 验证原型(Phase 0/1)—— 一键部署脚本(test 环境)
|
|
||||||
# 端口唯一事实源:projects/pbls/env/test.json(9380);本脚本不硬编码端口,从 env 读取
|
|
||||||
# 门禁:建表 37 张 + 9 项种子全绿 + self_check PASS 74/74 + py_compile 全通过
|
|
||||||
# =====================================================================
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
APP_NAME="pbls"
|
|
||||||
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
WS_DIR="$(cd "${APP_DIR}/../.." && pwd)"
|
|
||||||
ENV_FILE="${WS_DIR}/projects/pbls/env/test.json"
|
|
||||||
DDL_FILE="${APP_DIR}/scripts/ddl/pbls_tables.sql"
|
|
||||||
VENV_DIR="${APP_DIR}/venv"
|
|
||||||
PY="${VENV_DIR}/bin/python"
|
|
||||||
|
|
||||||
log() { echo "[build.sh][$(date '+%F %T')] $*"; }
|
|
||||||
die() { echo "[build.sh][FATAL] $*" >&2; exit 1; }
|
|
||||||
|
|
||||||
# ---- 0. 前置检查:env 唯一事实源必须存在且无占位 ----
|
|
||||||
[ -f "${ENV_FILE}" ] || die "缺少环境事实源 ${ENV_FILE}"
|
|
||||||
if grep -Eq '待明确|TBD|占位|CHANGE_ME|xxx' "${ENV_FILE}"; then
|
|
||||||
die "${ENV_FILE} 含占位/待明确字段,禁止部署"
|
|
||||||
fi
|
|
||||||
|
|
||||||
read_env() { python3 -c "import json,sys;print(json.load(open('${ENV_FILE}'))$1)"; }
|
|
||||||
PORT="$(read_env "['port']")"
|
|
||||||
DB_HOST="$(read_env "['db']['host']")"
|
|
||||||
DB_PORT="$(read_env "['db']['port']")"
|
|
||||||
DB_USER="$(read_env "['db']['user']")"
|
|
||||||
DB_PASS="$(read_env "['db']['password']")"
|
|
||||||
DB_NAME="$(read_env "['db']['dbname']")"
|
|
||||||
DEPLOY_PATH="$(read_env "['deploy']['path']")"
|
|
||||||
# 外网口自检用(复测门禁 ① 同时 curl 127.0.0.1 与 192.168.16.3 两个地址);
|
|
||||||
# env 无 ssh.host 时回退 127.0.0.1,不让 read_env 抛 KeyError 中断部署。
|
|
||||||
SSH_HOST="$(python3 -c "import json;print(json.load(open('${ENV_FILE}')).get('ssh',{}).get('host') or '127.0.0.1')")"
|
|
||||||
log "env 事实源: port=${PORT} db=${DB_NAME}@${DB_HOST}:${DB_PORT} deploy=${DEPLOY_PATH}"
|
|
||||||
|
|
||||||
# ---- 1. 端口一致性核验(conf/config.json 必须与 env 同值)----
|
|
||||||
CONF_PORT="$(python3 -c "import json;print(json.load(open('${APP_DIR}/conf/config.json'))['port'])")"
|
|
||||||
[ "${CONF_PORT}" = "${PORT}" ] || die "端口不一致: conf=${CONF_PORT} env=${PORT}"
|
|
||||||
log "端口一致性核验通过: ${PORT}"
|
|
||||||
|
|
||||||
# ---- 2. 应用仓库不得另存 env/(端口唯一事实源在项目侧)----
|
|
||||||
[ ! -d "${APP_DIR}/env" ] || die "应用仓库存在 env/ 目录,违反端口唯一事实源规则"
|
|
||||||
log "应用仓库无 env/ 目录,核验通过"
|
|
||||||
|
|
||||||
# ---- 3. 入口硬门禁核验(6.1 / 6.2)----
|
|
||||||
ENTRY="${APP_DIR}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${ENTRY}" ] || die "缺少入口 ${ENTRY}"
|
|
||||||
grep -q 'from ahserver.webapp import webapp' "${ENTRY}" || die "入口未使用 ahserver.webapp.webapp"
|
|
||||||
grep -q 'webapp(init)' "${ENTRY}" || die "入口未调用 webapp(init)"
|
|
||||||
if grep -Eq 'Flask|FastAPI|Django|http\.server|jsonify|@app\.route' "${ENTRY}"; then
|
|
||||||
die "入口含禁用框架关键字(Flask/FastAPI/Django/http.server/jsonify/@app.route)"
|
|
||||||
fi
|
|
||||||
log "硬门禁 6.1 通过: ahserver.webapp + webapp(init),无禁用框架"
|
|
||||||
|
|
||||||
# ServerEnv() 行号必须早于 load_rbac() 行号
|
|
||||||
ENV_LINE="$(grep -n 'ServerEnv()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
RBAC_LINE="$(grep -n 'load_rbac()' "${ENTRY}" | head -1 | cut -d: -f1)"
|
|
||||||
[ -n "${ENV_LINE}" ] && [ -n "${RBAC_LINE}" ] || die "入口缺 ServerEnv() 或 load_rbac() 显式调用"
|
|
||||||
[ "${ENV_LINE}" -lt "${RBAC_LINE}" ] || die "ServerEnv() 必须早于 load_rbac()(env 行 ${ENV_LINE} >= rbac 行 ${RBAC_LINE})"
|
|
||||||
grep -q 'load_appbase()' "${ENTRY}" || die "入口缺 load_appbase() 显式调用"
|
|
||||||
grep -q 'load_pybricks()' "${ENTRY}" || die "入口缺 load_pybricks() 显式调用"
|
|
||||||
log "硬门禁 6.2 通过: ServerEnv()(L${ENV_LINE}) < load_rbac()(L${RBAC_LINE}),load_appbase/load_rbac/load_pybricks 均显式调用"
|
|
||||||
|
|
||||||
# ---- 4. DDL 方言核验(6.4:mariadb,BIGINT AUTO_INCREMENT,零 SERIAL/FK/ENUM/TIMESTAMP)----
|
|
||||||
# 【Q4 修复 / deploy_test 第 3 轮实测】旧版直接对 DDL **原文** grep,存在两类假阳性:
|
|
||||||
# (1) DDL 文末「契约自检」注释块为说明"零命中项",逐字写出了
|
|
||||||
# FOREIGN KEY / REFERENCES / ENUM( / SERIAL / BIGSERIAL / nextval / TIMESTAMP
|
|
||||||
# (实测原文命中 3 行:L6 头部说明、L220 COMMENT 串内的 references、L891 契约自检块),
|
|
||||||
# 旧 `grep -Eiq` 一律触发 die —— 注释/COMMENT 串被当成真实 DDL,门禁必然误杀;
|
|
||||||
# (2) `CREATE TABLE` / `AUTO_INCREMENT` 计数同理会把注释行与 COMMENT '...' 串内字样算进去,
|
|
||||||
# 表数不可信(且旧断言 36 与 DDL 实际 37 张不符,新增 pbl_compile_task 后未同步)。
|
|
||||||
# 修复:改为「先剥 SQL 注释 + 字符串字面量(含 COMMENT 串),再在纯 DDL 正文上扫描」,
|
|
||||||
# 由 scripts/ddl_gate_check.py 单遍字符扫描实现(非逐行正则,串内 -- / # 不会误判为注释),
|
|
||||||
# 期望值与 DDL 实际表数对齐为 37(表数 / BIGINT AUTO_INCREMENT 主键数同为 37)。
|
|
||||||
[ -f "${DDL_FILE}" ] || die "缺少 DDL ${DDL_FILE}"
|
|
||||||
DDL_GATE="${APP_DIR}/scripts/ddl_gate_check.py"
|
|
||||||
[ -f "${DDL_GATE}" ] || die "缺少 DDL 门禁扫描器 ${DDL_GATE}(6.4 依赖,禁止回退到原文 grep)"
|
|
||||||
|
|
||||||
# 4a. 扫描器自身可信度:先跑内置最小单测(剥注释/剥串/幂等/真实违规不漏报)
|
|
||||||
SELFTEST_RC=0
|
|
||||||
python3 "${DDL_GATE}" --selftest || SELFTEST_RC=$?
|
|
||||||
[ "${SELFTEST_RC}" = "0" ] || die "DDL 门禁扫描器自检未通过(--selftest rc=${SELFTEST_RC}),扫描结果不可信,终止部署"
|
|
||||||
|
|
||||||
# 4b. 在降噪后的纯 DDL 正文上核验:方言零命中 + 表数 37 + BIGINT AUTO_INCREMENT 37
|
|
||||||
GATE_RC=0
|
|
||||||
python3 "${DDL_GATE}" --ddl "${DDL_FILE}" --expect-tables 37 --expect-autoinc 37 || GATE_RC=$?
|
|
||||||
[ "${GATE_RC}" = "0" ] || die "硬门禁 6.4 未通过(ddl_gate_check.py rc=${GATE_RC}):非 mariadb 方言关键字命中 / 表数≠37 / BIGINT AUTO_INCREMENT≠37,详见上方回显"
|
|
||||||
log "硬门禁 6.4 通过: mariadb 方言(剥注释+COMMENT 串后零命中),37 表 / 37 处 BIGINT AUTO_INCREMENT"
|
|
||||||
|
|
||||||
# ---- 5. 依赖声明核验(6.5)----
|
|
||||||
for dep in ahserver sqlor apppublic appbase rbac PyMySQL; do
|
|
||||||
grep -qi "^${dep}" "${APP_DIR}/requirements.txt" || die "requirements.txt 缺依赖 ${dep}"
|
|
||||||
done
|
|
||||||
log "硬门禁 6.5 通过: ahserver/sqlor/apppublic/appbase/rbac/PyMySQL 均已声明"
|
|
||||||
|
|
||||||
# ---- 6. website.processors / indexes 核验(缺一会导致 / 返回 500)----
|
|
||||||
python3 - <<'PYEOF'
|
|
||||||
import json, sys
|
|
||||||
conf = json.load(open('conf/config.json'))
|
|
||||||
procs = dict((p[0], p[1]) for p in conf['website']['processors'])
|
|
||||||
for ext, name in (('.tmpl', 'tmpl'), ('.ui', 'bui'), ('.dspy', 'dspy')):
|
|
||||||
assert procs.get(ext) == name, 'processors 缺 %s->%s,实际 %r' % (ext, name, procs.get(ext))
|
|
||||||
assert 'index.ui' in conf['website']['indexes'], 'indexes 缺 index.ui'
|
|
||||||
print('[build.sh] website 核验通过: processors=%s indexes=%s' % (procs, conf['website']['indexes']))
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 7. venv + 依赖安装 ----
|
|
||||||
if [ ! -x "${PY}" ]; then
|
|
||||||
log "创建 venv: ${VENV_DIR}"
|
|
||||||
python3 -m venv "${VENV_DIR}"
|
|
||||||
fi
|
|
||||||
"${PY}" -m pip install --upgrade pip wheel setuptools
|
|
||||||
|
|
||||||
# [deploy_test R5 修复 #1] 平台基础包(ahserver/sqlor/apppublic/appbase/rbac/bricks_for_python/accounting)
|
|
||||||
# 原走内网私有源 http://10.20.30.41:8081 —— 目标机 192.168.16.3 实测 connect timeout(5 次重试全失败),
|
|
||||||
# build.sh 在此处 BUILD_RC=1 终止(R4 部署失败根因,见 evidence/r4_build.log)。
|
|
||||||
# 改为:机构 wheelhouse 离线供给(--find-links,绝对路径,不依赖 cwd)+ 公网 pypi.org 取第三方包
|
|
||||||
# (目标机实测 https://pypi.org/simple/pymysql/ HTTP=200)。
|
|
||||||
WHEELHOUSE="${WS_DIR}/wheelhouse"
|
|
||||||
[ -d "${WHEELHOUSE}" ] || die "缺少机构 wheelhouse ${WHEELHOUSE}(平台基础包离线源,内网 pypi 在目标机不可达)"
|
|
||||||
for _w in ahserver sqlor apppublic appbase rbac bricks_for_python accounting; do
|
|
||||||
ls "${WHEELHOUSE}"/${_w}-*.whl >/dev/null 2>&1 || die "wheelhouse 缺 ${_w} 的 whl(${WHEELHOUSE})"
|
|
||||||
done
|
|
||||||
log "wheelhouse 就位: $(ls "${WHEELHOUSE}"/*.whl | wc -l) 个 whl @ ${WHEELHOUSE}"
|
|
||||||
"${PY}" -m pip install --find-links "${WHEELHOUSE}" -r "${APP_DIR}/requirements.txt"
|
|
||||||
|
|
||||||
# ---- 7b. 测试前置依赖(DISC.2)----
|
|
||||||
# 运行时依赖(requirements.txt)刻意不含 pytest;测试依赖单列 requirements-test.txt,
|
|
||||||
# 由本步装进同一个 venv,使
|
|
||||||
# source venv/bin/activate && python3 -m pytest tests/test_m5a_idempotency.py -v
|
|
||||||
# 在**不手工注入 PYTHONPATH** 的前提下可直接执行(此前靠 PYTHONPATH=<ws>/.pylibs
|
|
||||||
# 临时注入跑通,换机器/换角色即失效,test 角色会误判为环境不可用)。
|
|
||||||
# 只要运行时依赖的场景(如生产镜像)用 PBL_SKIP_TEST_DEPS=1 显式跳过。
|
|
||||||
REQ_TEST="${APP_DIR}/requirements-test.txt"
|
|
||||||
if [ "${PBL_SKIP_TEST_DEPS:-0}" = "1" ]; then
|
|
||||||
log "PBL_SKIP_TEST_DEPS=1:跳过测试前置依赖(venv 内将没有 pytest,测试角色需自行引导)"
|
|
||||||
elif [ ! -f "${REQ_TEST}" ]; then
|
|
||||||
die "缺测试依赖清单 ${REQ_TEST}(DISC.2 要求显式声明;确要跳过请设 PBL_SKIP_TEST_DEPS=1)"
|
|
||||||
else
|
|
||||||
log "安装测试前置依赖: ${REQ_TEST}"
|
|
||||||
"${PY}" -m pip install -r "${REQ_TEST}"
|
|
||||||
# 自证:装完必须真能 import + 跑 --version,否则本步等于没做(不让注释与事实矛盾)
|
|
||||||
"${PY}" -c 'import pytest' >/dev/null 2>&1 \
|
|
||||||
|| die "安装后 venv 内仍无法 import pytest(${PY})"
|
|
||||||
log "7b 自证通过: $("${PY}" -m pytest --version 2>&1 | head -1) @ ${PY}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 8. 本地安装 15 个 pbl_* 模块 + 复用模块(可编辑模式)----
|
|
||||||
# [deploy_test R5 修复 #2] 原循环只匹配 modules/pbl_*,但入口 app/pbls.py 还显式 import
|
|
||||||
# world / scene / entity / script_engine / scense_runtime / scense / scense_game
|
|
||||||
# 这 7 个复用域模块(pbls_spec.json referenced_modules)。漏装 → 入口 ImportError →
|
|
||||||
# 进程起不来(端口无 LISTEN、healthz=000)。此处按 spec.json referenced_modules 补装,
|
|
||||||
# 并做「入口 import 全部可解析」自证门禁,装不上就 die,不留到启动阶段才炸。
|
|
||||||
REUSE_MODULES="$(python3 -c "import json;print(' '.join(json.load(open('${WS_DIR}/projects/pbls/pbls_spec.json'))['referenced_modules']))")"
|
|
||||||
INSTALL_DIRS=""
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}" ] || continue
|
|
||||||
INSTALL_DIRS="${INSTALL_DIRS} ${mod_dir}"
|
|
||||||
done
|
|
||||||
for rm in ${REUSE_MODULES}; do
|
|
||||||
# 平台基础包(apppublic/sqlor/ahserver/appbase/rbac/accounting)已由 wheelhouse 装好,跳过
|
|
||||||
case " ${rm} " in *" apppublic "*|*" sqlor "*|*" ahserver "*|*" appbase "*|*" rbac "*|*" accounting "*) continue;; esac
|
|
||||||
[ -d "${WS_DIR}/modules/${rm}" ] || die "spec.json referenced_modules 声明 ${rm},但 ${WS_DIR}/modules/${rm} 不存在"
|
|
||||||
INSTALL_DIRS="${INSTALL_DIRS} ${WS_DIR}/modules/${rm}"
|
|
||||||
done
|
|
||||||
for mod_dir in ${INSTALL_DIRS}; do
|
|
||||||
if [ -f "${mod_dir}/pyproject.toml" ] || [ -f "${mod_dir}/setup.py" ]; then
|
|
||||||
log "安装模块 $(basename "${mod_dir}")"
|
|
||||||
"${PY}" -m pip install --find-links "${WHEELHOUSE}" -e "${mod_dir}"
|
|
||||||
else
|
|
||||||
export PYTHONPATH="${mod_dir}:${PYTHONPATH:-}"
|
|
||||||
log "模块 $(basename "${mod_dir}") 无打包描述,加入 PYTHONPATH"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# ---- 8a. 入口 import 可解析性自证(部署前拦截 ImportError,不留到启动阶段)----
|
|
||||||
log "入口 import 自证:$(basename "${ENTRY}") 全部 import 必须可解析"
|
|
||||||
IMPORT_RC=0
|
|
||||||
"${PY}" - <<'PYIMP' || IMPORT_RC=$?
|
|
||||||
import ast, importlib.util, sys
|
|
||||||
src = open('app/pbls.py', encoding='utf-8').read()
|
|
||||||
mods = set()
|
|
||||||
for node in ast.walk(ast.parse(src)):
|
|
||||||
if isinstance(node, ast.ImportFrom) and node.module:
|
|
||||||
mods.add(node.module.split('.')[0])
|
|
||||||
elif isinstance(node, ast.Import):
|
|
||||||
for a in node.names:
|
|
||||||
mods.add(a.name.split('.')[0])
|
|
||||||
mods -= set(sys.stdlib_module_names)
|
|
||||||
mods -= {'json', 'os', 'sys', 'ast'}
|
|
||||||
missing = sorted(m for m in mods if importlib.util.find_spec(m) is None)
|
|
||||||
print('[import-check] 需解析顶层模块 %d 个: %s' % (len(mods), ' '.join(sorted(mods))))
|
|
||||||
if missing:
|
|
||||||
print('[import-check][FAIL] 无法解析: %s' % ' '.join(missing))
|
|
||||||
sys.exit(1)
|
|
||||||
print('[import-check] PASS 全部可解析')
|
|
||||||
PYIMP
|
|
||||||
[ "${IMPORT_RC}" = "0" ] || die "入口 import 自证失败(rc=${IMPORT_RC}):上列模块在 venv 内不可解析,应用必然启动失败,终止部署"
|
|
||||||
|
|
||||||
# ---- 8b. M9 覆盖层页面壳生成(构建产物,不入库)+ 一致性门禁 ----
|
|
||||||
# 页面壳由 pbl_scense_ext 的生成器产出;产物目录 wwwroot/pbl_overlay/ 在模块仓
|
|
||||||
# .gitignore 中被排除,因此必须在构建期生成,否则新克隆/新部署环境该目录为空、
|
|
||||||
# /pbl_overlay/<key>.ui 导航全量 404。位置:模块安装之后、模块 wwwroot 软链(紧随其后的
|
|
||||||
# 「8c. 模块 wwwroot 挂载」段)之前——生成物落在模块仓,必须再经 8c 挂载到应用 wwwroot
|
|
||||||
# 才能在应用侧 URL /pbl_scense_ext/pbl_overlay/<key>.ui 上取到。
|
|
||||||
OVERLAY_GEN="${WS_DIR}/modules/pbl_scense_ext/scripts/gen_overlay_pages.py"
|
|
||||||
[ -f "${OVERLAY_GEN}" ] || die "缺少覆盖层页面壳生成器: ${OVERLAY_GEN}(modules/pbl_scense_ext 未就位)"
|
|
||||||
log "生成 PBL 覆盖层页面壳: ${OVERLAY_GEN}"
|
|
||||||
# 注意:不能用 `if ! cmd; then die "... rc=$?"` —— 进入分支时 $? 已被 if 取反为 0,
|
|
||||||
# 打印出的 rc 恒为 0 会误导排障;改为显式捕获真实返回码。
|
|
||||||
GEN_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" || GEN_RC=$?
|
|
||||||
if [ "${GEN_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳生成失败(gen_overlay_pages.py rc=${GEN_RC}):wwwroot/pbl_overlay/ 将为空,/pbl_overlay/<key>.ui 全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳门禁校验(gen_overlay_pages.py --check)"
|
|
||||||
CHK_RC=0
|
|
||||||
"${PY}" "${OVERLAY_GEN}" --check || CHK_RC=$?
|
|
||||||
if [ "${CHK_RC}" -ne 0 ]; then
|
|
||||||
die "覆盖层页面壳门禁校验未通过(--check rc=${CHK_RC}):i18n zh/en key 不一致,或页面壳与 PAGES 注册表不一致,终止部署"
|
|
||||||
fi
|
|
||||||
log "覆盖层页面壳生成 + 门禁校验通过(12 个页面壳 / i18n key 一致)"
|
|
||||||
|
|
||||||
# ---- 8c. 模块 wwwroot 挂载(软链)+ 应用侧可达性门禁(QC 退回意见 #1/#2)----
|
|
||||||
# 上一版只做了 8b「生成」,生成物落在模块仓 modules/<m>/wwwroot/ 下,但本脚本从未把
|
|
||||||
# 模块 wwwroot 挂进应用 wwwroot —— 应用 website.root=wwwroot 只指向应用自身目录,
|
|
||||||
# 因此 build.sh 跑完后应用侧依然取不到 /pbl_scense_ext/pbl_overlay/<key>.ui(可达链路
|
|
||||||
# 断在挂载环节,全量 404 并未消除)。此处按 web-application-spec「挂载模块 9 步流程」
|
|
||||||
# 第 5 步(wwwroot 软链,非 cp,保持与模块仓单一副本同步)补齐,并立即以应用侧路径
|
|
||||||
# 实测 12 个页面壳数量作为门禁,rc≠0 终止部署。
|
|
||||||
LINKED_MODULES=""
|
|
||||||
for mod_dir in "${WS_DIR}"/modules/pbl_*; do
|
|
||||||
[ -d "${mod_dir}/wwwroot" ] || continue
|
|
||||||
mod_name="$(basename "${mod_dir}")"
|
|
||||||
www_link="${APP_DIR}/wwwroot/${mod_name}"
|
|
||||||
# 已存在但不是软链(应用仓自带同名真实目录)时不覆盖,直接终止让人工处置,
|
|
||||||
# 避免 ln -sfn 把软链建进既有目录里、或静默删掉应用仓跟踪文件。
|
|
||||||
if [ -e "${www_link}" ] && [ ! -L "${www_link}" ]; then
|
|
||||||
die "wwwroot 挂载冲突:${www_link} 已存在且不是软链(应用仓自带目录?),请人工处置后重试"
|
|
||||||
fi
|
|
||||||
ln -sfn "${mod_dir}/wwwroot" "${www_link}"
|
|
||||||
LINKED_MODULES="${LINKED_MODULES} ${mod_name}"
|
|
||||||
done
|
|
||||||
[ -n "${LINKED_MODULES}" ] || die "未发现任何含 wwwroot/ 的 pbl_* 模块,模块 wwwroot 挂载为空"
|
|
||||||
log "模块 wwwroot 软链挂载完成:${LINKED_MODULES}"
|
|
||||||
|
|
||||||
# 挂载后应用侧可达性门禁:覆盖层页面壳必须能通过应用 wwwroot 路径取到
|
|
||||||
APP_OVERLAY_DIR="${APP_DIR}/wwwroot/pbl_scense_ext/pbl_overlay"
|
|
||||||
APP_UI_COUNT=0
|
|
||||||
if [ -d "${APP_OVERLAY_DIR}" ]; then
|
|
||||||
APP_UI_COUNT="$(find -L "${APP_OVERLAY_DIR}" -maxdepth 1 -name '*.ui' | wc -l | tr -d '[:space:]')"
|
|
||||||
fi
|
|
||||||
if [ "${APP_UI_COUNT}" != "12" ]; then
|
|
||||||
die "应用侧覆盖层页面壳应为 12 个,实际 ${APP_UI_COUNT}(${APP_OVERLAY_DIR}):模块 wwwroot 未挂载成功或生成器未产出,/pbl_scense_ext/pbl_overlay/<key>.ui 将全量 404,终止部署"
|
|
||||||
fi
|
|
||||||
log "应用侧可达性核验通过: ${APP_OVERLAY_DIR}/*.ui = ${APP_UI_COUNT}"
|
|
||||||
|
|
||||||
# ---- 9. 语法验证:全部 .py 跑 py_compile(QC 退回意见 #16)----
|
|
||||||
log "py_compile 全量语法验证"
|
|
||||||
FAIL_SYNTAX=0
|
|
||||||
while IFS= read -r pyfile; do
|
|
||||||
if ! "${PY}" -m py_compile "${pyfile}"; then
|
|
||||||
echo "[build.sh][SYNTAX FAIL] ${pyfile}"
|
|
||||||
FAIL_SYNTAX=1
|
|
||||||
fi
|
|
||||||
done < <(find "${APP_DIR}/app" "${WS_DIR}/modules/pbl_common" "${WS_DIR}/modules/pbl_appcodes" -name '*.py' -not -path '*/venv/*' 2>/dev/null)
|
|
||||||
[ "${FAIL_SYNTAX}" = "0" ] || die "存在语法错误文件,终止部署"
|
|
||||||
log "py_compile 全部通过"
|
|
||||||
|
|
||||||
# ---- 10. 建表(mariadb,显式 TCP,禁 socket)----
|
|
||||||
log "执行 DDL: ${DDL_FILE}"
|
|
||||||
mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
--default-character-set=utf8mb4 "${DB_NAME}" < "${DDL_FILE}"
|
|
||||||
ACTUAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
# Q4 修复:与 DDL 实际表数对齐(36 → 37,新增 pbl_compile_task)
|
|
||||||
[ "${ACTUAL_TABLES}" = "37" ] || die "建表数应为 37,实际 ${ACTUAL_TABLES}"
|
|
||||||
log "建表完成: pbl_* 共 ${ACTUAL_TABLES} 张"
|
|
||||||
|
|
||||||
# ---- 10b. 运行目录前置创建(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本到步骤 13 才 mkdir logs/run,而步骤 11 已经 `tee ${APP_DIR}/logs/self_check.log`。
|
|
||||||
# 全新克隆(仓库不含 logs/,.gitignore 也排除 *.log)时 tee 打不开文件 →
|
|
||||||
# set -euo pipefail 下整条管道非零 → die,部署在「建表已成功、自检未跑」处中断,
|
|
||||||
# 表现为门禁 ③ 表数 0/37(DDL 事务已提交但脚本没走到断言)与门禁 ②③ 全红。
|
|
||||||
mkdir -p "${APP_DIR}/logs" "${APP_DIR}/run"
|
|
||||||
log "运行目录就位: ${APP_DIR}/logs ${APP_DIR}/run"
|
|
||||||
|
|
||||||
# ---- 11. 枚举数量契约自检(QC 退回意见 #15:必须真实可运行输出)----
|
|
||||||
"${PY}" -m pbl_appcodes.self_check | tee "${APP_DIR}/logs/self_check.log"
|
|
||||||
grep -q 'SELF_CHECK pbl_appcodes: PASS 74/74' "${APP_DIR}/logs/self_check.log" \
|
|
||||||
|| die "pbl_appcodes 数量契约自检未通过"
|
|
||||||
|
|
||||||
# ---- 12. 9 项种子数据幂等注入(重复执行零差异)----
|
|
||||||
log "种子注入(第 1 轮)"
|
|
||||||
"${PY}" - <<'PYEOF'
|
|
||||||
import json
|
|
||||||
from pbl_appcodes.seed import seed_all
|
|
||||||
s1 = seed_all()
|
|
||||||
print('[seed round1]', json.dumps(s1, ensure_ascii=False, sort_keys=True))
|
|
||||||
s2 = seed_all()
|
|
||||||
print('[seed round2]', json.dumps(s2, ensure_ascii=False, sort_keys=True))
|
|
||||||
assert s2['groups_inserted'] == 0 and s2['groups_updated'] == 0, '重复执行组有差异'
|
|
||||||
assert s2['items_inserted'] == 0 and s2['items_updated'] == 0, '重复执行项有差异'
|
|
||||||
assert s2['idempotent'] is True, '幂等校验失败'
|
|
||||||
print('[seed] 幂等零差异校验通过: 8 组 74 项')
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ---- 12.5 RBAC 路径注册(Bug WIu0Rkn0KwB0XTjYZz0qk 修复:原脚本从不注册)----
|
|
||||||
# 中央聚合器 apps/pbls/scripts/load_path.py 把各模块 + 宿主自有端点(含本次新增的
|
|
||||||
# /healthz、/healthz.dspy、/api/public/health)逐条写入 permission/rolepermission。
|
|
||||||
# 不注册的后果:进程起来了、端口监听了,但 curl 一律 401/403 → 冒烟门禁 ① HTTP!=200。
|
|
||||||
# 先跑 --check 做通配符 + 聚合完整性门禁(rc!=0 即终止,禁止带缺口部署)。
|
|
||||||
LOAD_PATH="${APP_DIR}/scripts/load_path.py"
|
|
||||||
[ -f "${LOAD_PATH}" ] || die "缺少 RBAC 注册入口 ${LOAD_PATH}"
|
|
||||||
log "RBAC 一致性门禁: load_path.py --check"
|
|
||||||
CHK_RBAC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" --check || CHK_RBAC=$?
|
|
||||||
[ "${CHK_RBAC}" = "0" ] || die "RBAC 聚合门禁未通过(rc=${CHK_RBAC}):存在通配符或整模块聚合失败,终止部署"
|
|
||||||
log "RBAC 注册: load_path.py(逐条显式,无通配符)"
|
|
||||||
REG_RC=0
|
|
||||||
WS_DIR="${WS_DIR}" "${PY}" "${LOAD_PATH}" || REG_RC=$?
|
|
||||||
if [ "${REG_RC}" != "0" ]; then
|
|
||||||
# set_role_perm.py 不在本机时 register() 返回 PENDING 清单且 rc=1;
|
|
||||||
# 这不是代码缺陷而是部署机缺工具,明确回显后终止(不静默放过 → 否则又是 403)。
|
|
||||||
die "RBAC 注册未成功(rc=${REG_RC}):set_role_perm.py 不可用或写库失败,端点将 401/403,终止部署"
|
|
||||||
fi
|
|
||||||
log "RBAC 注册完成"
|
|
||||||
|
|
||||||
# ---- 12.8 落地部署目录 DEPLOY_PATH(Bug WIu0Rkn0KwB0XTjYZz0qk 修复)----
|
|
||||||
# 原脚本把 DEPLOY_PATH 读出来只用于最后一行日志 echo,从未创建/同步任何内容,
|
|
||||||
# 于是复测门禁 ④「ls -la /d/pbl/pbls」恒为 No such file or directory,
|
|
||||||
# 且 env/test.json 的 start_cmd(${DEPLOY_PATH}/venv/bin/python ${DEPLOY_PATH}/app/pbls.py)
|
|
||||||
# 指向的解释器与入口都不存在 → 门禁 ②③(进程数 0 / 端口未监听)不可能转绿。
|
|
||||||
# 处置:把应用树同步到 DEPLOY_PATH,venv 以软链复用(venv 内 shebang/符号链接是
|
|
||||||
# 绝对路径,复制换目录必坏,软链才安全),随后从 DEPLOY_PATH 启动。
|
|
||||||
if [ "${DEPLOY_PATH}" = "${APP_DIR}" ]; then
|
|
||||||
log "DEPLOY_PATH 与仓库目录同址(${DEPLOY_PATH}),跳过同步"
|
|
||||||
RUN_DIR="${APP_DIR}"
|
|
||||||
RUN_PY="${PY}"
|
|
||||||
RUN_ENTRY="${ENTRY}"
|
|
||||||
else
|
|
||||||
log "同步应用树到部署目录: ${DEPLOY_PATH}"
|
|
||||||
mkdir -p "${DEPLOY_PATH}"
|
|
||||||
# -a 保留权限/时间并**保留软链本身**(8c 建的模块 wwwroot 软链是绝对路径,换机仍可解析);
|
|
||||||
# 排除 venv/logs/run/.git:venv 不可搬移,logs/run 由目标侧自建,.git 不属于运行态。
|
|
||||||
rsync -a --delete \
|
|
||||||
--exclude 'venv/' --exclude '.venv/' --exclude 'logs/' --exclude 'run/' \
|
|
||||||
--exclude '.git/' --exclude '__pycache__/' --exclude '*.pyc' \
|
|
||||||
"${APP_DIR}/" "${DEPLOY_PATH}/"
|
|
||||||
mkdir -p "${DEPLOY_PATH}/logs" "${DEPLOY_PATH}/run"
|
|
||||||
# venv 软链复用(目标侧 start_cmd 要求 ${DEPLOY_PATH}/venv/bin/python 可用)
|
|
||||||
if [ ! -e "${DEPLOY_PATH}/venv" ]; then
|
|
||||||
ln -sfn "${VENV_DIR}" "${DEPLOY_PATH}/venv"
|
|
||||||
fi
|
|
||||||
[ -x "${DEPLOY_PATH}/venv/bin/python" ] || die "部署目录 venv 不可用: ${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
[ -f "${DEPLOY_PATH}/app/${APP_NAME}.py" ] || die "部署目录缺入口: ${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
[ -f "${DEPLOY_PATH}/conf/config.json" ] || die "部署目录缺配置: ${DEPLOY_PATH}/conf/config.json"
|
|
||||||
log "部署目录就位: ${DEPLOY_PATH}(app/conf/wwwroot/scripts + venv 软链 + logs/run)"
|
|
||||||
RUN_DIR="${DEPLOY_PATH}"
|
|
||||||
RUN_PY="${DEPLOY_PATH}/venv/bin/python"
|
|
||||||
RUN_ENTRY="${DEPLOY_PATH}/app/${APP_NAME}.py"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 13. 启动应用(唯一入口 + 唯一端口,从部署目录启动)----
|
|
||||||
# 先停旧进程(幂等重跑不撞端口):按 pid 文件 + 入口路径双口径清理。
|
|
||||||
if [ -f "${RUN_DIR}/run/${APP_NAME}.pid" ]; then
|
|
||||||
OLD_PID="$(cat "${RUN_DIR}/run/${APP_NAME}.pid" 2>/dev/null || true)"
|
|
||||||
if [ -n "${OLD_PID:-}" ] && kill -0 "${OLD_PID}" 2>/dev/null; then
|
|
||||||
log "停止旧进程 pid=${OLD_PID}"
|
|
||||||
kill "${OLD_PID}" 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
pkill -f "${RUN_ENTRY}" 2>/dev/null || true
|
|
||||||
sleep 1
|
|
||||||
|
|
||||||
log "启动 ${APP_NAME} 于端口 ${PORT}(cwd=${RUN_DIR})"
|
|
||||||
# cwd 必须是 RUN_DIR:conf/config.json 的 website.root="wwwroot" 是相对路径,
|
|
||||||
# 从别处启动会解析不到 wwwroot → 所有 .ui/.dspy 404(含 /healthz)。
|
|
||||||
( cd "${RUN_DIR}" && nohup "${RUN_PY}" "${RUN_ENTRY}" \
|
|
||||||
> "${RUN_DIR}/logs/${APP_NAME}.out" 2>&1 & echo $! > "${RUN_DIR}/run/${APP_NAME}.pid" )
|
|
||||||
sleep 6
|
|
||||||
|
|
||||||
# 进程 + 端口双自证(对应复测门禁 ②):起不来就把日志尾部打出来再 die,
|
|
||||||
# 避免「HTTP=000 但没人知道为什么」的四轮同构复测。
|
|
||||||
PROC_N="$(ps -ef | grep -F "${APP_NAME}.py" | grep -v grep | wc -l | tr -d '[:space:]')"
|
|
||||||
log "进程数=${PROC_N}"
|
|
||||||
if [ "${PROC_N}" = "0" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "应用进程未存活(ps 计数 0),门禁 ② 不成立,详见上方日志尾部"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ---- 14. 健康检查(两条口径都验:/healthz = 复测门禁 ①,/api/public/health = 原自检口径)----
|
|
||||||
check_http() {
|
|
||||||
curl -s -m 8 -o /dev/null -w '%{http_code}' "$1" 2>/dev/null || echo 000
|
|
||||||
}
|
|
||||||
HZ_CODE="$(check_http "http://127.0.0.1:${PORT}/healthz")"
|
|
||||||
AH_CODE="$(check_http "http://127.0.0.1:${PORT}/api/public/health")"
|
|
||||||
IP_CODE="$(check_http "http://${SSH_HOST:-127.0.0.1}:${PORT}/healthz")"
|
|
||||||
log "健康检查: /healthz=${HZ_CODE} /api/public/health=${AH_CODE} 外网口 /healthz=${IP_CODE}"
|
|
||||||
if [ "${HZ_CODE}" != "200" ] || [ "${AH_CODE}" != "200" ]; then
|
|
||||||
echo "----- ${RUN_DIR}/logs/${APP_NAME}.out (tail 60) -----"
|
|
||||||
tail -60 "${RUN_DIR}/logs/${APP_NAME}.out" 2>/dev/null || true
|
|
||||||
echo "----------------------------------------------------"
|
|
||||||
die "健康检查失败(/healthz=${HZ_CODE}, /api/public/health=${AH_CODE}),门禁 ① 不成立"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 表数终态复述(对应复测门禁 ③):部署完成后 pbl 库必须有 37 张 pbl_* 表。
|
|
||||||
FINAL_TABLES="$(mysql --protocol=TCP -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
|
|
||||||
-N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='${DB_NAME}' AND table_name LIKE 'pbl\\_%'" "${DB_NAME}")"
|
|
||||||
log "终态表数: ${DB_NAME}.pbl_* = ${FINAL_TABLES}/37"
|
|
||||||
[ "${FINAL_TABLES}" = "37" ] || die "终态表数 ${FINAL_TABLES} != 37,门禁 ③ 不成立"
|
|
||||||
|
|
||||||
log "BUILD SUCCESS: ${APP_NAME} 已运行于 http://127.0.0.1:${PORT}/ (部署路径 ${DEPLOY_PATH},/healthz=200,表数 ${FINAL_TABLES}/37)"
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
# PBL Agent OS 验证原型(Phase 0/1)—— 应用依赖声明
|
|
||||||
# 平台基础依赖(ahserver / sqlor / apppublic / appbase / rbac)由机构私有源提供,
|
|
||||||
# 通过 --index-url 指向内网 pypi;缺失时 build.sh 会 fail-fast 报错而非静默跳过。
|
|
||||||
--extra-index-url http://10.20.30.41:8081/repository/pypi-hosted/simple
|
|
||||||
--trusted-host 10.20.30.41
|
|
||||||
|
|
||||||
# ---- 平台基础框架(必需,硬门禁 6.5)----
|
|
||||||
ahserver>=1.4.0
|
|
||||||
sqlor>=1.3.0
|
|
||||||
apppublic>=1.2.0
|
|
||||||
appbase>=1.3.0
|
|
||||||
rbac>=1.2.0
|
|
||||||
bricks_for_python>=1.2.0 # 前端积木(.ui/.bui 渲染 + load_pybricks)
|
|
||||||
pybricks>=1.1.0
|
|
||||||
accounting>=1.0.0
|
|
||||||
|
|
||||||
# ---- DB 驱动(mariadb 方言,显式 TCP,禁 socket)----
|
|
||||||
PyMySQL>=1.1.0
|
|
||||||
mysqlclient>=2.2.0
|
|
||||||
SQLAlchemy>=2.0.0
|
|
||||||
|
|
||||||
# ---- 业务模块(本项目新建 15 个 pbl_*,本地路径安装)----
|
|
||||||
# 由 build.sh 以 pip install -e modules/{模块} 方式安装,不发布到公共源
|
|
||||||
# pbl_common / pbl_appcodes / pbl_governance / pbl_blueprint / pbl_template
|
|
||||||
# pbl_validation / pbl_compiler / pbl_agent_runtime / pbl_evidence / pbl_assessment
|
|
||||||
# pbl_domain_ext / pbl_scense_ext / pbl_runtime_ext / pbl_kdb_ext / pbl_analytics
|
|
||||||
|
|
||||||
# ---- 通用运行时依赖 ----
|
|
||||||
requests>=2.31.0
|
|
||||||
python-dateutil>=2.8.2
|
|
||||||
pytz>=2024.1
|
|
||||||
jsonschema>=4.21.0
|
|
||||||
PyYAML>=6.0.1
|
|
||||||
hashlib-additional>=1.0.1 ; python_version < "3.0"
|
|
||||||
|
|
||||||
# ---- 前端/模板处理(website.processors: .tmpl/.ui/.dspy)----
|
|
||||||
Mako>=1.3.0
|
|
||||||
Jinja2>=3.1.0
|
|
||||||
|
|
||||||
# ---- 部署与自检 ----
|
|
||||||
psutil>=5.9.0
|
|
||||||
setuptools>=68.0.0
|
|
||||||
wheel>=0.42.0
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
# PBL Agent OS 验证原型(Phase 0/1)—— 应用依赖声明
|
|
||||||
# 平台基础依赖(ahserver / sqlor / apppublic / appbase / rbac)由机构私有源提供,
|
|
||||||
# 通过 --index-url 指向内网 pypi;缺失时 build.sh 会 fail-fast 报错而非静默跳过。
|
|
||||||
# [deploy_test R5 修复] 原 --extra-index-url http://10.20.30.41:8081/... 在目标机 192.168.16.3
|
|
||||||
# 实测 connect timeout(5 次重试全失败 → build.sh BUILD_RC=1,部署卡在依赖安装)。
|
|
||||||
# 平台基础包改由机构 wheelhouse 离线供给(--find-links 相对本文件定位 WS/wheelhouse),
|
|
||||||
# 第三方包走公网 pypi.org(目标机实测 HTTP=200 可达)。
|
|
||||||
--find-links ../../wheelhouse
|
|
||||||
|
|
||||||
# ---- 平台基础框架(必需,硬门禁 6.5)----
|
|
||||||
ahserver>=1.2.0
|
|
||||||
sqlor>=2.1.2
|
|
||||||
apppublic>=5.3.2
|
|
||||||
appbase>=1.0.0
|
|
||||||
rbac>=1.0.0
|
|
||||||
bricks_for_python>=0.0.5 # 前端积木(.ui/.bui 渲染 + load_pybricks)
|
|
||||||
# [deploy_test R5 修复] 移除 pybricks>=1.1.0:load_pybricks 实际由 bricks_for_python.init 提供,
|
|
||||||
# wheelhouse 无此包,公网同名包为 LEGO 机器人库(误装风险)——幻影依赖。
|
|
||||||
accounting>=0.1.0
|
|
||||||
|
|
||||||
# ---- DB 驱动(mariadb 方言,显式 TCP,禁 socket)----
|
|
||||||
PyMySQL>=1.1.0
|
|
||||||
# [deploy_test R5 修复] 移除 mysqlclient>=2.2.0:需 mysql_config(目标机实测 which mysql_config 无输出,
|
|
||||||
# pbl 无 sudo 装不了 mariadb-devel)。DB 驱动由 PyMySQL + aiomysql 承担(sqlor/ahserver 传递依赖)。
|
|
||||||
SQLAlchemy>=2.0.0
|
|
||||||
|
|
||||||
# ---- 业务模块(本项目新建 15 个 pbl_*,本地路径安装)----
|
|
||||||
# 由 build.sh 以 pip install -e modules/{模块} 方式安装,不发布到公共源
|
|
||||||
# pbl_common / pbl_appcodes / pbl_governance / pbl_blueprint / pbl_template
|
|
||||||
# pbl_validation / pbl_compiler / pbl_agent_runtime / pbl_evidence / pbl_assessment
|
|
||||||
# pbl_domain_ext / pbl_scense_ext / pbl_runtime_ext / pbl_kdb_ext / pbl_analytics
|
|
||||||
|
|
||||||
# ---- 通用运行时依赖 ----
|
|
||||||
requests>=2.31.0
|
|
||||||
python-dateutil>=2.8.2
|
|
||||||
pytz>=2024.1
|
|
||||||
jsonschema>=4.21.0
|
|
||||||
PyYAML>=6.0.1
|
|
||||||
hashlib-additional>=1.0.1 ; python_version < "3.0"
|
|
||||||
|
|
||||||
# ---- 前端/模板处理(website.processors: .tmpl/.ui/.dspy)----
|
|
||||||
Mako>=1.3.0
|
|
||||||
Jinja2>=3.1.0
|
|
||||||
|
|
||||||
# ---- 部署与自检 ----
|
|
||||||
psutil>=5.9.0
|
|
||||||
setuptools>=68.0.0
|
|
||||||
wheel>=0.42.0
|
|
||||||
@ -1,893 +0,0 @@
|
|||||||
-- =====================================================================
|
|
||||||
-- PBL Agent OS 验证原型(Phase 0/1)—— 36 表可执行 DDL
|
|
||||||
-- 方言:mariadb(InnoDB + utf8mb4/utf8mb4_general_ci)
|
|
||||||
-- 规则:id BIGINT NOT NULL AUTO_INCREMENT 物理主键 + xxx_code VARCHAR(32) 业务主键(唯一索引)
|
|
||||||
-- tenant_id VARCHAR(32) NOT NULL 为第一业务列,所有索引以 tenant_id 打头
|
|
||||||
-- 禁止 FOREIGN KEY / 禁止 ENUM / 禁止原生 TIMESTAMP(用 DATETIME)
|
|
||||||
-- append-only 表无 update_time、无物理删除(11 张,见文末清单)
|
|
||||||
-- 权威来源:projects/pbls/docs/01-design/data-model.md
|
|
||||||
-- 执行:apps/pbls/build.sh -> mysql --protocol=TCP -h127.0.0.1 -P3306 pbls < pbls_tables.sql
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
SET FOREIGN_KEY_CHECKS = 0;
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_governance(3 表)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 1. pbl_tenant 租户主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_tenant (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tenant_code VARCHAR(32) NOT NULL COMMENT '租户业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID(第一业务列)',
|
|
||||||
tenant_name VARCHAR(128) NOT NULL COMMENT '租户名称',
|
|
||||||
org_code VARCHAR(32) DEFAULT NULL COMMENT '所属机构编码(rbac 侧只读引用)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/suspended/closed(appcodes_kv: pbl_tenant_status)',
|
|
||||||
quota_bp INT NOT NULL DEFAULT 100 COMMENT '蓝图配额',
|
|
||||||
quota_stu INT NOT NULL DEFAULT 500 COMMENT '学生席位配额',
|
|
||||||
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_tenant_code (tenant_code),
|
|
||||||
KEY idx_pbl_tenant_tid_status (tenant_id, status),
|
|
||||||
KEY idx_pbl_tenant_tid_org (tenant_id, org_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 租户主表';
|
|
||||||
|
|
||||||
-- 2. pbl_role_binding 租户内角色绑定(复用 rbac,不改 rbac 基表)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_role_binding (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
binding_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
user_code VARCHAR(32) NOT NULL COMMENT '用户编码(rbac 只读引用)',
|
|
||||||
role_code VARCHAR(32) NOT NULL COMMENT '角色编码:teacher/assistant/student/observer/admin(appcodes_kv: pbl_role)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'tenant' COMMENT '作用域类型:tenant/blueprint/class/team',
|
|
||||||
scope_code VARCHAR(32) DEFAULT NULL COMMENT '作用域对象编码',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/revoked',
|
|
||||||
effective_from DATETIME DEFAULT NULL COMMENT '生效起',
|
|
||||||
effective_to DATETIME DEFAULT NULL COMMENT '生效止',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_role_binding_code (binding_code),
|
|
||||||
KEY idx_pbl_role_binding_tid_user (tenant_id, user_code, status),
|
|
||||||
KEY idx_pbl_role_binding_tid_scope (tenant_id, scope_type, scope_code),
|
|
||||||
KEY idx_pbl_role_binding_tid_role (tenant_id, role_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 租户角色绑定';
|
|
||||||
|
|
||||||
-- 3. pbl_audit_log 审计日志(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_audit_log (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
audit_code VARCHAR(32) NOT NULL COMMENT '审计业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
actor_type VARCHAR(16) NOT NULL DEFAULT 'user' COMMENT '主体类型:user/agent/system',
|
|
||||||
actor_code VARCHAR(32) DEFAULT NULL COMMENT '主体编码',
|
|
||||||
action_code VARCHAR(64) NOT NULL COMMENT '动作编码(appcodes_kv: pbl_audit_action)',
|
|
||||||
object_type VARCHAR(32) DEFAULT NULL COMMENT '对象类型',
|
|
||||||
object_code VARCHAR(32) DEFAULT NULL COMMENT '对象编码',
|
|
||||||
result VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT '结果:success/denied/error',
|
|
||||||
detail TEXT DEFAULT NULL COMMENT '详情 JSON',
|
|
||||||
trace_id VARCHAR(64) DEFAULT NULL COMMENT '链路追踪ID',
|
|
||||||
client_ip VARCHAR(64) DEFAULT NULL COMMENT '来源IP',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '发生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_audit_code (audit_code),
|
|
||||||
KEY idx_pbl_audit_tid_time (tenant_id, create_time),
|
|
||||||
KEY idx_pbl_audit_tid_obj (tenant_id, object_type, object_code),
|
|
||||||
KEY idx_pbl_audit_tid_actor (tenant_id, actor_type, actor_code),
|
|
||||||
KEY idx_pbl_audit_tid_trace (tenant_id, trace_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 审计日志(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_blueprint(11 表,M1a)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 4. pbl_blueprint 蓝图聚合根
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_name VARCHAR(128) NOT NULL COMMENT '蓝图名称',
|
|
||||||
subject_code VARCHAR(32) DEFAULT NULL COMMENT '学科编码(appcodes_kv: pbl_subject)',
|
|
||||||
grade_code VARCHAR(32) DEFAULT NULL COMMENT '学段编码(appcodes_kv: pbl_grade)',
|
|
||||||
driving_q TEXT DEFAULT NULL COMMENT '驱动性问题',
|
|
||||||
summary VARCHAR(1024) DEFAULT NULL COMMENT '蓝图摘要',
|
|
||||||
duration_hours INT NOT NULL DEFAULT 0 COMMENT '总课时(小时)',
|
|
||||||
team_size_min INT NOT NULL DEFAULT 1 COMMENT '最小团队人数',
|
|
||||||
team_size_max INT NOT NULL DEFAULT 6 COMMENT '最大团队人数',
|
|
||||||
cur_version INT NOT NULL DEFAULT 1 COMMENT '当前版本号(乐观锁)',
|
|
||||||
quality_level VARCHAR(16) NOT NULL DEFAULT 'L0_draft' COMMENT '质量等级 L0~L4(appcodes_kv: pbl_quality_level)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/validating/validated/compiling/published/archived(appcodes_kv: pbl_bp_status)',
|
|
||||||
source_type VARCHAR(16) NOT NULL DEFAULT 'manual' COMMENT '来源:manual/template/fork/agent',
|
|
||||||
source_code VARCHAR(32) DEFAULT NULL COMMENT '来源对象编码(模板/父蓝图)',
|
|
||||||
owner_code VARCHAR(32) DEFAULT NULL COMMENT '归属教师',
|
|
||||||
lock_holder VARCHAR(32) DEFAULT NULL COMMENT '当前编辑锁持有者',
|
|
||||||
lock_expire DATETIME DEFAULT NULL COMMENT '编辑锁过期时间',
|
|
||||||
extra TEXT DEFAULT NULL COMMENT '扩展 JSON',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_blueprint_code (bp_code),
|
|
||||||
KEY idx_pbl_bp_tid_status (tenant_id, status),
|
|
||||||
KEY idx_pbl_bp_tid_owner (tenant_id, owner_code),
|
|
||||||
KEY idx_pbl_bp_tid_subject (tenant_id, subject_code, grade_code),
|
|
||||||
KEY idx_pbl_bp_tid_quality (tenant_id, quality_level),
|
|
||||||
KEY idx_pbl_bp_tid_source (tenant_id, source_type, source_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图聚合根';
|
|
||||||
|
|
||||||
-- 5. pbl_blueprint_version 版本快照 + change_delta(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_version (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
version_code VARCHAR(32) NOT NULL COMMENT '版本业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '版本号(单调递增)',
|
|
||||||
snapshot LONGTEXT DEFAULT NULL COMMENT '蓝图全量快照 JSON',
|
|
||||||
change_delta LONGTEXT DEFAULT NULL COMMENT '相对上一版本的差异 JSON(可回放)',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT '快照 sha256',
|
|
||||||
reason VARCHAR(512) DEFAULT NULL COMMENT '变更原因',
|
|
||||||
created_by VARCHAR(32) DEFAULT NULL COMMENT '提交人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '提交时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_version_code (version_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_version_no (tenant_id, bp_code, version_no),
|
|
||||||
KEY idx_pbl_bp_version_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_bp_version_tid_ck (tenant_id, checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图版本快照(append-only)';
|
|
||||||
|
|
||||||
-- 6. pbl_blueprint_lock 编辑锁
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_lock (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
lock_code VARCHAR(32) NOT NULL COMMENT '锁业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
holder_code VARCHAR(32) NOT NULL COMMENT '持有者',
|
|
||||||
session_id VARCHAR(64) DEFAULT NULL COMMENT '会话ID',
|
|
||||||
lock_state VARCHAR(16) NOT NULL DEFAULT 'held' COMMENT '锁状态:held/released/expired/forced',
|
|
||||||
acquired_at DATETIME DEFAULT NULL COMMENT '获取时间',
|
|
||||||
expire_at DATETIME DEFAULT NULL COMMENT '过期时间',
|
|
||||||
released_at DATETIME DEFAULT NULL COMMENT '释放时间',
|
|
||||||
version_no INT NOT NULL DEFAULT 0 COMMENT '加锁时版本号',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_lock_code (lock_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_lock_active (tenant_id, bp_code, lock_state),
|
|
||||||
KEY idx_pbl_bp_lock_tid_holder (tenant_id, holder_code),
|
|
||||||
KEY idx_pbl_bp_lock_tid_expire (tenant_id, expire_at)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图编辑锁';
|
|
||||||
|
|
||||||
-- 7. pbl_subobject 7 类子对象泛化主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '所属蓝图',
|
|
||||||
obj_type VARCHAR(32) NOT NULL COMMENT '子对象类型:objective/stage/task/artifact_spec/rubric_ref/resource_ref/assessment_plan(appcodes_kv: pbl_subobj_type)',
|
|
||||||
parent_code VARCHAR(32) DEFAULT NULL COMMENT '父子对象编码(树形)',
|
|
||||||
obj_name VARCHAR(128) NOT NULL COMMENT '名称',
|
|
||||||
obj_summary VARCHAR(1024) DEFAULT NULL COMMENT '摘要',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '同级排序',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '类型化载荷 JSON(泛化契约)',
|
|
||||||
schema_ver VARCHAR(16) NOT NULL DEFAULT 'v1' COMMENT '载荷 schema 版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled/deleted',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '乐观锁版本号',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobject_code (obj_code),
|
|
||||||
KEY idx_pbl_subobj_tid_bp_type (tenant_id, bp_code, obj_type, sort_no),
|
|
||||||
KEY idx_pbl_subobj_tid_parent (tenant_id, parent_code),
|
|
||||||
KEY idx_pbl_subobj_tid_status (tenant_id, status)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图 7 类子对象泛化主表';
|
|
||||||
|
|
||||||
-- 8. pbl_subobject_attr 子对象泛化属性(KV)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_attr (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
attr_code VARCHAR(32) NOT NULL COMMENT '属性业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
attr_key VARCHAR(64) NOT NULL COMMENT '属性键',
|
|
||||||
attr_value TEXT DEFAULT NULL COMMENT '属性值',
|
|
||||||
value_type VARCHAR(16) NOT NULL DEFAULT 'string' COMMENT '值类型:string/int/float/bool/json/date',
|
|
||||||
required_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否必填 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_attr_code (attr_code),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_attr_key (tenant_id, obj_code, attr_key),
|
|
||||||
KEY idx_pbl_subobj_attr_tid_obj (tenant_id, obj_code, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 子对象泛化属性 KV';
|
|
||||||
|
|
||||||
-- 9. pbl_subobject_rel 子对象间关系
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_rel (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
rel_code VARCHAR(32) NOT NULL COMMENT '关系业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '所属蓝图',
|
|
||||||
src_obj_code VARCHAR(32) NOT NULL COMMENT '源子对象',
|
|
||||||
dst_obj_code VARCHAR(32) NOT NULL COMMENT '目标子对象',
|
|
||||||
rel_type VARCHAR(32) NOT NULL COMMENT '关系类型:depends_on/produces/assesses/references/contains(appcodes_kv: pbl_rel_type)',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '关系权重',
|
|
||||||
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_rel_code (rel_code),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_rel_triple (tenant_id, src_obj_code, dst_obj_code, rel_type),
|
|
||||||
KEY idx_pbl_subobj_rel_tid_bp (tenant_id, bp_code, rel_type),
|
|
||||||
KEY idx_pbl_subobj_rel_tid_dst (tenant_id, dst_obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 子对象关系';
|
|
||||||
|
|
||||||
-- 10. pbl_subobject_asset 子对象资产引用(只读引用平台资产,不落资产本体)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_asset (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
asset_code VARCHAR(32) NOT NULL COMMENT '资产引用业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
asset_kind VARCHAR(32) NOT NULL COMMENT '资产种类:image/model/audio/video/doc/url(appcodes_kv: pbl_asset_kind)',
|
|
||||||
asset_uri VARCHAR(512) DEFAULT NULL COMMENT '资产 URI',
|
|
||||||
asset_name VARCHAR(128) DEFAULT NULL COMMENT '资产名称',
|
|
||||||
mime_type VARCHAR(64) DEFAULT NULL COMMENT 'MIME',
|
|
||||||
size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '字节数',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
license_code VARCHAR(32) DEFAULT NULL COMMENT '许可编码',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_asset_code (asset_code),
|
|
||||||
KEY idx_pbl_subobj_asset_tid_obj (tenant_id, obj_code, sort_no),
|
|
||||||
KEY idx_pbl_subobj_asset_tid_kind (tenant_id, asset_kind)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 子对象资产引用';
|
|
||||||
|
|
||||||
-- 11. pbl_subobject_ref 子对象对复用域对象的只读引用(world/scene/entity/script)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_ref (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
ref_code VARCHAR(32) NOT NULL COMMENT '引用业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '被引用模块:world/scene/entity/script_engine/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '被引用表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '被引用行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '被引用业务编码',
|
|
||||||
ref_mode VARCHAR(16) NOT NULL DEFAULT 'readonly' COMMENT '引用模式:readonly(Phase 0/1 唯一合法值)',
|
|
||||||
snapshot_json TEXT DEFAULT NULL COMMENT '引用时只读快照(不改基表)',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_ref_code (ref_code),
|
|
||||||
KEY idx_pbl_subobj_ref_tid_obj (tenant_id, obj_code),
|
|
||||||
KEY idx_pbl_subobj_ref_tid_mod (tenant_id, ref_module, ref_table, ref_biz_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 子对象对复用域只读引用';
|
|
||||||
|
|
||||||
-- 12. pbl_subobject_review 子对象评审意见(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_review (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
review_code VARCHAR(32) NOT NULL COMMENT '评审业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '子对象编码(空=蓝图级)',
|
|
||||||
reviewer_type VARCHAR(16) NOT NULL DEFAULT 'user' COMMENT '评审主体:user/agent_critic/agent_designer',
|
|
||||||
reviewer_code VARCHAR(32) DEFAULT NULL COMMENT '评审主体编码',
|
|
||||||
verdict VARCHAR(16) NOT NULL DEFAULT 'comment' COMMENT '结论:pass/comment/revise/reject',
|
|
||||||
score DECIMAL(6,2) DEFAULT NULL COMMENT '评分',
|
|
||||||
comment TEXT DEFAULT NULL COMMENT '意见正文',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '评审时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_review_code (review_code),
|
|
||||||
KEY idx_pbl_subobj_review_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_subobj_review_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 子对象评审意见(append-only)';
|
|
||||||
|
|
||||||
-- 13. pbl_blueprint_tag 蓝图标签
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_tag (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tag_code VARCHAR(32) NOT NULL COMMENT '标签业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
tag_key VARCHAR(64) NOT NULL COMMENT '标签键',
|
|
||||||
tag_value VARCHAR(128) DEFAULT NULL COMMENT '标签值',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_tag_code (tag_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_tag_kv (tenant_id, bp_code, tag_key),
|
|
||||||
KEY idx_pbl_bp_tag_tid_key (tenant_id, tag_key, tag_value)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图标签';
|
|
||||||
|
|
||||||
-- 14. pbl_blueprint_publish 发布记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_publish (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
publish_code VARCHAR(32) NOT NULL COMMENT '发布业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '发布版本号',
|
|
||||||
gd_code VARCHAR(32) DEFAULT NULL COMMENT '关联 Game Definition 编码',
|
|
||||||
channel VARCHAR(32) NOT NULL DEFAULT 'class' COMMENT '发布渠道:class/school/public',
|
|
||||||
target_code VARCHAR(32) DEFAULT NULL COMMENT '目标对象(班级编码)',
|
|
||||||
publish_state VARCHAR(16) NOT NULL DEFAULT 'published' COMMENT '状态:published/revoked',
|
|
||||||
published_by VARCHAR(32) DEFAULT NULL COMMENT '发布人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '发布时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_publish_code (publish_code),
|
|
||||||
KEY idx_pbl_bp_publish_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_bp_publish_tid_target (tenant_id, channel, target_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 蓝图发布记录(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_template(2 表,M1b)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 15. pbl_template 模板主表(含离线兜底模板)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_template (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tpl_code VARCHAR(32) NOT NULL COMMENT '模板业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID(__system__ 表示平台内置)',
|
|
||||||
tpl_name VARCHAR(128) NOT NULL COMMENT '模板名称',
|
|
||||||
tpl_kind VARCHAR(32) NOT NULL DEFAULT 'blueprint' COMMENT '模板种类:blueprint/subobject/rubric',
|
|
||||||
subject_code VARCHAR(32) DEFAULT NULL COMMENT '适用学科',
|
|
||||||
grade_code VARCHAR(32) DEFAULT NULL COMMENT '适用学段',
|
|
||||||
offline_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否离线兜底模板 0否1是',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT '模板正文 JSON(含子对象树)',
|
|
||||||
id_prefix_rule VARCHAR(64) DEFAULT NULL COMMENT '实例化 ID 重映射前缀规则',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '模板版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_template_code (tpl_code),
|
|
||||||
KEY idx_pbl_template_tid_kind (tenant_id, tpl_kind, status),
|
|
||||||
KEY idx_pbl_template_tid_subj (tenant_id, subject_code, grade_code),
|
|
||||||
KEY idx_pbl_template_tid_offline (tenant_id, offline_flag)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 模板主表';
|
|
||||||
|
|
||||||
-- 16. pbl_template_item 模板条目(实例化 ID 重映射映射表)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_template_item (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
item_code VARCHAR(32) NOT NULL COMMENT '条目业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
tpl_code VARCHAR(32) NOT NULL COMMENT '模板编码',
|
|
||||||
tpl_obj_key VARCHAR(64) NOT NULL COMMENT '模板内占位键(如 __OBJ_1__)',
|
|
||||||
obj_type VARCHAR(32) NOT NULL COMMENT '子对象类型',
|
|
||||||
parent_key VARCHAR(64) DEFAULT NULL COMMENT '父占位键',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '条目载荷 JSON',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_template_item_code (item_code),
|
|
||||||
UNIQUE KEY uk_pbl_template_item_key (tenant_id, tpl_code, tpl_obj_key),
|
|
||||||
KEY idx_pbl_template_item_tid_tpl (tenant_id, tpl_code, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 模板条目';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_validation(3 表,M2:14 维 + 5 级质量状态)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 17. pbl_validation_ruleset 校验规则集
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_ruleset (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
ruleset_code VARCHAR(32) NOT NULL COMMENT '规则集业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
ruleset_name VARCHAR(128) NOT NULL COMMENT '规则集名称',
|
|
||||||
dim_code VARCHAR(32) NOT NULL COMMENT '维度编码 D01~D14(appcodes_kv: pbl_valid_dim)',
|
|
||||||
dim_name VARCHAR(64) NOT NULL COMMENT '维度名称',
|
|
||||||
rule_expr TEXT DEFAULT NULL COMMENT '规则表达式 JSON(确定性可执行)',
|
|
||||||
severity VARCHAR(16) NOT NULL DEFAULT 'error' COMMENT '严重度:blocker/error/warning/info',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '维度权重',
|
|
||||||
level_effect VARCHAR(16) DEFAULT NULL COMMENT '对质量等级的影响:downgrade/gate/none',
|
|
||||||
enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '维度排序',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_ruleset_code (ruleset_code),
|
|
||||||
KEY idx_pbl_valid_ruleset_tid_dim (tenant_id, dim_code, enabled),
|
|
||||||
KEY idx_pbl_valid_ruleset_tid_sort (tenant_id, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 14 维校验规则集';
|
|
||||||
|
|
||||||
-- 18. pbl_validation_run 校验执行批次
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_run (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
run_code VARCHAR(32) NOT NULL COMMENT '执行批次业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '被校验版本号',
|
|
||||||
ruleset_ver VARCHAR(32) DEFAULT NULL COMMENT '规则集版本',
|
|
||||||
trigger_type VARCHAR(16) NOT NULL DEFAULT 'manual' COMMENT '触发:manual/auto/agent/pre_compile',
|
|
||||||
run_state VARCHAR(16) NOT NULL DEFAULT 'running' COMMENT '状态:running/passed/failed/error',
|
|
||||||
dims_total INT NOT NULL DEFAULT 14 COMMENT '维度总数',
|
|
||||||
dims_passed INT NOT NULL DEFAULT 0 COMMENT '通过维度数',
|
|
||||||
blocker_count INT NOT NULL DEFAULT 0 COMMENT 'blocker 数',
|
|
||||||
error_count INT NOT NULL DEFAULT 0 COMMENT 'error 数',
|
|
||||||
warning_count INT NOT NULL DEFAULT 0 COMMENT 'warning 数',
|
|
||||||
score DECIMAL(6,2) DEFAULT NULL COMMENT '加权得分',
|
|
||||||
quality_level VARCHAR(16) DEFAULT NULL COMMENT '产出质量等级 L0~L4',
|
|
||||||
started_at DATETIME DEFAULT NULL COMMENT '开始时间',
|
|
||||||
finished_at DATETIME DEFAULT NULL COMMENT '结束时间',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_run_code (run_code),
|
|
||||||
KEY idx_pbl_valid_run_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_valid_run_tid_state (tenant_id, run_state),
|
|
||||||
KEY idx_pbl_valid_run_tid_level (tenant_id, quality_level)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 校验执行批次';
|
|
||||||
|
|
||||||
-- 19. pbl_validation_finding 校验发现明细(append-only 留痕)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_finding (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
finding_code VARCHAR(32) NOT NULL COMMENT '发现业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
run_code VARCHAR(32) NOT NULL COMMENT '执行批次编码',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
dim_code VARCHAR(32) NOT NULL COMMENT '维度编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '命中子对象',
|
|
||||||
severity VARCHAR(16) NOT NULL DEFAULT 'error' COMMENT '严重度',
|
|
||||||
finding_msg VARCHAR(1024) DEFAULT NULL COMMENT '问题描述',
|
|
||||||
suggestion VARCHAR(1024) DEFAULT NULL COMMENT '修复建议',
|
|
||||||
evidence_json TEXT DEFAULT NULL COMMENT '证据 JSON',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '产生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_finding_code (finding_code),
|
|
||||||
KEY idx_pbl_valid_finding_tid_run (tenant_id, run_code, dim_code),
|
|
||||||
KEY idx_pbl_valid_finding_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_valid_finding_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 校验发现明细(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_compiler(4 表,M3a/M3b:确定性编译 + Game Definition)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 20. pbl_compile_job 编译作业
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_job (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '作业业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '被编译版本号',
|
|
||||||
run_code VARCHAR(32) DEFAULT NULL COMMENT '前置校验批次(必须 passed)',
|
|
||||||
compiler_ver VARCHAR(32) NOT NULL DEFAULT 'v1' COMMENT '编译器版本',
|
|
||||||
job_state VARCHAR(16) NOT NULL DEFAULT 'queued' COMMENT '状态:queued/running/success/failed/cancelled',
|
|
||||||
input_checksum VARCHAR(64) DEFAULT NULL COMMENT '输入快照 sha256(确定性依据)',
|
|
||||||
output_checksum VARCHAR(64) DEFAULT NULL COMMENT '输出 Game Definition sha256',
|
|
||||||
gd_code VARCHAR(32) DEFAULT NULL COMMENT '产出 Game Definition 编码',
|
|
||||||
error_msg VARCHAR(1024) DEFAULT NULL COMMENT '错误信息',
|
|
||||||
started_at DATETIME DEFAULT NULL,
|
|
||||||
finished_at DATETIME DEFAULT NULL,
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_job_code (job_code),
|
|
||||||
KEY idx_pbl_compile_job_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_compile_job_tid_state (tenant_id, job_state),
|
|
||||||
KEY idx_pbl_compile_job_tid_ick (tenant_id, input_checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 编译作业';
|
|
||||||
|
|
||||||
-- 21. pbl_compile_artifact 编译中间产物
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_artifact (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
artifact_code VARCHAR(32) NOT NULL COMMENT '产物业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '编译作业编码',
|
|
||||||
stage_code VARCHAR(32) NOT NULL COMMENT '编译阶段:normalize/resolve/link/optimize/emit',
|
|
||||||
artifact_kind VARCHAR(32) NOT NULL COMMENT '产物种类:node_set/edge_set/rule_set/asset_map/gd_draft',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT '产物正文 JSON',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '阶段排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_artifact_code (artifact_code),
|
|
||||||
KEY idx_pbl_compile_artifact_tid_job (tenant_id, job_code, sort_no),
|
|
||||||
KEY idx_pbl_compile_artifact_tid_kind (tenant_id, artifact_kind)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 编译中间产物';
|
|
||||||
|
|
||||||
-- 22. pbl_game_definition Game Definition(编译最终产出)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_game_definition (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
gd_code VARCHAR(32) NOT NULL COMMENT 'Game Definition 业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '来源蓝图',
|
|
||||||
version_no INT NOT NULL COMMENT '来源蓝图版本',
|
|
||||||
gd_version INT NOT NULL DEFAULT 1 COMMENT 'GD 自身版本',
|
|
||||||
schema_ver VARCHAR(16) NOT NULL DEFAULT 'gd-v1' COMMENT 'GD schema 版本',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT 'Game Definition 全文 JSON(schema 校验通过)',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256(同输入同输出)',
|
|
||||||
world_ref VARCHAR(64) DEFAULT NULL COMMENT '引用世界编码(只读)',
|
|
||||||
scene_ref VARCHAR(64) DEFAULT NULL COMMENT '引用场景编码(只读)',
|
|
||||||
script_ref VARCHAR(64) DEFAULT NULL COMMENT '引用脚本编码(只读)',
|
|
||||||
gd_state VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/validated/published/archived',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_gd_code (gd_code),
|
|
||||||
UNIQUE KEY uk_pbl_gd_bp_ver (tenant_id, bp_code, version_no, gd_version),
|
|
||||||
KEY idx_pbl_gd_tid_state (tenant_id, gd_state),
|
|
||||||
KEY idx_pbl_gd_tid_ck (tenant_id, checksum),
|
|
||||||
KEY idx_pbl_gd_tid_world (tenant_id, world_ref, scene_ref)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL Game Definition';
|
|
||||||
|
|
||||||
-- 23. pbl_compile_log 编译日志(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_log (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
log_code VARCHAR(32) NOT NULL COMMENT '日志业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '编译作业编码',
|
|
||||||
stage_code VARCHAR(32) DEFAULT NULL COMMENT '阶段',
|
|
||||||
log_level VARCHAR(16) NOT NULL DEFAULT 'info' COMMENT '级别:debug/info/warn/error',
|
|
||||||
log_msg VARCHAR(2048) DEFAULT NULL COMMENT '日志正文',
|
|
||||||
context_json TEXT DEFAULT NULL COMMENT '上下文 JSON',
|
|
||||||
seq_no BIGINT NOT NULL DEFAULT 0 COMMENT '作业内单调序号',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '记录时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_log_code (log_code),
|
|
||||||
KEY idx_pbl_compile_log_tid_job (tenant_id, job_code, seq_no),
|
|
||||||
KEY idx_pbl_compile_log_tid_level (tenant_id, log_level, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 编译日志(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_agent_runtime(4 表,M4a/M4b:Designer/Critic + fail-closed 裁决)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 24. pbl_agent_session Agent 会话
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_session (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '关联蓝图',
|
|
||||||
agent_role VARCHAR(32) NOT NULL COMMENT 'Agent 角色:designer/critic(appcodes_kv: pbl_agent_role)',
|
|
||||||
model_name VARCHAR(64) DEFAULT NULL COMMENT '使用的平台模型注册名',
|
|
||||||
session_state VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/paused/closed/error',
|
|
||||||
owner_code VARCHAR(32) DEFAULT NULL COMMENT '发起用户',
|
|
||||||
turn_count INT NOT NULL DEFAULT 0 COMMENT '轮次数',
|
|
||||||
tool_call_count INT NOT NULL DEFAULT 0 COMMENT '工具调用总数',
|
|
||||||
tool_deny_count INT NOT NULL DEFAULT 0 COMMENT '被拒绝调用数',
|
|
||||||
context_json LONGTEXT DEFAULT NULL COMMENT '会话上下文 JSON',
|
|
||||||
started_at DATETIME DEFAULT NULL,
|
|
||||||
closed_at DATETIME DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_session_code (session_code),
|
|
||||||
KEY idx_pbl_agent_session_tid_bp (tenant_id, bp_code, session_state),
|
|
||||||
KEY idx_pbl_agent_session_tid_role (tenant_id, agent_role),
|
|
||||||
KEY idx_pbl_agent_session_tid_owner (tenant_id, owner_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL Agent 会话';
|
|
||||||
|
|
||||||
-- 25. pbl_agent_message Agent 消息(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_message (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
msg_code VARCHAR(32) NOT NULL COMMENT '消息业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话编码',
|
|
||||||
turn_no INT NOT NULL DEFAULT 0 COMMENT '轮次号',
|
|
||||||
role VARCHAR(16) NOT NULL COMMENT '角色:system/user/assistant/tool',
|
|
||||||
content LONGTEXT DEFAULT NULL COMMENT '消息正文',
|
|
||||||
token_in INT NOT NULL DEFAULT 0 COMMENT '输入 token',
|
|
||||||
token_out INT NOT NULL DEFAULT 0 COMMENT '输出 token',
|
|
||||||
latency_ms INT NOT NULL DEFAULT 0 COMMENT '耗时毫秒',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '产生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_msg_code (msg_code),
|
|
||||||
KEY idx_pbl_agent_msg_tid_sess (tenant_id, session_code, turn_no),
|
|
||||||
KEY idx_pbl_agent_msg_tid_time (tenant_id, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL Agent 消息(append-only)';
|
|
||||||
|
|
||||||
-- 26. pbl_agent_tool_call 工具调用记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_tool_call (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
call_code VARCHAR(32) NOT NULL COMMENT '调用业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话编码',
|
|
||||||
turn_no INT NOT NULL DEFAULT 0 COMMENT '轮次号',
|
|
||||||
tool_name VARCHAR(64) NOT NULL COMMENT '工具名(13 启用 / 9 禁用白名单外)',
|
|
||||||
tool_state VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态:pending/allowed/executed/denied/error',
|
|
||||||
params_json LONGTEXT DEFAULT NULL COMMENT '入参 JSON',
|
|
||||||
result_json LONGTEXT DEFAULT NULL COMMENT '返回 JSON',
|
|
||||||
latency_ms INT NOT NULL DEFAULT 0 COMMENT '耗时毫秒',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '调用时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_call_code (call_code),
|
|
||||||
KEY idx_pbl_agent_call_tid_sess (tenant_id, session_code, turn_no),
|
|
||||||
KEY idx_pbl_agent_call_tid_tool (tenant_id, tool_name, tool_state),
|
|
||||||
KEY idx_pbl_agent_call_tid_time (tenant_id, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL Agent 工具调用(append-only)';
|
|
||||||
|
|
||||||
-- 27. pbl_agent_arbitration fail-closed 裁决记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_arbitration (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
arb_code VARCHAR(32) NOT NULL COMMENT '裁决业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '会话编码',
|
|
||||||
call_code VARCHAR(32) DEFAULT NULL COMMENT '关联调用编码',
|
|
||||||
tool_name VARCHAR(64) NOT NULL COMMENT '被裁决工具名',
|
|
||||||
decision VARCHAR(16) NOT NULL DEFAULT 'deny' COMMENT '裁决:allow/deny(默认 deny,fail-closed)',
|
|
||||||
deny_reason VARCHAR(32) NOT NULL DEFAULT 'not_in_whitelist' COMMENT '拒绝原因:not_in_whitelist/invalid_params/missing_tenant_context/write_protected/quota_exceeded',
|
|
||||||
rule_ref VARCHAR(64) DEFAULT NULL COMMENT '命中规则引用',
|
|
||||||
detail_json TEXT DEFAULT NULL COMMENT '裁决详情 JSON',
|
|
||||||
arbiter VARCHAR(32) NOT NULL DEFAULT 'system' COMMENT '裁决者',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '裁决时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_arb_code (arb_code),
|
|
||||||
KEY idx_pbl_agent_arb_tid_tool (tenant_id, tool_name, decision),
|
|
||||||
KEY idx_pbl_agent_arb_tid_sess (tenant_id, session_code, create_time),
|
|
||||||
KEY idx_pbl_agent_arb_tid_reason (tenant_id, deny_reason)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL Agent fail-closed 裁决记录(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_evidence(4 表,M5a/M5b:产出物采集 + 证据幂等)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 28. pbl_artifact 学生产出物
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_artifact (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
artifact_code VARCHAR(32) NOT NULL COMMENT '产出物业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '运行会话编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '对应产出物规格子对象',
|
|
||||||
artifact_name VARCHAR(128) NOT NULL COMMENT '产出物名称',
|
|
||||||
artifact_kind VARCHAR(32) NOT NULL COMMENT '种类:doc/image/model/audio/video/code/log/chat(appcodes_kv: pbl_artifact_kind)',
|
|
||||||
storage_uri VARCHAR(512) DEFAULT NULL COMMENT '存储 URI',
|
|
||||||
mime_type VARCHAR(64) DEFAULT NULL COMMENT 'MIME',
|
|
||||||
size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '字节数',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
content_text LONGTEXT DEFAULT NULL COMMENT '文本类正文(可索引)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/archived/deleted',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_artifact_code (artifact_code),
|
|
||||||
KEY idx_pbl_artifact_tid_bp (tenant_id, bp_code, artifact_kind),
|
|
||||||
KEY idx_pbl_artifact_tid_team (tenant_id, team_code),
|
|
||||||
KEY idx_pbl_artifact_tid_sess (tenant_id, session_code),
|
|
||||||
KEY idx_pbl_artifact_tid_ck (tenant_id, checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 学生产出物';
|
|
||||||
|
|
||||||
-- 29. pbl_evidence 证据主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
evidence_code VARCHAR(32) NOT NULL COMMENT '证据业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
artifact_code VARCHAR(32) DEFAULT NULL COMMENT '关联产出物',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '关联子对象(学习目标/任务)',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
student_code VARCHAR(32) DEFAULT NULL COMMENT '学生编码',
|
|
||||||
evidence_type VARCHAR(32) NOT NULL COMMENT '证据类型:artifact/event/dialogue/state_snapshot/metric(appcodes_kv: pbl_evidence_type)',
|
|
||||||
idem_key VARCHAR(128) NOT NULL COMMENT '幂等键(tenant+bp+type+source+hash)',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '证据载荷 JSON',
|
|
||||||
occurred_at DATETIME DEFAULT NULL COMMENT '证据发生时间',
|
|
||||||
trust_level VARCHAR(16) NOT NULL DEFAULT 'normal' COMMENT '可信度:high/normal/low',
|
|
||||||
anonymized TINYINT NOT NULL DEFAULT 0 COMMENT '是否已匿名化 0否1是',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/invalidated',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_code (evidence_code),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem (tenant_id, idem_key),
|
|
||||||
KEY idx_pbl_evidence_tid_bp (tenant_id, bp_code, evidence_type),
|
|
||||||
KEY idx_pbl_evidence_tid_team (tenant_id, team_code, student_code),
|
|
||||||
KEY idx_pbl_evidence_tid_obj (tenant_id, obj_code),
|
|
||||||
KEY idx_pbl_evidence_tid_time (tenant_id, occurred_at)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 证据主表(幂等键唯一)';
|
|
||||||
|
|
||||||
-- 30. pbl_evidence_idem 证据幂等提交台账(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence_idem (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
idem_code VARCHAR(32) NOT NULL COMMENT '幂等台账业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
idem_key VARCHAR(128) NOT NULL COMMENT '幂等键',
|
|
||||||
evidence_code VARCHAR(32) DEFAULT NULL COMMENT '首次落库证据编码',
|
|
||||||
submit_count INT NOT NULL DEFAULT 1 COMMENT '累计提交次数(重复提交只增此计数,不新增证据行)',
|
|
||||||
last_payload_hash VARCHAR(64) DEFAULT NULL COMMENT '最近一次载荷 hash',
|
|
||||||
conflict_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否载荷冲突 0否1是',
|
|
||||||
source_channel VARCHAR(32) DEFAULT NULL COMMENT '来源渠道:runtime/manual/agent/import',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '首次提交时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem_code (idem_code),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem_key (tenant_id, idem_key),
|
|
||||||
KEY idx_pbl_evidence_idem_tid_time (tenant_id, create_time),
|
|
||||||
KEY idx_pbl_evidence_idem_tid_conf (tenant_id, conflict_flag)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 证据幂等台账(append-only)';
|
|
||||||
|
|
||||||
-- 31. pbl_evidence_binding 证据与复用域对象绑定(零改表关联)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence_binding (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bind_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
evidence_code VARCHAR(32) NOT NULL COMMENT '证据编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '复用域模块:world/scene/entity/script_engine/scense_runtime/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '复用域表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '复用域行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '复用域业务编码',
|
|
||||||
bind_role VARCHAR(32) NOT NULL DEFAULT 'source' COMMENT '绑定角色:source/target/context',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_binding_code (bind_code),
|
|
||||||
KEY idx_pbl_evidence_binding_tid_ev (tenant_id, evidence_code),
|
|
||||||
KEY idx_pbl_evidence_binding_tid_ref (tenant_id, ref_module, ref_table, ref_biz_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 证据与复用域绑定(零改表)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_assessment(3 表,M6:Rubric 加权评估与报告)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 32. pbl_rubric 量规主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_rubric (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '关联蓝图(空=通用量规)',
|
|
||||||
rubric_name VARCHAR(128) NOT NULL COMMENT '量规名称',
|
|
||||||
level_count INT NOT NULL DEFAULT 5 COMMENT '等级数(默认 5 级)',
|
|
||||||
total_weight DECIMAL(8,3) NOT NULL DEFAULT 100.000 COMMENT '总权重',
|
|
||||||
pass_score DECIMAL(6,2) NOT NULL DEFAULT 60.00 COMMENT '及格分',
|
|
||||||
grade_rule TEXT DEFAULT NULL COMMENT '等级映射规则 JSON',
|
|
||||||
is_default TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认量规 0否1是',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_rubric_code (rubric_code),
|
|
||||||
KEY idx_pbl_rubric_tid_bp (tenant_id, bp_code, status),
|
|
||||||
KEY idx_pbl_rubric_tid_default (tenant_id, is_default)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 量规主表';
|
|
||||||
|
|
||||||
-- 33. pbl_rubric_criterion 量规评价维度条目
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_rubric_criterion (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
crit_code VARCHAR(32) NOT NULL COMMENT '条目业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '关联学习目标子对象',
|
|
||||||
crit_name VARCHAR(128) NOT NULL COMMENT '维度名称',
|
|
||||||
crit_desc VARCHAR(1024) DEFAULT NULL COMMENT '维度说明',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '权重',
|
|
||||||
level_desc TEXT DEFAULT NULL COMMENT '各等级描述 JSON(level -> desc)',
|
|
||||||
max_score DECIMAL(6,2) NOT NULL DEFAULT 5.00 COMMENT '最高分',
|
|
||||||
evidence_required TINYINT NOT NULL DEFAULT 1 COMMENT '是否必须有证据支撑 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_rubric_crit_code (crit_code),
|
|
||||||
KEY idx_pbl_rubric_crit_tid_rub (tenant_id, rubric_code, sort_no),
|
|
||||||
KEY idx_pbl_rubric_crit_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 量规评价维度';
|
|
||||||
|
|
||||||
-- 34. pbl_assessment_report 评估报告
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_assessment_report (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
report_code VARCHAR(32) NOT NULL COMMENT '报告业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
student_code VARCHAR(32) DEFAULT NULL COMMENT '学生编码(团队报告时为空)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'team' COMMENT '范围:team/student/blueprint',
|
|
||||||
score_total DECIMAL(6,2) NOT NULL DEFAULT 0.00 COMMENT '加权总分(可复算)',
|
|
||||||
grade_code VARCHAR(16) DEFAULT NULL COMMENT '等级 A/B/C/D/E',
|
|
||||||
detail_json LONGTEXT DEFAULT NULL COMMENT '逐维度得分明细 JSON(含证据引用)',
|
|
||||||
evidence_count INT NOT NULL DEFAULT 0 COMMENT '引用证据数',
|
|
||||||
missing_evidence_count INT NOT NULL DEFAULT 0 COMMENT '缺证据维度数',
|
|
||||||
assessor_type VARCHAR(16) NOT NULL DEFAULT 'agent' COMMENT '评估者:agent/user/mixed',
|
|
||||||
assessor_code VARCHAR(32) DEFAULT NULL COMMENT '评估者编码',
|
|
||||||
report_state VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/published/archived',
|
|
||||||
calc_checksum VARCHAR(64) DEFAULT NULL COMMENT '复算校验 sha256',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_assess_report_code (report_code),
|
|
||||||
KEY idx_pbl_assess_report_tid_bp (tenant_id, bp_code, scope_type),
|
|
||||||
KEY idx_pbl_assess_report_tid_team (tenant_id, team_code, student_code),
|
|
||||||
KEY idx_pbl_assess_report_tid_state (tenant_id, report_state),
|
|
||||||
KEY idx_pbl_assess_report_tid_grade (tenant_id, grade_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 评估报告';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_domain_ext(1 表,M8:world/scene/entity 租户·班级·团队关联,不改基表)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 35. pbl_domain_binding 复用域对象绑定(零改表唯一关联落点)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_domain_binding (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bind_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
bind_kind VARCHAR(32) NOT NULL COMMENT '绑定种类:tenant_world/class_scene/team_entity/bp_script/session_world(appcodes_kv: pbl_bind_kind)',
|
|
||||||
class_code VARCHAR(32) DEFAULT NULL COMMENT '班级编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '蓝图编码',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '运行会话编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '复用域模块:world/scene/entity/script_engine/scense_runtime/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '复用域表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '复用域行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '复用域业务编码',
|
|
||||||
access_mode VARCHAR(16) NOT NULL DEFAULT 'readonly' COMMENT '访问模式:readonly(Phase 0/1 唯一合法值,写保护)',
|
|
||||||
bind_state VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/released',
|
|
||||||
extra_json TEXT DEFAULT NULL COMMENT '扩展 JSON',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_domain_binding_code (bind_code),
|
|
||||||
UNIQUE KEY uk_pbl_domain_binding_ref (tenant_id, bind_kind, ref_module, ref_table, ref_biz_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_class (tenant_id, class_code, team_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_bp (tenant_id, bp_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_sess (tenant_id, session_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_ref (tenant_id, ref_module, ref_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 复用域绑定(零改表)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_analytics(1 表,M10:分析看板快照,append-only)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 36. pbl_analytics_snapshot 分析快照(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_analytics_snapshot (
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
snap_code VARCHAR(32) NOT NULL COMMENT '快照业务主键',
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
metric_group VARCHAR(32) NOT NULL COMMENT '指标组:blueprint/validation/compile/runtime/evidence/assessment(appcodes_kv: pbl_metric_group)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'tenant' COMMENT '范围:tenant/class/team/blueprint',
|
|
||||||
scope_code VARCHAR(32) DEFAULT NULL COMMENT '范围对象编码',
|
|
||||||
period_key VARCHAR(16) NOT NULL COMMENT '周期键:2026-09 / 2026-W38 / 2026-09-15',
|
|
||||||
metrics_json LONGTEXT DEFAULT NULL COMMENT '指标 JSON(计数/均值/分布)',
|
|
||||||
anonymized TINYINT NOT NULL DEFAULT 1 COMMENT '是否匿名聚合 0否1是(KDB 侧强制 1)',
|
|
||||||
sample_size INT NOT NULL DEFAULT 0 COMMENT '样本量',
|
|
||||||
computed_at DATETIME DEFAULT NULL COMMENT '计算时间',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '入库时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_analytics_snap_code (snap_code),
|
|
||||||
UNIQUE KEY uk_pbl_analytics_snap_dim (tenant_id, metric_group, scope_type, scope_code, period_key),
|
|
||||||
KEY idx_pbl_analytics_snap_tid_group (tenant_id, metric_group, period_key),
|
|
||||||
KEY idx_pbl_analytics_snap_tid_scope (tenant_id, scope_type, scope_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='PBL 分析看板快照(append-only)';
|
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 契约自检(供 build.sh / QC grep 核验)
|
|
||||||
-- 表总数:36(pbl_governance 3 + pbl_blueprint 11 + pbl_template 2 + pbl_validation 3
|
|
||||||
-- + pbl_compiler 4 + pbl_agent_runtime 4 + pbl_evidence 4 + pbl_assessment 3
|
|
||||||
-- + pbl_domain_ext 1 + pbl_analytics 1 = 36)
|
|
||||||
-- append-only 表(11,无 update_time):
|
|
||||||
-- pbl_audit_log / pbl_blueprint_version / pbl_subobject_review / pbl_blueprint_publish
|
|
||||||
-- pbl_validation_finding / pbl_compile_log / pbl_agent_message / pbl_agent_tool_call
|
|
||||||
-- pbl_agent_arbitration / pbl_evidence_idem / pbl_analytics_snapshot
|
|
||||||
-- 零命中项(mariadb 方言硬约束):
|
|
||||||
-- FOREIGN KEY / REFERENCES / ENUM( / SERIAL / BIGSERIAL / nextval / TIMESTAMP
|
|
||||||
-- 全部索引以 tenant_id 打头;每表 id BIGINT NOT NULL AUTO_INCREMENT + xxx_code 唯一业务主键。
|
|
||||||
-- =====================================================================
|
|
||||||
@ -1,924 +0,0 @@
|
|||||||
-- =====================================================================
|
|
||||||
-- PBL Agent OS 验证原型(Phase 0/1)—— 36 表可执行 DDL
|
|
||||||
-- 方言:mariadb(InnoDB + utf8mb4/utf8mb4_general_ci)
|
|
||||||
-- 规则:id BIGINT NOT NULL AUTO_INCREMENT 物理主键 + xxx_code VARCHAR(32) 业务主键(唯一索引)
|
|
||||||
-- tenant_id VARCHAR(32) NOT NULL 为第一业务列,所有索引以 tenant_id 打头
|
|
||||||
-- 禁止 FOREIGN KEY / 禁止 ENUM / 禁止原生 TIMESTAMP(用 DATETIME)
|
|
||||||
-- append-only 表无 update_time、无物理删除(11 张,见文末清单)
|
|
||||||
-- 权威来源:projects/pbls/docs/01-design/data-model.md
|
|
||||||
-- 执行:apps/pbls/build.sh -> mysql --protocol=TCP -h127.0.0.1 -P3306 pbls < pbls_tables.sql
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
SET NAMES utf8mb4;
|
|
||||||
SET FOREIGN_KEY_CHECKS = 0;
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_governance(3 表)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 1. pbl_tenant 租户主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_tenant (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID(第一业务列)',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tenant_code VARCHAR(32) NOT NULL COMMENT '租户业务主键',
|
|
||||||
tenant_name VARCHAR(128) NOT NULL COMMENT '租户名称',
|
|
||||||
org_code VARCHAR(32) DEFAULT NULL COMMENT '所属机构编码(rbac 侧只读引用)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/suspended/closed(appcodes_kv: pbl_tenant_status)',
|
|
||||||
quota_bp INT NOT NULL DEFAULT 100 COMMENT '蓝图配额',
|
|
||||||
quota_stu INT NOT NULL DEFAULT 500 COMMENT '学生席位配额',
|
|
||||||
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL COMMENT '创建人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '创建时间',
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL COMMENT '更新人',
|
|
||||||
update_time DATETIME DEFAULT NULL COMMENT '更新时间',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_tenant_code (tenant_id, tenant_code),
|
|
||||||
KEY idx_pbl_tenant_tid_status (tenant_id, status),
|
|
||||||
KEY idx_pbl_tenant_tid_org (tenant_id, org_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 租户主表';
|
|
||||||
|
|
||||||
-- 2. pbl_role_binding 租户内角色绑定(复用 rbac,不改 rbac 基表)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_role_binding (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
binding_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
user_code VARCHAR(32) NOT NULL COMMENT '用户编码(rbac 只读引用)',
|
|
||||||
role_code VARCHAR(32) NOT NULL COMMENT '角色编码:teacher/assistant/student/observer/admin(appcodes_kv: pbl_role)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'tenant' COMMENT '作用域类型:tenant/blueprint/class/team',
|
|
||||||
scope_code VARCHAR(32) DEFAULT NULL COMMENT '作用域对象编码',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/revoked',
|
|
||||||
effective_from DATETIME DEFAULT NULL COMMENT '生效起',
|
|
||||||
effective_to DATETIME DEFAULT NULL COMMENT '生效止',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_role_binding_code (tenant_id, binding_code),
|
|
||||||
KEY idx_pbl_role_binding_tid_user (tenant_id, user_code, status),
|
|
||||||
KEY idx_pbl_role_binding_tid_scope (tenant_id, scope_type, scope_code),
|
|
||||||
KEY idx_pbl_role_binding_tid_role (tenant_id, role_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 租户角色绑定';
|
|
||||||
|
|
||||||
-- 3. pbl_audit_log 审计日志(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_audit_log (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
audit_code VARCHAR(32) NOT NULL COMMENT '审计业务主键',
|
|
||||||
actor_type VARCHAR(16) NOT NULL DEFAULT 'user' COMMENT '主体类型:user/agent/system',
|
|
||||||
actor_code VARCHAR(32) DEFAULT NULL COMMENT '主体编码',
|
|
||||||
action_code VARCHAR(64) NOT NULL COMMENT '动作编码(appcodes_kv: pbl_audit_action)',
|
|
||||||
object_type VARCHAR(32) DEFAULT NULL COMMENT '对象类型',
|
|
||||||
object_code VARCHAR(32) DEFAULT NULL COMMENT '对象编码',
|
|
||||||
result VARCHAR(16) NOT NULL DEFAULT 'success' COMMENT '结果:success/denied/error',
|
|
||||||
detail TEXT DEFAULT NULL COMMENT '详情 JSON',
|
|
||||||
trace_id VARCHAR(64) DEFAULT NULL COMMENT '链路追踪ID',
|
|
||||||
client_ip VARCHAR(64) DEFAULT NULL COMMENT '来源IP',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '发生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_audit_code (tenant_id, audit_code),
|
|
||||||
KEY idx_pbl_audit_tid_time (tenant_id, create_time),
|
|
||||||
KEY idx_pbl_audit_tid_obj (tenant_id, object_type, object_code),
|
|
||||||
KEY idx_pbl_audit_tid_actor (tenant_id, actor_type, actor_code),
|
|
||||||
KEY idx_pbl_audit_tid_trace (tenant_id, trace_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 审计日志(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_blueprint(11 表,M1a)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 4. pbl_blueprint 蓝图聚合根
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图业务主键',
|
|
||||||
bp_name VARCHAR(128) NOT NULL COMMENT '蓝图名称',
|
|
||||||
subject_code VARCHAR(32) DEFAULT NULL COMMENT '学科编码(appcodes_kv: pbl_subject)',
|
|
||||||
grade_code VARCHAR(32) DEFAULT NULL COMMENT '学段编码(appcodes_kv: pbl_grade)',
|
|
||||||
driving_q TEXT DEFAULT NULL COMMENT '驱动性问题',
|
|
||||||
summary VARCHAR(1024) DEFAULT NULL COMMENT '蓝图摘要',
|
|
||||||
duration_hours INT NOT NULL DEFAULT 0 COMMENT '总课时(小时)',
|
|
||||||
team_size_min INT NOT NULL DEFAULT 1 COMMENT '最小团队人数',
|
|
||||||
team_size_max INT NOT NULL DEFAULT 6 COMMENT '最大团队人数',
|
|
||||||
cur_version INT NOT NULL DEFAULT 1 COMMENT '当前版本号(乐观锁)',
|
|
||||||
quality_level VARCHAR(16) NOT NULL DEFAULT 'L0_draft' COMMENT '质量等级 L0~L4(appcodes_kv: pbl_quality_level)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/validating/validated/compiling/published/archived(appcodes_kv: pbl_bp_status)',
|
|
||||||
source_type VARCHAR(16) NOT NULL DEFAULT 'manual' COMMENT '来源:manual/template/fork/agent',
|
|
||||||
source_code VARCHAR(32) DEFAULT NULL COMMENT '来源对象编码(模板/父蓝图)',
|
|
||||||
owner_code VARCHAR(32) DEFAULT NULL COMMENT '归属教师',
|
|
||||||
lock_holder VARCHAR(32) DEFAULT NULL COMMENT '当前编辑锁持有者',
|
|
||||||
lock_expire DATETIME DEFAULT NULL COMMENT '编辑锁过期时间',
|
|
||||||
extra TEXT DEFAULT NULL COMMENT '扩展 JSON',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_blueprint_code (tenant_id, bp_code),
|
|
||||||
KEY idx_pbl_bp_tid_status (tenant_id, status),
|
|
||||||
KEY idx_pbl_bp_tid_owner (tenant_id, owner_code),
|
|
||||||
KEY idx_pbl_bp_tid_subject (tenant_id, subject_code, grade_code),
|
|
||||||
KEY idx_pbl_bp_tid_quality (tenant_id, quality_level),
|
|
||||||
KEY idx_pbl_bp_tid_source (tenant_id, source_type, source_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图聚合根';
|
|
||||||
|
|
||||||
-- 5. pbl_blueprint_version 版本快照 + change_delta(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_version (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
version_code VARCHAR(32) NOT NULL COMMENT '版本业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '版本号(单调递增)',
|
|
||||||
snapshot LONGTEXT DEFAULT NULL COMMENT '蓝图全量快照 JSON',
|
|
||||||
change_delta LONGTEXT DEFAULT NULL COMMENT '相对上一版本的差异 JSON(可回放)',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT '快照 sha256',
|
|
||||||
reason VARCHAR(512) DEFAULT NULL COMMENT '变更原因',
|
|
||||||
created_by VARCHAR(32) DEFAULT NULL COMMENT '提交人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '提交时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_version_code (tenant_id, version_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_version_no (tenant_id, bp_code, version_no),
|
|
||||||
KEY idx_pbl_bp_version_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_bp_version_tid_ck (tenant_id, checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图版本快照(append-only)';
|
|
||||||
|
|
||||||
-- 6. pbl_blueprint_lock 编辑锁
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_lock (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
lock_code VARCHAR(32) NOT NULL COMMENT '锁业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
holder_code VARCHAR(32) NOT NULL COMMENT '持有者',
|
|
||||||
session_id VARCHAR(64) DEFAULT NULL COMMENT '会话ID',
|
|
||||||
lock_state VARCHAR(16) NOT NULL DEFAULT 'held' COMMENT '锁状态:held/released/expired/forced',
|
|
||||||
acquired_at DATETIME DEFAULT NULL COMMENT '获取时间',
|
|
||||||
expire_at DATETIME DEFAULT NULL COMMENT '过期时间',
|
|
||||||
released_at DATETIME DEFAULT NULL COMMENT '释放时间',
|
|
||||||
version_no INT NOT NULL DEFAULT 0 COMMENT '加锁时版本号',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_lock_code (tenant_id, lock_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_lock_active (tenant_id, bp_code, lock_state),
|
|
||||||
KEY idx_pbl_bp_lock_tid_holder (tenant_id, holder_code),
|
|
||||||
KEY idx_pbl_bp_lock_tid_expire (tenant_id, expire_at)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图编辑锁';
|
|
||||||
|
|
||||||
-- 7. pbl_subobject 7 类子对象泛化主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '所属蓝图',
|
|
||||||
obj_type VARCHAR(32) NOT NULL COMMENT '子对象类型:objective/stage/task/artifact_spec/rubric_ref/resource_ref/assessment_plan(appcodes_kv: pbl_subobj_type)',
|
|
||||||
parent_code VARCHAR(32) DEFAULT NULL COMMENT '父子对象编码(树形)',
|
|
||||||
obj_name VARCHAR(128) NOT NULL COMMENT '名称',
|
|
||||||
obj_summary VARCHAR(1024) DEFAULT NULL COMMENT '摘要',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '同级排序',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '类型化载荷 JSON(泛化契约)',
|
|
||||||
schema_ver VARCHAR(16) NOT NULL DEFAULT 'v1' COMMENT '载荷 schema 版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled/deleted',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '乐观锁版本号',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobject_code (tenant_id, obj_code),
|
|
||||||
KEY idx_pbl_subobj_tid_bp_type (tenant_id, bp_code, obj_type, sort_no),
|
|
||||||
KEY idx_pbl_subobj_tid_parent (tenant_id, parent_code),
|
|
||||||
KEY idx_pbl_subobj_tid_status (tenant_id, status)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图 7 类子对象泛化主表';
|
|
||||||
|
|
||||||
-- 8. pbl_subobject_attr 子对象泛化属性(KV)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_attr (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
attr_code VARCHAR(32) NOT NULL COMMENT '属性业务主键',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
attr_key VARCHAR(64) NOT NULL COMMENT '属性键',
|
|
||||||
attr_value TEXT DEFAULT NULL COMMENT '属性值',
|
|
||||||
value_type VARCHAR(16) NOT NULL DEFAULT 'string' COMMENT '值类型:string/int/float/bool/json/date',
|
|
||||||
required_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否必填 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_attr_code (tenant_id, attr_code),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_attr_key (tenant_id, obj_code, attr_key),
|
|
||||||
KEY idx_pbl_subobj_attr_tid_obj (tenant_id, obj_code, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 子对象泛化属性 KV';
|
|
||||||
|
|
||||||
-- 9. pbl_subobject_rel 子对象间关系
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_rel (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
rel_code VARCHAR(32) NOT NULL COMMENT '关系业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '所属蓝图',
|
|
||||||
src_obj_code VARCHAR(32) NOT NULL COMMENT '源子对象',
|
|
||||||
dst_obj_code VARCHAR(32) NOT NULL COMMENT '目标子对象',
|
|
||||||
rel_type VARCHAR(32) NOT NULL COMMENT '关系类型:depends_on/produces/assesses/references/contains(appcodes_kv: pbl_rel_type)',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '关系权重',
|
|
||||||
remark VARCHAR(512) DEFAULT NULL COMMENT '备注',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_rel_code (tenant_id, rel_code),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_rel_triple (tenant_id, src_obj_code, dst_obj_code, rel_type),
|
|
||||||
KEY idx_pbl_subobj_rel_tid_bp (tenant_id, bp_code, rel_type),
|
|
||||||
KEY idx_pbl_subobj_rel_tid_dst (tenant_id, dst_obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 子对象关系';
|
|
||||||
|
|
||||||
-- 10. pbl_subobject_asset 子对象资产引用(只读引用平台资产,不落资产本体)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_asset (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
asset_code VARCHAR(32) NOT NULL COMMENT '资产引用业务主键',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
asset_kind VARCHAR(32) NOT NULL COMMENT '资产种类:image/model/audio/video/doc/url(appcodes_kv: pbl_asset_kind)',
|
|
||||||
asset_uri VARCHAR(512) DEFAULT NULL COMMENT '资产 URI',
|
|
||||||
asset_name VARCHAR(128) DEFAULT NULL COMMENT '资产名称',
|
|
||||||
mime_type VARCHAR(64) DEFAULT NULL COMMENT 'MIME',
|
|
||||||
size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '字节数',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
license_code VARCHAR(32) DEFAULT NULL COMMENT '许可编码',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_asset_code (tenant_id, asset_code),
|
|
||||||
KEY idx_pbl_subobj_asset_tid_obj (tenant_id, obj_code, sort_no),
|
|
||||||
KEY idx_pbl_subobj_asset_tid_kind (tenant_id, asset_kind)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 子对象资产引用';
|
|
||||||
|
|
||||||
-- 11. pbl_subobject_ref 子对象对复用域对象的只读引用(world/scene/entity/script)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_ref (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
ref_code VARCHAR(32) NOT NULL COMMENT '引用业务主键',
|
|
||||||
obj_code VARCHAR(32) NOT NULL COMMENT '子对象编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '被引用模块:world/scene/entity/script_engine/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '被引用表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '被引用行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '被引用业务编码',
|
|
||||||
ref_mode VARCHAR(16) NOT NULL DEFAULT 'readonly' COMMENT '引用模式:readonly(Phase 0/1 唯一合法值)',
|
|
||||||
snapshot_json TEXT DEFAULT NULL COMMENT '引用时只读快照(不改基表)',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_ref_code (tenant_id, ref_code),
|
|
||||||
KEY idx_pbl_subobj_ref_tid_obj (tenant_id, obj_code),
|
|
||||||
KEY idx_pbl_subobj_ref_tid_mod (tenant_id, ref_module, ref_table, ref_biz_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 子对象对复用域只读引用';
|
|
||||||
|
|
||||||
-- 12. pbl_subobject_review 子对象评审意见(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_subobject_review (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
review_code VARCHAR(32) NOT NULL COMMENT '评审业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '子对象编码(空=蓝图级)',
|
|
||||||
reviewer_type VARCHAR(16) NOT NULL DEFAULT 'user' COMMENT '评审主体:user/agent_critic/agent_designer',
|
|
||||||
reviewer_code VARCHAR(32) DEFAULT NULL COMMENT '评审主体编码',
|
|
||||||
verdict VARCHAR(16) NOT NULL DEFAULT 'comment' COMMENT '结论:pass/comment/revise/reject',
|
|
||||||
score DECIMAL(6,2) DEFAULT NULL COMMENT '评分',
|
|
||||||
comment TEXT DEFAULT NULL COMMENT '意见正文',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '评审时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_subobj_review_code (tenant_id, review_code),
|
|
||||||
KEY idx_pbl_subobj_review_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_subobj_review_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 子对象评审意见(append-only)';
|
|
||||||
|
|
||||||
-- 13. pbl_blueprint_tag 蓝图标签
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_tag (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tag_code VARCHAR(32) NOT NULL COMMENT '标签业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
tag_key VARCHAR(64) NOT NULL COMMENT '标签键',
|
|
||||||
tag_value VARCHAR(128) DEFAULT NULL COMMENT '标签值',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_tag_code (tenant_id, tag_code),
|
|
||||||
UNIQUE KEY uk_pbl_bp_tag_kv (tenant_id, bp_code, tag_key),
|
|
||||||
KEY idx_pbl_bp_tag_tid_key (tenant_id, tag_key, tag_value)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图标签';
|
|
||||||
|
|
||||||
-- 14. pbl_blueprint_publish 发布记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_blueprint_publish (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
publish_code VARCHAR(32) NOT NULL COMMENT '发布业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '发布版本号',
|
|
||||||
gd_code VARCHAR(32) DEFAULT NULL COMMENT '关联 Game Definition 编码',
|
|
||||||
channel VARCHAR(32) NOT NULL DEFAULT 'class' COMMENT '发布渠道:class/school/public',
|
|
||||||
target_code VARCHAR(32) DEFAULT NULL COMMENT '目标对象(班级编码)',
|
|
||||||
publish_state VARCHAR(16) NOT NULL DEFAULT 'published' COMMENT '状态:published/revoked',
|
|
||||||
published_by VARCHAR(32) DEFAULT NULL COMMENT '发布人',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '发布时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_bp_publish_code (tenant_id, publish_code),
|
|
||||||
KEY idx_pbl_bp_publish_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_bp_publish_tid_target (tenant_id, channel, target_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 蓝图发布记录(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_template(2 表,M1b)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 15. pbl_template 模板主表(含离线兜底模板)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_template (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID(__system__ 表示平台内置)',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
tpl_code VARCHAR(32) NOT NULL COMMENT '模板业务主键',
|
|
||||||
tpl_name VARCHAR(128) NOT NULL COMMENT '模板名称',
|
|
||||||
tpl_kind VARCHAR(32) NOT NULL DEFAULT 'blueprint' COMMENT '模板种类:blueprint/subobject/rubric',
|
|
||||||
subject_code VARCHAR(32) DEFAULT NULL COMMENT '适用学科',
|
|
||||||
grade_code VARCHAR(32) DEFAULT NULL COMMENT '适用学段',
|
|
||||||
offline_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否离线兜底模板 0否1是',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT '模板正文 JSON(含子对象树)',
|
|
||||||
id_prefix_rule VARCHAR(64) DEFAULT NULL COMMENT '实例化 ID 重映射前缀规则',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '模板版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_template_code (tenant_id, tpl_code),
|
|
||||||
KEY idx_pbl_template_tid_kind (tenant_id, tpl_kind, status),
|
|
||||||
KEY idx_pbl_template_tid_subj (tenant_id, subject_code, grade_code),
|
|
||||||
KEY idx_pbl_template_tid_offline (tenant_id, offline_flag)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 模板主表';
|
|
||||||
|
|
||||||
-- 16. pbl_template_item 模板条目(实例化 ID 重映射映射表)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_template_item (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
item_code VARCHAR(32) NOT NULL COMMENT '条目业务主键',
|
|
||||||
tpl_code VARCHAR(32) NOT NULL COMMENT '模板编码',
|
|
||||||
tpl_obj_key VARCHAR(64) NOT NULL COMMENT '模板内占位键(如 __OBJ_1__)',
|
|
||||||
obj_type VARCHAR(32) NOT NULL COMMENT '子对象类型',
|
|
||||||
parent_key VARCHAR(64) DEFAULT NULL COMMENT '父占位键',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '条目载荷 JSON',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_template_item_code (tenant_id, item_code),
|
|
||||||
UNIQUE KEY uk_pbl_template_item_key (tenant_id, tpl_code, tpl_obj_key),
|
|
||||||
KEY idx_pbl_template_item_tid_tpl (tenant_id, tpl_code, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 模板条目';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_validation(3 表,M2:14 维 + 5 级质量状态)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 17. pbl_validation_ruleset 校验规则集
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_ruleset (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
ruleset_code VARCHAR(32) NOT NULL COMMENT '规则集业务主键',
|
|
||||||
ruleset_name VARCHAR(128) NOT NULL COMMENT '规则集名称',
|
|
||||||
dim_code VARCHAR(32) NOT NULL COMMENT '维度编码 D01~D14(appcodes_kv: pbl_valid_dim)',
|
|
||||||
dim_name VARCHAR(64) NOT NULL COMMENT '维度名称',
|
|
||||||
rule_expr TEXT DEFAULT NULL COMMENT '规则表达式 JSON(确定性可执行)',
|
|
||||||
severity VARCHAR(16) NOT NULL DEFAULT 'error' COMMENT '严重度:blocker/error/warning/info',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '维度权重',
|
|
||||||
level_effect VARCHAR(16) DEFAULT NULL COMMENT '对质量等级的影响:downgrade/gate/none',
|
|
||||||
enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '维度排序',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_ruleset_code (tenant_id, ruleset_code),
|
|
||||||
KEY idx_pbl_valid_ruleset_tid_dim (tenant_id, dim_code, enabled),
|
|
||||||
KEY idx_pbl_valid_ruleset_tid_sort (tenant_id, sort_no)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 14 维校验规则集';
|
|
||||||
|
|
||||||
-- 18. pbl_validation_run 校验执行批次
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_run (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
run_code VARCHAR(32) NOT NULL COMMENT '执行批次业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '被校验版本号',
|
|
||||||
ruleset_ver VARCHAR(32) DEFAULT NULL COMMENT '规则集版本',
|
|
||||||
trigger_type VARCHAR(16) NOT NULL DEFAULT 'manual' COMMENT '触发:manual/auto/agent/pre_compile',
|
|
||||||
run_state VARCHAR(16) NOT NULL DEFAULT 'running' COMMENT '状态:running/passed/failed/error',
|
|
||||||
dims_total INT NOT NULL DEFAULT 14 COMMENT '维度总数',
|
|
||||||
dims_passed INT NOT NULL DEFAULT 0 COMMENT '通过维度数',
|
|
||||||
blocker_count INT NOT NULL DEFAULT 0 COMMENT 'blocker 数',
|
|
||||||
error_count INT NOT NULL DEFAULT 0 COMMENT 'error 数',
|
|
||||||
warning_count INT NOT NULL DEFAULT 0 COMMENT 'warning 数',
|
|
||||||
score DECIMAL(6,2) DEFAULT NULL COMMENT '加权得分',
|
|
||||||
quality_level VARCHAR(16) DEFAULT NULL COMMENT '产出质量等级 L0~L4',
|
|
||||||
started_at DATETIME DEFAULT NULL COMMENT '开始时间',
|
|
||||||
finished_at DATETIME DEFAULT NULL COMMENT '结束时间',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_run_code (tenant_id, run_code),
|
|
||||||
KEY idx_pbl_valid_run_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_valid_run_tid_state (tenant_id, run_state),
|
|
||||||
KEY idx_pbl_valid_run_tid_level (tenant_id, quality_level)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 校验执行批次';
|
|
||||||
|
|
||||||
-- 19. pbl_validation_finding 校验发现明细(append-only 留痕)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_validation_finding (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
finding_code VARCHAR(32) NOT NULL COMMENT '发现业务主键',
|
|
||||||
run_code VARCHAR(32) NOT NULL COMMENT '执行批次编码',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
dim_code VARCHAR(32) NOT NULL COMMENT '维度编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '命中子对象',
|
|
||||||
severity VARCHAR(16) NOT NULL DEFAULT 'error' COMMENT '严重度',
|
|
||||||
finding_msg VARCHAR(1024) DEFAULT NULL COMMENT '问题描述',
|
|
||||||
suggestion VARCHAR(1024) DEFAULT NULL COMMENT '修复建议',
|
|
||||||
evidence_json TEXT DEFAULT NULL COMMENT '证据 JSON',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '产生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_valid_finding_code (tenant_id, finding_code),
|
|
||||||
KEY idx_pbl_valid_finding_tid_run (tenant_id, run_code, dim_code),
|
|
||||||
KEY idx_pbl_valid_finding_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_valid_finding_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 校验发现明细(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_compiler(4 表,M3a/M3b:确定性编译 + Game Definition)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 20. pbl_compile_job 编译作业
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_job (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '作业业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
version_no INT NOT NULL COMMENT '被编译版本号',
|
|
||||||
run_code VARCHAR(32) DEFAULT NULL COMMENT '前置校验批次(必须 passed)',
|
|
||||||
compiler_ver VARCHAR(32) NOT NULL DEFAULT 'v1' COMMENT '编译器版本',
|
|
||||||
job_state VARCHAR(16) NOT NULL DEFAULT 'queued' COMMENT '状态:queued/running/success/failed/cancelled',
|
|
||||||
input_checksum VARCHAR(64) DEFAULT NULL COMMENT '输入快照 sha256(确定性依据)',
|
|
||||||
output_checksum VARCHAR(64) DEFAULT NULL COMMENT '输出 Game Definition sha256',
|
|
||||||
gd_code VARCHAR(32) DEFAULT NULL COMMENT '产出 Game Definition 编码',
|
|
||||||
error_msg VARCHAR(1024) DEFAULT NULL COMMENT '错误信息',
|
|
||||||
started_at DATETIME DEFAULT NULL,
|
|
||||||
finished_at DATETIME DEFAULT NULL,
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_job_code (tenant_id, job_code),
|
|
||||||
KEY idx_pbl_compile_job_tid_bp (tenant_id, bp_code, create_time),
|
|
||||||
KEY idx_pbl_compile_job_tid_state (tenant_id, job_state),
|
|
||||||
KEY idx_pbl_compile_job_tid_ick (tenant_id, input_checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 编译作业';
|
|
||||||
|
|
||||||
-- 21. pbl_compile_artifact 编译中间产物
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_artifact (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
artifact_code VARCHAR(32) NOT NULL COMMENT '产物业务主键',
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '编译作业编码',
|
|
||||||
stage_code VARCHAR(32) NOT NULL COMMENT '编译阶段:normalize/resolve/link/optimize/emit',
|
|
||||||
artifact_kind VARCHAR(32) NOT NULL COMMENT '产物种类:node_set/edge_set/rule_set/asset_map/gd_draft',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT '产物正文 JSON',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '阶段排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_artifact_code (tenant_id, artifact_code),
|
|
||||||
KEY idx_pbl_compile_artifact_tid_job (tenant_id, job_code, sort_no),
|
|
||||||
KEY idx_pbl_compile_artifact_tid_kind (tenant_id, artifact_kind)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 编译中间产物';
|
|
||||||
|
|
||||||
-- 22. pbl_game_definition Game Definition(编译最终产出)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_game_definition (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
gd_code VARCHAR(32) NOT NULL COMMENT 'Game Definition 业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '来源蓝图',
|
|
||||||
version_no INT NOT NULL COMMENT '来源蓝图版本',
|
|
||||||
gd_version INT NOT NULL DEFAULT 1 COMMENT 'GD 自身版本',
|
|
||||||
schema_ver VARCHAR(16) NOT NULL DEFAULT 'gd-v1' COMMENT 'GD schema 版本',
|
|
||||||
body LONGTEXT DEFAULT NULL COMMENT 'Game Definition 全文 JSON(schema 校验通过)',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256(同输入同输出)',
|
|
||||||
world_ref VARCHAR(64) DEFAULT NULL COMMENT '引用世界编码(只读)',
|
|
||||||
scene_ref VARCHAR(64) DEFAULT NULL COMMENT '引用场景编码(只读)',
|
|
||||||
script_ref VARCHAR(64) DEFAULT NULL COMMENT '引用脚本编码(只读)',
|
|
||||||
gd_state VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/validated/published/archived',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_gd_code (tenant_id, gd_code),
|
|
||||||
UNIQUE KEY uk_pbl_gd_bp_ver (tenant_id, bp_code, version_no, gd_version),
|
|
||||||
KEY idx_pbl_gd_tid_state (tenant_id, gd_state),
|
|
||||||
KEY idx_pbl_gd_tid_ck (tenant_id, checksum),
|
|
||||||
KEY idx_pbl_gd_tid_world (tenant_id, world_ref, scene_ref)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL Game Definition';
|
|
||||||
|
|
||||||
-- 23. pbl_compile_log 编译日志(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_compile_log (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
log_code VARCHAR(32) NOT NULL COMMENT '日志业务主键',
|
|
||||||
job_code VARCHAR(32) NOT NULL COMMENT '编译作业编码',
|
|
||||||
stage_code VARCHAR(32) DEFAULT NULL COMMENT '阶段',
|
|
||||||
log_level VARCHAR(16) NOT NULL DEFAULT 'info' COMMENT '级别:debug/info/warn/error',
|
|
||||||
log_msg VARCHAR(2048) DEFAULT NULL COMMENT '日志正文',
|
|
||||||
context_json TEXT DEFAULT NULL COMMENT '上下文 JSON',
|
|
||||||
seq_no BIGINT NOT NULL DEFAULT 0 COMMENT '作业内单调序号',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '记录时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_compile_log_code (tenant_id, log_code),
|
|
||||||
KEY idx_pbl_compile_log_tid_job (tenant_id, job_code, seq_no),
|
|
||||||
KEY idx_pbl_compile_log_tid_level (tenant_id, log_level, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 编译日志(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_agent_runtime(4 表,M4a/M4b:Designer/Critic + fail-closed 裁决)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 24. pbl_agent_session Agent 会话
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_session (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话业务主键',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '关联蓝图',
|
|
||||||
agent_role VARCHAR(32) NOT NULL COMMENT 'Agent 角色:designer/critic(appcodes_kv: pbl_agent_role)',
|
|
||||||
model_name VARCHAR(64) DEFAULT NULL COMMENT '使用的平台模型注册名',
|
|
||||||
session_state VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/paused/closed/error',
|
|
||||||
owner_code VARCHAR(32) DEFAULT NULL COMMENT '发起用户',
|
|
||||||
turn_count INT NOT NULL DEFAULT 0 COMMENT '轮次数',
|
|
||||||
tool_call_count INT NOT NULL DEFAULT 0 COMMENT '工具调用总数',
|
|
||||||
tool_deny_count INT NOT NULL DEFAULT 0 COMMENT '被拒绝调用数',
|
|
||||||
context_json LONGTEXT DEFAULT NULL COMMENT '会话上下文 JSON',
|
|
||||||
started_at DATETIME DEFAULT NULL,
|
|
||||||
closed_at DATETIME DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_session_code (tenant_id, session_code),
|
|
||||||
KEY idx_pbl_agent_session_tid_bp (tenant_id, bp_code, session_state),
|
|
||||||
KEY idx_pbl_agent_session_tid_role (tenant_id, agent_role),
|
|
||||||
KEY idx_pbl_agent_session_tid_owner (tenant_id, owner_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL Agent 会话';
|
|
||||||
|
|
||||||
-- 25. pbl_agent_message Agent 消息(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_message (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
msg_code VARCHAR(32) NOT NULL COMMENT '消息业务主键',
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话编码',
|
|
||||||
turn_no INT NOT NULL DEFAULT 0 COMMENT '轮次号',
|
|
||||||
role VARCHAR(16) NOT NULL COMMENT '角色:system/user/assistant/tool',
|
|
||||||
content LONGTEXT DEFAULT NULL COMMENT '消息正文',
|
|
||||||
token_in INT NOT NULL DEFAULT 0 COMMENT '输入 token',
|
|
||||||
token_out INT NOT NULL DEFAULT 0 COMMENT '输出 token',
|
|
||||||
latency_ms INT NOT NULL DEFAULT 0 COMMENT '耗时毫秒',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '产生时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_msg_code (tenant_id, msg_code),
|
|
||||||
KEY idx_pbl_agent_msg_tid_sess (tenant_id, session_code, turn_no),
|
|
||||||
KEY idx_pbl_agent_msg_tid_time (tenant_id, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL Agent 消息(append-only)';
|
|
||||||
|
|
||||||
-- 26. pbl_agent_tool_call 工具调用记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_tool_call (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
call_code VARCHAR(32) NOT NULL COMMENT '调用业务主键',
|
|
||||||
session_code VARCHAR(32) NOT NULL COMMENT '会话编码',
|
|
||||||
turn_no INT NOT NULL DEFAULT 0 COMMENT '轮次号',
|
|
||||||
tool_name VARCHAR(64) NOT NULL COMMENT '工具名(13 启用 / 9 禁用白名单外)',
|
|
||||||
tool_state VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态:pending/allowed/executed/denied/error',
|
|
||||||
params_json LONGTEXT DEFAULT NULL COMMENT '入参 JSON',
|
|
||||||
result_json LONGTEXT DEFAULT NULL COMMENT '返回 JSON',
|
|
||||||
latency_ms INT NOT NULL DEFAULT 0 COMMENT '耗时毫秒',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '调用时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_call_code (tenant_id, call_code),
|
|
||||||
KEY idx_pbl_agent_call_tid_sess (tenant_id, session_code, turn_no),
|
|
||||||
KEY idx_pbl_agent_call_tid_tool (tenant_id, tool_name, tool_state),
|
|
||||||
KEY idx_pbl_agent_call_tid_time (tenant_id, create_time)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL Agent 工具调用(append-only)';
|
|
||||||
|
|
||||||
-- 27. pbl_agent_arbitration fail-closed 裁决记录(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_agent_arbitration (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
arb_code VARCHAR(32) NOT NULL COMMENT '裁决业务主键',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '会话编码',
|
|
||||||
call_code VARCHAR(32) DEFAULT NULL COMMENT '关联调用编码',
|
|
||||||
tool_name VARCHAR(64) NOT NULL COMMENT '被裁决工具名',
|
|
||||||
decision VARCHAR(16) NOT NULL DEFAULT 'deny' COMMENT '裁决:allow/deny(默认 deny,fail-closed)',
|
|
||||||
deny_reason VARCHAR(32) NOT NULL DEFAULT 'not_in_whitelist' COMMENT '拒绝原因:not_in_whitelist/invalid_params/missing_tenant_context/write_protected/quota_exceeded',
|
|
||||||
rule_ref VARCHAR(64) DEFAULT NULL COMMENT '命中规则引用',
|
|
||||||
detail_json TEXT DEFAULT NULL COMMENT '裁决详情 JSON',
|
|
||||||
arbiter VARCHAR(32) NOT NULL DEFAULT 'system' COMMENT '裁决者',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '裁决时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_agent_arb_code (tenant_id, arb_code),
|
|
||||||
KEY idx_pbl_agent_arb_tid_tool (tenant_id, tool_name, decision),
|
|
||||||
KEY idx_pbl_agent_arb_tid_sess (tenant_id, session_code, create_time),
|
|
||||||
KEY idx_pbl_agent_arb_tid_reason (tenant_id, deny_reason)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL Agent fail-closed 裁决记录(append-only)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_evidence(4 表,M5a/M5b:产出物采集 + 证据幂等)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 28. pbl_artifact 学生产出物
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_artifact (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
artifact_code VARCHAR(32) NOT NULL COMMENT '产出物业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '运行会话编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '对应产出物规格子对象',
|
|
||||||
artifact_name VARCHAR(128) NOT NULL COMMENT '产出物名称',
|
|
||||||
artifact_kind VARCHAR(32) NOT NULL COMMENT '种类:doc/image/model/audio/video/code/log/chat(appcodes_kv: pbl_artifact_kind)',
|
|
||||||
storage_uri VARCHAR(512) DEFAULT NULL COMMENT '存储 URI',
|
|
||||||
mime_type VARCHAR(64) DEFAULT NULL COMMENT 'MIME',
|
|
||||||
size_bytes BIGINT NOT NULL DEFAULT 0 COMMENT '字节数',
|
|
||||||
checksum VARCHAR(64) DEFAULT NULL COMMENT 'sha256',
|
|
||||||
content_text LONGTEXT DEFAULT NULL COMMENT '文本类正文(可索引)',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/archived/deleted',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_artifact_code (tenant_id, artifact_code),
|
|
||||||
KEY idx_pbl_artifact_tid_bp (tenant_id, bp_code, artifact_kind),
|
|
||||||
KEY idx_pbl_artifact_tid_team (tenant_id, team_code),
|
|
||||||
KEY idx_pbl_artifact_tid_sess (tenant_id, session_code),
|
|
||||||
KEY idx_pbl_artifact_tid_ck (tenant_id, checksum)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 学生产出物';
|
|
||||||
|
|
||||||
-- 29. pbl_evidence 证据主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
evidence_code VARCHAR(32) NOT NULL COMMENT '证据业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
artifact_code VARCHAR(32) DEFAULT NULL COMMENT '关联产出物',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '关联子对象(学习目标/任务)',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
student_code VARCHAR(32) DEFAULT NULL COMMENT '学生编码',
|
|
||||||
evidence_type VARCHAR(32) NOT NULL COMMENT '证据类型:artifact/event/dialogue/state_snapshot/metric(appcodes_kv: pbl_evidence_type)',
|
|
||||||
idem_key VARCHAR(128) NOT NULL COMMENT '幂等键(tenant+bp+type+source+hash)',
|
|
||||||
payload LONGTEXT DEFAULT NULL COMMENT '证据载荷 JSON',
|
|
||||||
occurred_at DATETIME DEFAULT NULL COMMENT '证据发生时间',
|
|
||||||
trust_level VARCHAR(16) NOT NULL DEFAULT 'normal' COMMENT '可信度:high/normal/low',
|
|
||||||
anonymized TINYINT NOT NULL DEFAULT 0 COMMENT '是否已匿名化 0否1是',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/invalidated',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_code (tenant_id, evidence_code),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem (tenant_id, idem_key),
|
|
||||||
KEY idx_pbl_evidence_tid_bp (tenant_id, bp_code, evidence_type),
|
|
||||||
KEY idx_pbl_evidence_tid_team (tenant_id, team_code, student_code),
|
|
||||||
KEY idx_pbl_evidence_tid_obj (tenant_id, obj_code),
|
|
||||||
KEY idx_pbl_evidence_tid_time (tenant_id, occurred_at)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 证据主表(幂等键唯一)';
|
|
||||||
|
|
||||||
-- 30. pbl_evidence_idem 证据幂等提交台账(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence_idem (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
idem_code VARCHAR(32) NOT NULL COMMENT '幂等台账业务主键',
|
|
||||||
idem_key VARCHAR(128) NOT NULL COMMENT '幂等键',
|
|
||||||
evidence_code VARCHAR(32) DEFAULT NULL COMMENT '首次落库证据编码',
|
|
||||||
submit_count INT NOT NULL DEFAULT 1 COMMENT '累计提交次数(重复提交只增此计数,不新增证据行)',
|
|
||||||
last_payload_hash VARCHAR(64) DEFAULT NULL COMMENT '最近一次载荷 hash',
|
|
||||||
conflict_flag TINYINT NOT NULL DEFAULT 0 COMMENT '是否载荷冲突 0否1是',
|
|
||||||
source_channel VARCHAR(32) DEFAULT NULL COMMENT '来源渠道:runtime/manual/agent/import',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '首次提交时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem_code (tenant_id, idem_code),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_idem_key (tenant_id, idem_key),
|
|
||||||
KEY idx_pbl_evidence_idem_tid_time (tenant_id, create_time),
|
|
||||||
KEY idx_pbl_evidence_idem_tid_conf (tenant_id, conflict_flag)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 证据幂等台账(append-only)';
|
|
||||||
|
|
||||||
-- 31. pbl_evidence_binding 证据与复用域对象绑定(零改表关联)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_evidence_binding (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bind_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
evidence_code VARCHAR(32) NOT NULL COMMENT '证据编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '复用域模块:world/scene/entity/script_engine/scense_runtime/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '复用域表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '复用域行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '复用域业务编码',
|
|
||||||
bind_role VARCHAR(32) NOT NULL DEFAULT 'source' COMMENT '绑定角色:source/target/context',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_evidence_binding_code (tenant_id, bind_code),
|
|
||||||
KEY idx_pbl_evidence_binding_tid_ev (tenant_id, evidence_code),
|
|
||||||
KEY idx_pbl_evidence_binding_tid_ref (tenant_id, ref_module, ref_table, ref_biz_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 证据与复用域绑定(零改表)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_assessment(3 表,M6:Rubric 加权评估与报告)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 32. pbl_rubric 量规主表
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_rubric (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规业务主键',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '关联蓝图(空=通用量规)',
|
|
||||||
rubric_name VARCHAR(128) NOT NULL COMMENT '量规名称',
|
|
||||||
level_count INT NOT NULL DEFAULT 5 COMMENT '等级数(默认 5 级)',
|
|
||||||
total_weight DECIMAL(8,3) NOT NULL DEFAULT 100.000 COMMENT '总权重',
|
|
||||||
pass_score DECIMAL(6,2) NOT NULL DEFAULT 60.00 COMMENT '及格分',
|
|
||||||
grade_rule TEXT DEFAULT NULL COMMENT '等级映射规则 JSON',
|
|
||||||
is_default TINYINT NOT NULL DEFAULT 0 COMMENT '是否默认量规 0否1是',
|
|
||||||
version_no INT NOT NULL DEFAULT 1 COMMENT '版本',
|
|
||||||
status VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/disabled',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_rubric_code (tenant_id, rubric_code),
|
|
||||||
KEY idx_pbl_rubric_tid_bp (tenant_id, bp_code, status),
|
|
||||||
KEY idx_pbl_rubric_tid_default (tenant_id, is_default)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 量规主表';
|
|
||||||
|
|
||||||
-- 33. pbl_rubric_criterion 量规评价维度条目
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_rubric_criterion (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
crit_code VARCHAR(32) NOT NULL COMMENT '条目业务主键',
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规编码',
|
|
||||||
obj_code VARCHAR(32) DEFAULT NULL COMMENT '关联学习目标子对象',
|
|
||||||
crit_name VARCHAR(128) NOT NULL COMMENT '维度名称',
|
|
||||||
crit_desc VARCHAR(1024) DEFAULT NULL COMMENT '维度说明',
|
|
||||||
weight DECIMAL(6,3) NOT NULL DEFAULT 1.000 COMMENT '权重',
|
|
||||||
level_desc TEXT DEFAULT NULL COMMENT '各等级描述 JSON(level -> desc)',
|
|
||||||
max_score DECIMAL(6,2) NOT NULL DEFAULT 5.00 COMMENT '最高分',
|
|
||||||
evidence_required TINYINT NOT NULL DEFAULT 1 COMMENT '是否必须有证据支撑 0否1是',
|
|
||||||
sort_no INT NOT NULL DEFAULT 0 COMMENT '排序',
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_rubric_crit_code (tenant_id, crit_code),
|
|
||||||
KEY idx_pbl_rubric_crit_tid_rub (tenant_id, rubric_code, sort_no),
|
|
||||||
KEY idx_pbl_rubric_crit_tid_obj (tenant_id, obj_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 量规评价维度';
|
|
||||||
|
|
||||||
-- 34. pbl_assessment_report 评估报告
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_assessment_report (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
report_code VARCHAR(32) NOT NULL COMMENT '报告业务主键',
|
|
||||||
bp_code VARCHAR(32) NOT NULL COMMENT '蓝图编码',
|
|
||||||
rubric_code VARCHAR(32) NOT NULL COMMENT '量规编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
student_code VARCHAR(32) DEFAULT NULL COMMENT '学生编码(团队报告时为空)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'team' COMMENT '范围:team/student/blueprint',
|
|
||||||
score_total DECIMAL(6,2) NOT NULL DEFAULT 0.00 COMMENT '加权总分(可复算)',
|
|
||||||
grade_code VARCHAR(16) DEFAULT NULL COMMENT '等级 A/B/C/D/E',
|
|
||||||
detail_json LONGTEXT DEFAULT NULL COMMENT '逐维度得分明细 JSON(含证据引用)',
|
|
||||||
evidence_count INT NOT NULL DEFAULT 0 COMMENT '引用证据数',
|
|
||||||
missing_evidence_count INT NOT NULL DEFAULT 0 COMMENT '缺证据维度数',
|
|
||||||
assessor_type VARCHAR(16) NOT NULL DEFAULT 'agent' COMMENT '评估者:agent/user/mixed',
|
|
||||||
assessor_code VARCHAR(32) DEFAULT NULL COMMENT '评估者编码',
|
|
||||||
report_state VARCHAR(16) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/published/archived',
|
|
||||||
calc_checksum VARCHAR(64) DEFAULT NULL COMMENT '复算校验 sha256',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_assess_report_code (tenant_id, report_code),
|
|
||||||
KEY idx_pbl_assess_report_tid_bp (tenant_id, bp_code, scope_type),
|
|
||||||
KEY idx_pbl_assess_report_tid_team (tenant_id, team_code, student_code),
|
|
||||||
KEY idx_pbl_assess_report_tid_state (tenant_id, report_state),
|
|
||||||
KEY idx_pbl_assess_report_tid_grade (tenant_id, grade_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 评估报告';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_domain_ext(1 表,M8:world/scene/entity 租户·班级·团队关联,不改基表)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 35. pbl_domain_binding 复用域对象绑定(零改表唯一关联落点)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_domain_binding (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
bind_code VARCHAR(32) NOT NULL COMMENT '绑定业务主键',
|
|
||||||
bind_kind VARCHAR(32) NOT NULL COMMENT '绑定种类:tenant_world/class_scene/team_entity/bp_script/session_world(appcodes_kv: pbl_bind_kind)',
|
|
||||||
class_code VARCHAR(32) DEFAULT NULL COMMENT '班级编码',
|
|
||||||
team_code VARCHAR(32) DEFAULT NULL COMMENT '团队编码',
|
|
||||||
bp_code VARCHAR(32) DEFAULT NULL COMMENT '蓝图编码',
|
|
||||||
session_code VARCHAR(32) DEFAULT NULL COMMENT '运行会话编码',
|
|
||||||
ref_module VARCHAR(32) NOT NULL COMMENT '复用域模块:world/scene/entity/script_engine/scense_runtime/scense/scense_game',
|
|
||||||
ref_table VARCHAR(64) NOT NULL COMMENT '复用域表名',
|
|
||||||
ref_id BIGINT DEFAULT NULL COMMENT '复用域行 id',
|
|
||||||
ref_biz_code VARCHAR(64) DEFAULT NULL COMMENT '复用域业务编码',
|
|
||||||
access_mode VARCHAR(16) NOT NULL DEFAULT 'readonly' COMMENT '访问模式:readonly(Phase 0/1 唯一合法值,写保护)',
|
|
||||||
bind_state VARCHAR(16) NOT NULL DEFAULT 'active' COMMENT '状态:active/released',
|
|
||||||
extra_json TEXT DEFAULT NULL COMMENT '扩展 JSON',
|
|
||||||
create_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
create_time DATETIME DEFAULT NULL,
|
|
||||||
update_by VARCHAR(32) DEFAULT NULL,
|
|
||||||
update_time DATETIME DEFAULT NULL,
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_domain_binding_code (tenant_id, bind_code),
|
|
||||||
UNIQUE KEY uk_pbl_domain_binding_ref (tenant_id, bind_kind, ref_module, ref_table, ref_biz_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_class (tenant_id, class_code, team_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_bp (tenant_id, bp_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_sess (tenant_id, session_code),
|
|
||||||
KEY idx_pbl_domain_binding_tid_ref (tenant_id, ref_module, ref_id)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 复用域绑定(零改表)';
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 模块 pbl_analytics(1 表,M10:分析看板快照,append-only)
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
-- 36. pbl_analytics_snapshot 分析快照(append-only)
|
|
||||||
CREATE TABLE IF NOT EXISTS pbl_analytics_snapshot (
|
|
||||||
tenant_id VARCHAR(32) NOT NULL COMMENT '租户ID',
|
|
||||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
||||||
snap_code VARCHAR(32) NOT NULL COMMENT '快照业务主键',
|
|
||||||
metric_group VARCHAR(32) NOT NULL COMMENT '指标组:blueprint/validation/compile/runtime/evidence/assessment(appcodes_kv: pbl_metric_group)',
|
|
||||||
scope_type VARCHAR(16) NOT NULL DEFAULT 'tenant' COMMENT '范围:tenant/class/team/blueprint',
|
|
||||||
scope_code VARCHAR(32) DEFAULT NULL COMMENT '范围对象编码',
|
|
||||||
period_key VARCHAR(16) NOT NULL COMMENT '周期键:2026-09 / 2026-W38 / 2026-09-15',
|
|
||||||
metrics_json LONGTEXT DEFAULT NULL COMMENT '指标 JSON(计数/均值/分布)',
|
|
||||||
anonymized TINYINT NOT NULL DEFAULT 1 COMMENT '是否匿名聚合 0否1是(KDB 侧强制 1)',
|
|
||||||
sample_size INT NOT NULL DEFAULT 0 COMMENT '样本量',
|
|
||||||
computed_at DATETIME DEFAULT NULL COMMENT '计算时间',
|
|
||||||
create_time DATETIME DEFAULT NULL COMMENT '入库时间(append-only,无 update_time)',
|
|
||||||
PRIMARY KEY (id),
|
|
||||||
UNIQUE KEY uk_pbl_analytics_snap_code (tenant_id, snap_code),
|
|
||||||
UNIQUE KEY uk_pbl_analytics_snap_dim (tenant_id, metric_group, scope_type, scope_code, period_key),
|
|
||||||
KEY idx_pbl_analytics_snap_tid_group (tenant_id, metric_group, period_key),
|
|
||||||
KEY idx_pbl_analytics_snap_tid_scope (tenant_id, scope_type, scope_code)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PBL 分析看板快照(append-only)';
|
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 1;
|
|
||||||
|
|
||||||
-- =====================================================================
|
|
||||||
-- 契约自检(供 build.sh / QC grep 核验)
|
|
||||||
-- 表总数:37(pbl_governance 3 + pbl_blueprint 11 + pbl_template 2 + pbl_validation 3
|
|
||||||
-- + pbl_compiler 5 + pbl_agent_runtime 4 + pbl_evidence 4 + pbl_assessment 3
|
|
||||||
-- + pbl_domain_ext 1 + pbl_analytics 1 = 37)
|
|
||||||
-- 注:pbl_compiler 由 4 增至 5(新增 pbl_compile_task),build.sh 门禁 6.4 表数断言同步 36→37
|
|
||||||
-- append-only 表(11,无 update_time):
|
|
||||||
-- pbl_audit_log / pbl_blueprint_version / pbl_subobject_review / pbl_blueprint_publish
|
|
||||||
-- pbl_validation_finding / pbl_compile_log / pbl_agent_message / pbl_agent_tool_call
|
|
||||||
-- pbl_agent_arbitration / pbl_evidence_idem / pbl_analytics_snapshot
|
|
||||||
-- 零命中项(mariadb 方言硬约束):外键约束、外键引用子句、枚举列类型、
|
|
||||||
-- 自增序列类型(PG 风格)与序列取值函数、原生时间戳列类型 —— 一律禁用。
|
|
||||||
-- ⚠ 本块刻意**不逐字写出**上述 SQL 关键字:旧版 build.sh 门禁 6.4 直接对 DDL 原文
|
|
||||||
-- grep,这段说明文字自身就会触发假阳性 die(deploy_test 第 3 轮 Q4 实测)。
|
|
||||||
-- 现门禁改为 scripts/ddl_gate_check.py「先剥注释 + 字符串字面量(含 COMMENT 串),
|
|
||||||
-- 再在纯 DDL 正文上扫描」,注释不再参与判定;此处仍保持不逐字罗列,双重保险。
|
|
||||||
-- 全部索引以 tenant_id 打头;每表 id BIGINT NOT NULL AUTO_INCREMENT + xxx_code 唯一业务主键。
|
|
||||||
-- =====================================================================
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS `pbl_compile_task` (
|
|
||||||
`tenant_id` VARCHAR(64) NOT NULL COMMENT 租户ID(强制打头),
|
|
||||||
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键,
|
|
||||||
`task_no` VARCHAR(64) NOT NULL COMMENT "任务流水号(确定性派生 CT+blueprint_id+version+seq)",
|
|
||||||
`blueprint_id` BIGINT UNSIGNED NOT NULL COMMENT "蓝图ID",
|
|
||||||
`blueprint_version` INT NOT NULL DEFAULT 0 COMMENT "蓝图版本(须已审批)",
|
|
||||||
`compiler_version` VARCHAR(32) NOT NULL COMMENT "编译器版本号",
|
|
||||||
`ruleset_version` VARCHAR(32) NOT NULL COMMENT "规则集版本",
|
|
||||||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT "appcodes:pbl_compile_status(pending/running/success/failed)",
|
|
||||||
`approval_id` BIGINT UNSIGNED NULL COMMENT "关联审批记录(未审批拒绝 F-CP-01)",
|
|
||||||
`game_def_id` BIGINT UNSIGNED NULL COMMENT "产出的 Game Definition ID(success 回填)",
|
|
||||||
`content_fingerprint` VARCHAR(64) NULL COMMENT "产物 SHA-256 指纹(success 回填,US-11 比对锚点)",
|
|
||||||
`error_code` VARCHAR(64) NULL COMMENT "失败错误码(PBL_E_*)",
|
|
||||||
`error_msg` VARCHAR(2048) NULL COMMENT "失败原因(fail-closed 可追溯)",
|
|
||||||
`triggered_by` VARCHAR(64) NULL COMMENT "触发人(审计)",
|
|
||||||
`duration_ms` INT NOT NULL DEFAULT 0 COMMENT "耗时(易变,不参与指纹)",
|
|
||||||
`started_at` DATETIME NULL COMMENT "开始时间",
|
|
||||||
`finished_at` DATETIME NULL COMMENT "结束时间",
|
|
||||||
`created_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "创建时间(应用层写入)",
|
|
||||||
`updated_at` DATETIME NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT "更新时间(应用层写入)",
|
|
||||||
PRIMARY KEY (`id`),
|
|
||||||
UNIQUE KEY `uk_ct_task_no` (`tenant_id`, `task_no`),
|
|
||||||
KEY `idx_ct_blueprint` (`tenant_id`, `blueprint_id`, `blueprint_version`),
|
|
||||||
KEY `idx_ct_status` (`tenant_id`, `status`)
|
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT="编译任务(approval_id 门禁,F-CP-01/US-10)";
|
|
||||||
Loading…
x
Reference in New Issue
Block a user