deliver: 交付收口(引擎代为提交)
This commit is contained in:
parent
b3ad3f40cf
commit
28eb4ebc59
28
README.md
28
README.md
@ -185,6 +185,34 @@ python3 scripts/load_path.py --check
|
||||
python3 scripts/selfcheck_m5a.py
|
||||
```
|
||||
|
||||
### 收口自证(git 收口前必跑,`scripts/verify_m5a_closure.py`)
|
||||
|
||||
`selfcheck_m5a.py` 验的是「模块该长什么样」;`verify_m5a_closure.py` 验的是
|
||||
「**磁盘上的代码 == 门禁所声称的那份代码**」+ 工作空间卫生 + 文档/代码条数一致,
|
||||
专防 QC 退回过的三类问题:① 交付件声称的新增门禁函数其实没落盘;② 以工作空间根的
|
||||
一次性补丁脚本(`tmp_patch_*.py`)充当交付件本体;③ README 条数与代码 CHECKS 漂移。
|
||||
|
||||
> 一条命令,7 项全通过输出 `CLOSURE VERIFY ALL PASS (7/7)` 且 exit 0:
|
||||
>
|
||||
> ```bash
|
||||
> cd modules/pbl_evidence && python3 scripts/verify_m5a_closure.py
|
||||
> ```
|
||||
>
|
||||
> | 项 | 验什么 |
|
||||
> |---|---|
|
||||
> | V1 | `selfcheck_m5a.py` 的 4 个新增门禁函数(`check_harness_static` / `check_no_hardcoded_credentials` / `check_evidence_logs` / `check_harness_matches_model`)已落盘**且装配进 `CHECKS`**(防「定义了但不可达」死代码) |
|
||||
> | V2 | `tests/m5a_live_db_harness.py` 崩溃版特征(`test123` / `args or {}` / `params or {}` / `dict(params or {})`)0 命中 + `ast.parse` 通过 |
|
||||
> | V3 | `projects/{项目}/env/test.json` 的 `db.sandbox` 段齐备(`scope=sandbox_only` + `sandbox_schema` + host/port/user/password)——凭据唯一事实源在 env,代码内零明文 |
|
||||
> | V4 | `docs/02-develop` 三份运行证据日志齐备,harness 日志含 C-3.1~C-3.4 全 `[PASS]` 与 IDM.6 的 `1062 Duplicate entry ... uk_ev_dedup` 真防重原文,且无裸 `Traceback` |
|
||||
> | V5 | 工作空间根一次性产物卫生:`tmp_*.py` / `_scratch_*.py` / `*_patch.py` / `*_out.txt` / `*_ev<N>.txt` / `r<N>.txt` / `*.bak` / 根级临时目录全类扫描;M5a 自身转储必须已删,他链产物必须在 `WS_KEPT` 白名单里**逐项写明引用它的文档路径**,否则 FAIL |
|
||||
> | V6 | README 声称的 selfcheck 门禁条数 == 代码 `CHECKS` 条数(12) |
|
||||
> | V7 | README 本小节声称的收口条数 == 本脚本 `CHECKS` 条数(7),且脚本路径磁盘命中 |
|
||||
>
|
||||
> **复跑范围**:V1/V2/V6/V7 是模块内自证,任何宿主可复跑;V3/V4/V5 读工作空间级兄弟产物
|
||||
> (`projects/{项目}/...` 与工作空间根),仅在 pbls 项目工作空间内、或显式设置
|
||||
> `SDLC_WS_ROOT`(配合可选 `SDLC_PROJECT` 覆盖项目名,默认 `pbls`)时生效;定位不到工作空间
|
||||
> 根时这三项返回 `SKIPPED`(不计 FAIL),避免别的宿主挂载本模块时把「路径不存在」误判成质量回归。
|
||||
|
||||
## Integration(宿主挂载方式)
|
||||
|
||||
模块只依赖:基础包(sqlor、ahserver ServerEnv、appPublic 工具)、`pbl_common`(租户上下文/
|
||||
|
||||
@ -19,8 +19,25 @@
|
||||
# 全通过 → 逐条 [PASS] + `CLOSURE VERIFY ALL PASS (N/N)`,exit 0
|
||||
# 任一失败 → [FAIL] + 原因,exit 1
|
||||
|
||||
幂等契约:只读(不连库、不写文件、不取时间戳/随机数/绝对路径),所有明细 sorted()
|
||||
后输出,重复执行逐字节一致。
|
||||
复跑范围与路径解析(QC #3)
|
||||
--------------------------
|
||||
本脚本的 V3/V4/V5 三项要读**工作空间级**的兄弟产物(`projects/{项目}/env/test.json`、
|
||||
`projects/{项目}/docs/02-develop/*.log`、工作空间根的一次性产物),这些不在模块仓库
|
||||
内。解析优先级(**不再无条件硬推层级**):
|
||||
|
||||
1. 环境变量 `SDLC_WS_ROOT` —— 显式指定机构工作空间根(跨宿主复跑用这个)
|
||||
2. 环境变量 `SDLC_PROJECT` —— 指定项目名(默认 `pbls`),只影响 `projects/{p}/` 段
|
||||
3. 缺省:按 `modules/{模块}/scripts/ → up 3 级` 推导,并**校验推导结果确实是工作空间
|
||||
根**(同时存在 `projects/` 与 `modules/` 两个子目录)。推不出来时 V3/V4/V5 返回
|
||||
明确的 SKIP 语义(`SKIPPED: 非 pbls 工作空间/未设 SDLC_WS_ROOT`)而不是伪报 FAIL,
|
||||
避免别的宿主挂载本模块时把「路径不存在」误判成质量回归。
|
||||
|
||||
结论:**本脚本的 V1/V2/V6/V7 是模块内自证(任何宿主可复跑);V3/V4/V5 是工作空间级
|
||||
取证,仅在 pbls 项目工作空间内或显式设置 `SDLC_WS_ROOT` 时生效**。
|
||||
|
||||
幂等契约:只读(不连库、不写文件、不取时间戳/随机数),所有明细 sorted() 后输出,
|
||||
同一工作状态下重复执行逐字节一致(`os.path.abspath` 仅用于定位,不进入输出文本;
|
||||
输出里一律用相对工作空间根的 posix 路径)。
|
||||
"""
|
||||
|
||||
import ast
|
||||
@ -34,10 +51,35 @@ 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')
|
||||
# 项目名可由环境变量覆盖(QC #3:不把 pbls 写死进常量)
|
||||
PROJECT = os.environ.get('SDLC_PROJECT', 'pbls')
|
||||
|
||||
|
||||
def _looks_like_ws_root(path):
|
||||
"""工作空间根的机械判据:同时含 projects/ 与 modules/ 两个子目录。"""
|
||||
return (os.path.isdir(os.path.join(path, 'projects'))
|
||||
and os.path.isdir(os.path.join(path, 'modules')))
|
||||
|
||||
|
||||
def resolve_ws_root():
|
||||
"""返回 (ws_root 或 None, 解析来源说明)。环境变量优先,其次层级推导+校验。"""
|
||||
env_root = os.environ.get('SDLC_WS_ROOT')
|
||||
if env_root:
|
||||
cand = os.path.abspath(env_root)
|
||||
if _looks_like_ws_root(cand):
|
||||
return cand, 'SDLC_WS_ROOT 显式指定'
|
||||
return None, ('SDLC_WS_ROOT=%s 不像机构工作空间根(缺 projects/ 或 modules/)'
|
||||
% env_root)
|
||||
cand = os.path.abspath(os.path.join(ROOT, os.pardir, os.pardir))
|
||||
if _looks_like_ws_root(cand):
|
||||
return cand, '按 modules/{模块}/scripts → up 3 级推导并校验 projects/+modules/ 命中'
|
||||
return None, ('无法定位机构工作空间根(推导结果 %s 缺 projects/ 或 modules/);'
|
||||
'跨宿主复跑请设 SDLC_WS_ROOT' % cand)
|
||||
|
||||
|
||||
WS_ROOT, WS_ROOT_SRC = resolve_ws_root()
|
||||
DOCS = os.path.join(WS_ROOT, 'projects', PROJECT, 'docs', '02-develop') if WS_ROOT else None
|
||||
ENV_TEST = os.path.join(WS_ROOT, 'projects', PROJECT, 'env', 'test.json') if WS_ROOT else None
|
||||
|
||||
# ① selfcheck 必须定义的 4 个新增门禁函数(QC #1 点名的改法)
|
||||
GATE_FUNCS = (
|
||||
@ -51,11 +93,62 @@ 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',)
|
||||
|
||||
# ④ 工作空间根卫生门禁(QC #2 退回项:口径从「只匹配 tmp_*.py」扩到全类一次性产物)
|
||||
#
|
||||
# 一次性产物的**机械判据**(命中任一模式即视为需处置,不再只看 tmp_ 前缀):
|
||||
TMP_PATTERNS = (
|
||||
r'^tmp_.*\.py$', # 一次性补丁/取证脚本
|
||||
r'^_scratch_.*\.py$', # 一次性补丁脚本(下划线前缀变体)
|
||||
r'^.*_patch\.py$', # 名字里带 patch 的脚本
|
||||
r'^.*_out\.txt$', # 运行转储
|
||||
r'^.*_after\.txt$', # 改造前后对照转储
|
||||
r'^.*_ev\d+\.txt$', # 取证转储分片
|
||||
r'^.*evidence.*\.txt$', # 取证转储
|
||||
r'^r\d+\.txt$', # run #N 复跑转储
|
||||
r'^closure_out\.txt$', # 收口转储
|
||||
r'^.*\.bak$', # 编辑备份件
|
||||
)
|
||||
# 根级临时**目录**判据:以 `.` 开头且不是 VCS/工具配置白名单,或名字含 scratch/tmp/work
|
||||
WS_DIR_ALLOW = ('.git', '.pylibs', '.pytest_cache', '.cache', '.ssh', '.config',
|
||||
'.venv', '.local', '.viminfo')
|
||||
TMP_DIR_PATTERNS = (r'^\.[dqtms_]', r'^_scratch$', r'^tmp_tools$', r'.*scratch.*',
|
||||
r'.*tmp.*', r'.*work.*')
|
||||
|
||||
# **白名单**:确需保留的历史取证产物,逐项写明「谁引用它」(文档路径),
|
||||
# 未列入本表(且不属于他链保留表)的根级一次性产物一律判 FAIL。
|
||||
# 保留理由:这些文件被 projects/pbls 仓内的验收文档逐条引用作「复跑入口」或「留证原件」,
|
||||
# 删除会破坏**其他任务链**(M11b-1c / M11b-2d / M11b-4 / M9 / M1b)的历史验收可复现性;
|
||||
# 本任务(M5a 收口)只删 M5a 链路自身产生的转储,不代处置他链产物。
|
||||
WS_KEPT = {
|
||||
'tmp_m11b1c_diff_check.py': 'M11b-1c 正式取证脚本,被 projects/pbls/docs/M11b-1c-A_revert_generator_overreach.md、projects/pbls/docs/02-develop/dev-notes-m11b1c-b.md、dev-notes-m11b1-pbl_runtime_event.md 引用为复跑入口',
|
||||
'_scratch_m11b2d_a2_patch.py': 'M11b-2d-A2 补丁原件,被 projects/pbls/deliverables/agent.develop/OLdcikgd7ELN0ked7f33d_agent.develop_fallback.md 引用留证',
|
||||
'_scratch/': 'M11b-2d 补丁脚本目录,同上(_scratch_m11b2d_a2_patch.py 的目录形态副本)',
|
||||
'_m11b2d_a2_after.txt': 'M11b-2d-A2 改造后对照转储,被 projects/pbls/deliverables/agent.develop/OLdcikgd7ELN0ked7f33d_agent.develop_fallback.md 引用',
|
||||
'm11b4_out.txt': 'M11b-4 自测转储,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 登记为「他链产物·保留原样」',
|
||||
'm1b_evidence.txt': 'M1b 取证转储,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 登记为「他链产物·保留原样」',
|
||||
'm1b_ev2.txt': 'M1b 取证转储分片,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 末段(清单明列 m1b_ev2..ev6.txt)登记为「他链产物·保留原样」',
|
||||
'm1b_ev3.txt': 'M1b 取证转储分片,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 末段(清单明列 m1b_ev2..ev6.txt)登记为「他链产物·保留原样」',
|
||||
'm1b_ev4.txt': 'M1b 取证转储分片,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 末段(清单明列 m1b_ev2..ev6.txt)登记为「他链产物·保留原样」',
|
||||
'm1b_ev5.txt': 'M1b 取证转储分片,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 末段(清单明列 m1b_ev2..ev6.txt)登记为「他链产物·保留原样」',
|
||||
'm1b_ev6.txt': 'M1b 取证转储分片,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 末段(清单明列 m1b_ev2..ev6.txt)登记为「他链产物·保留原样」',
|
||||
'.dev-notes.bak': 'dev-notes.md 编辑备份,被 projects/pbls/docs/02-develop/m9-closureD-cleanup-record.md §4 判定「保留不动,仅作观察上报」',
|
||||
'tmp_tools/': 'M11b-4a 取证脚本目录,projects/pbls 仓 commit e8c08f1 已记录其处置结论(保留待 M11b-4c 收口)',
|
||||
'.qc_tmp/': 'QC 侧(agent.qc)工作目录,非 develop 产物,归属 QC 任务链处置',
|
||||
'.qc_tools/': 'QC 侧工具目录,非 develop 产物,归属 QC 任务链处置',
|
||||
'.dev_tmp/': 'M11b-2c-T2a 编辑中转目录,属 M11b-2c 任务链产物(该链收口任务运行中,不代处置)',
|
||||
'.tmpdev/': 'M11b-2c-T2 round3 补丁脚本目录,属 M11b-2c 任务链产物(该链收口任务运行中,不代处置)',
|
||||
'.m11b2work/': 'M11b-2 运行转储目录,属 M11b-2 任务链产物',
|
||||
'.scratch/': 'M10-a 桩目录(含 verify_m10a.py),属 M10-a 任务链产物(该链任务运行中)',
|
||||
'.m10a_verify/': 'M10-a 验证中转目录(stub + verify.py),属并行运行中的 M10-a 任务链(task NBrM2qPSIMSbtMH9lM6Lu)产物,本任务不代处置',
|
||||
'.tmpb/': 'M11b-4 v4 汇总草稿目录,属 M11b-4 任务链产物',
|
||||
'.d/': '空目录(编辑器/工具误建),无内容、无证据价值,本任务已删除;若复跑再出现则判 FAIL',
|
||||
}
|
||||
|
||||
# M5a 链路自身产生的转储(本任务必须删除,不允许进白名单)——列出来作为「反向门禁」:
|
||||
# 只要它们还在磁盘上就 FAIL,删掉后自然不再命中模式检查。
|
||||
M5A_OWN_DUMP_GONE = ('evidence_raw.txt', 'r1.txt', 'r2.txt', 'closure_out.txt',
|
||||
'.scratch_m5a/')
|
||||
|
||||
|
||||
def _read(path):
|
||||
@ -64,10 +157,12 @@ def _read(path):
|
||||
|
||||
|
||||
def _rel(path):
|
||||
try:
|
||||
return os.path.relpath(path, WS_ROOT).replace(os.sep, '/')
|
||||
except Exception:
|
||||
return os.path.basename(path)
|
||||
if path and WS_ROOT:
|
||||
try:
|
||||
return os.path.relpath(path, WS_ROOT).replace(os.sep, '/')
|
||||
except Exception:
|
||||
pass
|
||||
return os.path.basename(path or '')
|
||||
|
||||
|
||||
# ── V1 selfcheck 四个门禁函数已落盘且被 CHECKS 装配(防「定义了但不可达」死代码)
|
||||
@ -125,6 +220,8 @@ def verify_harness_clean():
|
||||
|
||||
# ── V3 env/test.json 的 db.sandbox 段(凭据唯一事实源,代码内零明文)
|
||||
def verify_env_sandbox():
|
||||
if not WS_ROOT:
|
||||
return True, 'SKIPPED: 未定位到机构工作空间根(%s),本项为工作空间级取证,跨宿主复跑不计分' % WS_ROOT_SRC
|
||||
if not os.path.isfile(ENV_TEST):
|
||||
return False, '缺少 %s' % _rel(ENV_TEST)
|
||||
try:
|
||||
@ -152,11 +249,14 @@ def verify_env_sandbox():
|
||||
if problems:
|
||||
return False, '; '.join(problems)
|
||||
detail.append('连接必填字段齐备(host/port/user/password),schema 由 harness 自建自删')
|
||||
detail.append('路径来源: %s / 项目名=%s' % (WS_ROOT_SRC, PROJECT))
|
||||
return True, ' | '.join(detail)
|
||||
|
||||
|
||||
# ── V4 三份证据日志齐备,且 harness 日志含 C-3 fail-closed + 1062 真防重原文
|
||||
def verify_evidence_logs():
|
||||
if not WS_ROOT:
|
||||
return True, 'SKIPPED: 未定位到机构工作空间根(%s),本项为工作空间级取证,跨宿主复跑不计分' % WS_ROOT_SRC
|
||||
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)))
|
||||
@ -198,24 +298,63 @@ def verify_evidence_logs():
|
||||
return True, ' | '.join(detail)
|
||||
|
||||
|
||||
# ── V5 工作空间卫生:QC #8 点名的一次性补丁已删,历史取证脚本按文档保留
|
||||
# ── V5 工作空间卫生(QC #2):全类一次性产物扫描 + 白名单必须写明引用文档
|
||||
def verify_tmp_hygiene():
|
||||
if not WS_ROOT:
|
||||
return True, 'SKIPPED: 未定位到机构工作空间根(%s),本项为工作空间级取证,跨宿主复跑不计分' % WS_ROOT_SRC
|
||||
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))
|
||||
entries = sorted(os.listdir(WS_ROOT))
|
||||
files = [e for e in entries if os.path.isfile(os.path.join(WS_ROOT, e))]
|
||||
dirs = [e for e in entries if os.path.isdir(os.path.join(WS_ROOT, e))]
|
||||
|
||||
# 5.1 M5a 链路自身转储必须已删除(反向门禁:出现即 FAIL,不许进白名单)
|
||||
own_left = sorted(p for p in M5A_OWN_DUMP_GONE
|
||||
if (p in files) or (p in dirs))
|
||||
if own_left:
|
||||
problems.append('M5a 链路自身产生的一次性转储仍未删除: %s' % ', '.join(own_left))
|
||||
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))
|
||||
detail.append('M5a 自身转储已全部删除: %s' % ', '.join(M5A_OWN_DUMP_GONE))
|
||||
|
||||
# 5.2 文件类一次性产物:命中模式 → 必须在白名单(且白名单写明引用文档)
|
||||
hit_files, kept_files = [], []
|
||||
for f in files:
|
||||
if any(re.match(p, f) for p in TMP_PATTERNS):
|
||||
(kept_files if f in WS_KEPT else hit_files).append(f)
|
||||
if hit_files:
|
||||
problems.append('工作空间根存在未登记的一次性产物(须删除或进 WS_KEPT 并写明引用文档): %s'
|
||||
% ', '.join(sorted(hit_files)))
|
||||
if kept_files:
|
||||
no_doc = sorted(k for k in kept_files if 'projects/pbls' not in WS_KEPT[k])
|
||||
if no_doc:
|
||||
problems.append('白名单项缺「引用它的文档路径」说明: %s' % ', '.join(no_doc))
|
||||
else:
|
||||
detail.append('按他链文档保留 %d 项(逐项已写明引用文档): %s'
|
||||
% (len(kept_files), ', '.join(sorted(kept_files))))
|
||||
|
||||
# 5.3 目录类一次性产物:命中模式 → 必须在白名单
|
||||
hit_dirs, kept_dirs = [], []
|
||||
for d in dirs:
|
||||
if d in WS_DIR_ALLOW:
|
||||
continue
|
||||
if any(re.match(p, d) for p in TMP_DIR_PATTERNS):
|
||||
(kept_dirs if d + '/' in WS_KEPT else hit_dirs).append(d)
|
||||
if hit_dirs:
|
||||
problems.append('工作空间根存在未登记的临时目录(须删除或进 WS_KEPT 并写明归属任务链): %s'
|
||||
% ', '.join(sorted(hit_dirs)))
|
||||
if kept_dirs:
|
||||
detail.append('他链临时目录按归属保留: %s' % ', '.join(sorted(kept_dirs)))
|
||||
|
||||
# 5.4 空目录一律不许留在根上(无证据价值,且会让下一次扫描出现「幽灵项」)
|
||||
empty = sorted(d for d in dirs if d not in WS_DIR_ALLOW
|
||||
and not os.listdir(os.path.join(WS_ROOT, d)))
|
||||
if empty:
|
||||
problems.append('工作空间根有空目录: %s' % ', '.join(empty))
|
||||
else:
|
||||
detail.append('根目录无空目录残留')
|
||||
|
||||
if problems:
|
||||
return False, '; '.join(problems)
|
||||
detail.append('路径来源: %s' % WS_ROOT_SRC)
|
||||
return True, ' | '.join(detail)
|
||||
|
||||
|
||||
@ -251,25 +390,55 @@ def verify_readme_matches_code():
|
||||
return True, ' | '.join(detail)
|
||||
|
||||
|
||||
# ── V7 README 对本收口脚本的描述与代码一致(收口脚本自身也不许「文档漂移」)
|
||||
def verify_readme_matches_closure():
|
||||
if not os.path.isfile(README):
|
||||
return False, '缺少 README.md'
|
||||
rtxt = _read(README)
|
||||
if 'verify_m5a_closure.py' not in rtxt:
|
||||
return False, 'README 未引用 scripts/verify_m5a_closure.py(本脚本成为无人知晓的孤儿件)'
|
||||
# 取 README 中提及本脚本的那个小节(从该标题行到下一个二级标题)
|
||||
lines = rtxt.splitlines()
|
||||
start = next((i for i, ln in enumerate(lines) if 'verify_m5a_closure.py' in ln), None)
|
||||
if start is None:
|
||||
return False, 'README 未提及本脚本'
|
||||
end = start
|
||||
while end < len(lines) and not (end > start and lines[end].startswith('## ')):
|
||||
end += 1
|
||||
section = '\n'.join(lines[start:end])
|
||||
claimed = sorted(set(re.findall(r'CLOSURE VERIFY ALL PASS \((\d+)/\d+\)', section)))
|
||||
n = len(CHECKS)
|
||||
if claimed != [str(n)]:
|
||||
return False, ('README 收口小节声称 %s 与代码 CHECKS 条数 %d 不一致'
|
||||
% (claimed or '无条数声明', n))
|
||||
return True, 'README 收口小节声称条数 = 代码 CHECKS 条数 = %d,且脚本路径磁盘命中' % n
|
||||
|
||||
|
||||
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),
|
||||
('工作空间根一次性产物卫生门禁(全类模式扫描 + 白名单须写明引用文档)', verify_tmp_hygiene),
|
||||
('README 门禁条数与 selfcheck CHECKS 条数一致', verify_readme_matches_code),
|
||||
('README 收口小节条数与本脚本 CHECKS 条数一致(防收口件文档漂移)', verify_readme_matches_closure),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
print('verify_m5a_closure: target = modules/pbl_evidence')
|
||||
print('workspace root = %s (%s); project = %s'
|
||||
% (WS_ROOT or 'UNRESOLVED', WS_ROOT_SRC, PROJECT))
|
||||
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))
|
||||
# 跨宿主复跑时工作空间级取证项返回 SKIPPED(不计分也不冒充 PASS 结论)
|
||||
skipped = ok and detail.startswith('SKIPPED')
|
||||
print('[%s] V%d. %s' % ('SKIP' if skipped else ('PASS' if ok else 'FAIL'),
|
||||
i, name))
|
||||
print(' %s' % detail)
|
||||
if not ok:
|
||||
failed.append(name)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user