#!/usr/bin/env python3
# v1.6.7 AC1 反向闸 —— 二守(禁翻面 ∧ 禁重生)之【谓词载体】
#
# 令源: ar 裁决 20(`docs/V1.6.7_RULINGS_08.md`)—— `AC1` 之反向闸扩为【二守】:
#   ① 禁翻面: `[patch."<URL>"]` 段之【键与条目同 scheme】⇒ 报红
#   ② 禁重生: 终态已达(`ssh` 字面量三面归零)而 `[patch]` 段仍在 ⇒ 报红
# 并: 裁决 24②(RULINGS_12)—— 插件侧 `24` 段之 `ssh` 条目【禁翻面】(翻之 ⇒ cargo rc=101);
#     裁决 27(RULINGS_13)—— 射程扩至插件侧 ∧ 终态降条件腿入池。
#
# ★★★ v1.7.4 裁决 01(`docs/V1.7.4_RULINGS_01.md` §二)—— 【守① 收紧 ∧ 守② 前件修正 ∧ 新增守③】:
#   · 守① 谓词【收紧】: 「同 `scheme`」⇒「**剥离 `?...` 后逐字同形**」——
#     此即 cargo 之【同源】**实判据**(`ref` **不参与**源身份)。实证(`AC2` 落笔期一手):
#     条目由 `ssh://git@…` 直翻 `https://…` ⇒ 键源 == 条目源 ⇒
#     `error: patch for 'xw-subagent' points to the same source, but patches must point to different sources` `rc=101`。
#   · 守② 前件【修正】: 「`ssh` 字面量二面归零」⇒「**`[patch]` 段数 = 0**」(= **真终态**之定义)。
#     据: `[patch]` 段之**真职**为【覆写传递钉】(**版本收敛**)—— 去段即图碎裂(一手: `same_name_multi` `0 → 20`,
#     `kernel` 五版本并存)⇒ 「`ssh` 归零」**不蕴含**「段已无职」⇒ 旧前件于 `AC2` 之后**恒真**而必假红。
#   · 新增守③【`ssh` 零容忍】: 声明面 `ssh://`(非注释行)> 0 ∨ 锁面 `git+ssh` > 0 ⇒ 报红。
#     据: 二守修正后,`ssh` 回归若无专守即漏网 ⇒ 本守补之(防回归网失落)。
#
# ★★ 机制之本体(为何直翻即毁): `[patch."<URL>"]` 之键 = 【被覆写之源】, 条目 = 【覆写后之源】;
#    cargo 硬约束 = 二者【须为异源】⇒ **键与条目须 URL 异形**(`scheme` 差或 userinfo 差皆可)。
#    `v1.7.4 AC2` 之形态 = 键 `https://gitcode.com/…`(**标签**, cargo **不取**)
#    ∧ 条目 `https://git@gitcode.com/…`(**实取**)—— `git@` 系 RFC 3986 `userinfo`(**非凭据**)。
#
# ★ 面 ∧ 域 ∧ 单位(承立条(二)):
#   面  = 所给 manifest(主线 `Cargo.toml` / 插件侧各仓【仓根】manifest)
#   域  = 该 manifest 之 `[patch.*]` 段(段内 `name = { git = … }` 条目行)
#   单位= 段 / 条目 / 行
#
# ★★ 出口码(承裁定 70 第五要件 ∧ AC11 之律【零 fail ∧ 零 exit ⇒ 恒 rc=0 ⇒ 永不 FAIL】):
#   0 = 判据成立(无违规 ∧ 对照全符)
#   1 = 有违规(判据不成立)
#   2 = 阳性对照【失效】⇒ 谓词不承证力 ⇒ 本刻之零命中【不可读作绿】
#       ★ v1.8.0 `M0`(承审计 `F2`): 守③ 之【空面】(二面分母皆 `0` ⇒ 无分母之零不承证力)
#         亦取 `2`,并发 `__SSHZERO_EMPTY__` 标记 —— 与「载体异常」以标记相区别。
#   3 = 【不适用(前件未达)】—— ★ v1.8.0 `M0`(承审计 `F1`)新增第三码: 守② 之前件
#       (`[patch]` 段数 = 0)未达 ⇒ 本守**不适用**。★ 旧形返 `0`(与「判据成立(绿)」**同码**)
#       ⇒ 以 `rc == 0` 判绿之调用方得【永久性假绿】且无从分辨 ⇒ 今以第三码显名区分。
#   ★ 并载(承裁 `06` ② 之判级器约定): 调用方对 `2`/`3` 之判级 = 【判据件陈旧 / 不适用】,
#     **非**【产品面缺陷】—— 出口码即判级之依据面。
#
# 用法:
#   ac1_patch_guard.py flip    <manifest> [<manifest> …]   # 守①: 列【同源】违规(剥离 query 后逐字同形)
#   ac1_patch_guard.py rebirth <manifest> <lockfile>       # 守②: 前件(段数 = 0)达 ∧ 段仍在 ⇒ 违规
#   ac1_patch_guard.py sshzero <manifest> <lockfile>       # 守③: 二面 ssh 零容忍(> 0 ⇒ 违规)
#   ac1_patch_guard.py face    <manifest>                  # 读数(段 / 条目 / ssh 声明行)
#   ac1_patch_guard.py align   <manifest>                  # 读数(仅声明面 / 仅 patch 面 之实面集)
#   ac1_patch_guard.py transitive <manifest> <lockfile>    # 读数(唯传递钉之实面集)
#   ac1_patch_guard.py align-check <manifest> <lockfile> <decl_reg> <patch_reg> <trans_reg>
#                                                          # 检查24: 三集【名册 ⇄ 实面】比较
#                                                          #   ★ v1.9.2 裁甲取丙: 比较逻辑入本载体
#                                                          #     ⇒ 夹具级覆盖(承 test 复核 捕一)
#                                                          #   出口码: 0 合 / 1 失对齐 / 2 前提不成立
#   ac1_patch_guard.py selftest                            # 夹具(含阳性对照)
import os
import re
import sys
import tempfile

PATCH_SEG_RE = re.compile(r'^\[patch\.(?:"(?P<url>[^"]+)"|(?P<bare>[^\]]+))\]')
# ★ 条目正则【禁】行尾锚 `\}\s*$` —— 实态有 8 条带行尾 `# deref …` 注(如 `:549`)⇒
#   锚行尾者【静默跳过】之(覆盖面 22 → 14 而 rc 仍 0)。此系在册已知族
#   (「锥锁正则静默跳过带行尾注释之 pin 行」)之再犯 ⇒ 取 `[^}]*\}` 不收行尾。
ENTRY_RE = re.compile(r'^\s*(?P<name>[A-Za-z0-9_.\-]+)\s*=\s*\{(?P<body>[^}]*)\}')
GIT_RE = re.compile(r'\bgit\s*=\s*"(?P<url>[^"]+)"')
# ★ 禁 POSIX 类 `[[:space:]]` —— Python `re` 不支持(视作嵌套集, 恒不匹配 ⇒ 静默零命中);
#   须书 `[ \t]`。实态本谓词若误书 ⇒ `ssh_decl` 恒 0 ⇒ 守② 于过渡态【误咬合】⇒ 假红。
SSH_DECL_RE = re.compile(r'^[ \t]*[^#]*ssh://')
GIT_LINE_RE = re.compile(r'\bgit\s*=')


def scheme_of(url):
    m = re.match(r'^([A-Za-z][A-Za-z0-9+.\-]*)://', url)
    return m.group(1).lower() if m else None


def strip_query(url):
    """⇒ 剥离 `?...`(`#…` 不在本形内)后之 URL —— cargo 源身份之【实判据面】。

    ★ v1.7.4 裁决 01 §二.2: `ref`(`?tag=`/`?rev=`/`?branch=`)**不参与**源身份比较
      ⇒ 二 URL 之【同源】判定 = 剥离 query 后**逐字同形**。
    """
    return url.split('?', 1)[0]


def same_source(key_url, entry_url):
    """⇒ 二 URL 是否【同源】(= 剥离 query 后逐字同形)—— cargo 之实判据。"""
    return strip_query(key_url) == strip_query(entry_url)


def parse_sections(text):
    """⇒ [(段名, 键url 或 None, [条目名…], [(条目名, 条目url)…]), …]"""
    segs = []
    cur = None
    for line in text.splitlines():
        m = PATCH_SEG_RE.match(line)
        if m:
            cur = {'seg': line.strip(), 'key': m.group('url'), 'names': [], 'entries': []}
            segs.append(cur)
            continue
        if line.startswith('['):
            cur = None
            continue
        if cur is None or line.lstrip().startswith('#'):
            continue
        e = ENTRY_RE.match(line)
        if not e:
            continue
        g = GIT_RE.search(e.group('body'))
        if not g:
            continue
        cur['names'].append(e.group('name'))
        cur['entries'].append((e.group('name'), g.group('url')))
    return segs


def flip_violations(text):
    """守① 禁同源 ⇒ [(段名, 条目名, 条目url), …]

    ★★ v1.7.4 裁决 01 §二.2【谓词收紧】: 「同 `scheme`」⇒「剥离 `?...` 后逐字同形」。
      旧谓词系【代理】—— `https://git@…` 形**同 scheme 而实异源**(cargo `rc=0` 实证)
      ⇒ 代理【假阳】; 新谓词**即 cargo 之实判据**, 系**收紧**兼去假阳。
    ★ 具名注册表键(如 `[patch.crates-io]`, 无 scheme)非本守之面 ⇒ 跳过。
    """
    out = []
    for s in parse_sections(text):
        kurl = s['key']
        if not kurl or scheme_of(kurl) is None:
            continue          # 具名注册表键(如 crates-io)非本守之面
        for name, url in s['entries']:
            if same_source(kurl, url):
                out.append((s['seg'], name, url))
    return out


def ssh_decl_count(text):
    return sum(1 for ln in text.splitlines() if SSH_DECL_RE.match(ln))


def lock_ssh_count(text):
    return sum(1 for ln in text.splitlines() if 'git+ssh://' in ln)


# ── 守③ 之【非空分母】(v1.8.0 `M0` · 承审计 `F2`)───────────────────────────
# ★ 由: 守③ 之谓词「二面 ssh 归零」**无分母** ⇒ 零条目 manifest 与「ssh 真归零」不可区分
#   ⇒ 空面(二 0 字节档)亦报 `rc=0`(= 假绿)。守① `flip` 已有分母(`parsed != raw` ⇒ `rc=2`),
#   守③ 补等价物: 二面分母皆 0 ⇒ `rc=2` 报「空面不可读作绿」(与 `flip` 之 `__BLIND__` 同律)。
LOCK_GIT_SRC_RE = re.compile(r'^\s*source = "git\+')
URL_TOKEN_RE = re.compile(r'://')


def lock_git_source_count(text):
    """锁面【`git+` 源行】数(分母)—— 守③ 非空前提。"""
    return sum(1 for ln in text.splitlines() if LOCK_GIT_SRC_RE.match(ln))


def manifest_url_token_count(text):
    """声明面【非注释行含 scheme 记号 `://`】数(分母)—— 守③ 非空前提(承裁决 24① 剔注释口径)。"""
    return sum(1 for ln in text.splitlines()
               if not ln.lstrip().startswith('#') and URL_TOKEN_RE.search(ln))


# ★★ 守② 之【终态基线】(承 ar 裁决 36 —— 谓词须取【增量形】):
#   基线 = 终态时应存之 `[patch]` 段数 = **0** —— 据 `AC1` 终态之定义(`[patch]` 段【全删】)。
#   判据 = 【前件(终态已达)】∧ 【增量(现段数 − 基线)> 0】。
#   ★ 禁【绝对形】(段数 > 0 而不设前件): 态 A 段数 = 16 ⇒ 绝对形【恒红】= 假红(非缺陷之征)。
TERMINAL_BASELINE_SEG = 0


def rebirth_state(manifest_text, lock_text):
    """守② 禁重生 ⇒ (前件已达?, 段数, 终态基线, 增量, 违规数)

    ★★ 谓词形【增量形】(承 ar 裁决 36 · `docs/V1.6.7_RULINGS_16.md`):
       增量 = 现段数 − 【终态基线】; 判据 = 前件 ∧ 增量 > 0。
       ★ 禁【绝对形】(段数 > 0 而不设前件)—— 于【态 A】段数 = 16 ⇒ 绝对形【恒红】= 假红,
         而其红【非缺陷之征】。
    ★★ 前件(v1.7.4 裁决 01 §二.3【修正】)= 【终态已达成】, 谓词 = **`[patch]` 段数 = 0**
       (= 真终态之**定义**)。旧前件「`ssh` 字面量二面归零」**已废** ——
       据: 段之真职为【覆写传递钉】(版本收敛), 去段即图碎裂 ⇒ 「`ssh` 归零」**不蕴含**「段已无职」
       ⇒ 旧前件于 `AC2` 之后**恒真** ⇒ 必假红(实测: `pre=yes ∧ 段数=19` ⇒ 增量 `19`)。
       前件未达 ⇒ 本守【不适用(前件未达)】—— ★ 禁书【非绿】(裁决 36 原文)。
    ★★ ★ 射程之【照实】(免读者误读为「已守」): 段数【随版本漂】⇒ 以实跑 `__FACE__` 读数为准
       (`v1.8.0` 本刻 = `17`; 旧文本此处硬写 `19` 系 `v1.7.4` 之值, 随 `v1.7.5` 之 N1 逐段清偿降至 `17`
        —— 硬写之值必腐, 故改为【指路】形)。⇒ 本仓本刻**前件未达** ⇒ 本守**不适用**。
       且【增量恒 `0`】(前件达 ⟺ 段数 = 0 ⇒ 增量 = 0 − 0)⇒ **无阳性对照可接** ⇒
       承裁定 241「恒真之谓词不承证力」⇒ **本守本刻不承证力**, 照实载明。
       ⇒ `AC2` 之后之防护网 = **守①**(已收紧至 cargo 实判据)∧ **守③**(新增 · `ssh` 零容忍)。
    ★★ v1.8.0 `M0`(承审计 `F1`): 前件未达时出口码由 `0`(与绿同码)改 **`3`**(= 不适用)——
       旧形下「前件未达(不适用)」与「判据成立(绿)」**同码** ⇒ 以 `rc == 0` 判绿之调用方
       拿到的是**永久性假绿**。今以第三码显名区分(`0` 绿 / `1` 违规 / `2` 载体异常 / `3` 不适用)。
    """
    p = patch_seg_count(manifest_text)
    pre = (p == 0)
    base = TERMINAL_BASELINE_SEG
    delta = p - base
    return pre, p, base, delta, (delta if (pre and delta > 0) else 0)


def ssh_zero_violations(manifest_text, lock_text):
    """守③ `ssh` 零容忍(v1.7.4 裁决 01 §二.4 · 新增)⇒ (声明面数, 锁面数, 违规数)

    谓词: 声明面 `ssh://` 字面量(**非注释行** · 承裁决 24① 口径)> 0
          ∨ 锁面 `git+ssh` > 0 ⇒ 违规。
    ★ 面之显名: 本守之面 = **主线 manifest**(`Cargo.toml`)+ **`Cargo.lock`**;
      插件侧 manifest 之 `ssh` 字面量**不在**本守面内(其归 守① 之插件侧面)。
    ★ 由(补强之据): 二守修正后 `ssh` 回归若无专守即漏网 —— 守① 只管【同源】,
      不管【`ssh` 是否回来】⇒ 本守补之。
    """
    s = ssh_decl_count(manifest_text)
    l = lock_ssh_count(lock_text)
    return s, l, (1 if (s > 0 or l > 0) else 0)


def face_sets(text):
    """⇒ (声明面集, patch 面集) —— 二者皆取【git 项/条目】之名(单位 = 名)。

    面: 同一 manifest 之二段 —— 声明面 = `[workspace.dependencies]` ∧ patch 面 = `[patch.*]`。
    ★ 二面【同轴可比】之据: 皆以 `git =` 声明同一批仓; 然【非同一对象】——
      声明面定【主钉】, patch 面定【覆写】⇒ 仅一面有名者 = 【单面项】。
    """
    decl, patch = set(), set()
    mode = None
    for ln in text.splitlines():
        if ln.startswith('[workspace.dependencies]'):
            mode = 'd'
            continue
        if ln.startswith('[patch.'):
            mode = 'p'
            continue
        if ln.startswith('['):
            mode = None
            continue
        if mode is None or ln.lstrip().startswith('#'):
            continue
        if not GIT_LINE_RE.search(ln):
            continue
        m = re.match(r'^\s*([A-Za-z0-9_.\-]+)\s*=', ln)
        if m:
            (decl if mode == 'd' else patch).add(m.group(1))
    return decl, patch


def lock_git_pkg_names(text):
    """`Cargo.lock` 中【`git+` 源】包之【名集】(块法 · 与 `lock_stats` 同器)。

    ★ v1.8.0 `M0`(承审计 `A4.3`/`F1` · ar 择 (b)): 供 `check24` 之【第三集】——
      唯传递钉 = 锁面 git 包名 − 声明面名 − patch 面名(= **二面皆无**者)。
    """
    names = set()
    for b in re.findall(r'^\[\[(?:package|patch\.unused)\]\]\n(.*?)(?=^\[\[|\Z)', text, re.M | re.S):
        if re.search(r'^source = "git\+', b, re.M):
            m = re.search(r'^name = "([^"]+)"', b, re.M)
            if m:
                names.add(m.group(1))
    return names


def lock_stats(text):
    """⇒ dict —— `Cargo.lock` 之三读数(承 ar 令: AC1 判据须三读数并报)。

    域 = `[[package]]` 段(★ 先切 `[[patch.unused]]` —— 该块系在册已知条件且【非】`[[package]]`,
        不切则 `git+ssh://` 行数法 `35` ≠ 逐块法 `34`, 差 `1` 即此块)。
    单位 = 包实例 / 名。
      · pkgs          = `[[package]]` 实例数
      · git_pkgs      = 其中 `source` 以 `git+` 起首者(本项目自有 + 传递 git 源, 二者【同域】)
      · same_name_multi = 【git 源同名多实例】(= `test` 所订【无双实例】之域; 基线 = `0`)
      · revtag_dual   = 【同 commit 之 `rev`/`tag` 二形 DUAL】(承裁决 33 扩载)
    """
    body = text.partition('[[patch.unused]]')[0]
    names = {}
    by_commit = {}
    total = 0
    for b in body.split('[[package]]')[1:]:
        n = re.search(r'^name = "([^"]+)"', b, re.M)
        s = re.search(r'^source = "([^"]+)"', b, re.M)
        if not n:
            continue
        total += 1
        src = s.group(1) if s else ''
        if not src.startswith('git+'):
            continue
        names.setdefault(n.group(1), []).append(src)
        m = re.match(r'git\+([^?]+)\?(rev|tag)=([^#]+)#(.+)$', src)
        if m:
            by_commit.setdefault(m.group(4), {}).setdefault(m.group(2), set()).add(
                f"{n.group(1)}@{m.group(3)}")
    dual = {c: v for c, v in by_commit.items() if 'rev' in v and 'tag' in v}
    return {
        'pkgs': total,
        'git_pkgs': sum(len(v) for v in names.values()),
        'same_name_multi': sum(1 for v in names.values() if len(v) > 1),
        'commits': len(by_commit),
        'revtag_dual': len(dual),
        'dual_detail': dual,
    }


def patch_seg_count(text):
    return sum(1 for ln in text.splitlines() if ln.startswith('[patch.'))


def entry_line_count(text):
    """段内【原始】`git =` 行数(计数谓词)—— 与解析所得条目数(解析谓词)对表,
    二者之差即【盲区】(在册族: 计数谓词 vs 解析谓词之差即盲区 ⇒ 报「全部」须附分母)。"""
    n, inp = 0, False
    for ln in text.splitlines():
        if ln.startswith('[patch.'):
            inp = True
            continue
        if ln.startswith('['):
            inp = False
            continue
        if inp and not ln.lstrip().startswith('#') and GIT_LINE_RE.search(ln):
            n += 1
    return n


def _reg_set(raw):
    """名册参 ⇒ 名集(`,` 分隔 · 剥空白 · 弃空项)。★ 空串 ⇒ 空集(**非**错误)。"""
    return {t.strip() for t in raw.split(',') if t.strip()}


def align_check(manifest_text, lock_text, decl_reg, patch_reg, trans_reg):
    """⇒ `(rc, lines)` —— `检查24` 之三集【名册 ⇄ 实面】比较(承 `ar` 裁决 04 · 裁甲取丙)。

    ★ 立条之由(`v1.9.2` 三环批复核 `test` 捕一 · 已裁): 比较逻辑原在 `verify_release.sh`
      之内联段 ⇒ 载体 `selftest` **结构性不可达** ⇒ 「全量红侧 `PASS`」**不承**该面之证力。
      移入载体 ⇒ 夹具级(秒级)覆盖 ⇒ 零门禁跑。

    射程(照实): 唯【比较】; 面之解析仍由 `face_sets` / `lock_git_pkg_names` /
      `patch_seg_count` 承(**非**本函数自造等价形)。

    ★ 三集(承 `v1.6.7` 裁决 20 ∧ `v1.8.0 M0` 择 (b)):
      · 集一 `decl_only` = 仅声明面项(有 `[workspace.dependencies]` ∧ 无 `[patch.*]`)
      · 集二 `patch_only` = 仅 `patch` 面项
      · 集三 `trans_only` = 唯传递钉(二面皆无 · 锁面 `git+` 包名 − 二面名)

    ★★ `seg == 0`(终态之形 · `[patch]` 段数 = 0): 依在册判例(裁决 20 · 反向守归 `检查23`
      守②)⇒ 集一/集二之比较**对象消解** ⇒ 本函数**唯判集三**(与 `verify_release.sh`
      原内联段之早退**逐义同**)。★ 此系【语义搬运】非【语义变更】。

    出口码阶梯(承在册「出口码阶梯禁以未判掩红」· 语义序 `FAIL > GAP > 绿`):
      `0` = 应判之集**皆合**(绿)
      `1` = 有**失对齐**(`FAIL`)
      `2` = **前提不成立 / 载体异常**(`GAP` · 未判)
    ★ 二码不得互掩: 前提不成立 ⇒ 无从判 `FAIL` ⇒ 唯出 `2`; 一旦可判 ⇒ `FAIL` **优先于**绿。
    ★ 并载【低序计数】(本形: 失对齐集数与名数)—— 免以单码掩其量。
    """
    lines = []
    if manifest_text is None or lock_text is None:
        lines.append("__VERDICT__ decl_only=GAP patch_only=GAP trans_only=GAP")
        lines.append("__ALIGN_RC__ 2")
        lines.append("__NOTE__ 前提不成立(manifest / lock 不可读)⇒ 三集【未判】")
        return 2, lines

    decl, patch = face_sets(manifest_text)
    segs = patch_seg_count(manifest_text)
    lock = lock_git_pkg_names(lock_text)
    d_now, p_now, t_now = decl - patch, patch - decl, lock - decl - patch

    lines.append(f"__FACE__ decl={len(decl)} patch={len(patch)} both={len(decl & patch)} "
                 f"decl_only={len(d_now)} patch_only={len(p_now)} patch_seg={segs}")
    lines.append("__DECL_ONLY__" + ",".join(sorted(d_now)))
    lines.append("__PATCH_ONLY__" + ",".join(sorted(p_now)))
    lines.append("__TRANSITIVE__ lock_git={} decl={} patch={} trans_only={}".format(
        len(lock), len(decl), len(patch), len(t_now)))
    lines.append("__TRANS_ONLY__" + ",".join(sorted(t_now)))

    d_reg, p_reg, t_reg = _reg_set(decl_reg), _reg_set(patch_reg), _reg_set(trans_reg)
    lines.append("__REG__ decl=[{}] patch=[{}] trans=[{}]".format(
        ",".join(sorted(d_reg)), ",".join(sorted(p_reg)), ",".join(sorted(t_reg))))

    # ── 集三(唯传递钉): 恒判(终态之形下亦在 · 承 `v1.8.0 M0` 之「射程不随段数而变」)──
    checks = [("trans_only", t_now, t_reg)]
    # ── 集一/集二: 唯 `seg > 0` 时判(终态之形 ⇒ 对象消解 · 早退语义搬运)──
    if segs > 0:
        checks = [("decl_only", d_now, d_reg), ("patch_only", p_now, p_reg)] + checks

    bad = []
    for name, now, reg in checks:
        hit = now == reg
        lines.append("__VERDICT__ {}={}".format(name, "OK" if hit else "MISMATCH"))
        if not hit:
            bad.append((name, now, reg))
    if segs == 0:
        lines.append("__VERDICT__ decl_only=SKIP patch_only=SKIP")
        lines.append("__NOTE__ 终态之形(patch 段数 = 0)⇒ 集一/集二 对象消解(承裁决 20 · 反向守归 检查23 守②)")

    if bad:
        for name, now, reg in bad:
            lines.append("__NOTE__ 失对齐 {}: 现 [{}] ⇄ 册 [{}]".format(
                name, ",".join(sorted(now)), ",".join(sorted(reg))))
        lines.append("__ALIGN_MISMATCH_FACES__ {}".format(len(bad)))
        lines.append("__ALIGN_RC__ 1")
        return 1, lines
    lines.append("__ALIGN_MISMATCH_FACES__ 0")
    lines.append("__ALIGN_RC__ 0")
    return 0, lines


def coverage(text):
    """⇒ (解析条目数, 原始条目行数);二者不等 ⇒ 谓词有盲区, 不得以零命中充绿。"""
    parsed = sum(len(s['entries']) for s in parse_sections(text))
    return parsed, entry_line_count(text)


# ── 夹具(阳性对照接入出口码 —— 任一不符 ⇒ rc=2)────────────────────────────
_FIX_OK = '''\
[patch."https://gitcode.com/kemeng1/xwkernel.git"]
kernel = { git = "ssh://git@gitcode.com/kemeng1/xwkernel.git", tag = "v1.4.8" }
kernel-host = { git = "ssh://git@gitcode.com/kemeng1/xwkernel.git", tag = "v1.4.8" }
'''
_FIX_FLIPPED = '''\
[patch."https://gitcode.com/kemeng1/xwkernel.git"]
kernel = { git = "https://gitcode.com/kemeng1/xwkernel.git", tag = "v1.4.8" }
'''
_FIX_MIXED = '''\
[patch."https://gitcode.com/kemeng1/xwkernel.git"]
kernel = { git = "ssh://git@gitcode.com/kemeng1/xwkernel.git", tag = "v1.4.8" }

[patch.crates-io]
foo = { git = "https://example.com/foo.git", tag = "v1" }
'''
_FIX_TRANSITION = '''\
ssh://git@gitcode.com/kemeng1/xwcore.git
ssh://git@gitcode.com/kemeng1/xwtools.git

[patch."https://gitcode.com/kemeng1/xwcore.git"]
xwcore = { git = "ssh://git@gitcode.com/kemeng1/xwcore.git", tag = "v0.1.29" }
'''
_FIX_TERMINAL = '''\
https://gitcode.com/kemeng1/xwcore.git

[patch."https://gitcode.com/kemeng1/xwcore.git"]
'''
_FIX_TERMINAL_LOCK = '''\
version = 4

[[package]]
name = "xwcore"
version = "0.1.29"
source = "git+https://gitcode.com/kemeng1/xwcore.git?tag=v0.1.29#790082df"
'''
# ★ 行尾注释条目(实态 8/22 条之形)—— 锚行尾者会静默漏掉 ⇒ 本夹具即其【阳性对照】
_FIX_TRAILING = '''\
[patch."https://gitcode.com/kemeng1/xwtui.git"]
xwtui = { git = "ssh://git@gitcode.com/kemeng1/xwtui.git", tag = "v0.3.15" }  # deref ec22f7faa80f353e7d233bf92862dc97f1e164bd
xwbad = { git = "https://gitcode.com/kemeng1/xwtui.git", tag = "v0.3.15" }  # deref x
'''
# ★★ v1.7.4 裁决 01 §二.2 之【本批实态形】(`https://git@` 条目)—— 阴性对照之【要件】:
#   旧谓词(同 scheme)于此形【假阳】(`https` == `https`)⇒ 新谓词须【零命中】。
#   ⇒ 本夹具即「收紧后不误伤本批交付面」之【唯一可检面】。
_FIX_V174 = '''\
[patch."https://gitcode.com/kemeng1/xwtui.git"]
xwtui = { git = "https://git@gitcode.com/kemeng1/xwtui.git", tag = "v0.3.19" }  # deref 119444601d463df220ee868c75d1766cde51afaf
'''
# ★★ v1.7.4 裁决 01 §二.3 之【前件未达之实态形】(段数 19 之缩影)—— 守② 不适用之阴性对照
_FIX_SEGS_NONTERMINAL = '''\
[patch."https://gitcode.com/kemeng1/xwcore.git"]
xwcore = { git = "https://git@gitcode.com/kemeng1/xwcore.git", tag = "v0.1.31" }
'''
_FIX_LOCK_SSH = '''\
version = 4

[[package]]
name = "xwcore"
version = "0.1.31"
source = "git+ssh://git@gitcode.com/kemeng1/xwcore.git?tag=v0.1.31#ed2dcfad"
'''
_FIX_LOCK_HTTPS = '''\
version = 4

[[package]]
name = "xwcore"
version = "0.1.31"
source = "git+https://git@gitcode.com/kemeng1/xwcore.git?tag=v0.1.31#ed2dcfad"
'''


# ── v1.9.2 裁甲取丙(`align-check`)之夹具面 ──────────────────────────────────
# 造形: 声明面 = {d1, d2} ∧ patch 面 = {d1, p1} ⇒ 仅声明面 {d2} ∧ 仅 patch 面 {p1} ∧ 双面 {d1};
#   锁面 `git+` 包 = {d1, d2, p1, t1} ⇒ 唯传递钉 {t1}(二面皆无)。
_FIX_ALIGN_MANIFEST = '''\
[workspace.dependencies]
d1 = { git = "https://gitcode.com/kemeng1/d1.git", tag = "v1" }
d2 = { git = "https://gitcode.com/kemeng1/d2.git", tag = "v1" }

[patch."https://gitcode.com/kemeng1/d1.git"]
d1 = { git = "https://git@gitcode.com/kemeng1/d1.git", tag = "v1" }

[patch."https://gitcode.com/kemeng1/p1.git"]
p1 = { git = "https://git@gitcode.com/kemeng1/p1.git", tag = "v1" }
'''
_FIX_ALIGN_LOCK = '''\
[[package]]
name = "d1"
version = "0.1.0"
source = "git+https://git@gitcode.com/kemeng1/d1.git?tag=v1#aaa"

[[package]]
name = "d2"
version = "0.1.0"
source = "git+https://git@gitcode.com/kemeng1/d2.git?tag=v1#bbb"

[[package]]
name = "p1"
version = "0.1.0"
source = "git+https://git@gitcode.com/kemeng1/p1.git?tag=v1#ccc"

[[package]]
name = "t1"
version = "0.1.0"
source = "git+https://git@gitcode.com/kemeng1/t1.git?tag=v1#ddd"
'''


def selftest():
    ok = True
    def chk(label, got, want):
        nonlocal ok
        good = got == want
        print(f"  [{'✓' if good else '✗'}] {label}: got={got} want={want}")
        if not good:
            ok = False

    print("夹具一 阴性对照(ssh 条目形: https 键 + ssh 条目 ⇒ 异源)")
    chk("守①违规数", len(flip_violations(_FIX_OK)), 0)
    chk("段数", patch_seg_count(_FIX_OK), 1)
    chk("条目数", sum(len(s['entries']) for s in parse_sections(_FIX_OK)), 2)

    print("夹具二 ★阳性对照(真同源形: 键与条目 URL 逐字同形)")
    chk("守①违规数", len(flip_violations(_FIX_FLIPPED)), 1)
    v = flip_violations(_FIX_FLIPPED)
    chk("违规条目名", v[0][1] if v else None, "kernel")
    chk("违规条目 url", v[0][2] if v else None, "https://gitcode.com/kemeng1/xwkernel.git")

    print("夹具三 混合形(具名注册表键不涉本守)")
    chk("守①违规数", len(flip_violations(_FIX_MIXED)), 0)
    chk("段数", patch_seg_count(_FIX_MIXED), 2)

    print("夹具四 守② 前件未达(段数 > 0 ⇒ 不适用)—— ★ 承裁决 01 §二.3")
    pre4, segs4, _b4, _d4, vio4 = rebirth_state(_FIX_SEGS_NONTERMINAL, _FIX_LOCK_HTTPS)
    chk("前件(段数 = 0)?", pre4, False)
    chk("段数", segs4, 1)
    chk("守②违规数(不适用 ⇒ 0 · 禁书「非绿」)", vio4, 0)
    # ★ 并载: 旧前件(ssh 二面归零)于此形【必假红】之【阳性对照】—— 本形下 ssh 面已归零,
    #   若前件仍为旧谓词 ⇒ 必判违规。此对照钉住【前件确已改】。
    chk("ssh 声明行(本形)", ssh_decl_count(_FIX_SEGS_NONTERMINAL), 0)
    chk("锁面 ssh 行(本形)", lock_ssh_count(_FIX_LOCK_HTTPS), 0)

    print("夹具五 阴性对照(守② 真终态形: 段数 = 0 ⇒ 前件达 ∧ 增量 0 ⇒ 无违规)")
    pre5, segs5, base5, delta5, vio5 = rebirth_state("https://gitcode.com/kemeng1/xwcore.git\n", _FIX_LOCK_HTTPS)
    chk("前件(段数 = 0)?", pre5, True)
    chk("段数", segs5, 0)
    chk("终态基线", base5, 0)
    chk("增量(= 段数 − 基线)", delta5, 0)
    chk("守②违规数", vio5, 0)

    print("夹具六 ★照实(守② 无阳性对照可接 —— 前件达 ⇒ 增量恒 0 ⇒ 恒不触发; 承裁定 241)")
    chk("前件达之形之违规数(恒 0)", rebirth_state("", _FIX_LOCK_HTTPS)[4], 0)
    chk("前件达?", rebirth_state("", _FIX_LOCK_HTTPS)[0], True)

    print("夹具七 ★阳性对照(行尾注释条目须入覆盖面 ∧ 其同源须被捕)")
    chk("解析条目数", coverage(_FIX_TRAILING)[0], 2)
    chk("原始条目行数", coverage(_FIX_TRAILING)[1], 2)
    chk("覆盖面无盲区", coverage(_FIX_TRAILING)[0] == coverage(_FIX_TRAILING)[1], True)
    chk("守①违规数(唯 xwbad 同源)", len(flip_violations(_FIX_TRAILING)), 1)

    print("夹具八 ★阳性对照(覆盖面盲区须可捕)")
    blind_fix = '''\
[patch."https://gitcode.com/kemeng1/x.git"]
x = { git = "ssh://git@gitcode.com/kemeng1/x.git" }
'''
    chk("无盲区之面", coverage(blind_fix)[0] == coverage(blind_fix)[1], True)

    print("夹具九 ★★阳性对照(v1.7.4 本批实态形: https 键 + `https://git@` 条目 ⇒ 异源 ⇒ 零命中)")
    chk("守①违规数(旧谓词于此形假阳 ⇒ 新谓词须 0)", len(flip_violations(_FIX_V174)), 0)
    chk("段数", patch_seg_count(_FIX_V174), 1)
    chk("条目数", sum(len(s['entries']) for s in parse_sections(_FIX_V174)), 1)
    chk("同源判定(键 vs 条目)", same_source("https://gitcode.com/kemeng1/xwtui.git",
                                              "https://git@gitcode.com/kemeng1/xwtui.git"), False)
    chk("同源判定(剥离 query 后仍同形 ⇒ True)", same_source("https://gitcode.com/a.git?tag=v1",
                                                              "https://gitcode.com/a.git?tag=v2"), True)

    print("夹具十 ★阳性对照(守③: 锁面 `git+ssh` > 0 ⇒ 违规)")
    chk("守③违规数(锁面 ssh 态)", ssh_zero_violations(_FIX_V174, _FIX_LOCK_SSH)[2], 1)
    chk("守③声明面数(本形 0)", ssh_zero_violations(_FIX_V174, _FIX_LOCK_SSH)[0], 0)
    chk("守③锁面数(本形 1)", ssh_zero_violations(_FIX_V174, _FIX_LOCK_SSH)[1], 1)

    print("夹具十一 阴性对照(守③: 二面皆零 ⇒ 无违规)")
    chk("守③违规数(本批实态形)", ssh_zero_violations(_FIX_V174, _FIX_LOCK_HTTPS)[2], 0)

    print("夹具十二 ★阳性对照(守③: 声明面 `ssh://` 非注释行 > 0 ⇒ 违规)")
    chk("守③违规数(声明面 ssh 态)", ssh_zero_violations(_FIX_OK, _FIX_LOCK_HTTPS)[2], 1)
    chk("守③声明面数(本形 2)", ssh_zero_violations(_FIX_OK, _FIX_LOCK_HTTPS)[0], 2)

    print("夹具十三 ★★阳性对照 + 阴性对照(v1.8.0 `M0` 守② 出口码: 前件未达 ⇒ `rc=3` ≠ 绿 `rc=0`)")
    with tempfile.TemporaryDirectory() as _td:
        _mp, _lp = os.path.join(_td, 'Cargo.toml'), os.path.join(_td, 'Cargo.lock')
        with open(_mp, 'w', encoding='utf-8') as _f:
            _f.write(_FIX_SEGS_NONTERMINAL)
        with open(_lp, 'w', encoding='utf-8') as _f:
            _f.write(_FIX_LOCK_HTTPS)
        chk("守② rc(前件未达 ⇒ 不适用)", main(['ac1_patch_guard.py', 'rebirth', _mp, _lp]), 3)
    with tempfile.TemporaryDirectory() as _td:
        _mp, _lp = os.path.join(_td, 'Cargo.toml'), os.path.join(_td, 'Cargo.lock')
        with open(_mp, 'w', encoding='utf-8') as _f:
            _f.write("https://gitcode.com/kemeng1/xwcore.git\n")
        with open(_lp, 'w', encoding='utf-8') as _f:
            _f.write(_FIX_LOCK_HTTPS)
        chk("守② rc(前件已达 ⇒ 绿)", main(['ac1_patch_guard.py', 'rebirth', _mp, _lp]), 0)

    print("夹具十四 ★★阳性对照 + 阴性对照(v1.8.0 `M0` 守③ 空面 ⇒ `rc=2`; 非空面 ⇒ `rc=0`)")
    with tempfile.TemporaryDirectory() as _td:
        _mp, _lp = os.path.join(_td, 'Cargo.toml'), os.path.join(_td, 'Cargo.lock')
        open(_mp, 'w', encoding='utf-8').close()
        open(_lp, 'w', encoding='utf-8').close()
        chk("守③ rc(空面 ⇒ 不可读作绿)", main(['ac1_patch_guard.py', 'sshzero', _mp, _lp]), 2)
    with tempfile.TemporaryDirectory() as _td:
        _mp, _lp = os.path.join(_td, 'Cargo.toml'), os.path.join(_td, 'Cargo.lock')
        with open(_mp, 'w', encoding='utf-8') as _f:
            _f.write(_FIX_V174)
        with open(_lp, 'w', encoding='utf-8') as _f:
            _f.write(_FIX_LOCK_HTTPS)
        chk("守③ rc(非空面 ∧ 二面零 ⇒ 绿)", main(['ac1_patch_guard.py', 'sshzero', _mp, _lp]), 0)
    chk("守③ 分母(非空面: 声明面记号数 > 0)", manifest_url_token_count(_FIX_V174) > 0, True)
    chk("守③ 分母(非空面: 锁面 git 源行数 > 0)", lock_git_source_count(_FIX_LOCK_HTTPS) > 0, True)

    print("夹具十五 ★★阳性对照 + 阴性对照 + 判别力 + GAP(v1.9.2 裁甲取丙: 检查24 三集比较入载体)")
    # ★ 立条之由(`v1.9.2` 三环批复核 `test` 捕一): 比较逻辑原在 verify_release.sh 内联段 ⇒
    #   本 selftest **结构性不可达** ⇒ 「全量红侧 PASS」不承该面之证力。本夹具即补此覆盖面。
    _mf, _lf = _FIX_ALIGN_MANIFEST, _FIX_ALIGN_LOCK
    # 甲: 三集名册皆合 ⇒ rc=0(阴性 · 不误红)
    chk("甲 rc(三集皆合 ⇒ 绿)", align_check(_mf, _lf, "d2", "p1", "t1")[0], 0)
    chk("甲 实面: 仅声明面", ",".join(sorted(face_sets(_mf)[0] - face_sets(_mf)[1])), "d2")
    chk("甲 实面: 仅 patch 面", ",".join(sorted(face_sets(_mf)[1] - face_sets(_mf)[0])), "p1")
    chk("甲 实面: 唯传递钉", ",".join(sorted(lock_git_pkg_names(_lf) - set(face_sets(_mf)[0])
                                              - set(face_sets(_mf)[1]))), "t1")
    # 乙: 名册缺一(集三)⇒ rc=1(阳性 · 真病相 · 即 v1.9.2 三环批之实病形)
    chk("乙 rc(唯传递钉名册缺 ⇒ 失对齐)", align_check(_mf, _lf, "d2", "p1", "")[0], 1)
    # 丙: 名册【名异而数同】(patch 面 1 vs 1)⇒ rc=1 ⇒ 证【唯计数】之谓词不承证力
    _rc_c, _ln_c = align_check(_mf, _lf, "d2", "p9", "t1")
    chk("丙 rc(名异数同 ⇒ 仍失对齐)", _rc_c, 1)
    chk("丙 判别力(计数谓词于此形相等 ⇒ 故计数不承证力)",
        len(face_sets(_mf)[1] - face_sets(_mf)[0]) == len({"p9"}), True)
    chk("丙 名面之差可显", any("失对齐 patch_only" in x for x in _ln_c), True)
    # 丁: 前提不成立(manifest / lock 不可读)⇒ rc=2(GAP · 未判 · 禁读作绿)
    chk("丁 rc(manifest 不可读 ⇒ GAP)", align_check(None, _lf, "d2", "p1", "t1")[0], 2)
    chk("丁 rc(lock 不可读 ⇒ GAP)", align_check(_mf, None, "d2", "p1", "t1")[0], 2)
    # 戊: 终态之形(patch 段数 = 0)⇒ 集一/集二 对象消解 ⇒ 唯判集三(语义搬运之证)
    #   ★ 实面: decl={d1} ∧ patch={} ⇒ 唯传递钉 = 锁面 {d1,d2,p1,t1} − {d1} = **{d2,p1,t1}**
    #     (`p1` 于终态之形下**亦**成唯传递钉 —— 无 patch 段即无「仅 patch 面」)
    _term = '[workspace.dependencies]\nd1 = { git = "https://gitcode.com/kemeng1/d1.git", tag = "v1" }\n'
    _rc_e, _ln_e = align_check(_term, _lf, "", "", "d2,p1,t1")
    chk("戊 rc(终态之形 · 集三合 ⇒ 绿)", _rc_e, 0)
    chk("戊 集一/集二 显名 SKIP", any("decl_only=SKIP" in x for x in _ln_e), True)
    # ★ 戊之阳性对照: 终态之形下【集三】仍须咬合(射程不随段数而变 · 承 v1.8.0 M0)
    chk("戊 rc(终态之形 · 集三缺 p1 ⇒ 仍失对齐)", align_check(_term, _lf, "", "", "d2,t1")[0], 1)
    # 己: 出口码阶梯之【并载低序计数】∧ 汇总行锚行首
    chk("己 汇总行锚行首(末段取值)",
        [x.split()[-1] for x in align_check(_mf, _lf, "d2", "p9", "t1")[1]
         if x.startswith("__ALIGN_MISMATCH_FACES__")][-1], "1")

    print(f"selftest {'全符 ✓' if ok else '★ 不符 ⇒ 谓词不承证力'}")
    return 0 if ok else 2


def main(argv):
    if len(argv) < 2:
        print(__doc__ or "usage: ac1_patch_guard.py {flip|rebirth|face|selftest} …", file=sys.stderr)
        return 2
    cmd = argv[1]
    if cmd == 'selftest':
        return selftest()
    if cmd == 'flip':
        if len(argv) < 3:
            print("flip 须给至少一个 manifest", file=sys.stderr)
            return 2
        total = 0
        blind = 0
        for path in argv[2:]:
            try:
                text = open(path, encoding='utf-8', errors='replace').read()
            except OSError as e:
                print(f"__UNREACHABLE__ {path} {e}", file=sys.stderr)
                return 2
            parsed, raw = coverage(text)
            print(f"__COVER__ {path} parsed={parsed} raw={raw}")
            if parsed != raw:
                print(f"__BLIND__ {path} 解析条目 {parsed} ≠ 原始条目行 {raw} ⇒ 谓词有盲区, "
                      f"本刻之零命中【不可读作绿】", file=sys.stderr)
                blind += 1
            for seg, name, url in flip_violations(text):
                print(f"{path}: 同源 {seg} 条目 {name} url={url}")
                total += 1
        print(f"__FLIP_COUNT__{total}")
        if blind:
            return 2
        return 1 if total else 0
    if cmd == 'sshzero':
        if len(argv) < 4:
            print("sshzero 须给 <manifest> <lockfile>", file=sys.stderr)
            return 2
        try:
            mtxt = open(argv[2], encoding='utf-8', errors='replace').read()
            ltxt = open(argv[3], encoding='utf-8', errors='replace').read()
        except OSError as e:
            print(f"__UNREACHABLE__ {e}", file=sys.stderr)
            return 2
        s, l, vio = ssh_zero_violations(mtxt, ltxt)
        dm, dl = manifest_url_token_count(mtxt), lock_git_source_count(ltxt)
        print(f"__SSHZERO__ decl={s} lock={l} denom_manifest={dm} denom_lock={dl} face={argv[2]}")
        print(f"__SSHZERO_COUNT__{vio}")
        # ★ v1.8.0 `M0`(承审计 `F2`): 【空面】⇒ `rc=2`(与 `flip` 之 `__BLIND__` 同律)——
        #   二面分母皆 0 ⇒ 本刻零命中【不可读作绿】(无分母之零不承证力)。
        if dm == 0 and dl == 0:
            print("__SSHZERO_EMPTY__ 空面: 声明面无 scheme 记号 ∧ 锁面无 `git+` 源行 ⇒ "
                  "本刻零命中【不可读作绿】(无分母之零不承证力)", file=sys.stderr)
            return 2
        return 1 if vio else 0
    if cmd == 'rebirth':
        if len(argv) < 4:
            print("rebirth 须给 <manifest> <lockfile>", file=sys.stderr)
            return 2
        try:
            mtxt = open(argv[2], encoding='utf-8', errors='replace').read()
            ltxt = open(argv[3], encoding='utf-8', errors='replace').read()
        except OSError as e:
            print(f"__UNREACHABLE__ {e}", file=sys.stderr)
            return 2
        s, l, p = ssh_decl_count(mtxt), lock_ssh_count(ltxt), patch_seg_count(mtxt)
        pre, segs, base, delta, vio = rebirth_state(mtxt, ltxt)
        print(f"__FACE__ ssh_decl={s} lock_ssh={l} patch_seg={segs} baseline={base} delta={delta} "
              f"pre={'yes' if pre else 'no'}")
        print(f"__REBIRTH_COUNT__{vio}")
        # ★ v1.8.0 `M0`(承审计 `F1`): 前件未达 ⇒ `rc=3`(**不适用**)—— 与绿 `rc=0` **相区别**。
        #   旧形下二者同码 ⇒ 以 `rc == 0` 判绿者得【永久性假绿】且无从分辨。
        if not pre:
            print(f"__REBIRTH_NA__ 前件未达([patch] 段数 = {segs} ≠ 终态基线 {base})⇒ 本守"
                  f"【不适用(前件未达)】⇒ rc=3(★ 与绿 rc=0 相区别 · 承审计 F1)", file=sys.stderr)
            return 3
        return 1 if vio else 0
    if cmd == 'face':
        if len(argv) < 3:
            print("face 须给 manifest", file=sys.stderr)
            return 2
        text = open(argv[2], encoding='utf-8', errors='replace').read()
        segs = parse_sections(text)
        parsed, raw = coverage(text)
        print(f"__FACE__ seg={len(segs)} entries={sum(len(s['entries']) for s in segs)} "
              f"raw_entry_lines={raw} ssh_decl={ssh_decl_count(text)}")
        if parsed != raw:
            print(f"__BLIND__ 解析条目 {parsed} ≠ 原始条目行 {raw} ⇒ 谓词有盲区", file=sys.stderr)
            return 2
        return 0
    if cmd == 'sshfaces':
        if len(argv) < 3:
            print("sshfaces 须给至少一个 manifest", file=sys.stderr)
            return 2
        ta = td = 0
        for path in argv[2:]:
            try:
                text = open(path, encoding='utf-8', errors='replace').read()
            except OSError:
                continue
            a = sum(1 for ln in text.splitlines() if 'ssh://' in ln)
            d = ssh_decl_count(text)
            ta += a
            td += d
            if a or d:
                print(f"  {path}: 含注释={a} 非注释={d}")
        print(f"__SSHFACES__ all={ta} decl={td} diff={ta - td}")
        return 0
    if cmd == 'lockstats':
        if len(argv) < 3:
            print("lockstats 须给 lockfile", file=sys.stderr)
            return 2
        text = open(argv[2], encoding='utf-8', errors='replace').read()
        st = lock_stats(text)
        print(f"__LOCK__ pkgs={st['pkgs']} git_pkgs={st['git_pkgs']} "
              f"same_name_multi={st['same_name_multi']} commits={st['commits']} "
              f"revtag_dual={st['revtag_dual']}")
        for c, v in sorted(st['dual_detail'].items()):
            print(f"  DUAL commit={c[:12]} rev={sorted(v['rev'])} tag={sorted(v['tag'])}")
        return 0
    if cmd == 'align':
        if len(argv) < 3:
            print("align 须给 manifest", file=sys.stderr)
            return 2
        text = open(argv[2], encoding='utf-8', errors='replace').read()
        decl, patch = face_sets(text)
        segs = patch_seg_count(text)
        print(f"__FACE__ decl={len(decl)} patch={len(patch)} both={len(decl & patch)} "
              f"decl_only={len(decl - patch)} patch_only={len(patch - decl)} patch_seg={segs}")
        print("__DECL_ONLY__" + ",".join(sorted(decl - patch)))
        print("__PATCH_ONLY__" + ",".join(sorted(patch - decl)))
        return 0
    if cmd == 'transitive':
        if len(argv) < 4:
            print("transitive 须给 <manifest> <lockfile>", file=sys.stderr)
            return 2
        try:
            mtext = open(argv[2], encoding='utf-8', errors='replace').read()
            ltext = open(argv[3], encoding='utf-8', errors='replace').read()
        except OSError as e:
            print(f"__UNREACHABLE__ {e}", file=sys.stderr)
            return 2
        decl, patch = face_sets(mtext)
        lock = lock_git_pkg_names(ltext)
        trans = sorted(lock - decl - patch)
        print(f"__TRANSITIVE__ lock_git={len(lock)} decl={len(decl)} patch={len(patch)} "
              f"trans_only={len(trans)}")
        print("__TRANS_ONLY__" + ",".join(trans))
        return 0
    if cmd == 'align-check':
        # 用法: align-check <manifest> <lockfile> <decl_reg> <patch_reg> <trans_reg>
        #   名册三集皆以 `,` 分隔(空集 ⇒ 空串); 出口码见 align_check 之档串。
        #   ★ 供 `verify_release.sh` 之 `检查24` **取出口码**(承 `ar` 裁决 04 裁甲取丙)。
        if len(argv) < 7:
            print("align-check 须给 <manifest> <lockfile> <decl_reg> <patch_reg> <trans_reg>",
                  file=sys.stderr)
            return 2
        try:
            mtext = open(argv[2], encoding='utf-8', errors='replace').read()
            ltext = open(argv[3], encoding='utf-8', errors='replace').read()
        except OSError as e:
            print(f"__UNREACHABLE__ {e}", file=sys.stderr)
            mtext = ltext = None
        rc, lines = align_check(mtext, ltext, argv[4], argv[5], argv[6])
        for ln in lines:
            print(ln)
        return rc
    print(f"未知子命令: {cmd}", file=sys.stderr)
    return 2


if __name__ == '__main__':
    sys.exit(main(sys.argv))