diff --git a/spike/hel857/ab_scenarios.py b/spike/hel857/ab_scenarios.py index eeed470..d8fc375 100644 --- a/spike/hel857/ab_scenarios.py +++ b/spike/hel857/ab_scenarios.py @@ -93,6 +93,26 @@ HEL-865 同一承诺是谁答应又告诉了谁(总管对 070f4fd 独立复核 中英文声明支持即同等核验;不加八句/人称禁词黑名单,不新增自然语言 引擎,不重做实验,不限制产品表达。 +HEL-866 动词类别与参数类型/对象绑定(总管对 5ebe407 独立复核六组 + "receive/fulfill 后直接宾语误当 tell 接收者"反例):HEL-865 的状态小句 + 把动词后槽位一律当"告知接收者"核准——receive/fulfill 类动词后的槽位 + 实际是直接宾语(被收到/接待的对象、被履行的事项),与 tell 的接收者 + 不能共用一个槽后只核准"我/me"就通过。本轮只修按动词类别声明并核准 + 允许参数: + - tell 类(告诉/告知/说;told/informed/notified):动词后=告知接收者槽, + 须为场景接收者(我/me,省略继承); + - receive 类(收到/拿到;received/got):动词后=被收到对象槽(面试 + 结果,省略按原回指规则继承)——显式人物代词(received me/收到我) + 是收到"人"而非面试结果,"received me the interview result"属多余 + 人物参数,均不绑定 → 保守失败;已结受收方主语规则不变(场景接收者 + 或省略;未结侧兼容接收者未收到告知/承诺者未收到结果两读法); + - fulfill 类(兑现/解决):动词后=被履行事项槽(承诺/约定,省略继承) + ——显式人物代词(兑现我)不是可核验的被履行对象 → 保守失败; + 主语槽规则沿 HEL-865 不变。中英文同等核验;不加六句/人称禁词黑名单, + 不新增通用自然语言引擎,不重做实验,不扩大产品表达限制;全部既有 + 正例("我还没有收到"、haven't received it yet 三读法、省略继承形态) + 保留通过。 + 对照设计(批准范围第5条): 现状路径 = docs/reuse-hel847/probe_recall.py 的生产语义重放(总管已核对的 v2 基线, 词面 FTS trigram 检索 + fixture gist + 带门回读),不读写真实库; @@ -808,18 +828,23 @@ _PROMISE_CLAUSE_ZH = re.compile( # HEL-865:tell 类接收者槽绑定场景接收者——"现在已经告诉你了"(告知的 # 是用户本人)不能证明已告知伴侣;receive 类受收方=场景接收者(省略/话题 # 形态继承,无显式角色槽)。 +# HEL-866:动词后槽按动词类别核准(_bind_state_clause)——tell 后=告知 +# 接收者;receive/fulfill 类已结形态语法本身不含动词后代词槽("收到我 +# 了"无法完整匹配,保守失败)。 _CLOSED_STATE_ZH = tuple(re.compile(p) for p in ( rf"(?:面试结果|结果)?(?:现在)?(?:已经|已)?(?P告诉|告知)" rf"(?P我|你)?了", rf"(?:面试结果|结果)?(?:现在)?(?:已经|已)?(?P收到|拿到|兑现|解决)" rf"(?:了)?")) # O 未结状态小句:正确对象/裸回指?→现在?→主语我?→还没有/还没/尚未→ -# 告知/收到/拿到/兑现/说→接收者?。 +# 告知/收到/拿到/兑现/说→动词后槽?。 # HEL-865 按动词类别绑定主语:tell 类(告诉/告知/说)主语=承诺者(省略 # 继承"你");receive 类(收到/拿到)主语=场景接收者("我还没有收到", # 省略兼容两读法);fulfill 类(兑现)主语=承诺者(兑现承诺的一方)。 -# 接收者槽一律必须是场景接收者(我,省略继承)——"还没有告诉你"不能 -# 证明尚未告知"我"。 +# HEL-866:动词后槽不再一律当告知接收者——tell 类=告知接收者(须场景 +# 接收者"我",省略继承,"还没有告诉你"不能证明尚未告知"我");receive +# 类=被收到对象(省略继承面试结果,显式"我/你"是收到"人"→失败);fulfill +# 类=被履行事项(省略继承,显式人物代词→失败)。绑定见 _bind_state_clause。 _OPEN_STATE_ZH = re.compile( rf"(?:面试结果|结果)?(?:现在)?(?P我)?(?:还没有|还没|尚未)" rf"(?P告诉|告知|收到|拿到|兑现|说)(?P我|你)?呢?") @@ -847,13 +872,20 @@ _OPEN_STATE_EN = re.compile( r"(?Pme|you|us|them)?\s*" r"(?:it|the result|the interview result|the notification)?\s*(?:yet)?" r"(?:\s*[.!?])?", re.IGNORECASE) +# HEL-866:EN 未结状态动词后两槽(人物代词槽+对象NP槽)按动词类别核准 +# (_bind_state_clause):tell 类人物代词槽=告知接收者(须 me,省略继承, +# 对象NP可跟);receive 类人物代词槽=被收到"人"——received me / got me / +# received me the interview result 均不绑定 → 保守失败,被收到对象只能 +# 是对象NP(it/the result/the interview result)或省略继承。 # 语气(封闭类,非逐句黑名单):问号或句末疑问/祈使语气词 → 非陈述语气。 _QUESTION_MARK_CHARS = ("?", "?") _ZH_MOOD_PARTICLES = "吗呢吧嘛呗" # 动词类别(角色绑定用,预声明闭类):tell=告知动作(主语=承诺者、 -# 接收者=场景接收者);receive=收到/拿到(已结受收方=场景接收者;未结主语 -# 可为接收者〔未收到告知〕或承诺者〔未收到结果、告知前提未成立〕); -# fulfill=兑现(主语=承诺者)。 +# 动词后=告知接收者=场景接收者);receive=收到/拿到(已结受收方=场景 +# 接收者;未结主语可为接收者〔未收到告知〕或承诺者〔未收到结果、告知 +# 前提未成立〕;动词后=被收到对象〔面试结果,省略继承;显式人物代词 +# 或人物+对象并见均失败〕);fulfill=兑现/解决(主语=承诺者;动词后= +# 被履行事项〔省略继承;显式人物代词失败〕)。 _TELL_VERBS_ZH = ("告诉", "告知", "说") _TELL_VERBS_EN = ("told", "informed", "notified") _RECEIVE_VERBS_ZH = ("收到", "拿到") @@ -898,18 +930,25 @@ def _bind_promise_clause(m) -> dict: def _bind_state_clause(m, kind: str, is_en: bool) -> dict: - """C/O 状态小句角色绑定(按动词类别): + """C/O 状态小句角色绑定(HEL-866 起按动词类别核准动词后参数槽): - tell 类(告诉/告知/说;told/informed/notified):主语=承诺者(省略 - 继承——状态小句承前省略主语即承诺者"你/you"),接收者=场景接收者; + 继承——状态小句承前省略主语即承诺者"你/you"),动词后是**告知接收者 + 槽**——须为场景接收者(我/me)或省略继承; - receive 类(收到/拿到;received/got):已结——受收方=场景接收者 (主语只认映射为接收者的代词或省略;承诺者"收到结果"不证明已告知, EN 已结受收主语槽本身不含承诺者代词,we/对象主语不绑定);未结—— 主语可为接收者(未收到告知)或承诺者(未收到结果、告知前提未成立), - 省略兼容两读法均证未结; - - fulfill 类(兑现):主语=承诺者(省略继承),其余不绑定。 - 接收者槽(me/我)一律必须是场景接收者或省略。显式角色与场景目标冲突、 - 他人或未知 → bound=False(与承诺小句不属于同一项声明)。 + 省略兼容两读法均证未结;动词后是**被收到对象槽**(面试结果,省略按 + 原回指规则继承)——显式人物代词("收到我"/received me)是被收到 + "人"而非面试结果,"received me the interview result"属多余人物参数, + 均不绑定本场景声明 → 保守失败; + - fulfill 类(兑现/解决):主语=承诺者(省略继承);动词后是**被履行 + 事项槽**(承诺/约定,省略继承)——显式人物代词("兑现我")不是可 + 核验的被履行对象 → 保守失败。 + 主语/接收者显式角色与场景目标冲突、他人或未知 → bound=False(与承诺 + 小句不属于同一项声明)。动词后槽按各类别声明核验,不再共用同一接收者 + 槽后只核准"我/me"。 """ gd = m.groupdict() subj = gd.get("subject") @@ -920,24 +959,36 @@ def _bind_state_clause(m, kind: str, is_en: bool) -> dict: else _pronoun_role(subj, _PRONOUN_ROLE_ZH)) r_role = (_en_pronoun_role(recv) if is_en else _pronoun_role(recv, _PRONOUN_ROLE_ZH)) - recv_ok = r_role in ("omitted", SCENE_RECEIVER) tell = verb_low in _TELL_VERBS_EN if is_en else verb in _TELL_VERBS_ZH receive = (verb_low in _RECEIVE_VERBS_EN if is_en else verb in _RECEIVE_VERBS_ZH) if tell: subj_ok = s_role in ("omitted", SCENE_PROMISOR) + # tell 类动词后=告知接收者槽:场景接收者(我/me)或省略继承 + recv_ok = r_role in ("omitted", SCENE_RECEIVER) + post_slot = "told_receiver" elif receive: if kind == "closed": subj_ok = s_role in ("omitted", SCENE_RECEIVER) else: subj_ok = s_role in ("omitted", SCENE_RECEIVER, SCENE_PROMISOR) + # receive 类动词后=被收到对象槽(面试结果,省略继承):显式人物 + # 代词是被收到"人"而非面试结果,人物+对象并见属多余人物参数, + # 均不绑定 → 保守失败 + recv_ok = not recv + post_slot = "received_object" else: # fulfill(兑现/解决)——兑现承诺的一方是承诺者 subj_ok = s_role in ("omitted", SCENE_PROMISOR) + # fulfill 类动词后=被履行事项槽(承诺/约定,省略继承):显式人物 + # 代词不是可核验的被履行对象 → 保守失败 + recv_ok = not recv + post_slot = "fulfilled_object" return { "kind": kind, "subject": subj or "", "subject_role": s_role, "receiver": recv or "", "receiver_role": r_role, - "verb": verb, "bound": bool(subj_ok and recv_ok), + "verb": verb, "post_verb_slot": post_slot, + "bound": bool(subj_ok and recv_ok), } @@ -1031,8 +1082,12 @@ def parse_supported_answer(text: str) -> dict: "者=我/me(伴侣),对象=面试结果。主语/接收者省略按预声" "明回指规则继承:承诺小句省略主语→承诺者;省略接收者→" "场景接收者;状态小句按动词类别继承(tell 类主语=承诺" - "者,receive 类受收方=场景接收者,未结 receive 主语兼容" - "接收者/承诺者两读法,fulfill 类主语=承诺者)。", + "者、动词后=告知接收者;receive 类受收方=场景接收者," + "未结 receive 主语兼容接收者/承诺者两读法,动词后=被" + "收到对象(面试结果,省略继承,显式人物代词失败);" + "fulfill 类主语=承诺者、动词后=被履行事项(省略继承," + "显式人物代词失败)——HEL-866 起动词后槽按动词类别" + "声明并核准,不共用同一接收者槽。", }, "role_conflict_clauses": role_conflicts, "conflict_free": not role_conflicts, @@ -1042,12 +1097,14 @@ def parse_supported_answer(text: str) -> dict: def affirms_promise_state(text: str, *, closed: bool) -> bool: """肯定陈述已结/未结状态并绑回同一项承诺声明(HEL-864 全量解析 + - HEL-865 场景角色绑定):全篇陈述语气 AND 每个小句都被受支持结构完整消费 - 且角色绑定场景目标 AND ≥1 承诺小句 AND ≥1 对应极性状态小句 AND 0 个 - 反向极性小句 AND 无不确定表述。状态短语属于别的对象(体检报告/体检情况)、 - 裸名词小句、全段冲突尾句、条件句、疑问语气、状态后否认均无法消费; - 告知接收者与承诺接收者不同("现在已经告诉你了"/told them)、承诺指向 - 他人(告诉她)、承诺方反转(我答应告诉你)均属另一项声明 → 保守判失败。 + HEL-865 场景角色绑定 + HEL-866 动词类别参数槽核准):全篇陈述语气 AND + 每个小句都被受支持结构完整消费且角色绑定场景目标 AND ≥1 承诺小句 AND + ≥1 对应极性状态小句 AND 0 个反向极性小句 AND 无不确定表述。状态短语 + 属于别的对象(体检报告/体检情况)、裸名词小句、全段冲突尾句、条件句、 + 疑问语气、状态后否认均无法消费;告知接收者与承诺接收者不同("现在已经 + 告诉你了"/told them)、承诺指向他人(告诉她)、承诺方反转(我答应告诉 + 你)、receive/fulfill 后直接宾语为人物(received me/收到我/兑现我)均属 + 另一项声明或不合格参数 → 保守判失败。 """ p = parse_supported_answer(text) wanted = p["closed_clauses"] if closed else p["open_clauses"] @@ -1170,19 +1227,22 @@ def s3_result(*, b_hits3: list, b_hits3b: list, m_hits3: list, m_hits3b: list, def s6_result(orch: dict, a_resolved: dict, a_unresolved: dict) -> dict: - """S6 判据(HEL-864 有界语法全量解析 + HEL-865 场景角色绑定): + """S6 判据(HEL-864 有界语法全量解析 + HEL-865 场景角色绑定 + + HEL-866 动词类别参数槽核准): 编排层(已知id置 resolved 留痕 + 未结对照块自洽)+ 状态进上下文层(正确对象 的 resolved 状态实际写入带门装配块)+ 模型使用层(两侧请求成功;两侧回答 均须全篇陈述语气且每个小句被受支持结构完整消费并绑定同一项场景声明—— 承诺小句"主语(=承诺者你/you 或省略继承)→承诺动词(未被否定)→告知 动作(未被否定)→接收者(=场景接收者我/me 或省略继承)→面试结果到此 - 为止"+ 对应极性状态小句(已结/未结,角色同样绑定);外来对象(含无"结果" - 后缀)、状态后置对象、裸名词小句、全段冲突尾句、条件句、疑问语气、状态 - 短语后否认、对象与动作串错、仅陈述告知而无承诺断言、告知接收者与承诺 - 接收者不同(告诉你/told them)、承诺指向他人(告诉她)、承诺方反转 - (我答应告诉你)、不确定表述、两侧同答不区分状态均判失败,不以字面 - 提到'告诉/答应'冒充状态正确;无法按受支持表达完整解析或角色不能绑定 - 同一项声明即保守判失败)。""" + 为止"+ 对应极性状态小句(已结/未结,角色与动词后参数槽按动词类别绑定: + tell 后=告知接收者,receive 后=被收到对象,fulfill 后=被履行事项);外来 + 对象(含无"结果"后缀)、状态后置对象、裸名词小句、全段冲突尾句、条件句、 + 疑问语气、状态短语后否认、对象与动作串错、仅陈述告知而无承诺断言、告知 + 接收者与承诺接收者不同(告诉你/told them)、承诺指向他人(告诉她)、承诺 + 方反转(我答应告诉你)、receive/fulfill 后直接宾语为人物(received me/ + 收到我/兑现我)或多余人物参数(received me the interview result)、不 + 确定表述、两侧同答不区分状态均判失败,不以字面提到'告诉/答应'冒充状态 + 正确;无法按受支持表达完整解析或角色不能绑定同一项声明即保守判失败)。""" ans_r = (a_resolved or {}).get("answer") or "" ans_u = (a_unresolved or {}).get("answer") or "" ok_r = bool((a_resolved or {}).get("ok")) @@ -1234,7 +1294,14 @@ def s6_result(orch: dict, a_resolved: dict, a_unresolved: dict) -> dict: "指向他人(告诉她)、承诺方反转(我答应)、不确定表述、" "两侧同答、已结侧答未结、未结侧宣称已收到均判失败(无法" "完整解析或角色不能绑定同一项声明即保守判失败,不以字面" - "提到'告诉/答应'冒充)"} + "告诉'冒充)", + "expect_verb_class_slots": "tell 类动词后=告知接收者(场景接收者" + "我/me,省略继承);receive 类动词后=被收到对象(面试" + "结果,省略继承;received me/收到我/拿到我=收到'人'," + "received me the interview result=多余人物参数,均判" + "失败);fulfill 类动词后=被履行事项(省略继承;兑现我" + "判失败)——动词后槽按动词类别声明并核准,不共用同一" + "接收者槽(HEL-866)。"} # HEL-860:无门 assemble_block 出口已删除——全部问答最终装配一律经 diff --git a/spike/hel857/audit866.py b/spike/hel857/audit866.py new file mode 100644 index 0000000..d3ff66d --- /dev/null +++ b/spike/hel857/audit866.py @@ -0,0 +1,555 @@ +#!/usr/bin/env python3 +"""HEL-866 负例复现脚本:总管独立复核《HEL-865-独立复核.md》六组 +"receive/fulfill 后直接宾语误当 tell 接收者"参数类型串绑反例。 + +零真实模型、零网络(socket 硬拒绝 + HF 离线)、零本地嵌入:S6 判据为纯函数 +代入(真实 s6_result 构造器同一入口);既有判据不回归部分复用 audit861 的 +两组纯函数节(S6 全套正负例 + S2/S3 总判据)、audit862 的四组复核用例、 +audit863 的三组复核用例、audit864 的七组+未结侧镜像及英文正负例与 +audit865 的八组+结构变体+省略继承正例。同一脚本在修复前(5ebe407)与 +修复后代码上均可运行: + + 修复前(--phase prefix,在 5ebe407 工作树上运行)→ 总管复核六组反例 + (已结侧固定规范正例、仅替换未结侧)全部 pass=true(复现);按动词 + 类别的主语×动词后槽位矩阵中人物代词在被收到/被履行对象槽的格子全部 + 误判成功(复现);receive/fulfill 类其他人物代词(收到你/received + them 等)与 audit865 及更早全部既有集维持预期 + 修复后(默认 postfix)→ 六组反例、矩阵人物代词格与结构变体全部判失败; + 规范正例、"我还没有收到"、英文 haven't received it yet 三读法(省略 + 主语/承诺者主语/接收者主语)、省略继承正例、tell 类带接收者+对象NP + 形态保留通过;140 角色组合、audit861/862/863/864/865 全部既有正负例、 + S2/S3 总判据维持预期 + +补正口径(固定合成语料领域内的有界槽位绑定,按动词类别声明并核准允许 +参数):HEL-865 的状态小句绑定把动词后槽位一律当"告知接收者"核准—— +receive/fulfill 类动词后的槽位实际是直接宾语(被收到/接待的对象、被履行 +的事项),与 tell 的接收者不能共用一个槽后只核准"我/me"就通过。本轮按 +动词类别核验:tell 类动词后=告知接收者(场景接收者"我"/me,省略继承); +receive 类动词后=被收到对象(面试结果,省略按原回指规则继承——显式人物 +代词 received me/收到我/拿到我是收到"人"而非面试结果,received me the +interview result 属多余人物参数,均不绑定 → 保守失败);fulfill 类动词 +后=被履行事项(承诺/约定,省略继承——"兑现我"不是可核验的被履行对象 → +保守失败)。主语槽规则沿 HEL-865 不变(tell/fulfill 类主语=承诺者或省略; +receive 类已结受收方=场景接收者或省略、未结主语兼容接收者未收到告知/ +承诺者未收到结果两读法)。中英文同等核验;不加六句/人称禁词黑名单, +不新增通用自然语言引擎,不重做实验,不扩大产品表达限制。 + +退出码真实依赖正负例:除全部用例满足预期外,还对每个用例做翻转自检 +(flip 任一用例的实际结果,用例总判必翻转);任一翻转不改变结论则退出 1。 + +用法:python -m spike.hel857.audit866 [--phase prefix|postfix] [输出json路径] +退出码:prefix=0 当且仅当六组反例与矩阵人物代词格全部复现(pass=true)且 +其余用例维持预期;postfix=0 当且仅当六组+矩阵人物格+结构变体全部判失败、 +正例与既有判据不回归、翻转自检通过、权威账与既有证据只读未动。权威嵌入账 +只读快照核对含逐行摘要(不补种不回退不双扣)。 +""" +from __future__ import annotations + +import hashlib +import itertools +import json +import os +import socket +import sqlite3 +import subprocess +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parents[2] +LEDGER = Path("/home/xiaomo/multica_workspaces/hello-af23464d222e/" + "hel-859-1f079e50f551/workdir/audit-data/embed_ledger.sqlite3") +os.environ["HEL857_EMBED_LEDGER"] = str(LEDGER) +os.environ.setdefault("HF_HUB_OFFLINE", "1") +os.environ.setdefault("TRANSFORMERS_OFFLINE", "1") + + +def deny_network(*a, **k): + raise RuntimeError("audit866 blocks network") + + +socket.socket.connect = deny_network +socket.socket.connect_ex = deny_network + +from . import ab_scenarios as A +from .audit861 import section_s2_s3_no_regression, section_s6_affirmative +from .audit862 import (HELD_S2S3_EXPECT, HELD_S6_EXPECT, ORCH6, + REVIEW_CASES, REVIEW_NEGATIVE_NAMES) +from .audit863 import ADDITIONAL_CASES +from .audit864 import (EN_NEGATIVE, EN_POSITIVE, GOOD_CLOSED, GOOD_OPEN, + SEVEN_CLOSED, SEVEN_OPEN_MIRROR) +from .audit865 import (EGC, EGO, HELD_VARIANTS, MANAGER_EIGHT, + OMISSION_INHERIT_POSITIVES, STRUCTURAL_NEGATIVES) + +OUT = {"real_network_allowed": False, "real_model_requests": 0, + "local_embeddings": 0} +PHASE = "postfix" + + +def ledger_snapshot(): + """权威账只读快照(含逐行摘要:全部行序列化 SHA256,前后核对用)。""" + with sqlite3.connect(f"file:{LEDGER}?mode=ro", uri=True) as c: + rows = c.execute("select * from embed_ops order by id").fetchall() + return {"counts": dict(c.execute("select kind,sum(items) from embed_ops " + "group by kind")), + "rows": len(rows), + "row_digest": hashlib.sha256( + json.dumps(rows).encode()).hexdigest()} + + +PRESERVED_EVIDENCE = [ + "ab_scenarios_run1.json", "ab_scenarios.json", "rework858_offline.json", + "rework859_offline.json", "rework860_offline.json", "audit859_prefix.json", + "audit859_postfix.json", "audit859_reuse860.json", "audit860_prefix.json", + "audit860_postfix.json", "audit861_prefix.json", "audit861_postfix.json", + "audit861_reuse862.json", "audit862_prefix.json", "audit862_postfix.json", + "audit861_reuse863.json", "audit863_prefix.json", "audit863_postfix.json", + "audit864_prefix.json", "audit864_postfix.json", "audit861_reuse864.json", + "audit865_prefix.json", "audit865_postfix.json", "audit861_reuse865.json", + "gate0_ledger.json", "gate1_backend.json", "gate2_privacy.json", + "preflight.json", "resource_manifest_pre.json", "lock-notes.md", +] + + +def evidence_hashes(): + res = {} + for name in PRESERVED_EVIDENCE: + p = REPO / "spike/hel857/results" / name + res[name] = (hashlib.sha256(p.read_bytes()).hexdigest() + if p.exists() else "missing") + return res + + +# --------------------------------------------------------------------------- +# 总管《HEL-865-独立复核.md》六组反例(原文逐字;已结侧固定规范正例, +# 每次仅替换未结一侧,排除其他原因)。同一真实 s6_result 入口。 +def s6_answer(s): + return {"answer": s, "ok": True} + + +P_EN = "You promised to tell me the interview result, " +P_ZH = "你答应周五晚告诉我面试结果," + +MANAGER_SIX = { + "en_receive_person": + (EGC, P_EN + "but you haven't received me yet."), + "en_get_person": + (EGC, P_EN + "but you haven't got me yet."), + "en_receive_person_and_result": + (EGC, P_EN + "but you haven't received me the interview result yet."), + "zh_receive_person": + (GOOD_CLOSED, P_ZH + "还没有收到我。"), + "zh_get_person": + (GOOD_CLOSED, P_ZH + "还没拿到我。"), + "zh_fulfill_person": + (GOOD_CLOSED, P_ZH + "还没兑现我。"), +} + +# 结构变体(证明按动词类别+参数槽绑定而非六句黑名单:人物代词在被收到/ +# 被履行对象槽的换代词/换对象NP/话题形态/多参数并见,修前同样误判成功; +# receive/fulfill 类其他人物代词在 5ebe407 已被角色绑定判失败,作双期负例)。 +STRUCTURAL_NEGATIVES_866 = { + "en_open_get_person_the_result": + (EGC, P_EN + "but you haven't got me the result yet."), + "en_open_receive_person_it": + (EGC, P_EN + "but you haven't received me it yet."), + "en_open_receive_person_omitted_subject": + (EGC, P_EN + "haven't received me yet."), + "zh_open_receive_person_topic": + (GOOD_CLOSED, P_ZH + "面试结果还没有收到我。"), + "en_receive_person_you": + (EGC, P_EN + "but you haven't received you yet."), + "en_receive_person_them": + (EGC, P_EN + "but you haven't received them yet."), + "en_get_person_us": + (EGC, P_EN + "but you haven't got us yet."), + "zh_open_receive_person_ni": + (GOOD_CLOSED, P_ZH + "还没有收到你。"), + "zh_open_get_person_ni": + (GOOD_CLOSED, P_ZH + "还没拿到你。"), + "zh_open_fulfill_person_ni": + (GOOD_CLOSED, P_ZH + "还没兑现你。"), +} +# 5ebe407 已判失败(维持负例,两期极性固定):其他人物代词/未映射代词在 +# 动词后槽已被 HEL-865 角色绑定拒绝。 +STRUCTURAL_ALWAYS_NEGATIVE_866 = ( + "en_receive_person_you", "en_receive_person_them", "en_get_person_us", + "zh_open_receive_person_ni", "zh_open_get_person_ni", + "zh_open_fulfill_person_ni") + +# 按动词类别的主语×动词后槽位矩阵(中英文,覆盖告知/接收/履行动作各自的 +# 主语、直接对象、接收者与多参数冲突): +# receive 类——人物代词格('我'/'me')prefix 期误判成功(复现)、postfix +# 期判失败;省略格与对象NP格(it)两期均通过(省略/对象=被收到对象, +# 按原回指规则继承面试结果); +# fulfill 类——省略格两期通过(被履行事项省略继承);主语'我'(接收者 +# 作兑现方)与人物代词格均失败(主语规则沿 HEL-865,双期负例部分); +# tell 类对照——接收者'我'格两期通过(动词后是告知接收者槽,证明未把 +# tell 一并收紧)。 +def _matrix_cases() -> dict[str, dict]: + cases: dict[str, dict] = {} + # EN receive:主语{you, i, ''} × 动词后槽{me, it}('it'=对象NP, + # 'me'=人物代词);期望:me 格 prefix 复现/postfix 失败,it 格两期通过 + for s, obj in itertools.product(["you", "i", ""], ["me", "it"]): + subj = f"{s} " if s else "" + name = f"en_receive_open_s-{s or 'omit'}_post-{obj}" + cases[name] = { + "verb_class": "receive", "lang": "en", + "resolved": EGC, + "unresolved": P_EN + f"but {subj}haven't received {obj} yet.", + "person_object": obj == "me", + "expect_always_pass": obj == "it", + } + # ZH receive:主语{我, ''} × 动词后槽{我, ''};期望:省略主语+省略槽 + # 与显式主语我+省略槽两期通过("我还没有收到"/"还没有收到"), + # 人物代词格 prefix 复现/postfix 失败 + for s, post in itertools.product(["我", ""], ["我", ""]): + subj = s or "" + name = f"zh_receive_open_s-{s or 'omit'}_post-{post or 'omit'}" + cases[name] = { + "verb_class": "receive", "lang": "zh", + "resolved": GOOD_CLOSED, + "unresolved": P_ZH + f"{subj}还没有收到{post}。", + "person_object": post == "我", + "expect_always_pass": post == "", + } + # ZH fulfill:主语{我, ''} × 动词后槽{我, ''};期望:双省略格两期通过 + # (被履行事项省略继承),主语我(接收者作兑现方,主语槽已拒绝)与 + # 主语我+人物代词格双期失败,省略主语+人物代词格(=六组之六)prefix + # 复现/postfix 失败 + for s, post in itertools.product(["我", ""], ["我", ""]): + subj = s or "" + name = f"zh_fulfill_open_s-{s or 'omit'}_post-{post or 'omit'}" + cases[name] = { + "verb_class": "fulfill", "lang": "zh", + "resolved": GOOD_CLOSED, + "unresolved": P_ZH + f"{subj}还没兑现{post}。", + "person_object": post == "我" and s == "", + "expect_always_pass": s == "" and post == "", + "expect_always_fail": s == "我", + } + # EN tell 对照:主语{you, ''} × 动词后槽{me, ''}——两期均通过 + # (tell 类动词后=告知接收者槽,保留 HEL-865 行为) + for s, recv in itertools.product(["you", ""], ["me", ""]): + subj = f"{s} " if s else "" + name = f"en_tell_open_s-{s or 'omit'}_post-{recv or 'omit'}" + cases[name] = { + "verb_class": "tell", "lang": "en", + "resolved": EGC, + "unresolved": P_EN + f"but {subj}haven't told {recv} yet.", + "person_object": False, + "expect_always_pass": True, + } + return cases + + +# 保留正例:规范正例、"我还没有收到"、英文 haven't received it yet 三读法 +# (省略主语/承诺者主语/接收者主语)、tell 类带接收者+对象NP、话题/裸回指/ +# 兑现省略形态——两期均须通过(绑定按动词类别+槽位,不按字面禁词)。 +HELD_POSITIVE_866 = { + "canonical_zh": (GOOD_CLOSED, GOOD_OPEN), + "canonical_en": (EGC, EGO), + "zh_receive_explicit_subject": (GOOD_CLOSED, P_ZH + "我还没有收到。"), + "zh_receive_omitted_subject": (GOOD_CLOSED, P_ZH + "还没有收到。"), + "zh_receive_topic": (GOOD_CLOSED, P_ZH + "面试结果还没有收到。"), + "zh_fulfill_omitted": (GOOD_CLOSED, P_ZH + "还没兑现。"), + "en_receive_recipient_reading": + (EGC, P_EN + "but I haven't received it yet."), + "en_receive_promisor_reading": + (EGC, P_EN + "but you haven't received it yet."), + "en_receive_omitted_subject": + (EGC, P_EN + "haven't received it yet."), + "en_tell_receiver_and_object": + (EGC, P_EN + "but you haven't told me the interview result yet."), +} + + +def _record(name: str, resolved: str, unresolved: str, expected: bool) -> dict: + result = A.s6_result(ORCH6, s6_answer(resolved), s6_answer(unresolved)) + return { + "resolved_input": resolved, "unresolved_input": unresolved, + "expected_pass": expected, "actual_pass": result["pass"], + "resolved_parse": A.parse_supported_answer(resolved), + "unresolved_parse": A.parse_supported_answer(unresolved), + "result": result, + } + + +def section_manager_six() -> dict: + """六组反例:prefix 期预期 pass=true(复现),postfix 期判失败。""" + return {name: _record(name, r, u, PHASE == "prefix") + for name, (r, u) in MANAGER_SIX.items()} + + +def section_structural() -> dict: + """结构变体:人物代词在被收到/被履行对象槽的换形(prefix 复现/ + postfix 失败)与双期负例(5ebe407 已被角色绑定拒绝)。""" + out = {} + for name, (r, u) in STRUCTURAL_NEGATIVES_866.items(): + expected = (False if name in STRUCTURAL_ALWAYS_NEGATIVE_866 + else PHASE == "prefix") + out[name] = _record(name, r, u, expected) + return out + + +def section_param_matrix() -> dict: + """按动词类别的主语×动词后槽位矩阵:人物代词格 prefix 复现/postfix + 失败;对象NP/省略格与 tell 对照格两期通过(期望按格声明)。""" + out = {} + for name, spec in _matrix_cases().items(): + if spec.get("expect_always_fail"): + expected = False + elif spec["person_object"]: + expected = PHASE == "prefix" + else: + expected = True + rec = _record(name, spec["resolved"], spec["unresolved"], expected) + rec.update({k: v for k, v in spec.items() + if k not in ("resolved", "unresolved")}) + out[name] = rec + return out + + +def section_role_matrix() -> dict: + """140 个中英文承诺主语×接收者角色组合(沿 review865 独立矩阵,与 + 本轮动词后槽无关的承诺/告知角色绑定不回归;极性两期固定)。""" + out = {} + + def record(name, r, u, expected): + out[name] = _record(name, r, u, expected) + + for s, r in itertools.product(["你", "我", "他", "我们", ""], + ["我", "你", "她", "他们", ""]): + p = f"{s}答应周五晚告诉{r}面试结果," + expected = s in ("你", "") and r in ("我", "") + record(f"zh_p_closed_{s}_{r}", p + "现在已经告诉我了。", GOOD_OPEN, + expected) + record(f"zh_p_open_{s}_{r}", GOOD_CLOSED, p + "还没有告诉我。", + expected) + for s, r in itertools.product(["You", "I", "We", "He", ""], + ["me", "you", "her", "them", ""]): + p = f"{s} promised to tell {r} the interview result, " + expected = s in ("You", "") and r in ("me", "") + record(f"en_p_closed_{s}_{r}", p + "you have already told me.", + GOOD_OPEN, expected) + record(f"en_p_open_{s}_{r}", GOOD_CLOSED, + p + "you haven't told me yet.", expected) + for s, r in itertools.product(["you", "i", "we", ""], + ["me", "you", "us", "them", ""]): + p = P_EN + expected = s in ("you", "") and r in ("me", "") + record(f"en_tell_closed_{s}_{r}", p + f"{s} have already told {r}.", + GOOD_OPEN, expected) + record(f"en_tell_open_{s}_{r}", GOOD_CLOSED, + p + f"{s} haven't told {r} yet.", expected) + return out + + +def section_held_positives() -> dict: + """保留正例:两期均须通过(含 tell 类带对象NP,证明未过度收紧)。""" + return {name: _record(name, r, u, True) + for name, (r, u) in HELD_POSITIVE_866.items()} + + +def section_held_865() -> dict: + """audit865 八组+结构变体+省略继承正例+review864 变体与规范正例 + (5ebe407 已按 HEL-865 修复,极性固定,不回归)。""" + out = {} + for name, (r, u) in MANAGER_EIGHT.items(): + out[f"held865.eight.{name}"] = _record(name, r, u, False) + for name, (r, u) in STRUCTURAL_NEGATIVES.items(): + out[f"held865.structural.{name}"] = _record(name, r, u, False) + for name, (r, u) in OMISSION_INHERIT_POSITIVES.items(): + out[f"held865.omission.{name}"] = _record(name, r, u, True) + for name, v in HELD_VARIANTS.items(): + out[f"held865.variant.{name}"] = _record(name, v, GOOD_OPEN, False) + out["held865.variant.canonical_positive"] = _record( + "canonical_positive", GOOD_CLOSED, GOOD_OPEN, True) + return out + + +def section_held_864() -> dict: + """audit864 七组+未结侧镜像与英文正负例(极性固定,不回归)。""" + out = {} + for name, closed in SEVEN_CLOSED.items(): + out[f"held864.{name}"] = _record(name, closed, GOOD_OPEN, False) + for name, opened in SEVEN_OPEN_MIRROR.items(): + out[f"held864.{name}"] = _record(name, GOOD_CLOSED, opened, False) + for name, (r, u) in EN_POSITIVE.items(): + out[f"held864.en.{name}"] = _record(name, r, u, True) + for name, (r, u) in EN_NEGATIVE.items(): + out[f"held864.en.{name}"] = _record(name, r, u, False) + return out + + +def section_held_rounds() -> dict: + """HEL-862 四组与 HEL-863 三组:极性固定,不回归。""" + out = {} + for name, (closed, opened) in REVIEW_CASES.items(): + out[f"held_rounds.review.{name}"] = _record( + name, closed, opened, name not in REVIEW_NEGATIVE_NAMES) + for name, closed in ADDITIONAL_CASES.items(): + out[f"held_rounds.additional.{name}"] = _record( + name, closed, GOOD_OPEN, False) + return out + + +def _case_table() -> tuple[dict, dict]: + """全部用例的实际 pass 与预期极性(退出码公式输入)。""" + table: dict[str, bool] = {} + expect: dict[str, bool] = {} + for prefix, section in ( + ("manager6", OUT["manager_six"]), + ("structural", OUT["structural_variants"]), + ("matrix", OUT["param_matrix"]), + ("roles140", OUT["role_matrix"]), + ("positives", OUT["held_positives"]), + ("held865", OUT["held_865"]), + ("held864", OUT["held_864"]), + ("held_rounds", OUT["held_rounds"])): + for name, c in section.items(): + key = f"{prefix}.{name}" + table[key] = bool(c["actual_pass"]) + expect[key] = bool(c["expected_pass"]) + for name, res in OUT["held_s6_affirmative"].items(): + key = f"held_s6.{name}" + table[key] = bool(res["pass"]) + expect[key] = HELD_S6_EXPECT[name] + for name, res in OUT["held_s2_s3_no_regression"].items(): + key = f"held_s2s3.{name}" + table[key] = bool(res["pass"]) + expect[key] = HELD_S2S3_EXPECT[name] + return table, expect + + +def _all_cases_match(table: dict, expect: dict) -> bool: + return all(table[k] == expect[k] for k in expect) + + +def _flip_dependence(table: dict, expect: dict) -> dict: + """翻转自检:任一用例实际值翻转必须改变用例总判(退出码真实依赖正负例)。""" + base = _all_cases_match(table, expect) + flips = {} + for k in expect: + flipped = dict(table) + flipped[k] = not flipped[k] + flips[k] = _all_cases_match(flipped, expect) != base + return {"base_all_match": base, "every_flip_changes_verdict": all( + flips.values()), "per_case": flips} + + +# --------------------------------------------------------------------------- +def main(argv: list[str]) -> dict: + global PHASE + argv = list(argv) + rest: list[str] = [] + PHASE = "postfix" + it = iter(argv[1:]) + for tok in it: + if tok == "--phase": + PHASE = next(it, "postfix") + elif tok.startswith("--phase="): + PHASE = tok.split("=", 1)[1] + elif not tok.startswith("--"): + rest.append(tok) + args = rest + OUT["phase"] = PHASE + + commit = subprocess.run(["git", "rev-parse", "HEAD"], + capture_output=True, text=True, + cwd=str(REPO)).stdout.strip() + OUT["commit"] = commit + OUT["ledger_before"] = ledger_snapshot() + OUT["preserved_evidence_before"] = evidence_hashes() + + OUT["manager_six"] = section_manager_six() + OUT["structural_variants"] = section_structural() + OUT["param_matrix"] = section_param_matrix() + OUT["role_matrix"] = section_role_matrix() + OUT["held_positives"] = section_held_positives() + OUT["held_865"] = section_held_865() + OUT["held_864"] = section_held_864() + OUT["held_rounds"] = section_held_rounds() + OUT["held_s6_affirmative"] = section_s6_affirmative() + OUT["held_s2_s3_no_regression"] = section_s2_s3_no_regression() + + table, expect = _case_table() + OUT["case_expect"] = {k: bool(v) for k, v in expect.items()} + OUT["case_actual"] = {k: bool(v) for k, v in table.items()} + OUT["all_cases_match_expectation"] = _all_cases_match(table, expect) + OUT["flip_dependence"] = _flip_dependence(table, expect) + + OUT["ledger_after"] = ledger_snapshot() + OUT["ledger_unchanged"] = OUT["ledger_before"] == OUT["ledger_after"] + OUT["preserved_evidence_after"] = evidence_hashes() + OUT["preserved_evidence_unchanged"] = ( + OUT["preserved_evidence_before"] == OUT["preserved_evidence_after"]) + + ms = OUT["manager_six"] + st = OUT["structural_variants"] + mx = OUT["param_matrix"] + person_cells = {k: c for k, c in mx.items() if c["person_object"]} + # ---- prefix:六组反例+人物代词格全部复现(pass=true)---- + six_reproduced = all(c["actual_pass"] for c in ms.values()) + person_cells_reproduced = all(c["actual_pass"] + for c in person_cells.values()) + # ---- postfix:六组反例+人物代词格+结构变体全部判失败 ---- + six_rejected = not any(c["actual_pass"] for c in ms.values()) + person_cells_rejected = not any(c["actual_pass"] + for c in person_cells.values()) + structural_reproduced = all( + st[n]["actual_pass"] for n in st + if n not in STRUCTURAL_ALWAYS_NEGATIVE_866) + structural_rejected = not any(c["actual_pass"] for c in st.values()) + always_negatives_rejected = not any( + st[n]["actual_pass"] for n in STRUCTURAL_ALWAYS_NEGATIVE_866) + # 正例与既有集保留 + OUT["role_matrix_pass"] = all( + c["actual_pass"] == c["expected_pass"] + for c in OUT["role_matrix"].values()) + positives_kept = (all(c["actual_pass"] + for c in OUT["held_positives"].values()) + and OUT["role_matrix_pass"]) + OUT["summary"] = { + "phase": PHASE, + "six_reproduced": six_reproduced, + "six_rejected": six_rejected, + "person_object_cells_reproduced": person_cells_reproduced, + "person_object_cells_rejected": person_cells_rejected, + "structural_variants_reproduced": structural_reproduced, + "structural_variants_rejected": structural_rejected, + "always_negatives_kept_rejected": always_negatives_rejected, + "positives_kept": positives_kept, + "all_cases_match_expectation": OUT["all_cases_match_expectation"], + "flip_dependence_holds": OUT["flip_dependence"][ + "every_flip_changes_verdict"], + "ledger_unchanged": OUT["ledger_unchanged"], + "preserved_evidence_unchanged": OUT["preserved_evidence_unchanged"], + "pass": (six_reproduced and person_cells_reproduced + and structural_reproduced + and PHASE == "prefix" + and OUT["all_cases_match_expectation"] + and OUT["flip_dependence"]["every_flip_changes_verdict"] + and OUT["ledger_unchanged"] + and OUT["preserved_evidence_unchanged"]) + or (six_rejected and person_cells_rejected + and structural_rejected and always_negatives_rejected + and positives_kept + and PHASE == "postfix" + and OUT["all_cases_match_expectation"] + and OUT["flip_dependence"]["every_flip_changes_verdict"] + and OUT["ledger_unchanged"] + and OUT["preserved_evidence_unchanged"]), + } + out_path = Path(args[0]) if args else ( + REPO / "spike/hel857/results/" + f"audit866_{'prefix' if PHASE == 'prefix' else 'postfix'}.json") + out_path.parent.mkdir(parents=True, exist_ok=True) + out_path.write_text(json.dumps(OUT, ensure_ascii=False, indent=2), + encoding="utf-8") + print(json.dumps(OUT["summary"] | {"out": str(out_path)}, + ensure_ascii=False)) + return OUT + + +if __name__ == "__main__": + raise SystemExit(0 if main(sys.argv)["summary"]["pass"] else 1)