第4轮复测三门禁全红的 develop 侧根因(实测核验,非照抄报单结论): 1. /healthz 端点根本不存在(grep -rn healthz apps/pbls 零命中),而复测门禁①判据正是 curl http://127.0.0.1:9380/healthz;build.sh 自检却走另一条 /api/public/health(同样无实现) → 进程活着也必然 HTTP!=200,且存在「自检绿/复测红」假通过隐患。 2. build.sh 从不调 scripts/load_path.py(grep 零命中)→ 新端点无 RBAC 登记即 401/403。 3. build.sh 读出 DEPLOY_PATH 后只用于 echo(L33/L34/L260),从未 mkdir/rsync → 门禁④ /d/pbl/pbls 恒不存在;env start_cmd 指向的解释器与入口均不存在 → 门禁②③不可能成立。 4. logs/ 目录到步骤13才 mkdir,而步骤11已 tee logs/self_check.log;set -euo pipefail 下 全新克隆必 die,部署在「建表已提交、自检未跑」处中断(实测 ls -d apps/pbls/logs 不存在)。 修复(同类问题全量排查,不只修报出来的那一个): - 新增 wwwroot/healthz/index.dspy、wwwroot/healthz.dspy、wwwroot/api/public/health/index.dspy 三个探针(零 import / 每分支显式 return json.dumps / dict 内不用 f-string / try-except 兜底 不抛500 / default=str / SQL 走 $ + get_sor_context 不硬编码库名 / 不返回敏感字段); status=ok 只表进程存活,DB 表计数为附加诊断不降级 status。 - build.sh 新增步骤 10b(mkdir logs run 前置)、12.5(load_path.py --check 门禁 + 正式注册, 失败明确 die 不静默)、12.8(mkdir DEPLOY_PATH + rsync -a --delete 同步 app/conf/wwwroot/scripts, 排除 venv/logs/run/.git;venv 软链复用因 shebang 是绝对路径;断言 python/入口/config 三者就位); 步骤13 改为 cd DEPLOY_PATH 后启动(website.root=wwwroot 是相对路径,从别处启动全量404) + pid/pkill 双口径停旧进程保幂等 + 进程数自证为0时打印日志尾部60行再 die; 步骤14 双口径健康检查(/healthz + /api/public/health + 外网口 SSH_HOST)+ 终态表数断言 37/37。 - scripts/load_path.py 新增 HOST_PATHS 四条(/healthz、/healthz.dspy、/api/public/health、 /api/public/health.dspy,role=any 免鉴权,禁通配符),collect() 末尾并入(原聚合器只扫 modules/*,宿主 wwwroot 端点无人登记);走同规则去重 + _stricter 冲突保留更严格 role。 - 旧口径 9300→9380 清理 4 处:build.sh:4 头注释、wwwroot/index.ui:179 状态栏(用户可见)、 pbl_appcodes/__init__.py:14、pbl_appcodes/init.py:16;_patch_common_contract_symbols.py:9 属历史修复台账,有意保留原文不改(改历史记录=伪造)。 - conf/config.json 同步新口径(port 9380 / 库 pbl / user test / module_dbname 全量 pbl), 与 projects/pbls/env/test.json 唯一事实源同值,满足 build.sh 步骤1 端口一致性门禁。 自证(真实回显):bash -n build.sh OK;py_compile load_path.py OK; load_path.py --check → total=284 wildcard=0 aggregation_failures=0 PASS(280→284,+4 宿主探针); --list | grep health → 四条 any 均在册;三个 .dspy 经 ast.parse 包装校验 AST OK、 no import/no print、return×2;grep -rn 9300 于四处改动文件零命中。 边界:未执行真实部署(建表/种子/rsync/起进程属 deploy_test 范畴,需 venv+真库+占端口), 不声称 healthz 已返回 200;未改任何 referenced_modules(rbac/apppublic/sqlor/ahserver/appbase/ accounting 只读)与 world/scene/entity/script_engine/scense* 复用域;硬编码库名同类问题经 grep 核验不存在(各模块均走 get_module_dbname),无需修。 详见 projects/pbls/docs/02-develop/dev-notes.md 2026-09-25 节。
pbls
PBL Agent OS 验证原型应用(唯一入口 app/pbls.py,端口以
projects/pbls/env/test.json 为唯一事实源)。业务模块见机构 modules/pbl_*。
测试依赖(DISC.2)
结论:pytest 已显式声明,不再依赖临时注入 PYTHONPATH。 宿主 python3 上没有 pytest
(python3 -m pytest → No module named pytest),且本机宿主 user site 是只读文件系统
(pip install --user 报 [Errno 30] Read-only file system),所以测试依赖的落点是 venv。
新增/相关文件清单
| 路径 | 层级 | 职责 |
|---|---|---|
apps/pbls/requirements-test.txt |
应用级 | 测试前置依赖清单(pytest>=7.0,<10),由 build.sh 第 7b 步装进应用 venv |
apps/pbls/build.sh 第 7b 步 |
应用级 | pip install -r requirements-test.txt + import pytest 自证;PBL_SKIP_TEST_DEPS=1 可跳过 |
modules/pbl_evidence/requirements-dev.txt |
模块级 | 同一版本区间的模块侧清单(不装本包也能跑离线单测的最短路径) |
modules/pbl_evidence/scripts/setup_test_env.sh |
模块级 | 建 .venv-test 并安装 requirements-dev.txt(含仓库卫生门禁) |
modules/pbl_evidence/scripts/activate_test_env.sh |
模块级 | 打印激活/直接调用测试 venv 解释器的方式 |
modules/pbl_evidence/scripts/run_tests.sh |
模块级 | 一条命令跑测试:无 pytest 时自动引导,再执行 pytest |
modules/pbl_evidence/README.md §Running tests |
文档 | 排障入口 + PBL_EVIDENCE_TEST_DB 开关用法 |
为什么是两份清单(应用级 vs 模块级)
requirements-test.txt(应用级)服务整应用的测试环境:跑build.sh后 venv 内即有 pytest, 任何modules/pbl_*/tests/都能用同一个解释器执行。requirements-dev.txt(模块级)服务只测本模块的 CI/离线场景:不安装 pbls 应用、不装本包 (pip install -e .)也能一条命令装好依赖。- 两份版本区间同源(
>=7.0,<10),避免"应用装了 9.x、模块 CI 装了 7.x"导致用例语义漂移。 - 两者都刻意与运行时清单
requirements.txt分离:pytest 不进生产镜像,也不进build.sh第 5 步的运行时依赖核验(该步只核验 ahserver/sqlor/apppublic/appbase/rbac/PyMySQL)。
推荐调用顺序
# 场景 1:完整应用环境(部署/联调机)
bash apps/pbls/build.sh # 第 7 步 venv → 7b 测试依赖 → 8 装模块
source apps/pbls/venv/bin/activate
python3 -m pytest modules/pbl_evidence/tests/test_m5a_idempotency.py -v
# 场景 2:只跑 pbl_evidence 单测(测试机/CI,最短路径)
bash modules/pbl_evidence/scripts/run_tests.sh # 无 pytest 时自动引导 .venv-test
# 场景 3:只要环境不要跑(手工用 venv 解释器)
bash modules/pbl_evidence/scripts/setup_test_env.sh
bash modules/pbl_evidence/scripts/activate_test_env.sh
期望结果与实测证据(两个场景各对应一份日志,不互相代证)
期望输出(两场景相同):
- 不带
PBL_EVIDENCE_TEST_DB:8 passed, 1 skipped,RC=0 (test_double_collect_is_idempotent_with_db需真实 MariaDB,未设开关时按设计 SKIPPED,属预期)。 - 带
PBL_EVIDENCE_TEST_DB(用法见modules/pbl_evidence/README.md§Running tests):9 passed。 - 禁止为凑通过修改测试断言逻辑。
| 场景 | 解释器 | 实测日志 | 状态 |
|---|---|---|---|
场景 1(build.sh 第 7b 步 → 应用 venv 内 pytest 可用) |
apps/pbls/venv/bin/python |
projects/pbls/docs/02-develop/m5a-disc2-build7b-run.log |
已实测 |
场景 2(模块级引导 run_tests.sh / .venv-test) |
modules/pbl_evidence/.venv-test/bin/python |
projects/pbls/docs/02-develop/m5a-disc2-pytest-run.log |
已实测 |
场景 1 日志中的关键真实输出行(由 shell tee 捕获,非手写):
[build.sh][...] 7b 自证通过: pytest 9.1.1 @ /d/pipeline/workspaces/0/sdlc_general/apps/pbls/venv/bin/python
[verify] PYTHONPATH = [<unset>]
========================= 8 passed, 1 skipped in 0.04s =========================
# pytest RC=0
即:7b 段把 pytest 装进应用 venv 后,source apps/pbls/venv/bin/activate 且 PYTHONPATH 未设置
的情况下直接跑 pytest 得到 8 passed, 1 skipped + RC=0 —— 证明 DISC.2 的原始痛点(靠临时注入
PYTHONPATH=<ws>/.pylibs 才跑得通)已被消除。
场景 1 的执行方式(受控截取,未强跑全流程)与已知限制
build.sh 第 8~14 步需要 MariaDB 建表、种子注入并启动应用,为验证 7b 而强跑全流程会污染测试库,
因此场景 1 的日志是用 projects/pbls/docs/02-develop/patches/m5a-disc2-build7b-runner.sh
按标记行从 build.sh 原样截取「头部(set -euo pipefail + 变量 + log()/die())+ 第 7 步
venv 创建与 pip 升级 + 完整 7b 段」后 bash 执行、tee 捕获得到,build.sh 本体一个字节未改
(日志末尾附该文件 md5 以供核对)。日志中同时打印了实际执行的截取脚本(cat -n)供逐行复核。
两处如实说明(均属本任务范围外的既有问题,已作为发现项上报 PM,未在本任务内改动):
- 截取时跳过了第 7 步的
pip install -r requirements.txt:该步指向机构内网 PyPI 源http://10.20.30.41:8081,本工作空间不可达(curlrc=124),会先于 7b 失败退出。 因此场景 1 日志证明的是「7b 段自身可执行并让 pytest 在应用 venv 内可用」, 不代表运行时依赖(ahserver/sqlor/…)在本机已装齐,也不代表build.sh全流程在本机跑通。 - 首轮曾按「截取第 1 行起的全部前缀」执行,
build.sh第 4 步 DDL 方言门禁 die:grep -Eiq '...|REFERENCES|ENUM\(|TIMESTAMP'命中了scripts/ddl/pbls_tables.sql里的 说明性注释文字(第 6 行「禁止 FOREIGN KEY / 禁止 ENUM / 禁止原生 TIMESTAMP(用 DATETIME)」、 第 891 行同类声明),而非真实建表语句。该门禁与 DDL 注释的冲突是既有问题(与 DISC.2 无关), 修它需要改第 4 步 grep 或改 DDL 注释,超出本任务范围,故本次截取范围从第 7 步起。 结论:build.sh第 06 步门禁本次未实测(静态核验 + 首轮部分实跑:第 03 步与 6.1/6.2 真实通过,第 4 步因上述注释冲突 die),第 7b 步已实测。