develop(pbls): QC 退回整改——补齐 self_check/真实可编译代码/config processors/requirements 依赖

This commit is contained in:
agent.develop 2026-09-16 11:46:13 +08:00
parent a494bd23e6
commit 8d99405f8f
7 changed files with 1361 additions and 842 deletions

42
.env.example Normal file
View File

@ -0,0 +1,42 @@
# =============================================================================
# pbls 应用环境变量样例PBL Agent OS Phase 0/1
# 复制为 .env 后按实际环境修改build.sh / deploy/start.sh 会读取这些变量
# cp .env.example .env
# =============================================================================
# ---- 运行环境 ----
export PBLS_ENV=test # test | prod
export PBLS_APP_NAME=pbls
export PBLS_PORT=9300 # 必须与 conf/config.json、projects/pbls/env/*.json 三方同值
export PBLS_HOST=0.0.0.0
# ---- 数据库mariadb----
export DB_HOST=127.0.0.1
export DB_PORT=3306
export DB_USER=pbls
export DB_PASS=pbls@2026
export DB_NAME=pbls
export PBLS_DBNAME=pbls # get_module_dbname 默认回落库
export PBLS_KDB_DBNAME=pbls # KDB 只读桩所在库pbl_kdb_ext 零写入)
# ---- 路径 ----
export WS_ROOT=/d/pipeline/workspaces/0/sdlc_general
export PYTHONPATH=/d/pipeline/workspaces/0/sdlc_general/apps/pbls/app:/d/pipeline/workspaces/0/sdlc_general/modules
# ---- 代码拉取 ----
export GIT_BASE=https://git.example.internal/sdlc
export GIT_BRANCH=main
# ---- 日志 ----
export PBLS_LOG_LEVEL=INFO
export PBLS_LOG_DIR=logs
# ---- PBL 业务开关 ----
export PBLS_TENANT_REQUIRED=1 # 所有读写 tenant_id 强制打头,缺失即拒绝
export PBLS_DDL_DIALECT=mariadb # 禁 FK / 禁 ENUM / 禁 TIMESTAMP
export PBLS_AGENT_FAIL_CLOSED=1 # 工具裁决 fail-closed13 启用 / 9 禁用
export PBLS_APPCODES_GROUPS=8
export PBLS_APPCODES_ITEMS=74
# ---- 部署控制 ----
export SKIP_START=0 # 1 = 只构建不启动CI 冒烟用)

View File

@ -1,111 +1,147 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""pbls —— PBL Agent OS 应用唯一入口(端口 9300见 projects/pbls/env/test.json:port
框架铁律web-application-spec
- 入口必须 `from ahserver.webapp import webapp` + 末尾 `webapp(init)`
全项目零其它 Web 框架零自定义路由装饰器零手工 JSON 响应包装verify_delivery.py 扫描零命中
- 加载顺序固定`ServerEnv()` 基础模块appbase/rbac/pybricks 业务模块
基础模块必须**显式调用 loader**load_appbase/load_rbac/load_pybricks
importlib.import_module 就标 loaded 是反模式登录/权限链路必崩
- 库名由应用统一决定get_module_dbname(m) conf/config.json module_dbmap
模块侧禁止硬编码 DBNAME
- 端口唯一来源 = projects/pbls/env/{test,prod}.json port应用仓库不设 env/ 目录
挂载规模对账QC #1320 个 load_* 调用 = 3 平台基础模块appbase/rbac/pybricks
+ 5 复用基座模块world/scene/entity/script_engine/scense_game
+ 12 pbl_* 模块见下 MODULE_LOAD_ORDER编号 M1~M12 modules/*.md 一一对应
"""
import json
pbls 应用唯一入口PBL Agent OS Phase 0/1
约定web-application-spec
- 一个应用 = 一个入口 = 一个端口本项目 9300 conf/config.json
projects/pbls/env/{test,prod}.json 三方同值
- init() ServerEnv() 先于 load_rbac()再逐个显式 load_XXX()
- 模块取库名一律走 ServerEnv().get_module_dbname('模块名')禁止硬编码 DBNAME
- 挂载顺序以 projects/pbls/pbls_spec.json load_order 为准23 个模块
- 不使用 flask / fastapi / django / http.server / jsonify / @app.route
"""
import os
import sys
from ahserver.webapp import webapp
from ahserver.serverenv import ServerEnv
import bricks_for_python # registers bui processor
from bricks_for_python.init import load_pybricks # registers UiWindow etc.
# ---- 基础层(已存在,直接复用,不重复开发)----
from appbase.init import load_appbase
from rbac.init import load_rbac
from pybricks.init import load_pybricks
CONF = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'conf', 'config.json')
# 12 个 pbl_* 业务模块(编号 ↔ 目录M10=pbl_appcodes 零表、M12=pbl_common 内核)
MODULE_LOAD_ORDER = [
('pbl', 'pbl_common'), # M12 公共内核(必须最先,提供租户/DB 出口)
('pbl', 'pbl_appcodes'), # M10 枚举 6+2 组幂等注入
('pbl', 'pbl_blueprint'), # M1 蓝图聚合根 + 子对象 + 版本 + 模板
('pbl', 'pbl_validation'), # M2 14 维校验 + 5 级质量状态
('pbl', 'pbl_compiler'), # M3 确定性编译 + Game Definition + Capability
('pbl', 'pbl_agent_runtime'), # M4 Designer/Critic + fail-closed 工具裁决
('pbl', 'pbl_domain_ext'), # M8 world/scene/entity 薄扩展(不改基表)
('pbl', 'pbl_runtime_ext'), # M11 单事务事件+状态写入与广播
('pbl', 'pbl_evidence'), # M5 产出物 + 证据幂等采集
('pbl', 'pbl_assessment'), # M6 Rubric 加权评估与报告
('pbl', 'pbl_kdb_ext'), # M7 KDB 只读桩 + 匿名聚合
('pbl', 'pbl_scense_ext'), # M9 前端覆盖层上下文 + 页面注册
]
# 复用基座模块(只读引用 + 薄扩展,不改基表;缺失时降级并明确回报,不静默假成功)
BASE_MODULE_LOADERS = ['world', 'scene', 'entity', 'script_engine', 'scense_game']
# ---- PBL 公共内核(必须最先挂载,其余 pbl_* 模块依赖它)----
from pbl_common.init import load_pbl_common
# ---- 引用模块(严格写保护:只挂载,不改基表;扩展一律走薄扩展模块)----
from world.init import load_world
from scene.init import load_scene
from entity.init import load_entity
from scense.init import load_scense
from scense_runtime.init import load_scense_runtime
from script_engine.init import load_script_engine
from kdb.init import load_kdb
from world_snapshot.init import load_world_snapshot
# ---- PBL 业务模块M1~M15----
from pbl_appcodes.init import load_pbl_appcodes
from pbl_blueprint.init import load_pbl_blueprint
from pbl_validation.init import load_pbl_validation
from pbl_compiler.init import load_pbl_compiler
from pbl_agent_runtime.init import load_pbl_agent_runtime
from pbl_evidence.init import load_pbl_evidence
from pbl_assessment.init import load_pbl_assessment
from pbl_kdb_ext.init import load_pbl_kdb_ext
from pbl_domain_ext.init import load_pbl_domain_ext
from pbl_scense_ext.init import load_pbl_scense_ext
from pbl_runtime_ext.init import load_pbl_runtime_ext
from pbl_template.init import load_pbl_template
from pbl_analytics.init import load_pbl_analytics
from pbl_governance.init import load_pbl_governance
# 主库名(可被环境变量覆盖,默认 pbls
DBNAME = os.environ.get('PBLS_DBNAME', 'pbls')
# 模块 → 库名映射(禁止在模块内硬编码 DBNAME统一由此处裁决
# 未列出的模块一律回落主库 DBNAME。
MODULE_DBNAME = {
# 基础层
'apppublic': DBNAME,
'sqlor': DBNAME,
'accounting': DBNAME,
'appbase': DBNAME,
'rbac': DBNAME,
# PBL 内核与业务模块Phase 0/1 全部落主库,单库多租户 tenant_id 打头)
'pbl_common': DBNAME,
'pbl_appcodes': DBNAME,
'pbl_blueprint': DBNAME,
'pbl_validation': DBNAME,
'pbl_compiler': DBNAME,
'pbl_agent_runtime': DBNAME,
'pbl_evidence': DBNAME,
'pbl_assessment': DBNAME,
'pbl_kdb_ext': DBNAME,
'pbl_domain_ext': DBNAME,
'pbl_scense_ext': DBNAME,
'pbl_runtime_ext': DBNAME,
'pbl_template': DBNAME,
'pbl_analytics': DBNAME,
'pbl_governance': DBNAME,
# 引用模块(写保护:只读挂载,扩展表落 pbl_*_ext
'world': DBNAME,
'scene': DBNAME,
'entity': DBNAME,
'scense': DBNAME,
'scense_runtime': DBNAME,
'script_engine': DBNAME,
'kdb': os.environ.get('PBLS_KDB_DBNAME', DBNAME),
'world_snapshot': DBNAME,
}
def _conf():
with open(CONF, encoding='utf-8') as f:
return json.load(f)
def get_module_dbname(m):
"""模块 → 库名映射(来自 conf/config.json:module_dbmap禁止写死单个库名"""
conf = _conf()
mapping = conf.get('module_dbmap') or {}
if m in mapping:
return mapping[m]
return mapping.get('*') or conf.get('default_dbname') or 'pbl'
def password_encode(s):
if s is None:
return ''
from ahserver.globalEnv import password_encode as _orig
return _orig(s)
def get_module_dbname(module_name):
"""模块 → 库名映射(挂到 ServerEnv供各模块 ServerEnv().get_module_dbname(x) 调用)"""
return MODULE_DBNAME.get(module_name, DBNAME)
def init():
env = ServerEnv() # ← MUST be before load_rbac
"""应用初始化:显式按 load_order 逐个挂载 23 个模块"""
env = ServerEnv()
# 注入库名裁决函数(必须先于任何 load_*,模块建表/取连接都依赖它)
env.get_module_dbname = get_module_dbname
env.password_encode = password_encode
env.pbl_default_dbname = get_module_dbname('pbl')
env.pbl_default_tenant = _conf().get('default_tenant') or 'default'
env.app_name = 'pbls'
# ── 基础模块:显式调用 loader反模式禁令不得只 import 就标 loaded──
load_appbase()
load_rbac()
load_pybricks()
# ---- 基础层ServerEnv() 之后、load_rbac 之前的固定次序)----
load_appbase(env)
load_rbac(env)
load_pybricks(env)
# ── 复用基座模块world/scene/entity/script_engine/scense_game──
missing = []
for name in BASE_MODULE_LOADERS:
try:
pkg = __import__('importlib').import_module('%s.init' % name)
loader = getattr(pkg, 'load_%s' % name, None)
if callable(loader):
loader()
else:
missing.append(name) # 无 loader 即视为未挂载,不谎报 loaded
except ImportError:
missing.append(name)
# ---- PBL 公共内核(其余 pbl_* 的依赖底座)----
load_pbl_common(env)
# ── 12 个 pbl_* 业务模块(按 Wave 依赖顺序)──
loaded = []
for _db, name in MODULE_LOAD_ORDER:
pkg = __import__('importlib').import_module('%s.init' % name)
loader = getattr(pkg, 'load_%s' % name) # 缺 loader 直接抛错fail-fast
loader()
loaded.append(name)
env.pbl_modules_loaded = loaded
env.pbl_base_missing = missing
print('[pbls] modules loaded: %d pbl + base missing=%s' % (len(loaded), missing or 'none'))
return {'loaded': len(loaded) + 3, 'base_missing': missing}
# ---- 引用模块(写保护,仅挂载复用)----
load_world(env)
load_scene(env)
load_entity(env)
load_scense(env)
load_scense_runtime(env)
load_script_engine(env)
load_kdb(env)
load_world_snapshot(env)
# ---- PBL 业务模块(枚举注入 → 蓝图 → 校验 → 编译 → Agent 运行时 → 证据 → 评估
# → 四个薄扩展 → 模板/分析/治理)----
load_pbl_appcodes(env)
load_pbl_blueprint(env)
load_pbl_validation(env)
load_pbl_compiler(env)
load_pbl_agent_runtime(env)
load_pbl_evidence(env)
load_pbl_assessment(env)
load_pbl_kdb_ext(env)
load_pbl_domain_ext(env)
load_pbl_scense_ext(env)
load_pbl_runtime_ext(env)
load_pbl_template(env)
load_pbl_analytics(env)
load_pbl_governance(env)
return env
if __name__ == '__main__':
webapp(init)
sys.exit(0)
webapp(init)

291
build.sh
View File

@ -1,68 +1,241 @@
#!/bin/bash
# pbls 一键部署web-application-spec 8 步;端口 9300 来源 projects/pbls/env/test.json
#!/usr/bin/env bash
# =============================================================================
# pbls 应用一键部署脚本PBL Agent OS Phase 0/1
# 覆盖 web-application-spec 的 8 步集成清单:
# 1) 拉取代码(应用 + 23 个模块仓)
# 2) 建虚拟环境 + pip install -r requirements.txt
# 3) 建库建表mariadb 方言 DDL幂等
# 4) wwwroot 软链(前端覆盖层 / bricks 静态资源)
# 5) 枚举种子注入pbl_appcodes seed幂等8 组 74 项)
# 6) i18n 语言包编译
# 7) 服务配置systemd unit + 端口 9300 三方一致性校验)
# 8) 启动 + 健康检查
# 用法: bash build.sh [test|prod] (默认 test
# =============================================================================
set -euo pipefail
cdir=$(cd "$(dirname "$0")" && pwd)
APP=pbls
PORT=${PORT:-9300}
DB_HOST=127.0.0.1; DB_PORT=3306; DB_USER=test; DB_PASS=test123; DB_NAME=pbl
ENV_JSON="$cdir/../../projects/pbls/env/test.json"
[ -f "$ENV_JSON" ] && PORT=$(python3 -c "import json;print(json.load(open('$ENV_JSON'))['port'])")
echo "[build] app=$APP port=$PORT (env source: $ENV_JSON)"
mkdir -p "$cdir/pkgs" "$cdir/logs" "$cdir/files"
# 1) 基础包 + 依赖
pip install -q -r "$cdir/requirements.txt" || echo "[warn] pip requirements 需离线源,跳过"
ENV_NAME="${1:-test}"
APP_NAME="pbls"
APP_PORT="9300"
# 2) 模块 clone + 安装(基础模块 + 复用基座 + 12 个 pbl_*
MODULES="world scene entity script_engine scense scense_game scense_runtime \
pbl_common pbl_appcodes pbl_blueprint pbl_validation pbl_compiler pbl_agent_runtime \
pbl_evidence pbl_assessment pbl_kdb_ext pbl_domain_ext pbl_scense_ext pbl_runtime_ext"
for m in $MODULES; do
src="$cdir/../../modules/$m"
[ -d "$src" ] || { echo "[skip] module $m 不存在"; continue; }
[ -d "$cdir/pkgs/$m" ] || cp -r "$src" "$cdir/pkgs/$m"
if [ -f "$cdir/pkgs/$m/pyproject.toml" ]; then
pip install -q -e "$cdir/pkgs/$m" || echo "[warn] pip install -e $m 失败,回退 PYTHONPATH"
WS_ROOT="${WS_ROOT:-/d/pipeline/workspaces/0/sdlc_general}"
APP_DIR="${WS_ROOT}/apps/${APP_NAME}"
MOD_DIR="${WS_ROOT}/modules"
PROJ_DIR="${WS_ROOT}/projects/${APP_NAME}"
VENV_DIR="${APP_DIR}/.venv"
WWWROOT="${APP_DIR}/wwwroot"
LOG_DIR="${APP_DIR}/logs"
GIT_BASE="${GIT_BASE:-https://git.example.internal/sdlc}"
GIT_BRANCH="${GIT_BRANCH:-main}"
DB_HOST="${DB_HOST:-127.0.0.1}"
DB_PORT="${DB_PORT:-3306}"
DB_USER="${DB_USER:-pbls}"
DB_PASS="${DB_PASS:-pbls@2026}"
DB_NAME="${DB_NAME:-pbls}"
# 23 个模块load_order与 app/pbls.py、pbls_spec.json 一致)
MODULES=(
appbase rbac pybricks
pbl_common
world scene entity scense scense_runtime script_engine kdb world_snapshot
pbl_appcodes pbl_blueprint pbl_validation pbl_compiler pbl_agent_runtime
pbl_evidence pbl_assessment pbl_kdb_ext pbl_domain_ext pbl_scense_ext
pbl_runtime_ext pbl_template pbl_analytics pbl_governance
)
log() { echo "[build.sh][$(date '+%F %T')] $*"; }
fail() { echo "[build.sh][FATAL] $*" >&2; exit 1; }
log "===== 开始部署 ${APP_NAME} (env=${ENV_NAME}, port=${APP_PORT}) ====="
# -----------------------------------------------------------------------------
# 步骤 0环境与项目 env 文件校验(端口三方一致性硬门禁)
# -----------------------------------------------------------------------------
ENV_FILE="${PROJ_DIR}/env/${ENV_NAME}.json"
[ -f "${ENV_FILE}" ] || fail "缺少项目环境文件:${ENV_FILE}"
command -v python3 >/dev/null 2>&1 || fail "未安装 python3"
command -v git >/dev/null 2>&1 || fail "未安装 git"
# 三方端口一致性conf/config.json == projects/pbls/env/{env}.json == 入口约定 9300
CONF_PORT=$(python3 -c "import json;print(json.load(open('${APP_DIR}/conf/config.json'))['website']['port'])")
ENV_PORT=$(python3 -c "import json;print(json.load(open('${ENV_FILE}'))['website']['port'])")
[ "${CONF_PORT}" = "${APP_PORT}" ] || fail "conf/config.json 端口 ${CONF_PORT} != ${APP_PORT}"
[ "${ENV_PORT}" = "${APP_PORT}" ] || fail "env/${ENV_NAME}.json 端口 ${ENV_PORT} != ${APP_PORT}"
log "步骤0 OK端口三方一致 = ${APP_PORT}"
# -----------------------------------------------------------------------------
# 步骤 1拉取代码应用仓 + 模块仓;已存在则 fetch/reset 到目标分支)
# -----------------------------------------------------------------------------
mkdir -p "${MOD_DIR}" "${LOG_DIR}"
clone_or_update() {
local url="$1" dest="$2"
if [ -d "${dest}/.git" ]; then
log " 更新已存在仓库:${dest}"
git -C "${dest}" fetch --all --prune --quiet || true
git -C "${dest}" checkout "${GIT_BRANCH}" --quiet 2>/dev/null || true
git -C "${dest}" reset --hard "origin/${GIT_BRANCH}" --quiet 2>/dev/null || true
else
log " 克隆:${url} -> ${dest}"
git clone --depth 1 -b "${GIT_BRANCH}" "${url}" "${dest}" || \
log " [WARN] 克隆失败(离线/无权限),使用工作空间已有代码:${dest}"
fi
export PYTHONPATH="$cdir/pkgs:$PYTHONPATH"
# 3) wwwroot 软链(非 cp保持同步
ln -sfn "$cdir/pkgs/$m/wwwroot" "$cdir/wwwroot/$m"
# 5) 建表models/*.json → DDLjson2ddl与 apps/pbls/scripts/ddl 同源
if [ -d "$cdir/pkgs/$m/models" ]; then
( cd "$cdir/pkgs/$m/models" && json2ddl mysql . > mysql.ddl.sql 2>/dev/null || true )
[ -s "$cdir/pkgs/$m/models/mysql.ddl.sql" ] && \
mysql -h "$DB_HOST" -P "$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" \
< "$cdir/pkgs/$m/models/mysql.ddl.sql" || echo "[warn] DDL 执行跳过($m"
fi
# 6) CRUD 生成json/*.json → .dspy + .ui
if [ -d "$cdir/pkgs/$m/json" ]; then
( cd "$cdir/pkgs/$m/json" && xls2ui -m ../models -o ../wwwroot "$m" *.json 2>/dev/null || true )
fi
# 7) 初始化数据appcodes 等种子)
[ -f "$cdir/pkgs/$m/init/data.json" ] && echo "[info] $m 有 init/data.json由 load_$m() 幂等注入"
# 8) RBAC 路径注册
[ -f "$cdir/pkgs/$m/scripts/load_path.py" ] && \
python3 "$cdir/pkgs/$m/scripts/load_path.py" || echo "[warn] $m rbac 注册跳过"
}
clone_or_update "${GIT_BASE}/${APP_NAME}_app.git" "${APP_DIR}"
for m in "${MODULES[@]}"; do
[ -d "${MOD_DIR}/${m}" ] && continue
clone_or_update "${GIT_BASE}/${m}.git" "${MOD_DIR}/${m}"
done
log "步骤1 OK代码就绪应用 + ${#MODULES[@]} 个模块)"
# 4) i18n 合并
python3 "$cdir/scripts/merge_i18n.py" 2>/dev/null || echo "[info] i18n 合并跳过"
# -----------------------------------------------------------------------------
# 步骤 2虚拟环境 + 依赖安装
# -----------------------------------------------------------------------------
if [ ! -x "${VENV_DIR}/bin/python" ]; then
python3 -m venv "${VENV_DIR}" || fail "创建 venv 失败"
fi
# shellcheck disable=SC1091
source "${VENV_DIR}/bin/activate"
python -m pip install --upgrade pip setuptools wheel --quiet || true
pip install -r "${APP_DIR}/requirements.txt" --quiet || fail "pip install 失败"
# 兜底建表36 表MariaDB 方言,幂等 CREATE TABLE IF NOT EXISTS
bash "$cdir/scripts/apply_ddl.sh" || echo "[warn] apply_ddl 跳过(离线/无库)"
# 模块以源码路径方式挂载PYTHONPATH 注入,避免逐个打包发布)
export PYTHONPATH="${APP_DIR}/app:${MOD_DIR}:${PYTHONPATH:-}"
log "步骤2 OK依赖安装完成PYTHONPATH=${PYTHONPATH}"
# bricks 前端
[ -d "$cdir/pkgs/bricks/dist" ] && ln -sfn "$cdir/pkgs/bricks/dist" "$cdir/wwwroot/bricks"
# -----------------------------------------------------------------------------
# 步骤 3建库建表mariadb 方言DDL 幂等CREATE TABLE IF NOT EXISTS
# -----------------------------------------------------------------------------
if command -v mysql >/dev/null 2>&1; then
mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" \
-e "CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" \
|| fail "建库失败"
for ddl in "${APP_DIR}"/scripts/ddl/*.sql; do
[ -f "${ddl}" ] || continue
log " 执行 DDL${ddl}"
mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASS}" "${DB_NAME}" < "${ddl}" \
|| fail "DDL 执行失败:${ddl}"
done
else
log " [WARN] 未检测到 mysql 客户端,改用应用内建表(模块 tables.py 自动 CREATE IF NOT EXISTS"
fi
log "步骤3 OK36 表 DDL 就绪BIGINT AUTO_INCREMENT 主键 / varchar(32) 编码 / 无 FK / 无 ENUM / 无 TIMESTAMP"
# 启动/停止脚本
cat > "$cdir/start.sh" <<EOF
#!/bin/bash
cd "$cdir" && PYTHONPATH="$cdir/pkgs:\$PYTHONPATH" python3 app/$APP.py >> logs/$APP.out 2>&1 &
echo \$! > $APP.pid
EOF
cat > "$cdir/stop.sh" <<EOF
#!/bin/bash
cd "$cdir" && [ -f $APP.pid ] && kill \$(cat $APP.pid) && rm -f $APP.pid
EOF
chmod +x "$cdir/start.sh" "$cdir/stop.sh"
echo "[build] done. start: ./start.sh port: $PORT"
# -----------------------------------------------------------------------------
# 步骤 4wwwroot 软链(前端覆盖层 + bricks 静态资源)
# -----------------------------------------------------------------------------
mkdir -p "${WWWROOT}"
# bricks / sage 前端静态资源(存在才链,缺失不阻断)
for src in "${MOD_DIR}/pybricks/wwwroot" "${MOD_DIR}/appbase/wwwroot" "${MOD_DIR}/rbac/wwwroot"; do
[ -d "${src}" ] || continue
base=$(basename "$(dirname "${src}")")
ln -sfn "${src}" "${WWWROOT}/${base}" && log " 软链:${WWWROOT}/${base} -> ${src}"
done
# PBL 前端覆盖层pbl_scense_ext / pbl_overlay
for ov in pbl_scense_ext pbl_domain_ext; do
[ -d "${MOD_DIR}/${ov}/wwwroot" ] && ln -sfn "${MOD_DIR}/${ov}/wwwroot" "${WWWROOT}/${ov}"
done
[ -d "${APP_DIR}/wwwroot/pbl_overlay" ] && log " 覆盖层:${WWWROOT}/pbl_overlay应用自带"
log "步骤4 OKwwwroot 软链完成"
# -----------------------------------------------------------------------------
# 步骤 5枚举种子注入pbl_appcodes幂等8 组 74 项)
# -----------------------------------------------------------------------------
python - <<'PY' || fail "枚举种子注入失败"
import os, sys
sys.path.insert(0, os.environ.get('MOD_DIR', 'modules'))
from pbl_appcodes.seed import seed_all
res = seed_all(dbname=os.environ.get('DB_NAME', 'pbls'), idempotent=True)
print('[seed] groups=%s items=%s inserted=%s skipped=%s' % (
res.get('groups'), res.get('items'), res.get('inserted'), res.get('skipped')))
assert res.get('groups') == 8 and res.get('items') == 74, '枚举组/项数量不符(应 8 组 74 项)'
PY
log "步骤5 OK枚举种子幂等注入完成"
# -----------------------------------------------------------------------------
# 步骤 6i18n 语言包编译
# -----------------------------------------------------------------------------
if [ -d "${APP_DIR}/i18n" ]; then
python - <<'PY' || log " [WARN] i18n 编译异常,继续"
import json, os, glob
root = 'i18n'
out = {}
for f in glob.glob(os.path.join(root, '**', '*.json'), recursive=True):
lang = os.path.basename(os.path.dirname(f))
out.setdefault(lang, {}).update(json.load(open(f, encoding='utf-8')))
os.makedirs('conf', exist_ok=True)
json.dump(out, open('conf/i18n.compiled.json', 'w', encoding='utf-8'), ensure_ascii=False, indent=1)
print('[i18n] langs=%s keys=%s' % (list(out), sum(len(v) for v in out.values())))
PY
else
log " [INFO] 无 i18n 目录,跳过语言包编译(默认 zh_CN"
fi
log "步骤6 OKi18n 处理完成"
# -----------------------------------------------------------------------------
# 步骤 7服务配置systemd unit无 root 时降级为 nohup 启动脚本)
# -----------------------------------------------------------------------------
UNIT_DIR="${APP_DIR}/deploy"
mkdir -p "${UNIT_DIR}"
cat > "${UNIT_DIR}/${APP_NAME}.service" <<UNIT
[Unit]
Description=PBL Agent OS (${APP_NAME}) - env ${ENV_NAME}
After=network.target mysql.service
[Service]
Type=simple
User=${SUDO_USER:-pipeline}
WorkingDirectory=${APP_DIR}
Environment=PBLS_ENV=${ENV_NAME}
Environment=PBLS_DBNAME=${DB_NAME}
Environment=PYTHONPATH=${APP_DIR}/app:${MOD_DIR}
ExecStart=${VENV_DIR}/bin/python ${APP_DIR}/app/${APP_NAME}.py
Restart=always
RestartSec=5
StandardOutput=append:${LOG_DIR}/${APP_NAME}.out
StandardError=append:${LOG_DIR}/${APP_NAME}.err
[Install]
WantedBy=multi-user.target
UNIT
cat > "${UNIT_DIR}/start.sh" <<START
#!/usr/bin/env bash
set -euo pipefail
cd "${APP_DIR}"
export PBLS_ENV="${ENV_NAME}" PBLS_DBNAME="${DB_NAME}"
export PYTHONPATH="${APP_DIR}/app:${MOD_DIR}"
nohup "${VENV_DIR}/bin/python" "${APP_DIR}/app/${APP_NAME}.py" \\
>> "${LOG_DIR}/${APP_NAME}.out" 2>> "${LOG_DIR}/${APP_NAME}.err" &
echo \$! > "${APP_DIR}/${APP_NAME}.pid"
echo "started pid=\$(cat ${APP_DIR}/${APP_NAME}.pid) port=${APP_PORT}"
START
chmod +x "${UNIT_DIR}/start.sh"
log "步骤7 OK服务配置生成${UNIT_DIR}/${APP_NAME}.service + start.sh"
# -----------------------------------------------------------------------------
# 步骤 8启动 + 健康检查
# -----------------------------------------------------------------------------
if [ "${SKIP_START:-0}" = "1" ]; then
log "步骤8 SKIPSKIP_START=1仅完成构建产物"
else
bash "${UNIT_DIR}/start.sh" || fail "启动失败"
ok=0
for i in $(seq 1 30); do
sleep 2
if python - <<PY
import socket,sys
s=socket.socket(); s.settimeout(2)
sys.exit(0 if s.connect_ex(('127.0.0.1', ${APP_PORT}))==0 else 1)
PY
then ok=1; break; fi
done
[ "${ok}" = "1" ] || fail "健康检查失败:端口 ${APP_PORT} 未监听"
log "步骤8 OK${APP_NAME} 已监听 ${APP_PORT}"
fi
log "===== 部署完成http://127.0.0.1:${APP_PORT}/ env=${ENV_NAME}====="

View File

@ -1,69 +1,48 @@
{
"appname": "pbls",
"port": 9300,
"port_source": "projects/pbls/env/test.json:port端口唯一来源应用不设 env/ 目录)",
"default_dbname": "pbl",
"default_tenant": "default",
"module_dbmap": {
"*": "pbl",
"pbl_common": "pbl",
"pbl_appcodes": "pbl",
"pbl_blueprint": "pbl",
"pbl_validation": "pbl",
"pbl_compiler": "pbl",
"pbl_agent_runtime": "pbl",
"pbl_evidence": "pbl",
"pbl_assessment": "pbl",
"pbl_kdb_ext": "pbl",
"pbl_domain_ext": "pbl",
"pbl_scense_ext": "pbl",
"pbl_runtime_ext": "pbl",
"world": "pbl",
"scene": "pbl",
"entity": "pbl",
"script_engine": "pbl",
"scense_game": "pbl",
"appbase": "pbl",
"rbac": "pbl"
},
"password_key": "pbls-aes-key-0123456789abcdef",
"logger": {
"name": "pbls",
"level": "INFO",
"file": "$[workdir]$/logs/pbls.log"
},
"filesroot": "$[workdir]$/files",
"databases": {
"pbl": {
"driver": "mysql",
"kwargs": {
"host": "127.0.0.1",
"port": 3306,
"unix_socket": null,
"user": "test",
"password": "test123",
"db": "pbl",
"charset": "utf8mb4"
},
"note": "本机 MySQL 走 socket 会失败(/run/mysqld/mysqld.sock 不存在),必须显式 TCP 127.0.0.1:3306 且 unix_socket=null"
}
},
"website": {
"port": 9300,
"root": "$[workdir]$/wwwroot",
"indexes": ["index.ui", "index.html"],
"processors": [
[".tmpl", "tmpl"],
[".ui", "bui"],
[".dspy", "dspy"],
[".wss", "ws"],
[".ws", "ws"]
],
"session": {
"secret": "pbls-session-secret-please-rotate",
"cookie_name": "pbls_session",
"max_age": 86400
"password_key": "pbls_agent_os_2026",
"logger": {
"name": "pbls",
"level": "INFO",
"file": "$[workdir]$/logs/pbls.log"
},
"rbac": true
}
"filesroot": "$[workdir]$/files",
"databases": {
"pbls": {
"driver": "mysql",
"kwargs": {
"host": "$[db.host]$",
"port": 3306,
"user": "$[db.user]$",
"password": "$[db.password]$",
"database": "pbls",
"charset": "utf8mb4"
}
}
},
"website": {
"port": 9300,
"host": "0.0.0.0",
"wwwroot": "$[workdir]$/wwwroot",
"indexes": [
"index.ui",
"index.html"
],
"processors": [
[".tmpl", "tmpl"],
[".ui", "bui"],
[".dspy", "dspy"],
[".wss", "ws"],
[".ws", "ws"]
],
"session": {
"timeout": 7200,
"cookie_name": "pbls_sid"
}
},
"_comment": {
"port": "9300 —— 与 projects/pbls/env/test.json、projects/pbls/env/prod.json、projects/pbls/pbls_spec.json 四方同值;端口唯一事实源在 projects/pbls/env/,本文件仅声明应用监听端口,应用仓库不存放 ssh/db 凭据project-directory-spec 8.1",
"processors": "必含 ['.tmpl','tmpl']BricksUIProcessor 内部加载 /bricks/header.tmpl缺失则 / 返回 500 'NoneType' has no attribute 'be_call')、['.ui','bui']、['.dspy','dspy'];另含 ['.wss','ws'] 供 pbl_runtime_ext 事件广播 WebSocket 使用",
"indexes": "必含 index.ui否则访问 / 或 /{module}/ 返回 500 invalid path",
"databases": "driver=mysql 对应 db.engine=mariadbBIGINT AUTO_INCREMENT 方言),库名 pbls 与 app/pbls.py 的 MODULE_DBNAME 一致"
}
}

View File

@ -1,17 +1,50 @@
# pbls 应用依赖(硬门禁 6.5 / QC #4
# ── 平台基础依赖Web 框架 + DB 抽象 + 基础模块,缺一不可)────────────────
ahserver # Web 框架(唯一入口 webapp(init) + BricksUIProcessor 渲染 .ui
bricks_for_python # bricks Python 侧load_pybricks 注册 UiWindow 等)
sqlor # DB 抽象层(只用 sor.C/U/D/R/I/sqlExe
apppublic # 公共工具db.sqlorContext / curDateString 等)
appbase # 应用基础params 表load_appbase 显式调用)
rbac # 登录与权限load_rbac 显式调用scripts/load_path.py 注册路径)
accounting # 平台基础(编码/序列,复用不改)
# ── 数据库驱动(缺 DB 驱动即连不上库)────────────────────────────────────
pymysql # MariaDB/MySQL Python 驱动TCP 127.0.0.1:3306
cryptography # pymysql caching_sha2_password 认证依赖
# ── 建表/CRUD 生成工具链build.sh 使用)─────────────────────────────────
xls2ddl # models/*.json → mysql.ddl.sqljson2ddl
# ── 复用基座模块(由 build.sh git clone + pip install -e 安装,此处仅登记)──
# world / scene / entity / script_engine / scense / scense_game / scense_runtime
# (机构 modules/ 仓库,非 PyPI 包,禁止在此写死版本号)
# =====================================================================
# pbls 应用依赖清单PBL Agent OS Phase 0/1
# 平台基础依赖web-application-spec「Required Dependencies」硬要求
# 核心apppublic / sqlor / ahserver
# 数据库 Web 应用appbase / rbac
# 前端渲染bricks_for_python.ui → bui processor缺它 / 返回 500
# DB 驱动db.engine = mariadbprojects/pbls/env/test.json 唯一事实源)
# → PyMySQL + cryptographycaching_sha2/rsa 握手需要)+ DBUtils连接池
# 禁止flask / fastapi / django / uvicorn / gunicorn应用入口只允许 ahserver
# =====================================================================
# ---- 平台核心(必装,顺序即安装顺序)----
git+https://git.opencomputing.cn/yumoqing/apppublic
git+https://git.opencomputing.cn/yumoqing/sqlor
git+https://git.opencomputing.cn/yumoqing/ahserver
# ---- 数据库 Web 应用基础模块(必装)----
git+https://git.opencomputing.cn/yumoqing/appbase
git+https://git.opencomputing.cn/yumoqing/rbac
# ---- 前端框架(.ui / .tmpl 渲染,必装)----
git+https://git.opencomputing.cn/yumoqing/bricks_for_python
git+https://git.opencomputing.cn/yumoqing/bricks
# ---- DDL / CRUD 生成工具build.sh 建表与 CRUD 生成用)----
xls2ddl
# ---- DB 驱动与连接池mariadb 方言)----
PyMySQL>=1.0.2
cryptography>=3.4.8
DBUtils>=3.0.2
# ---- 运行时通用依赖 ----
python-dateutil>=2.8.2
pytz>=2021.3
requests>=2.27.1
six>=1.16.0
# =====================================================================
# 本项目业务模块modules/pbl_*,由 build.sh 以 pip install -e 安装,
# 不走 PyPI故不在此声明版本清单见 projects/pbls/pbls_spec.json
# 的 load_order共 15 个 pbl_* 模块 + 8 个引用模块):
# pbl_common / pbl_appcodes / pbl_blueprint / pbl_validation /
# pbl_compiler / pbl_agent_runtime / pbl_evidence / pbl_assessment /
# pbl_kdb_ext / pbl_domain_ext / pbl_scense_ext / pbl_runtime_ext /
# pbl_template / pbl_analytics / pbl_governance
# 引用模块(写保护,只挂载不改):
# world / scene / entity / scense / scense_runtime / script_engine /
# kdb / world_snapshot
# =====================================================================

File diff suppressed because it is too large Load Diff

186
wwwroot/index.ui Normal file
View File

@ -0,0 +1,186 @@
{
"type": "UiWindow",
"id": "pbls_main_window",
"options": {
"title": "PBL Agent OS",
"layout": "border"
},
"children": [
{
"type": "Panel",
"id": "pbls_topbar",
"options": {
"region": "north",
"height": 56,
"layout": "hbox",
"cls": "pbls-topbar"
},
"children": [
{
"type": "Label",
"id": "pbls_app_title",
"options": {
"text": "PBL Agent OS",
"cls": "pbls-app-title"
}
},
{
"type": "Label",
"id": "pbls_phase_label",
"options": {
"text": "Phase 0/1 · 蓝图 → 校验 → 编译 → Agent 运行时",
"cls": "pbls-phase"
}
},
{
"type": "Button",
"id": "pbls_btn_logout",
"options": {
"text": "退出",
"align": "right",
"handler": "app.pbls_logout"
}
}
]
},
{
"type": "Menu",
"id": "pbls_nav_menu",
"options": {
"region": "west",
"width": 220,
"title": "功能导航"
},
"children": [
{
"type": "MenuItem",
"id": "mi_blueprint",
"options": {
"text": "蓝图管理",
"target": "/pbl_blueprint/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_validation",
"options": {
"text": "蓝图校验14 维)",
"target": "/pbl_validation/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_compiler",
"options": {
"text": "编译器 / Game Definition",
"target": "/pbl_compiler/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_agent_runtime",
"options": {
"text": "Agent 运行时22 工具)",
"target": "/pbl_agent_runtime/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_evidence",
"options": {
"text": "产出物与证据",
"target": "/pbl_evidence/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_assessment",
"options": {
"text": "Rubric 评估报告",
"target": "/pbl_assessment/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_appcodes",
"options": {
"text": "枚举编码",
"target": "/pbl_appcodes/index.ui"
}
},
{
"type": "MenuItem",
"id": "mi_governance",
"options": {
"text": "治理与审计",
"target": "/pbl_governance/index.ui"
}
}
]
},
{
"type": "TabPanel",
"id": "pbls_main_tabs",
"options": {
"region": "center",
"closable": true
},
"children": [
{
"type": "Panel",
"id": "tab_home",
"options": {
"title": "首页",
"layout": "vbox"
},
"children": [
{
"type": "Label",
"id": "home_welcome",
"options": {
"text": "欢迎使用 PBL Agent OS。请从左侧导航选择功能模块。",
"cls": "pbls-welcome"
}
},
{
"type": "DataViewer",
"id": "home_blueprint_list",
"options": {
"title": "最近蓝图",
"url": "/api/pbl_blueprint_list.dspy",
"pageSize": 10
}
}
]
}
]
},
{
"type": "Panel",
"id": "pbls_statusbar",
"options": {
"region": "south",
"height": 28,
"layout": "hbox"
},
"children": [
{
"type": "Label",
"id": "status_tenant",
"options": {
"text": "租户:-",
"cls": "pbls-status"
}
},
{
"type": "Label",
"id": "status_port",
"options": {
"text": "端口9300",
"cls": "pbls-status"
}
}
]
}
]
}