diff --git a/spike/hel857/ab_scenarios.py b/spike/hel857/ab_scenarios.py index d6a4154..3602aa4 100644 --- a/spike/hel857/ab_scenarios.py +++ b/spike/hel857/ab_scenarios.py @@ -51,9 +51,26 @@ HEL-863 动作极性与状态对象绑定(总管对 6859696 独立复核三组 更远处出现不再凑数; - 状态绑定回同一目标声明:状态短语所在小句若提及正确对象之外的结果对象 ("体检结果现在已经告诉我了"),该状态断言属于别的对象,不能证明面试 - 承诺已结/未结;裸回指"结果已收到"与"面试结果已经告诉我了"仍可证明; - - 同样限定受支持表达的完整语法,不靠整句黑名单或逐句禁词;不能按该语法 - 解析的额外对象/谓语/否定一律保守判失败。 + 承诺已结/未结;裸回指"结果已收到"与"面试结果已经告诉我了"仍可证明。 + +HEL-864 完整受支持语法全量解析(总管对 7aa9280 独立复核七组反例+未结侧镜像): + - 不再按"任一小句发现承诺词、另一小句发现状态词、再用'结果'字样排除 + 异物"拼装肯定验证;affirms_promise_claim/affirms_promise_state 改为 + 对整个回答做有界语法全量匹配(parse_supported_answer); + - 预声明受支持结构与语气(固定合成语料领域,中英文同等结构): + 语气——全篇陈述语气(?/? 或句末 吗/呢/吧/嘛/呗 一律非陈述,保守失败); + 小句——每个小句必须被且仅被三类之一完整消费(fullmatch,条件前缀、 + 额外对象、状态后置对象、尾随否认/残料均无法完整匹配 → 保守失败): + P 承诺小句(主语?→承诺动词→体标记?→周五(晚)?→告知动作→接收者?→ + 正确对象,到此为止)、C 已结状态小句、O 未结状态小句(词表见 + PROMISE_*/TELL_ACTION_* 预声明闭类); + - 整篇 ≥1 个 P 小句才存在被肯定的承诺断言;状态肯定另要求 ≥1 个对应 + 极性 C/O 小句且 0 个反向极性小句;跨小句只出现对象词、裸名词小句 + ("体检结果")、全段冲突尾句("但面试结果仍待告知")、条件句 + ("如果你答应……")、疑问("……吗?")、状态短语后否认("已经告诉 + 我了是假的")均判失败;对侧状态短语与不确定表述整篇排除不变; + - 不加七句黑名单、不补"报告/吗/假"禁词、不新增模型、不做通用语义 + 引擎;只约束本实验判据,不限制产品表达。 对照设计(批准范围第5条): 现状路径 = docs/reuse-hel847/probe_recall.py 的生产语义重放(总管已核对的 v2 基线, @@ -668,8 +685,9 @@ def has_any(text: str, terms_zh: list[str], terms_en: list[str]) -> bool: # 独立成函数以便离线负例直接代入错误模型答案验证判据本身。 # S6 承诺状态判据词集(HEL-860:固定合成语料的预声明等价集,不冒充通用语义 # 理解;离线先行证明,不靠真实请求调判据)。HEL-861 原混合词表(任一承诺词+ -# 状态词即过)拆分;HEL-862 起这些词表只作小句内状态短语词表,极性由 -# clause_affirms_state 按小句前缀否定结构判定。 +# 状态词即过)拆分;HEL-862/863 曾作小句内状态短语词表+前缀否定极性;HEL-864 +# 起肯定验证改为有界语法全量匹配(parse_supported_answer),这些词表继续用作 +# 对侧短语与不确定表述的整篇排除集(s6_result/未结块自洽)。 PROMISE_CLOSED_ZH = ["已经告诉我", "已告诉我", "已经告知", "已告知", "已经说了", "结果已收到", "已收到", "收到了", "已经拿到", "拿到了", "已兑现", "已解决"] @@ -690,16 +708,14 @@ PROMISE_VERB_EN = ["promis", "pledge", "commit"] TELL_ACTION_ZH = ["告诉", "告知"] TELL_ACTION_EN = ["tell", "inform", "notify"] # HEL-863 告知动作后的接收者代词(封闭类):对象必须紧随告知动作(至多隔一个 -# 接收者代词),"告诉我面试结果"成立、"告诉我体检结果而面试结果是另一件事" -# 的承诺对象错挂不成立。 +# 接收者代词),"告诉我面试结果"成立、"告诉我体检结果而面试结果是另一件事" +# 的承诺对象错挂不成立。HEL-864 起该封闭类并入受支持语法的接收者槽。 PROMISE_RECEIVER_ZH = ["我", "你", "他", "她", "它", "咱们", "我们", "你们", "他们", "她们", "它们"] PROMISE_RECEIVER_EN = ["me", "you", "us", "him", "her", "them"] -# 否定语素(封闭语法类,非逐句黑名单):出现在承诺动词/状态短语之前的同小句 -# 前缀内,即该断言未被肯定——"未曾答应/从未答应/并未答应/并非已经告诉我" -# 由结构拒绝,不靠不断枚举整句禁词。 -NEGATION_MORPHS_ZH = ["不", "没", "未", "非", "无", "别", "莫", "勿"] -NEGATION_MORPHS_EN = ["not", "n't", "never", "no "] +# HEL-864:否定语素不再作前缀扫描(原 _prefix_negated 随小句拼装判据一并移除) +# ——有界语法 fullmatch 天然不含否定槽位,"未曾答应/答应不告诉/并非已经告诉 +# 我/还没有告诉我是假的"等否定形态因结构无法完整消费而保守失败。 # 不确定/存疑模式(对承诺或状态的不知情、疑问、存疑)。 PROMISE_UNCERTAIN_ZH = ["不知道", "不确定", "是否", "说不清", "记不清", "可能"] PROMISE_UNCERTAIN_EN = ["don't know", "do not know", "not sure", "unclear", @@ -731,151 +747,120 @@ def iter_clauses(text: str) -> list[str]: return clauses -def _prefix_negated(clause: str, idx: int) -> bool: - """小句内位置 idx 之前的前缀是否含否定语素(未肯定极性,保守判定)。""" - if idx < 0: - return False - prefix = clause[:idx] - low = prefix.lower() - return any(m in prefix for m in NEGATION_MORPHS_ZH) or any( - m in low for m in NEGATION_MORPHS_EN) +# --------------------------------------------------------------------------- +# HEL-864 完整受支持语法(预声明,固定合成语料领域内的有界语法全量匹配)。 +# 肯定验证=全量解析:整个回答的每个小句必须被且仅被 P/C/O 三类结构完整消费 +# (fullmatch);语气必须为陈述;任何无法消费的额外小句、对象、谓语、条件、 +# 疑问、否认或冲突一律保守判失败(fail-closed)。词表均取上方预声明闭类。 +_ZH_PRONOUN_ALT = "|".join(PROMISE_RECEIVER_ZH) +# P 承诺小句:主语?→承诺动词→体标记?→时间(周五晚/周五)?→告知动作→ +# 接收者代词?→正确对象(面试结果),小句到此为止(尾随残料即失败—— +# "告诉我体检结果""答应不告诉""如果/未曾答应"等均无法完整匹配)。 +_PROMISE_CLAUSE_ZH = re.compile( + r"(?:你|我)?(?:答应|承诺|许诺)(?:过|了)?(?:周五晚|周五)?" + rf"(?:告诉|告知)(?:{_ZH_PRONOUN_ALT})?面试结果") +# C 已结状态小句:正确对象/裸回指?→现在?→已经/已?→告知动作→接收者?→了; +# 或 正确对象/裸回指?→现在?→已经/已?→收到/拿到/兑现/解决→了?。 +# 主语槽只认 面试结果/裸"结果"——"体检报告现在已经告诉我了"(外来对象无 +# "结果"后缀)与"现在已经告诉我了体检情况"(状态后置对象)均无法匹配。 +_CLOSED_STATE_ZH = tuple(re.compile(p) for p in ( + rf"(?:面试结果|结果)?(?:现在)?(?:已经|已)?(?:告诉|告知)(?:我|你)?了", + rf"(?:面试结果|结果)?(?:现在)?(?:已经|已)?(?:收到|拿到|兑现|解决)(?:了)?")) +# O 未结状态小句:正确对象/裸回指?→现在?→主语我?→还没有/还没/尚未→ +# 告知/收到/拿到/兑现/说→接收者?。 +_OPEN_STATE_ZH = re.compile( + rf"(?:面试结果|结果)?(?:现在)?(?:我)?(?:还没有|还没|尚未)" + rf"(?:告诉|告知|收到|拿到|兑现|说)(?:我|你)?呢?") +# EN 同等结构(大小写不敏感;句末允许 .!?;条件 if/unless 前缀与疑问天然 +# 无法完整匹配,另由语气检查整篇排除 ?/?)。 +_PROMISE_CLAUSE_EN = re.compile( + r"(?:you|i|we)?\s*(?:promised|pledged|have promised|has promised|" + r"committed to)\s*(?:to\s+)?(?:tell|inform|notify)\s*" + r"(?:me|you|us|him|her|them)?\s*(?:about\s+)?(?:the\s+)?" + r"(?:interview result|interview results|interview)" + r"(?:\s*[.!?])?", re.IGNORECASE) +_CLOSED_STATE_EN = tuple(re.compile( + p + r"(?:\s*[.!?])?", re.IGNORECASE) for p in ( + r"(?:the interview result|the result|i|we|you)?\s*(?:have|has)?\s*" + r"(?:already\s+)?(?:told|informed|notified)\s*(?:me|you|us|them)?\s*" + r"(?:about\s+it|it|the interview result|interview result)?", + r"(?:the interview result|the result|i|we)?\s*(?:have|has)?\s*" + r"(?:already\s+)?(?:received|got)\s*" + r"(?:the interview result|the result|it|the notification)?")) +_OPEN_STATE_EN = re.compile( + r"(?:you|i|we)?\s*(?:haven't|have not|hasn't|has not)\s*(?:yet\s*)?" + r"(?:told|informed|notified|received|got)\s*" + r"(?:me|you|us|them)?\s*" + r"(?:it|the result|the interview result|the notification)?\s*(?:yet)?" + r"(?:\s*[.!?])?", re.IGNORECASE) +# 语气(封闭类,非逐句黑名单):问号或句末疑问/祈使语气词 → 非陈述语气。 +_QUESTION_MARK_CHARS = ("?", "?") +_ZH_MOOD_PARTICLES = "吗呢吧嘛呗" -# HEL-863 对象一致性(结构类,非逐句黑名单):正确对象是"面试结果";同一小句 -# 内出现其他"…结果"对象提及(如体检结果),该小句的断言(承诺或状态)就绑定 -# 在别的对象上,不能用来证明面试承诺的任何一部分。裸"结果"(回指,无修饰前 -# 缀)与"面试结果"本身不算外来对象。 -_FOREIGN_OBJECT_ZH = "结果" -_FOREIGN_OBJECT_EN = "result" -_CORRECT_OBJECT_ZH = PROMISE_OBJECT_ZH[0] # 面试结果 -_CORRECT_OBJECT_EN = "interview result" +def _clause_kind(clause: str) -> str | None: + """小句被哪类受支持结构完整消费:'promise'/'closed'/'open',否则 None。""" + if _PROMISE_CLAUSE_ZH.fullmatch(clause) or _PROMISE_CLAUSE_EN.fullmatch(clause): + return "promise" + if any(r.fullmatch(clause) for r in _CLOSED_STATE_ZH) or any( + r.fullmatch(clause) for r in _CLOSED_STATE_EN): + return "closed" + if _OPEN_STATE_ZH.fullmatch(clause) or _OPEN_STATE_EN.fullmatch(clause): + return "open" + return None -def clause_foreign_object(clause: str) -> bool: - """小句是否提及正确对象之外的结果对象(体检结果等"…结果"形态提及)。""" - for m in re.finditer(_FOREIGN_OBJECT_ZH, clause): - k = m.start() - if k >= len(_CORRECT_OBJECT_ZH) - len(_FOREIGN_OBJECT_ZH) and ( - clause[k - (len(_CORRECT_OBJECT_ZH) - len(_FOREIGN_OBJECT_ZH)): - k + len(_FOREIGN_OBJECT_ZH)] == _CORRECT_OBJECT_ZH): - continue - if k == 0: # 裸回指"结果"(句首、无修饰前缀) - continue - return True - low = clause.lower() - _off_en = len(_CORRECT_OBJECT_EN) - len(_FOREIGN_OBJECT_EN) - for m in re.finditer(_FOREIGN_OBJECT_EN, low): - k = m.start() - if k >= _off_en and low[k - _off_en: - k + len(_FOREIGN_OBJECT_EN)] == _CORRECT_OBJECT_EN: - continue - if k == 0: - continue - return True - return False +def parse_supported_answer(text: str) -> dict: + """HEL-864 全量解析:整个回答按预声明受支持结构与语气逐小句绑定。 - -def clause_affirms_promise(clause: str) -> bool: - """单小句内肯定承诺断言(HEL-863 完整受支持语法,一次核对对象与两种动作 - 极性): - - 承诺动词(前缀无否定语素)→ 告知动作(承诺动词至告知动作的间隔内无否定 - 语素,即告知动作同样被肯定)→ 可选接收者代词 → 正确对象(面试结果)紧随 - 其后;且该小句不提及其他"…结果"对象。"答应周五晚不告诉我面试结果"是否定 - 告知(另一种承诺,非承诺告知),"告诉我体检结果而面试结果是另一件事"承诺 - 对象错挂,均不能证明"承诺告知面试结果"这一项声明。 + 返回 clauses/declarative/all_consumed/has_promise/closed_clauses/ + open_clauses/unparsed——肯定验证(affirms_*)与审计复现共用同一解析, + 不再按子串存在+否定词排除拼装。 """ - if clause_foreign_object(clause): - return False - for verb in PROMISE_VERB_ZH: - start = 0 - while (i := clause.find(verb, start)) >= 0: - if not _prefix_negated(clause, i): - after_verb = clause[i + len(verb):] - for tell in TELL_ACTION_ZH: - t = 0 - while (j := after_verb.find(tell, t)) >= 0: - if not _prefix_negated(after_verb, j): - after_tell = after_verb[j + len(tell):].lstrip() - for recv in PROMISE_RECEIVER_ZH: - if after_tell.startswith(recv): - after_tell = after_tell[len(recv):].lstrip() - break - if any(after_tell.startswith(obj) - for obj in PROMISE_OBJECT_ZH): - return True - t = j + 1 - start = i + 1 - low = clause.lower() - for verb in PROMISE_VERB_EN: - start = 0 - while (i := low.find(verb, start)) >= 0: - if not _prefix_negated(low, i): - after_verb = low[i + len(verb):] - for tell in TELL_ACTION_EN: - t = 0 - while (j := after_verb.find(tell + " ", t)) >= 0: - if not _prefix_negated(after_verb, j): - after_tell = after_verb[j + len(tell):].lstrip() - for recv in PROMISE_RECEIVER_EN: - if after_tell.startswith(recv): - after_tell = after_tell[len(recv):].lstrip() - break - for obj in PROMISE_OBJECT_EN: - if after_tell.startswith(obj): - return True - t = j + 1 - start = i + 1 - return False - - -def clause_affirms_state(clause: str, closed: bool) -> bool: - """单小句内肯定已结/未结状态且绑定回正确对象(HEL-863): - - 状态短语(预声明等价集)前缀无否定语素;且该小句不提及其他"…结果"对象 - ——"体检结果现在已经告诉我了"的状态断言属于体检结果,不能证明面试承诺 - 已结;裸回指"结果已收到"与"面试结果已经告诉我了"绑定正确对象可证明。 - "但并非已经告诉我"含已结短语但前缀被否定 → 未肯定;"还没有告诉我"/ - "尚未收到"是未结短语的肯定形态(短语本身表达未结,非否定已结)。 - """ - if clause_foreign_object(clause): - return False - terms_zh = PROMISE_CLOSED_ZH if closed else PROMISE_OPEN_ZH - terms_en = PROMISE_CLOSED_EN if closed else PROMISE_OPEN_EN - low = clause.lower() - for t in terms_zh: - start = 0 - while (i := clause.find(t, start)) >= 0: - if not _prefix_negated(clause, i): - return True - start = i + 1 - for t in terms_en: - start = 0 - while (i := low.find(t, start)) >= 0: - if not _prefix_negated(clause, i): - return True - start = i + 1 - return False + raw = text or "" + clauses = iter_clauses(raw) + kinds = [(c, _clause_kind(c)) for c in clauses] + unparsed = [c for c, k in kinds if k is None] + declarative = ( + not any(ch in raw for ch in _QUESTION_MARK_CHARS) + and not any(c and c[-1] in _ZH_MOOD_PARTICLES for c in clauses)) + return { + "clauses": [{"text": c, "kind": k} for c, k in kinds], + "declarative": declarative, + "all_consumed": bool(clauses) and not unparsed, + "has_promise": any(k == "promise" for _, k in kinds), + "closed_clauses": [c for c, k in kinds if k == "closed"], + "open_clauses": [c for c, k in kinds if k == "open"], + "unparsed": unparsed, + } def affirms_promise_state(text: str, *, closed: bool) -> bool: - """肯定陈述已结/未结状态:任一小句内状态短语被肯定(前缀无否定)。""" - return any(clause_affirms_state(c, closed) for c in iter_clauses(text)) + """肯定陈述已结/未结状态并绑回同一项承诺声明(HEL-864 全量解析): + 全篇陈述语气 AND 每个小句都被受支持结构完整消费 AND ≥1 承诺小句 AND + ≥1 对应极性状态小句 AND 0 个反向极性小句 AND 无不确定表述。状态短语 + 属于别的对象(体检报告/体检情况)、裸名词小句、全段冲突尾句、条件句、 + 疑问语气、状态后否认均无法消费 → 保守判失败。 + """ + p = parse_supported_answer(text) + wanted = p["closed_clauses"] if closed else p["open_clauses"] + opposite = p["open_clauses"] if closed else p["closed_clauses"] + return bool(p["declarative"] and p["all_consumed"] and p["has_promise"] + and wanted and not opposite + and not has_any(text, PROMISE_UNCERTAIN_ZH, PROMISE_UNCERTAIN_EN)) def affirms_promise_claim(text: str) -> bool: - """肯定陈述固定语料中的承诺(HEL-862/863 小句结构化判据): - - 存在单一小句按"承诺动词(前缀无否定语素)→ 告知动作(间隔内无否定语素, - 告知动作同样被肯定)→ 可选接收者代词 → 正确对象(面试结果)紧随其后" - 绑定,小句内无其他"…结果"对象提及,且全文无不确定表述。对象词与动作词 - 分处不同小句各出现一次不再凑数;承诺动词被否定(未曾答应/没有答应过)、 - 告知动作被否定(答应不告诉)、对象错挂(告诉我体检结果而面试结果是另一 - 件事)、仅陈述告知结果而无承诺断言(面试结果已经告诉我了)、答错对象 - (体检结果)均判 False;无法按受支持表达证明即保守判失败,不靠枚举整句 - 禁词冒充肯定验证。 + """肯定陈述固定语料中的承诺(HEL-864 全量解析): + 全篇陈述语气 AND 每个小句都被受支持结构完整消费 AND ≥1 承诺小句 AND + 无不确定表述。承诺动词被否定(未曾答应)、告知动作被否定(答应不告诉)、 + 对象错挂(体检结果/状态后置对象)、条件句(如果你答应)、跨小句各出现 + 一个词凑数、仅陈述告知结果而无承诺断言(面试结果已经告诉我了)均判 + False;无法按受支持表达完整解析即保守判失败,不靠枚举整句禁词或 + 七句黑名单冒充肯定验证。 """ - return bool(any(clause_affirms_promise(c) for c in iter_clauses(text)) + p = parse_supported_answer(text) + return bool(p["declarative"] and p["all_consumed"] and p["has_promise"] and not has_any(text, PROMISE_UNCERTAIN_ZH, PROMISE_UNCERTAIN_EN)) # 未结对照块自洽:与"未结"状态矛盾的结果类条目(已通过/已收到通知)须剔除 RESULT_RECEIVED_ZH = ["面试过了", "通过了", "拿到通知", "拿到了通知", "已收到", @@ -977,18 +962,15 @@ 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-863:小句结构化绑定承诺对象/两种动作极性/状态与目标声明): + """S6 判据(HEL-864:有界语法全量解析绑定承诺对象/动作极性/状态/语气): 编排层(已知id置 resolved 留痕 + 未结对照块自洽)+ 状态进上下文层(正确对象 - 的 resolved 状态实际写入带门装配块)+ 模型使用层(两侧请求成功;已结侧必须 - 在单一小句内按"承诺动词(前缀无否定)→告知动作(间隔内无否定)→(可选 - 接收者)→面试结果紧随其后"肯定陈述同一项承诺,小句内无其他"…结果"对象, - 且状态短语(已经告诉我等)所在小句前缀无否定语素、同样不提及其他"…结果" - 对象并肯定为已告知;未结侧同一绑定且肯定为尚未告知——对象与动作分处不同 - 小句各出现一次(体检承诺+面试另一件事)、告知动作被否定(答应不告诉)、 - 状态属于别的对象(体检结果已经告诉我了)、承诺被否定(未曾答应)、状态被 - 否定(并非已经告诉我)、仅陈述告知而无承诺断言(面试结果已经告诉我了)、 - "不知道是否"、两侧同答不区分状态、已结侧答未结、未结侧同义宣称已收到均判 - 失败,不以字面提到'告诉/答应'冒充状态正确;无法按受支持表达证明即保守判 + 的 resolved 状态实际写入带门装配块)+ 模型使用层(两侧请求成功;两侧回答 + 均须全篇陈述语气且每个小句被受支持结构完整消费——承诺小句"主语?→承诺动词 + (未被否定)→告知动作(未被否定)→(可选接收者)→面试结果到此为止"+ + 对应极性状态小句(已结/未结);外来对象(含无"结果"后缀)、状态后置对象、 + 裸名词小句、全段冲突尾句、条件句、疑问语气、状态短语后否认、对象与动作 + 串错、仅陈述告知而无承诺断言、不确定表述、两侧同答不区分状态均判失败, + 不以字面提到'告诉/答应'冒充状态正确;无法按受支持表达完整解析即保守判 失败)。""" ans_r = (a_resolved or {}).get("answer") or "" ans_u = (a_unresolved or {}).get("answer") or "" @@ -1028,16 +1010,15 @@ def s6_result(orch: dict, a_resolved: dict, a_unresolved: dict) -> dict: and model_use["unresolved_no_false_resolution"]) return {"layers": layers, "pass": ok, "expect": "已知id编排置 resolved 留痕且未结对照块剔除矛盾条目后自洽;" - "正确对象的状态实际写入带门装配块;模型沿状态区分且必须" - "肯定验证同一项声明:已结侧单一小句内'承诺动词(未被否定)→" - "告知动作(间隔内无否定、同样被肯定)→(可选接收者)→面试" - "结果紧随其后'绑定成立,小句内无其他'…结果'对象,已结状态" - "短语所在小句前缀无否定、不提及其他'…结果'对象并肯定为已" - "告知;未结侧同一绑定且肯定为尚未告知;对象与动作串错/答应" - "不告诉/状态属于体检结果等其他对象/未曾答应/并非已经告诉我/" - "仅陈述告知无承诺/不知道/是否/两侧同答/已结侧答未结/未结侧" - "宣称已收到均判失败(无法按受支持表达证明即保守判失败,不以" - "字面提到'告诉/答应'冒充)"} + "正确对象的状态实际写入带门装配块;模型沿状态区分且两侧" + "回答均须按预声明受支持语法全量解析:全篇陈述语气(无问号" + "/句末吗呢吧嘛呗)、每个小句被承诺小句(主语?→承诺动词→" + "告知动作→(可选接收者)→面试结果到此为止)或对应极性状态" + "小句完整消费、整篇含承诺断言——外来对象(含无'结果'后缀)、" + "状态后置对象、裸名词小句、全段冲突尾句、条件句、疑问、状态" + "短语后否认、对象与动作串错、仅陈述告知无承诺、不确定表述、" + "两侧同答、已结侧答未结、未结侧宣称已收到均判失败(无法完整" + "解析即保守判失败,不以字面提到'告诉/答应'冒充)"} # HEL-860:无门 assemble_block 出口已删除——全部问答最终装配一律经 diff --git a/spike/hel857/audit864.py b/spike/hel857/audit864.py new file mode 100644 index 0000000..247ed4a --- /dev/null +++ b/spike/hel857/audit864.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python3 +"""HEL-864 负例复现脚本:总管独立复核《HEL-863-独立复核.md》七组反例+未结侧镜像。 + +零真实模型、零网络(socket 硬拒绝 + HF 离线)、零本地嵌入:S6 判据为纯函数 +代入(真实 s6_result 构造器同一入口);既有判据不回归部分直接复用 audit861 +的两组纯函数节(S6 全套正负例 + S2/S3 总判据)、audit862 的四组复核用例与 +audit863 的三组复核用例(后两组在起点 7aa9280 已判失败,预期极性固定为不 +回归)。同一脚本在修复前(7aa9280)与修复后代码上均可运行: + + 修复前(--phase prefix,在 7aa9280 工作树上运行)→ 七组反例与七组未结侧 + 镜像全部 pass=true(复现),其余用例维持既有预期极性;英文为本轮新声明 + 支持,prefix 期仅记录实际值、不参与退出码 + 修复后(默认 postfix)→ 同一真实 s6_result 七组+镜像全部判失败;正确 + 正例(含 audit860/rework 形态)、HEL-862 四组、HEL-863 三组、audit861 + 既有正负例与 S2/S3 总判据维持预期;英文同等验证(正例过、反例败) + +补正口径(固定合成语料领域内的有界语法全量匹配,预声明受支持结构与语气): +整个回答必须为陈述语气且每个小句被且仅被三类结构完整消费——P 承诺小句 +(主语?→承诺动词→体标记?→周五(晚)?→告知动作→接收者?→正确对象,到此为止)、 +C 已结状态小句、O 未结状态小句;整篇 ≥1 个 P;状态肯定另要求 ≥1 对应极性 +C/O 且 0 个反向极性小句。外来对象(含无"结果"后缀,如体检报告/体检情况)、 +状态后置对象、裸名词小句(体检结果)、全段冲突尾句(但面试结果仍待告知)、 +条件句(如果你答应)、疑问语气(……吗?)、状态短语后否认(已经告诉我了是 +假的)一律保守判失败;已结侧与未结侧镜像输入同判。不加七句黑名单、不补 +"报告/吗/假"禁词、不新增模型、不做通用语义引擎;中英文同等结构验证。 + +退出码真实依赖正负例:除全部用例满足预期外,还对每个用例做翻转自检 +(flip 任一用例的实际结果,用例总判必翻转);任一翻转不改变结论则退出 1。 + +用法:python -m spike.hel857.audit864 [--phase prefix|postfix] [输出json路径] +退出码:prefix=0 当且仅当七组反例与七组镜像全部复现(pass=true)且其余用例 +维持预期;postfix=0 当且仅当七组+镜像全部判失败、正例保留、英文同等验证、 +既有判据不回归、权威账与既有证据只读未动、翻转自检全部通过。权威嵌入账 +只读快照核对(不补种不回退不双扣)。 +""" +from __future__ import annotations + +import hashlib +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("audit864 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 + +OUT = {"real_network_allowed": False, "real_model_requests": 0, + "local_embeddings": 0} +PHASE = "postfix" + + +def ledger_snapshot(): + with sqlite3.connect(f"file:{LEDGER}?mode=ro", uri=True) as c: + return {"counts": dict(c.execute("select kind,sum(items) from embed_ops " + "group by kind")), + "rows": c.execute("select count(*) from embed_ops").fetchone()[0]} + + +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"] + + +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-863-独立复核.md》七组反例(已结侧原文逐字)+ 未结侧镜像(同一缺口 +# 在 unresolved 侧的形态)。两侧均经同一真实 s6_result 入口,对侧用正确答案。 +def s6_answer(s): + return {"answer": s, "ok": True} + + +GOOD_CLOSED = "你答应周五晚告诉我面试结果,现在已经告诉我了。" +GOOD_OPEN = "你答应周五晚告诉我面试结果,还没有告诉我。" +P = "你答应周五晚告诉我面试结果," +SEVEN_CLOSED = { + "foreign_object_without_result_suffix": + P + "体检报告现在已经告诉我了。", + "foreign_object_after_state": + P + "现在已经告诉我了体检情况。", + "foreign_object_in_previous_clause": + P + "体检结果,现在已经告诉我了。", + "conflicting_target_state": + P + "现在已经告诉我了,但面试结果仍待告知。", + "question_not_assertion": + P + "现在已经告诉我了吗?", + "conditional_not_promise": + "如果你答应周五晚告诉我面试结果,现在已经告诉我了。", + "suffix_denies_state": + P + "已经告诉我了是假的。", +} +SEVEN_OPEN_MIRROR = { + "open_foreign_object_without_result_suffix": + P + "体检报告还没有告诉我。", + "open_foreign_object_after_state": + P + "还没有告诉我体检情况。", + "open_foreign_object_in_previous_clause": + P + "体检结果,还没有告诉我。", + "open_conflicting_target_state": + P + "还没有告诉我,但面试结果仍待告知。", + "open_question_not_assertion": + P + "还没有告诉我吗?", + "open_conditional_not_promise": + "如果你答应周五晚告诉我面试结果,还没有告诉我。", + "open_suffix_denies_state": + P + "还没有告诉我是假的。", +} +SEVEN_NAMES = tuple(SEVEN_CLOSED.keys()) +MIRROR_NAMES = tuple(SEVEN_OPEN_MIRROR.keys()) + +# 英文同等验证(本轮声明支持即同等结构核验:正例必须过、反例必须败)。 +# 值为 (已结侧回答, 未结侧回答)。 +EN_POSITIVE = { + "en_positive_resolved": ( + "You promised to tell me the interview result, " + "and you have already told me.", GOOD_OPEN), + "en_positive_open": ( + GOOD_CLOSED, "You promised to tell me the interview result, " + "but you haven't told me yet."), + "en_positive_open_not_received": ( + GOOD_CLOSED, "You promised to tell me the interview result, " + "you haven't received it yet."), +} +EN_NEGATIVE = { + "en_conditional_not_promise": ( + "If you promised to tell me the interview result, " + "you have already told me.", GOOD_OPEN), + "en_question_not_assertion": ( + "You promised to tell me the interview result, " + "have you already told me?", GOOD_OPEN), + "en_foreign_object_suffix": ( + "You promised to tell me the interview result, and you have " + "already told me the medical check result.", GOOD_OPEN), + "en_negated_promise": ( + "You never promised to tell me the interview result, " + "and you have already told me.", GOOD_OPEN), + "en_open_mirror_question": ( + GOOD_CLOSED, "You promised to tell me the interview result, " + "haven't you told me yet?"), + "en_open_mirror_conditional": ( + GOOD_CLOSED, "If you promised to tell me the interview result, " + "you haven't told me yet."), +} + + +def _record(name: str, resolved: str, unresolved: str, expected: bool) -> dict: + result = A.s6_result(ORCH6, s6_answer(resolved), s6_answer(unresolved)) + mu = result["layers"]["model_use"] + return { + "resolved_input": resolved, "unresolved_input": unresolved, + "expected_pass": expected, "actual_pass": result["pass"], + "layers": { + "resolved_states_promise": mu["resolved_states_promise"], + "resolved_affirms_closed": mu["resolved_affirms_closed"], + "unresolved_states_promise": mu["unresolved_states_promise"], + "unresolved_affirms_open": mu["unresolved_affirms_open"]}, + "result": result, + } + + +def section_seven_and_mirror() -> dict: + """七组反例+未结侧镜像:prefix 期预期 pass=true(复现),postfix 判失败。""" + out = {} + for name, closed in SEVEN_CLOSED.items(): + out[name] = _record(name, closed, GOOD_OPEN, PHASE == "prefix") + for name, opened in SEVEN_OPEN_MIRROR.items(): + out[name] = _record(name, GOOD_CLOSED, opened, PHASE == "prefix") + return out + + +def section_held_previous() -> dict: + """HEL-862 四组与 HEL-863 三组:起点 7aa9280 已按预期判失败,极性固定。""" + out = {} + for name, (closed, opened) in REVIEW_CASES.items(): + out[f"review.{name}"] = _record( + name, closed, opened, name not in REVIEW_NEGATIVE_NAMES) + for name, closed in ADDITIONAL_CASES.items(): + out[f"additional.{name}"] = _record(name, closed, + "你答应周五晚告诉我面试结果,还没有告诉我。", False) + return out + + +def section_english_cases() -> dict: + """英文同等验证:postfix 期参与退出码(正例过、反例败),prefix 期仅记录。""" + out = {} + for name, (resolved, unresolved) in EN_POSITIVE.items(): + out[name] = _record(name, resolved, unresolved, True) + for name, (resolved, unresolved) in EN_NEGATIVE.items(): + out[name] = _record(name, resolved, unresolved, False) + return out + + +def _case_table(seven_mirror: dict, english: dict, held_prev: dict, + held_s6: dict, held_s2s3: dict) -> dict: + """全部用例的实际 pass 与预期极性(退出码公式输入;英文仅 postfix 参与)。""" + table: dict[str, bool] = {} + expect: dict[str, bool] = {} + for name, c in seven_mirror.items(): + key = f"seven_mirror.{name}" + table[key] = bool(c["actual_pass"]) + expect[key] = bool(c["expected_pass"]) + if PHASE == "postfix": + for name, c in english.items(): + key = f"english.{name}" + table[key] = bool(c["actual_pass"]) + expect[key] = bool(c["expected_pass"]) + for name, c in held_prev.items(): + table[name] = bool(c["actual_pass"]) + expect[name] = bool(c["expected_pass"]) + for name, res in held_s6.items(): + key = f"held_s6.{name}" + table[key] = bool(res["pass"]) + expect[key] = HELD_S6_EXPECT[name] + for name, res in held_s2s3.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", "--short", "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["seven_and_mirror"] = section_seven_and_mirror() + OUT["english_cases"] = section_english_cases() + OUT["held_previous_rounds"] = section_held_previous() + held_s6 = section_s6_affirmative() + held_s2s3 = section_s2_s3_no_regression() + OUT["held_s6_affirmative"] = held_s6 + OUT["held_s2_s3_no_regression"] = held_s2s3 + + table, expect = _case_table(OUT["seven_and_mirror"], OUT["english_cases"], + OUT["held_previous_rounds"], held_s6, held_s2s3) + 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"]) + + sm = OUT["seven_and_mirror"] + # ---- prefix:七组反例与七组镜像全部复现(pass=true)---- + fourteen_reproduced = (all(sm[n]["actual_pass"] for n in SEVEN_NAMES) + and all(sm[n]["actual_pass"] for n in MIRROR_NAMES)) + # ---- postfix:七组+镜像全部判失败 ---- + fourteen_rejected = (not any(sm[n]["actual_pass"] for n in SEVEN_NAMES) + and not any(sm[n]["actual_pass"] for n in MIRROR_NAMES)) + # ---- postfix:英文同等验证(正例过、反例败)---- + english_ok = all(c["actual_pass"] == c["expected_pass"] + for c in OUT["english_cases"].values()) + OUT["summary"] = { + "phase": PHASE, + "fourteen_reproduced": fourteen_reproduced, + "fourteen_rejected": fourteen_rejected, + "english_equal_verification_pass": english_ok if PHASE == "postfix" + else None, + "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": (fourteen_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 (fourteen_rejected and PHASE == "postfix" + and english_ok + 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"audit864_{'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)