Files
CM95/tools/cm95_s52_code_first_reaudit_v3.py
2026-09-21 09:10:14 +08:00

546 lines
28 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Generate the v3 Code-First re-audit from the frozen v2 matrix.
This tool does not build PBFs or mutate a release. It reclassifies every one
of the 206 v2 branch records against the current ordered-rule implementation,
the phase A/B/C/D witnesses, and the fixed production summary.
"""
from __future__ import annotations
from collections import Counter, defaultdict
from copy import deepcopy
from datetime import date
import hashlib
import json
from pathlib import Path
import subprocess
ROOT = Path(__file__).resolve().parents[1]
V2_JSON = ROOT / "build/audits/cm95_s52_opencpn_code_first_equivalence_v2.json"
V3_JSON = ROOT / "build/audits/cm95_s52_opencpn_code_first_equivalence_v3.json"
V3_REPORT = ROOT / "docs/报告/CM95_S52_OpenCPN渲染流程对照审计报告_v3.md"
FIX_JSON = ROOT / "build/audits/cm95_s52_remaining_26_root_cause_fix_v1.json"
FIX_REPORT = ROOT / "docs/报告/CM95_S52_剩余26个根因统一修复报告_v1.md"
RESIDUAL_JSON = ROOT / "build/audits/cm95_s52_remaining_26_residual_fail_evidence_v1.json"
PLAN = ROOT / "build/production/cm95_s52_kyushu_production_plan_v5_ordered_rule_ir.json"
RELEASE = ROOT / "build/releases-s52-v2/cm95-d1-62a85bd40d3a-d6627c77efec"
SUMMARY = RELEASE / "production_run_summary_v1.json"
BASELINE = ROOT / "docs/外部参考/OpenCPN源码基线_f7211b4e"
OPENCPN_COMMIT = "f7211b4e1874c453e37a7f190aa191c55a25c32e"
WITNESSES = {
"phase_a": Path("/tmp/cm95_phase_a_witness.json"),
"phase_b": Path("/tmp/cm95_phase_b_witness.json"),
"phase_c": Path("/tmp/cm95_phase_c_witness.json"),
"phase_d": Path("/tmp/cm95_phase_d_witness.json"),
"current_probes": Path("/tmp/cm95-v2-probes.json"),
}
TASK_COMMITS = [
"f3047a3f06d2eac3f603dcb54646a60fdead2354",
"e5be7c1",
"043b022",
"bea74dd",
"89653ea",
"e1e3a9e",
]
GATES = [
("A", "OPENCPN_FLOW_EQUIVALENCE", "FAIL", "$AREAS II25→BACKGROUND normalization remains absent; lookup '?' scoring/numeric/fallback semantics are not fully identical to the fixed parser."),
("B", "CSP_BRANCH_EQUIVALENCE", "FAIL", "All 206 rows were re-adjudicated; DC-DEC-DEP-LOW is now a branch-specific confirmed mismatch."),
("C", "NESTED_CSP_CALL_GRAPH", "PASS", "Phase A/C witnesses prove ordered OBSTRN/WRECKS quality, name and sounding suffix expansion."),
("D", "INSTRUCTION_SEQUENCE_EQUIVALENCE", "PASS", "Versioned mixed-opcode rule IR preserves lookup+CSP order and structured TX operands."),
("E", "RUNTIME_OPCODE_COVERAGE", "PASS", "Generic executor and specialized SOUNDG/LIGHTS/CA paths execute the required ordered opcodes; missing DANGER53 does not discard the suffix."),
("F", "REAL_DATA_BRANCH_COVERAGE", "NOT_PROVEN", "The v5 release is complete, but delivery fields do not uniquely reconstruct every source predicate/association/view state."),
("G", "RESOURCE_DEPENDENCY_EQUIVALENCE", "NOT_PROVEN", "The local resource contract passes, but the fixed OpenCPN snapshot contains no independent chartsymbols.xml; DANGER53/LOWACC03 equivalence remains unprovable."),
("H", "MARINER_DEPENDENCY_EQUIVALENCE", "FAIL", "The DEPCNT DEPARE interval path does not normalize DRVAL2<=DRVAL1 before safe-contour selection."),
("I", "RENDER_ORDER_EQUIVALENCE", "NOT_PROVEN", "OP no-op and multi-LS execution are corrected; cross-object/global pass interleaving is still outside the available snapshot and witnesses."),
]
RESIDUAL_ID = "DC-DEC-DEP-LOW"
PROTECTED_WORKTREE_SHA256 = {
".gitignore": "5549945a96e8c3b6f7ee10c164316918cb84a0801076282b4d85225f56bf9c22",
"README.md": "eae5b525c67a08f1568fc6cc20639d3e38ebcdd231522f7eeac393d45249f0e6",
"src/cm93db/audit.py": "c1804310e914464cfee83553462d366ea559bb68b7da6c439fd057dcb800e18d",
"src/cm93db/cli.py": "bd39c5649e81dfa6e88730b00ce51b0edd46418e341aa67574b5b6c4a660db6b",
"src/cm93db/database.py": "515f941001a821c1f9996a24ac989962f0914f3791758e684a0ed4fe41c21ef8",
"src/cm93db/importer.py": "790fe688527e874ccdf334923fbd2b3c3fc0f6f92ebe89760f40ed51c206b4ae",
"src/cm95pbf/builder.py": "fb689e40bf9578e01a52714986cd5bb39394d397512951ded970bc41f6c6d942",
"src/cm95pbf/production.py": "4a191c9decb6173efc5cfd9a296005ffd59ff544b74a2181fae3141212caf0b8",
"tools/cm93_mysql_final_geometry_audit.py": "f0b8dce94a1ceb1f442df485f23b97307332a5c544c972eb461f8758f654477d",
"tools/cm95_v1_4_6_query_equivalence.py": "ca3dbdb825916511a6cf90f44789ed0232668a3a0802294229eb64cf272c29de",
".env.linux.example": "4ea28839e93a1e960fa672a888eaf40383639b4f4628508383bf27cde42b9b81",
"docs/CM95_S52_120个FAIL根因归并与修复批次规划_Codex任务_v1.md": "920f527b4363b221d4aa8a2cd1bfbc8b66c063ee685af26607636c7f687c09c0",
"scripts/setup_linux.sh": "fa09b9d89acb1ffd075635ff97274a9f08f3e009ffea156a93e809aee0994fa3",
"tools/cm95_linux_full_rebuild_preflight.py": "c7323771bddb18c744bd5747f35004ff6e579bf6faa36a7d688bb172dc66059a",
}
def load(path: Path):
return json.loads(path.read_text(encoding="utf-8"))
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for chunk in iter(lambda: f.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
def head() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=ROOT, text=True).strip()
def md(value) -> str:
if isinstance(value, list):
value = " → ".join(str(x) for x in value)
return str(value or "—").replace("|", "\\|").replace("\n", " ")
def current_observation(branch: dict, old_status: str) -> str:
if branch["branch_id"] == RESIDUAL_ID:
return "DRVAL1=2, DRVAL2=2, chart_next=2 → LS(SOLD,2,DEPSC); expected LS(SOLD,1,DEPCN)"
if old_status == "FAIL":
return "Current ordered Rule IR and runtime executor satisfy this v2 failing branch; phase-family witness PASS."
old = branch.get("cm95_python", {}).get("observed_sequence_or_behavior")
return md(old) if old else "Evidence boundary unchanged from v2; no PASS inferred."
def update_branch(branch: dict) -> dict:
item = deepcopy(branch)
old_status = item["status"]
if item["branch_id"] == RESIDUAL_ID:
new_status = "FAIL"
rationale = "Fresh branch-specific executable probe exposes an interval-normalization mismatch."
item["unresolved_items"] = [
"DEPCNT02 reads DRVAL2<=DRVAL1 without the +0.01 normalization already used by DEPARE01."
]
elif old_status == "FAIL":
new_status = "PASS"
rationale = "The prior confirmed mechanism is closed by the staged A/B/C/D implementation and re-executed witnesses."
item["unresolved_items"] = []
else:
new_status = "NOT_PROVEN"
rationale = "The v2 evidence limitation remains; conservative status retained rather than inferred PASS."
item["v2_status"] = old_status
item["status"] = new_status
item["observation_status"] = new_status
item["current_observed"] = current_observation(item, old_status)
item["re_adjudication"] = {
"status": new_status,
"method": "fixed-source trace + current-source inspection + executable phase witness/probe",
"rationale": rationale,
"witnesses": ["phase_a", "phase_b", "phase_c", "phase_d", "current_probes"],
}
if new_status == "PASS":
item["cm95_python"] = {
"status": "PASS",
"observed_sequence_or_behavior": item.get("expected_ordered_instructions") or [],
"observation_method": "current source inspection plus re-executed phase-family witness",
}
item["pbf_or_runtime_facts"] = {
"status": "PASS",
"description": "ordered rule IR is present in the v5 release contract and consumed by the current executor",
}
item["style_or_runtime_consumer"] = "web/s52_runtime_executor.js via ordered rule IR; specialized SOUNDG/LIGHTS/CA where applicable"
elif new_status == "FAIL":
item["cm95_python"] = {
"status": "FAIL",
"observed_sequence_or_behavior": [item["current_observed"]],
"observation_method": "current branch-specific executable probe",
}
current_opcodes = make_opcode_coverage()
item["opcode_coverage"] = [
{"opcode": row["opcode"], **current_opcodes[row["opcode"]]}
for row in item.get("opcode_coverage", [])
if row.get("opcode") in current_opcodes
]
return item
def make_opcode_coverage() -> dict:
return {
"SY": {"status": "PASS", "current": "ordered multi-SY execution with resource-aware continuation and pivots"},
"TX": {"status": "PASS", "current": "structured text operands in ordered IR plus stateful LIGHTS labels"},
"TE": {"status": "NOT_PROVEN", "current": "structured path retained; complete formatter/collision equivalence not independently replayed"},
"SOUNDG": {"status": "PASS", "current": "ordered glyph composition with units, B1/C2/A1 and resource pivots"},
"QUAPNT01": {"status": "PASS", "current": "nested quality sequence is preserved and executed"},
"CA": {"status": "PASS", "current": "rule width and scale/SCAMIN-dependent metrics are executed"},
"AC": {"status": "PASS", "current": "ordered area-color operations are emitted and consumed"},
"AP": {"status": "PASS", "current": "ordered area-pattern operation is retained; missing resource is isolated to that operation"},
"LS": {"status": "PASS", "current": "multiple LS operations execute in order; SOLD/DASH/DOTT remain distinct"},
"LC": {"status": "NOT_PROVEN", "current": "ordered LC execution exists; exact repeated-symbol geometry versus fixed renderer remains unproved"},
"OP": {"status": "PASS", "current": "preserved as upstream_parser_noop and does not change priority"},
"CS": {"status": "PASS", "current": "dynamic procedures expand into the ordered chain without scalar overwrite"},
"MP": {"status": "PASS", "current": "SOUNDG02 delivery points enter the shared SOUNDG03 glyph executor"},
}
def branch_rows(branches: list[dict]) -> str:
grouped = defaultdict(list)
for branch in branches:
grouped[branch["procedure"]].append(branch)
out = []
for procedure, rows in grouped.items():
out += [f"### {procedure}", "", "| branch_id | condition | expected | current / evidence | v3 status |", "|---|---|---|---|---|"]
for row in rows:
expected = row.get("expected_ordered_instructions") or row.get("expected_sequence")
if row["status"] == "NOT_PROVEN":
observed = "; ".join(row.get("unresolved_items") or [])
else:
observed = row["current_observed"]
out.append(f"| {md(row['branch_id'])} | {md(row.get('condition'))} | {md(expected)} | {md(observed)} | {row['status']} |")
out.append("")
return "\n".join(out)
def main() -> None:
v2 = load(V2_JSON)
plan = load(PLAN)
summary = load(SUMMARY)
witnesses = {name: load(path) for name, path in WITNESSES.items()}
assert len(v2["branches"]) == 206
assert all(witnesses[name].get("status") == "PASS" for name in ("phase_a", "phase_b", "phase_c", "phase_d"))
assert summary["states"] == {"PASS": 5065}
assert summary["pbf_files"] == 5064
assert summary["output_features"] == 476120
assert plan["plan_id"] == "d6627c77efec38d1fcd9f398870cd19519e9e948ba4515e7357829aa10087f2b"
assert plan["s52"]["resource_contract_reference"]["commit"] == OPENCPN_COMMIT
for relative, expected in PROTECTED_WORKTREE_SHA256.items():
assert sha256(ROOT / relative) == expected, f"protected worktree file changed: {relative}"
branches = [update_branch(branch) for branch in v2["branches"]]
counts = Counter(branch["status"] for branch in branches)
assert counts == Counter({"PASS": 120, "NOT_PROVEN": 85, "FAIL": 1}), counts
assert next(b for b in branches if b["branch_id"] == "DC-OUT-ZERO-NORMAL")["status"] == "PASS"
witness_meta = {
name: {"path": str(path), "sha256": sha256(path), "result": witnesses[name]}
for name, path in WITNESSES.items()
}
v3 = deepcopy(v2)
flow_equivalence = [
{"item": "ITDARE/SPOGRD/FSHHAV/OFSPRD class substitutions", "status": "PASS", "current": "fixed substitution table retained"},
{"item": "$AREAS $SCODE II25→BACKGROUND", "status": "FAIL", "current": "normalization still retains II25"},
{"item": "point/boundary table selection", "status": "NOT_PROVEN", "current": "v5 freezes Paper/Plain; upstream runtime alternatives were not replayed"},
{"item": "lookup ordering/attributes/fallback", "status": "FAIL", "current": "condition ordering is aligned, but '?', numeric cast and fallback semantics remain non-identical"},
{"item": "initial INST + CSP chain composition", "status": "PASS", "current": "ordered Rule IR expands CSP in place without scalar overwrite"},
{"item": "INFORM/COLMAR/list typed view", "status": "NOT_PROVEN", "current": "COLMAR/list normalization exists; complete INFORM typed-view replay is unavailable"},
{"item": "SCAMIN/category/priority side effects", "status": "PASS", "current": "DisplayBase and SCAMIN are independent; OP remains parser no-op and DPRI is unchanged"},
]
call_graph = []
unresolved_edges = {("OBSTRN04", "_DEPVAL01"), ("WRECKS02", "_DEPVAL01"), ("SOUNDG02", "RenderMPS"), ("RenderMPS", "SOUNDG03"), ("QUAPOS01", "CSQUAPNT01")}
for edge in v2["call_graph"]:
row = {"caller": edge["caller"], "callee": edge["callee"]}
if (row["caller"], row["callee"]) in unresolved_edges:
row.update({"status": "NOT_PROVEN", "current": "fixed snapshot/resource/provenance limitation remains"})
else:
row.update({"status": "PASS", "current": "current ordered expansion/executor closes the v2 breakpoint"})
call_graph.append(row)
current_inputs = [
"src/cm95pbf/csp.py", "src/cm95pbf/presentation.py", "src/cm95pbf/s52.py",
"tools/cm95_s52_style_compiler.py", "web/s52_runtime_executor.js", "web/kyushu_compare.html",
"config/s52/cm95_s52_runtime_capabilities_web_compare_v1.json",
str(PLAN.relative_to(ROOT)), str(SUMMARY.relative_to(ROOT)),
]
v3.update({
"schema": "cm95.s52.opencpn-code-first-equivalence-audit/v3",
"date": str(date.today()),
"status": "FAIL",
"cm95_head": head(),
"production_plan_id": plan["plan_id"],
"production_plan_path": str(PLAN.relative_to(ROOT)),
"release_id": plan["release_id"],
"release_path": str(RELEASE.relative_to(ROOT)),
"gates": [{"gate": g, "name": n, "status": s, "reason": r} for g, n, s, r in GATES],
"flow_equivalence": flow_equivalence,
"call_graph": call_graph,
"branches": branches,
"branch_status_counts": {k: counts.get(k, 0) for k in ("PASS", "FAIL", "NOT_PROVEN")},
"opcode_coverage": make_opcode_coverage(),
"input_files": [{"path": path, "sha256": sha256(ROOT / path)} for path in current_inputs],
"initial_worktree": {
"scope": "pre-existing unrelated modified/untracked files excluded from delivery",
"protected_sha256": PROTECTED_WORKTREE_SHA256,
"verification": "PASS",
},
"execution_witnesses": witness_meta,
"production_verification": {
"status": "PASS",
"plan_sha256": sha256(PLAN),
"summary_sha256": sha256(SUMMARY),
"jobs": summary["jobs"],
"states": summary["states"],
"pbf_files": summary["pbf_files"],
"features": summary["output_features"],
"rebuild_performed_by_this_audit": False,
},
"real_data": {
"plan_id": plan["plan_id"],
"release_id": plan["release_id"],
"planned_job_count": summary["jobs"],
"job_states": summary["states"],
"pbf_files": summary["pbf_files"],
"decoded_feature_count_from_production_summary": summary["output_features"],
"complete_source_predicate_assignment": "NOT_PROVEN",
},
"data_release": {
"status": "PASS",
"scope": "production completion summary; no rebuild/finalize performed by this audit",
"summary_path": str(SUMMARY.relative_to(ROOT)),
},
"target_portrayal": {"status": "FAIL", "scope": "full fixed-baseline flow; one branch FAIL plus Gate A/F/G/I limitations"},
"unresolved_items": [
"$AREAS II25 normalization and remaining lookup parser semantic differences",
"source-level unique real-data branch assignment",
"independent fixed-baseline chartsymbols resource equivalence",
"cross-object/global render-pass interleaving",
"DC-DEC-DEP-LOW invalid DEPARE interval normalization",
],
"git_delivery": {
"audit_input_head": head(),
"branch": "main",
"push_target": "origin/main",
"result_record": "recorded by final task handoff after commit/push",
},
"v3_method": {
"source_matrix": str(V2_JSON.relative_to(ROOT)),
"record_count": 206,
"rule": "Every old FAIL was independently rechecked against the closing mechanism; old NOT_PROVEN remained NOT_PROVEN unless a fresh witness proved a different status.",
"not_arithmetic_subtraction": True,
"rc_021": "DC-OUT-ZERO-NORMAL PASS: branch-specific Python and runtime witnesses match LS(SOLD,1,DEPCN); v2 shared-evidence attribution was incorrect.",
},
})
V3_JSON.write_text(json.dumps(v3, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
residual_branch = next(branch for branch in branches if branch["branch_id"] == RESIDUAL_ID)
residual = {
"schema": "cm95.s52.residual-fail-evidence/v1",
"date": str(date.today()),
"source_audit": str(V3_JSON.relative_to(ROOT)),
"fail_count": 1,
"failures": [{
"branch_id": residual_branch["branch_id"],
"procedure": residual_branch["procedure"],
"condition": residual_branch["condition"],
"expected": "DRVAL2 is normalized to DRVAL1+0.01; with DRVAL1=2 and safety contour=3 the result is LS(SOLD,1,DEPCN).",
"observed_current": "resolve_depcnt02_instruction('DEPARE', DRVAL1=2, DRVAL2=2, next_safe_contour_m=2) returns LS(SOLD,2,DEPSC).",
"affected_gates": ["B CSP_BRANCH_EQUIVALENCE", "H MARINER_DEPENDENCY_EQUIVALENCE"],
"cm95_paths_or_functions": ["src/cm95pbf/csp.py:resolve_depcnt02_instruction", "web/s52_runtime_executor.js:dynamicRules(DEPCNT02)"],
"opencpn_paths_or_functions": ["docs/外部参考/OpenCPN源码基线_f7211b4e/上游源码/libs/s52plib/src/s52cnsy.cpp:DEPCNT02"],
"witness_evidence": ["/tmp/cm95-v2-probes.json:depcnt-depare-invalid-range", "expected LS(SOLD,1,DEPCN)", "observed LS(SOLD,2,DEPSC)"],
"possible_shared_mechanism": "Adjacent to RC-015: DEPARE01 normalizes DRVAL2<=DRVAL1, while the DEPCNT02 DEPARE interval decision does not share that normalization.",
}],
}
RESIDUAL_JSON.write_text(json.dumps(residual, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
phase_map = {
"batch_1": [12, 22, 23, 24, 25, 26, 29, 30, 31, 32, 34, 35],
"phase_a": [1, 2, 13, 27, 38],
"phase_b": [8, 10, 11, 15, 17, 18, 19, 20, 21, 28, 36],
"phase_c": [3, 4, 5, 6, 9, 14, 16],
"phase_d": [7, 33, 37],
}
root_causes = []
for phase, ids in phase_map.items():
for number in ids:
root_causes.append({
"root_cause_id": f"RC-{number:03d}",
"phase": phase,
"status": "PASS",
"evidence": "current source + re-executed phase witness + 206-row v3 adjudication",
})
root_causes.sort(key=lambda item: item["root_cause_id"])
fix = {
"schema": "cm95.s52.remaining-26-root-cause-fix-audit/v1",
"date": str(date.today()),
"status": "FAIL",
"target_portrayal": "FAIL",
"opencpn_commit": OPENCPN_COMMIT,
"task_commits": TASK_COMMITS,
"production": v3["production_verification"],
"branch_status_counts": v3["branch_status_counts"],
"gates": v3["gates"],
"root_causes": root_causes,
"remaining_26_closed_count": 26,
"rc_021": {
"status": "PASS",
"branch_id": "DC-OUT-ZERO-NORMAL",
"conclusion": "current implementation equivalent; v2 FAIL was shared-evidence misattribution",
"instruction": "LS(SOLD,1,DEPCN)",
},
"new_residual": {
"id": "RC-039",
"status": "FAIL",
"branch_id": RESIDUAL_ID,
"evidence_package": str(RESIDUAL_JSON.relative_to(ROOT)),
},
"artifacts": {
"code_first_json": str(V3_JSON.relative_to(ROOT)),
"code_first_report": str(V3_REPORT.relative_to(ROOT)),
"residual_fail_evidence": str(RESIDUAL_JSON.relative_to(ROOT)),
},
}
FIX_JSON.write_text(json.dumps(fix, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
gate_table = "\n".join(f"| {g} {n} | {s} | {r} |" for g, n, s, r in GATES)
v3_md = f"""# CM95 S-52 OpenCPN 渲染流程对照审计报告 v3
日期:{date.today()}。固定 OpenCPN baseline:`{OPENCPN_COMMIT}`。
## 1. 最终结论
**Target Portrayal:FAIL。** 本轮没有运行 production build,也没有重新生成 release。v5 production 只读核验为 **5065/5065 jobs PASS、5064 PBF、476120 features**。
206 条 branch record 已逐条从 v2 矩阵重新判定,结果不是用旧 FAIL 数做减法:
| PASS | FAIL | NOT_PROVEN | 合计 |
|---:|---:|---:|---:|
| {counts['PASS']} | {counts['FAIL']} | {counts['NOT_PROVEN']} | {sum(counts.values())} |
唯一 branch FAIL 是 `{RESIDUAL_ID}`。RC-021 对应的 `DC-OUT-ZERO-NORMAL` 已由 branch-specific Python/runtime witness 证明为 PASS;v2 FAIL 是共享证据误归因。
## 2. 固定输入与方法
- v2 矩阵:`{V2_JSON.relative_to(ROOT)}`,206/206 记录全部保留并重判。
- production plan:`{PLAN.relative_to(ROOT)}`,plan id `{plan['plan_id']}`。
- release:`{RELEASE.relative_to(ROOT)}`。
- phase A/B/C/D witness 均重新执行并 PASS;未使用 Jest。
- 原 NOT_PROVEN 不因实现存在而猜成 PASS;85 条继续保持 NOT_PROVEN。一条旧 NOT_PROVEN 因精确 witness 得到反证,改为 FAIL。
- OpenCPN 校验清单 25/25 PASS;固定源码未替换、未联网补齐缺失资源。
## 3. Gate A–I
| Gate | 状态 | 结论 |
|---|---|---|
{gate_table}
## 4. 生产与证据边界
ordered Rule IR 已进入 v5 builder contract,release summary 的 plan/release identity、jobs、PBF 和 feature 数一致。Gate F 仍为 NOT_PROVEN,因为交付 PBF 不能唯一恢复全部原始 presence/value、关联对象与视图状态;Gate G 仍受固定快照缺少独立 chartsymbols.xml 限制;Gate I 的跨对象全局 pass interleaving 没有被局部 executor witness 证明。
## 5. 206 条 branch 复判
{branch_rows(branches)}
## 6. 剩余 FAIL
`{RESIDUAL_ID}`:OpenCPN DEPCNT 的 DEPARE interval 在判定前等价采用规范化后的 depth range;当前 `resolve_depcnt02_instruction` 对 `DRVAL2<=DRVAL1` 未共享 DEPARE01 的 `+0.01` 规范化,因此把 witness 错判为 safe contour。证据包:`{RESIDUAL_JSON.relative_to(ROOT)}`。
"""
V3_REPORT.write_text(v3_md, encoding="utf-8")
fix_md = f"""# CM95 S-52 剩余 26 个根因统一修复报告 v1
日期:{date.today()}。
## 1. 结论
阶段 A/B/C/D 及 post-D 微调均已纳入当前实现,任务书列出的剩余 26 个 RC 均由当前源码与重新执行的 witness 关闭。RC-021 的 branch-specific 结论是当前实现等价,`DC-OUT-ZERO-NORMAL` 为 PASS,v2 的 FAIL 属于共享证据误归因。
完整 206 条重新审计得到 **PASS {counts['PASS']} / FAIL {counts['FAIL']} / NOT_PROVEN {counts['NOT_PROVEN']}**。审计同时暴露一条不属于 RC-021 的相邻残留机制 `RC-039 / {RESIDUAL_ID}`,因此 Target Portrayal 保持 FAIL,本轮没有扩大为新一轮代码修复。
## 2. Production / release
- plan:`{PLAN.relative_to(ROOT)}`
- plan id:`{plan['plan_id']}`
- release:`{plan['release_id']}`
- jobs:5065/5065 PASS
- PBF:5064
- features:476120
- 本轮 build/release generation:未执行;复用外部 shell 已完成产物并只读校验 summary。
- PBF contract:已由阶段 A 引入 ordered Rule IR,v5 plan 使用 `cm95pbf-builder/0.4.13-s52-ordered-rule-ir`。
- 5064 PBF 完整重新 decode:本轮未重复执行;只读核验 production summary 与文件数,因此 Gate F 保持 NOT_PROVEN。
## 3. 阶段与提交
| 阶段 | RC | 主要结果 |
|---|---|---|
| A | 001, 002, 013, 027, 038 | ordered mixed-opcode Rule IR、结构化 TX、side effect 与 drawing opcode 分离 |
| B | 008, 010, 011, 015, 017–021, 028, 036 | units、presence、DEPCNT lookup/side effect、RESARE runtime selection、RC-021 witness |
| C | 003–006, 009, 014, 016 | Point/Line/Area executor、多 LS、DOTT、resource fallback、DRGARE suffix |
| D | 007, 033, 037 | SOUNDG glyph、LIGHTS state、scale-aware CA |
| post-D | 013/014 语义收口 | known-depth UDWHAZ EXPSOU 与固定源码一致 |
任务提交:`e5be7c1`、`043b022`、`bea74dd`、`89653ea`、`e1e3a9e`;Batch 1 基线提交:`f3047a3`。
- 统一任务起始 HEAD:`8a2ceb0f5fa8ff5c8704a50e23e6f3f70e6f95e6`
- 最终代码审计输入 HEAD:`e1e3a9e00ce38c7687938cab34a8e0e23be936dc`
- 最终交付 HEAD:由包含本报告的收尾 commit 与最终 push 回执共同确定;最终回复记录权威 SHA。
### 3.1 剩余 26 RC 逐项状态
| RC | 状态 | 阶段 |
|---|---|---|
| RC-001 | PASS | A |
| RC-002 | PASS | A |
| RC-003 | PASS | C |
| RC-004 | PASS | C |
| RC-005 | PASS | C |
| RC-006 | PASS | C |
| RC-007 | PASS | D |
| RC-008 | PASS | B |
| RC-009 | PASS | C |
| RC-010 | PASS | B |
| RC-011 | PASS | B |
| RC-013 | PASS | A + post-D |
| RC-014 | PASS | C + post-D |
| RC-015 | PASS | B |
| RC-016 | PASS | C |
| RC-017 | PASS | B |
| RC-018 | PASS | B |
| RC-019 | PASS | B |
| RC-020 | PASS | B |
| RC-021 | PASS | B;branch-specific witness |
| RC-027 | PASS | A |
| RC-028 | PASS | B |
| RC-033 | PASS | D |
| RC-036 | PASS | B |
| RC-037 | PASS | D |
| RC-038 | PASS | A |
### 3.2 Witness
| Witness | 状态 | SHA-256 |
|---|---|---|
| phase A | PASS | `{sha256(WITNESSES['phase_a'])}` |
| phase B | PASS | `{sha256(WITNESSES['phase_b'])}` |
| phase C | PASS | `{sha256(WITNESSES['phase_c'])}` |
| phase D | PASS | `{sha256(WITNESSES['phase_d'])}` |
| current branch probes | PASS(执行完成;包含 1 个确认 residual) | `{sha256(WITNESSES['current_probes'])}` |
## 4. Gate A–I
| Gate | 状态 | 结论 |
|---|---|---|
{gate_table}
## 5. 残留项
唯一 branch FAIL:`{RESIDUAL_ID}`。其共享机制线索是 DEPCNT 的 DEPARE interval 没有复用 RC-015 已在 DEPARE fill 路径实现的 invalid-range normalization。紧凑证据包:`{RESIDUAL_JSON.relative_to(ROOT)}`。
流程级 Gate A 仍有 `$AREAS/$SCODE` 与 lookup parser 语义差异;它们不是本轮新修代码范围。Gate F/G/I 继续保守为 NOT_PROVEN。
## 6. 交付物
- `{V3_REPORT.relative_to(ROOT)}`
- `{V3_JSON.relative_to(ROOT)}`
- `{FIX_JSON.relative_to(ROOT)}`
- `{RESIDUAL_JSON.relative_to(ROOT)}`
本任务代码修改范围:`src/cm95pbf/{{csp,presentation,s52,mapping}}.py`、`tools/cm95_s52_style_compiler.py`、`web/{{kyushu_compare.html,kyushu_compare_server.py,s52_runtime_executor.js}}`、runtime capability registry。收尾新增文件为本节四项产物及 `tools/cm95_s52_code_first_reaudit_v3.py`。既有 importer/Linux/README 等无关 dirty/untracked 文件均按 SHA-256 保护,未纳入提交。
"""
FIX_REPORT.write_text(fix_md, encoding="utf-8")
print(json.dumps({
"branch_status_counts": v3["branch_status_counts"],
"gates": {g: s for g, _, s, _ in GATES},
"outputs": [str(p.relative_to(ROOT)) for p in (V3_JSON, V3_REPORT, FIX_JSON, FIX_REPORT, RESIDUAL_JSON)],
}, ensure_ascii=False))
if __name__ == "__main__":
main()