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

This commit is contained in:
agent.develop 2026-09-22 20:45:25 +08:00
parent cc20471188
commit b3ad3f40cf
2 changed files with 290 additions and 4 deletions

View File

@ -150,8 +150,8 @@ INSERT —— `permission.permcode` = 路径本身,`rolepermission` 用 `role.
> **自检入口**:离线门禁自检脚本 `scripts/selfcheck_m5a.py` 已落盘(只读、不连库、
> 输出逐字节确定),一条命令覆盖「三处同步 / RBAC 清单 vs 磁盘 / 模型四段式 /
> 幂等键语义 / pyproject 依赖 / 种子 JSON / C-3 fail-closed / README 路径命中」8 项,
> 全通过输出 `ALL PASS (8/8 checks)` 且 exit 0:
> 幂等键语义 / pyproject 依赖 / 种子 JSON / C-3 fail-closed / README 路径命中」8 项 + 「harness 静态门禁 / harness 零明文凭据 / 运行证据日志 / harness DDL 与模型一致」4 项,共 12 项,
> 全通过输出 `ALL PASS (12/12 checks)` 且 exit 0:
>
> ```bash
> cd modules/pbl_evidence && python3 scripts/selfcheck_m5a.py
@ -181,7 +181,7 @@ python3 scripts/check_crud_json.py
# ④ RBAC 清单与 wwwroot 磁盘一致性核对(不连库)
python3 scripts/load_path.py --check
# ⑤ M5a 交付门禁一键自检(8 项全 PASS 才算可交付;失败 exit 1 并逐条打印原因)
# ⑤ M5a 交付门禁一键自检(12 项全 PASS 才算可交付;失败 exit 1 并逐条打印原因)
python3 scripts/selfcheck_m5a.py
```
@ -253,7 +253,7 @@ pbl_evidence/
├── init/data.json # 编码种子(Format B:3 组 appcodes + appcodes_kv 子项)
├── sql/pbl_evidence.sql # 建表 SQL 快照
├── scripts/load_path.py # RBAC 登记(模块层,禁通配符)+ --check 一致性核对
├── scripts/selfcheck_m5a.py # M5a 交付门禁自检(8 项,只读不连库,ALL PASS 收尾)
├── scripts/selfcheck_m5a.py # M5a 交付门禁自检(12 项,只读不连库,ALL PASS 收尾)
├── skill/SKILL.md # 面向 agent 的模块技能文档
└── pyproject.toml
```

View File

@ -0,0 +1,286 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""M5a 收口自证脚本(QC #1/#7/#8 退回项的**仓库内正式**取证工具)。
为什么存在
----------
上一轮把「新代码是否真在磁盘、证据日志是否齐备、临时补丁是否已清理」这套自证逻辑
写在工作空间根的 `tmp_patch_u7rb.py` 里,以一次性工具脚本充当 develop 交付件本体
(QC #8)。一次性脚本不入库 = 下一个人无从复跑,自证结论无法复核。本脚本把同一套
检查沉淀进模块仓库,与 `selfcheck_m5a.py` 互补:
* `selfcheck_m5a.py` —— 交付**门禁**(12 项,验模块结构/契约/幂等键/证据日志口径)
* 本脚本 —— **收口自证**(验「磁盘代码 == 门禁所声称的那份代码」+ 工作空间卫生 +
README 与代码的门禁条数一致,防文档/代码漂移)
用法
----
cd modules/pbl_evidence && python3 scripts/verify_m5a_closure.py
# 全通过 → 逐条 [PASS] + `CLOSURE VERIFY ALL PASS (N/N)`,exit 0
# 任一失败 → [FAIL] + 原因,exit 1
幂等契约:只读(不连库、不写文件、不取时间戳/随机数/绝对路径),所有明细 sorted()
后输出,重复执行逐字节一致。
"""
import ast
import json
import os
import re
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SELFCHK = os.path.join(ROOT, 'scripts', 'selfcheck_m5a.py')
HARNESS = os.path.join(ROOT, 'tests', 'm5a_live_db_harness.py')
README = os.path.join(ROOT, 'README.md')
# 机构工作空间根:modules/{模块}/scripts/ → up 3 级
WS_ROOT = os.path.abspath(os.path.join(ROOT, os.pardir, os.pardir))
DOCS = os.path.join(WS_ROOT, 'projects', 'pbls', 'docs', '02-develop')
ENV_TEST = os.path.join(WS_ROOT, 'projects', 'pbls', 'env', 'test.json')
# ① selfcheck 必须定义的 4 个新增门禁函数(QC #1 点名的改法)
GATE_FUNCS = (
'check_harness_static',
'check_no_hardcoded_credentials',
'check_evidence_logs',
'check_harness_matches_model',
)
# ② harness 里必须 0 命中的崩溃/吞参/明文凭据特征(QC #1/#2 点名的旧缺陷)
HARNESS_FORBIDDEN = ('test123', 'args or {}', 'params or {}', 'dict(params or {})')
# ③ 三份运行证据日志(与 selfcheck EVIDENCE_LOGS 同名单)
EVIDENCE_LOGS = ('m5a-live-db-harness.log', 'm5a-selfcheck.log', 'm5a-pytest-offline.log')
RC_MARKER = '# RC=0'
# ④ 一次性补丁脚本清理名单(QC #8 点名 + 本批 s1/s2 期间新产生的)
TMP_PATCHES = ('tmp_patch_u7rb.py',)
# 工作空间根允许保留的历史取证脚本(M11b-1c-B 文档明确「正式唯一取证脚本」,
# 且被 projects/pbls/docs/M11b-1c-*.md 逐条引用作复跑入口,删除会破坏历史验收可复现性)
TMP_KEPT = ('tmp_m11b1c_diff_check.py',)
def _read(path):
with open(path, 'r', encoding='utf-8') as fh:
return fh.read()
def _rel(path):
try:
return os.path.relpath(path, WS_ROOT).replace(os.sep, '/')
except Exception:
return os.path.basename(path)
# ── V1 selfcheck 四个门禁函数已落盘且被 CHECKS 装配(防「定义了但不可达」死代码)
def verify_gate_functions():
if not os.path.isfile(SELFCHK):
return False, '缺少 %s' % _rel(SELFCHK)
src = _read(SELFCHK)
try:
tree = ast.parse(src, filename=SELFCHK)
except Exception as exc:
return False, 'selfcheck_m5a.py 语法错误: %s' % exc
defined = {n.name for n in tree.body
if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))}
problems, detail = [], []
missing = sorted(f for f in GATE_FUNCS if f not in defined)
if missing:
problems.append('selfcheck 缺门禁函数: %s' % ', '.join(missing))
else:
detail.append('4 个门禁函数已定义: %s' % ', '.join(sorted(GATE_FUNCS)))
# 装配检查:函数名必须出现在 CHECKS 列表里,否则是永远不执行的死代码
m = re.search(r'^CHECKS\s*=\s*\[(.*?)^\]', src, re.S | re.M)
if not m:
problems.append('未找到 CHECKS 装配表')
else:
unwired = sorted(f for f in GATE_FUNCS
if not re.search(r'\b' + re.escape(f) + r'\b(?!\s*def)', m.group(1)))
if unwired:
problems.append('门禁函数未装配进 CHECKS(死代码): %s' % ', '.join(unwired))
else:
detail.append('均已装配进 CHECKS 装配表')
if problems:
return False, '; '.join(problems)
return True, ' | '.join(detail)
# ── V2 harness 修复到位:崩溃版特征 0 命中
def verify_harness_clean():
if not os.path.isfile(HARNESS):
return False, '缺少 %s' % _rel(HARNESS)
src = _read(HARNESS)
hits = []
for token in HARNESS_FORBIDDEN:
n = src.count(token)
if n:
hits.append('%s×%d' % (token, n))
if hits:
return False, 'harness 仍含崩溃/吞参/明文凭据特征: %s' % ', '.join(sorted(hits))
try:
ast.parse(src, filename=HARNESS)
except Exception as exc:
return False, 'harness 语法错误: %s' % exc
return True, ('%s 零命中 %s,且 ast.parse 通过(修复版已在磁盘)'
% (_rel(HARNESS), ' / '.join(HARNESS_FORBIDDEN)))
# ── V3 env/test.json 的 db.sandbox 段(凭据唯一事实源,代码内零明文)
def verify_env_sandbox():
if not os.path.isfile(ENV_TEST):
return False, '缺少 %s' % _rel(ENV_TEST)
try:
conf = json.loads(_read(ENV_TEST))
except Exception as exc:
return False, 'env/test.json 不是合法 JSON: %s' % exc
db = conf.get('db') or {}
sb = db.get('sandbox')
if not isinstance(sb, dict):
return False, 'env/test.json 缺 db.sandbox 段(harness 无凭据事实源)'
problems, detail = [], []
if sb.get('scope') != 'sandbox_only':
problems.append('db.sandbox.scope != sandbox_only(会被误用于业务库)')
else:
detail.append('scope=sandbox_only')
if not sb.get('sandbox_schema'):
problems.append('db.sandbox 缺 sandbox_schema(沙箱库名未声明)')
else:
detail.append('sandbox_schema=%s' % sb['sandbox_schema'])
# 沙箱库由 harness 运行时 CREATE DATABASE / DROP DATABASE,故不预置 dbname;
# 连接必填四项 + 自建 schema 名,才是这段配置的真实语义。
for key in ('host', 'port', 'user', 'password'):
if key not in sb:
problems.append('db.sandbox 缺连接字段 %s' % key)
if problems:
return False, '; '.join(problems)
detail.append('连接必填字段齐备(host/port/user/password),schema 由 harness 自建自删')
return True, ' | '.join(detail)
# ── V4 三份证据日志齐备,且 harness 日志含 C-3 fail-closed + 1062 真防重原文
def verify_evidence_logs():
if not os.path.isdir(DOCS):
return False, '缺少证据目录 %s' % _rel(DOCS)
top = sorted(f for f in os.listdir(DOCS) if os.path.isfile(os.path.join(DOCS, f)))
problems, detail = [], []
absent = sorted(set(EVIDENCE_LOGS) - set(top))
if absent:
problems.append('缺少证据日志: %s' % ', '.join(absent))
else:
detail.append('三份日志齐备: %s' % ', '.join(EVIDENCE_LOGS))
for name in EVIDENCE_LOGS:
if name not in top:
continue
text = _read(os.path.join(DOCS, name))
if name == 'm5a-selfcheck.log':
n = len(re.findall(r'^\[(?:PASS|FAIL)\] \d+\.', text, re.M))
if n != 12:
problems.append('m5a-selfcheck.log [PASS]/[FAIL] 行数 %d != 12' % n)
elif 'ALL PASS (12/12 checks)' not in text:
problems.append('m5a-selfcheck.log 结论行不是 ALL PASS (12/12 checks)')
else:
detail.append('自检日志 12 行齐备且结论为 ALL PASS (12/12 checks)')
elif RC_MARKER not in text:
problems.append('%s 缺 `%s` 收尾标记' % (name, RC_MARKER))
if re.search(r'^Traceback \(most recent call last\)', text, re.M):
problems.append('%s 含裸 Traceback 崩溃现场' % name)
if not problems and 'm5a-live-db-harness.log' in top:
hlog = _read(os.path.join(DOCS, 'm5a-live-db-harness.log'))
need = ['C-3.1', 'C-3.2', 'C-3.3', 'C-3.4', 'IDM.6']
miss = sorted(t for t in need
if not re.search(r'\[PASS\]\s*' + re.escape(t) + r'\b', hlog))
if miss:
problems.append('harness 日志缺 [PASS] 行: %s' % ', '.join(miss))
elif '1062' not in hlog or 'uk_ev_dedup' not in hlog:
problems.append('harness 日志缺 1062 Duplicate entry / uk_ev_dedup 真防重原文')
else:
detail.append('harness 日志含 C-3.1~C-3.4 全 [PASS] + IDM.6 1062(uk_ev_dedup) 原文')
if problems:
return False, '; '.join(problems)
return True, ' | '.join(detail)
# ── V5 工作空间卫生:QC #8 点名的一次性补丁已删,历史取证脚本按文档保留
def verify_tmp_hygiene():
problems, detail = [], []
still = sorted(f for f in TMP_PATCHES if os.path.isfile(os.path.join(WS_ROOT, f)))
if still:
problems.append('一次性补丁脚本仍在磁盘: %s' % ', '.join(still))
else:
detail.append('QC #8 点名脚本已删除: %s' % ', '.join(TMP_PATCHES))
left = sorted(f for f in os.listdir(WS_ROOT)
if f.startswith('tmp_') and f.endswith('.py'))
unexpected = sorted(set(left) - set(TMP_KEPT))
if unexpected:
problems.append('工作空间根仍有未清理的一次性补丁: %s' % ', '.join(unexpected))
kept = sorted(set(left) & set(TMP_KEPT))
if kept:
detail.append('按 M11b-1c-B 文档保留历史取证脚本: %s' % ', '.join(kept))
if problems:
return False, '; '.join(problems)
return True, ' | '.join(detail)
# ── V6 README 门禁条数与代码实际条数一致(防文档/代码漂移)
def verify_readme_matches_code():
problems, detail = [], []
if not os.path.isfile(README):
return False, '缺少 README.md'
if not os.path.isfile(SELFCHK):
return False, '缺少 scripts/selfcheck_m5a.py'
rtxt = _read(README)
stxt = _read(SELFCHK)
try:
tree = ast.parse(stxt, filename=SELFCHK)
except Exception as exc:
return False, 'selfcheck 语法错误: %s' % exc
n_checks = None
for node in tree.body:
if isinstance(node, ast.Assign) and any(
getattr(t, 'id', None) == 'CHECKS' for t in node.targets):
if isinstance(node.value, ast.List):
n_checks = len(node.value.elts)
if n_checks is None:
return False, '无法从 selfcheck 解析 CHECKS 条数'
claimed = sorted(set(re.findall(r'ALL PASS \((\d+)/\d+ checks\)', rtxt)))
if claimed != [str(n_checks)]:
problems.append('README 声称的门禁条数 %s 与代码 CHECKS 条数 %d 不一致'
% (claimed, n_checks))
else:
detail.append('README 门禁条数 = 代码 CHECKS 条数 = %d' % n_checks)
if problems:
return False, '; '.join(problems)
return True, ' | '.join(detail)
CHECKS = [
('selfcheck 四个新增门禁函数已落盘且装配进 CHECKS', verify_gate_functions),
('harness 修复版在磁盘:test123/args or {} 等 0 命中 + ast 可解析', verify_harness_clean),
('env/test.json db.sandbox 段齐备(scope=sandbox_only,凭据唯一事实源)', verify_env_sandbox),
('三份运行证据日志齐备(C-3.1~C-3.4 + 1062 真防重原文 + # RC=0)', verify_evidence_logs),
('工作空间根一次性补丁脚本已清理(QC #8)', verify_tmp_hygiene),
('README 门禁条数与 selfcheck CHECKS 条数一致', verify_readme_matches_code),
]
def main():
print('verify_m5a_closure: target = modules/pbl_evidence')
failed = []
for i, (name, fn) in enumerate(CHECKS, 1):
try:
ok, detail = fn()
except Exception as exc:
ok, detail = False, '检查执行异常 %s: %s' % (type(exc).__name__, exc)
print('[%s] V%d. %s' % ('PASS' if ok else 'FAIL', i, name))
print(' %s' % detail)
if not ok:
failed.append(name)
if failed:
print('CLOSURE VERIFY FAILED (%d/%d)' % (len(failed), len(CHECKS)))
for n in failed:
print(' - %s' % n)
return 1
print('CLOSURE VERIFY ALL PASS (%d/%d)' % (len(CHECKS), len(CHECKS)))
return 0
if __name__ == '__main__':
sys.exit(main())