fix(saa): B 收尾·客观门跨层对齐 advisory——objectiveGatesPass 读 verdict.advisoryGates

根因:8ac4ccc8 的「未驱动 E_live/H 降 advisory」只落在 play.cdp.cjs(verdict.pass/
advisoryGates),未传到生成环消费层 SaaGenNodes.objectiveGatesPass——后者把 E_live 当
硬客观门、完全不读 advisoryGates。未驱动游戏(打地鼠 menu 态无 driver→phase 恒 menu→
_forensicsView distinctStates:1)E_live 每轮必挂→1+maxRepairs(5)=6 次 M3 假失败救场,
与既有 line651 注释里早修过的 H_progress「空跑 8 轮救场/302K token」同一类病(driver
依赖门绑架生成)。

修:
- objectiveGatesPass / summarizeFailedGuards 读 verdict.advisoryGates 同口径降级。
  advisory 门要求【存在】(保住 C2 崩溃安全:缺门=harness 未跑完=失败)但【允许 pass=false】
  (未驱动无法公平验活性);driven 时 play.cdp.cjs 不列入 advisory → 自动恢复致命,回归不破。
- objectiveGatesPass 提包级可见(与 summarizeFailedGuards 同范式),供纯逻辑单测。

验证:SaaFullGraphE2eGateLogicTest 23/23 PASS(+5 advisory 案:未驱动 E_live→过/
driven E_live→挂/缺门→挂/全过/反馈跳 advisory)。e2e 已实证 verdict.pass=true、
E_live/H 列 advisory(advisory 跨层生效)。
注:e2e 绿基线仍被一个【独立·先存】问题阻断——过门游戏不产终态回调(player 节点 M3
首次在 pass 路被触达,疑 M3-vision 挂),非本修引入,下一步处置。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-22 23:38:59 -07:00
parent 8ac4ccc861
commit cbe42933d0
2 changed files with 105 additions and 5 deletions

View File

@ -715,25 +715,51 @@ final class SaaGenNodes {
* <p>遍历 verdict.guards 中出现的门只对客观门要求 passdriver 门跳过 guards 明细或未见任何客观门
* 保守退回聚合 verdict.pass(避免误放空壳)<b>不改九门判定本身</b>只改生成环对其的消费口径
*/
private static boolean objectiveGatesPass(JsonNode verdict) {
static boolean objectiveGatesPass(JsonNode verdict) { // 包级可见 SaaFullGraphE2eGateLogicTest 纯逻辑回归 summarizeFailedGuards 同范式
JsonNode guards = verdict.path("guards");
if (!guards.isObject() || guards.size() == 0) {
return verdict.path("pass").asBoolean(false);
}
// C2 CodeRabbit/Codex 双报2026-06-20客观门按必达集校验5 门必须全部存在且 pass缺任一即判失败
// B 收尾·跨层对齐 advisory2026-06-22play.cdp.cjs 未驱动(spec driver/inputs)时把 E_live/H_progress 降为
// advisory(verdict.advisoryGates) 并据此算 verdict.pass该降级必须同口径传到生成环消费层否则未驱动游戏每轮卡
// E_live 触发无谓救场重生成实证打地鼠 menu 态无 driverphase menu_forensicsView distinctStates:1E_live 必挂,
// 1+maxRepairs(5)=6 M3 生成全假失败( line 651 H_progress 空跑救场同一类病driver 依赖门绑架生成)
java.util.Set<String> advisory = readAdvisoryGates(verdict);
// C2 CodeRabbit/Codex 双报2026-06-20客观门按必达集校验5 门必须全部存在缺任一即判失败
// "遍历出现的门、出现才查"会被 harness 中途崩钻空子误判 PASSplay.cdp.cjs runDriver( seek-food)
// D_render/E_live 之前执行且未 try/catchconc=12 CDP 抖动抛错 catch 只补 A_bootverdict 只剩
// A_boot/C_frame/I_control旧逻辑 sawObjective=true 放行=崩掉根本没测到渲染/活性的游戏被判过
// 缺门=harness 未跑完=失败才安全 rc==0 口径反不会误判本修补回该安全性又不重新绑 driver
// 缺门=harness 未跑完=失败才安全advisory 门要求存在(保住该崩溃安全)允许 pass=false(未驱动无法公平验活性)
for (String gate : OBJECTIVE_GATES) {
JsonNode g = guards.path(gate);
if (g.isMissingNode() || !g.path("pass").asBoolean(false)) {
return false; // 客观门缺失或未过 生成硬门判失败
if (g.isMissingNode()) {
return false; // 客观门缺失=harness 未跑完 生成硬门判失败C2 崩溃安全
}
if (advisory.contains(gate)) {
continue; // advisory 存在即可允许未过如未驱动时 E_livedriven 恢复致命由 play.cdp.cjs 不列入 advisory 实现
}
if (!g.path("pass").asBoolean(false)) {
return false; // advisory 客观门未过 生成硬门判失败
}
}
return true;
}
/** 读 verdict.advisoryGatesplay.cdp.cjs 在未驱动时降级的非致命门,如 E_live/H_progress。缺/非数组→空集。 */
private static java.util.Set<String> readAdvisoryGates(JsonNode verdict) {
java.util.Set<String> advisory = new java.util.HashSet<>();
if (verdict == null) {
return advisory;
}
JsonNode adv = verdict.path("advisoryGates");
if (adv.isArray()) {
for (JsonNode a : adv) {
advisory.add(a.asText());
}
}
return advisory;
}
/**
* 把失败的守卫摘成回喂文字对齐 run.py:_verdict_feedback
*/
@ -742,6 +768,7 @@ final class SaaGenNodes {
return "真玩阶段无 verdict 产出(疑似装载即崩/超时)。";
}
List<String> lines = new ArrayList<>();
java.util.Set<String> advisory = readAdvisoryGates(verdict);
JsonNode guards = verdict.path("guards");
if (guards.isObject()) {
Iterator<Map.Entry<String, JsonNode>> it = guards.fields();
@ -751,6 +778,10 @@ final class SaaGenNodes {
if (!GATE_ALL9 && !OBJECTIVE_GATES.contains(e.getKey())) {
continue;
}
// B 收尾advisory (未驱动 E_live/H)非致命不回喂救场否则空喂"E_live 未过"驱动无谓重生成
if (!GATE_ALL9 && advisory.contains(e.getKey())) {
continue;
}
if (!e.getValue().path("pass").asBoolean(false)) {
String g = e.getValue().toString();
lines.add("- 守卫 " + e.getKey() + " 未过:" + (g.length() > 200 ? g.substring(0, 200) : g));

View File

@ -1,5 +1,7 @@
package com.wanxiang.huijing.game.module.aigc.saa;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanxiang.huijing.game.module.aigc.service.executor.AigcExecutorProperties;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@ -246,4 +248,71 @@ class SaaFullGraphE2eGateLogicTest {
assertEquals(1, clamp.size(), "k<1 → 至少 1 槽(防御,不返空)");
assertEquals(4320, clamp.get(0)[0]);
}
// B 收尾·客观门跨层对齐 advisory2026-06-22 未驱动 E_live 误触救场
//
// 背景play.cdp.cjs 未驱动(play-spec driver/inputs)时把 E_live/H_progress 降为 advisory 并据此算 verdict.pass
// 但生成环消费层 objectiveGatesPass 旧实现把 E_live 当硬客观门 未驱动游戏(打地鼠 menu )phase menu
// distinctStates:1 E_live 必挂 1+maxRepairs(5)=6 M3 假失败本组锁住客观门读 advisoryGates 同口径降级
/** 用 JSON 串构 verdict 节点(测试本地工具,避免手搭 ObjectNode。 */
private static JsonNode verdict(String json) {
try {
return new ObjectMapper().readTree(json);
} catch (Exception e) {
throw new RuntimeException("测试 verdict JSON 非法:" + e.getMessage(), e);
}
}
@Test
@DisplayName("objectiveGatesPass未驱动 advisory E_live 失败 → 仍 PASSB 收尾跨层对齐,不再误触救场)")
void objectiveGatesPass_advisoryEliveFails_stillPass() {
// 打地鼠 menu 态无 driverA-D 全过 + E_live 失败但列入 advisoryGates 客观门应判过 verdict.pass 同口径
JsonNode v = verdict("{\"pass\":true,\"advisoryGates\":[\"E_live\",\"H_progress\"],"
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},"
+ "\"E_live\":{\"pass\":false,\"distinctStates\":1}}}");
assertTrue(SaaGenNodes.objectiveGatesPass(v), "advisory E_live 失败不应否决客观门(否则未驱动游戏空跑救场)");
}
@Test
@DisplayName("objectiveGatesPassdrivenadvisoryGates 空E_live 失败 → FAILdriven 恢复致命,回归不破)")
void objectiveGatesPass_drivenEliveFails_fail() {
// driver play.cdp.cjs 不把 E_live 列入 advisory 客观门仍须 E_live 挂则判失败
JsonNode v = verdict("{\"pass\":false,\"advisoryGates\":[],"
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},"
+ "\"E_live\":{\"pass\":false}}}");
assertFalse(SaaGenNodes.objectiveGatesPass(v), "driven 时 E_live 仍是硬客观门");
}
@Test
@DisplayName("objectiveGatesPass客观门缺失harness 崩)→ FAILC2 崩溃安全不被 advisory 削弱)")
void objectiveGatesPass_missingGate_failEvenIfAdvisory() {
// D_render 缺失harness 中途崩没跑到渲染门即便 advisory D_render 也不得放行=没跑完=失败
JsonNode v = verdict("{\"advisoryGates\":[\"D_render\",\"E_live\"],"
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
+ "\"C_frame\":{\"pass\":true},\"E_live\":{\"pass\":true}}}");
assertFalse(SaaGenNodes.objectiveGatesPass(v), "客观门缺失=harness 未跑完advisory 也不得放行C2 崩溃安全)");
}
@Test
@DisplayName("objectiveGatesPass全客观门过 + 无 advisory → PASShappy")
void objectiveGatesPass_allPass() {
JsonNode v = verdict("{\"guards\":{\"A_boot\":{\"pass\":true},\"B_uncaught\":{\"pass\":true},"
+ "\"C_frame\":{\"pass\":true},\"D_render\":{\"pass\":true},\"E_live\":{\"pass\":true}}}");
assertTrue(SaaGenNodes.objectiveGatesPass(v));
}
@Test
@DisplayName("summarizeFailedGuardsadvisory 门不回喂救场;真硬门失败仍回喂")
void summarizeFailedGuards_skipsAdvisory() {
// advisory E_live 失败不进救场反馈否则空喂"E_live 未过"驱动无谓重生成真硬门 D_render 失败仍须回喂
JsonNode v = verdict("{\"advisoryGates\":[\"E_live\"],"
+ "\"guards\":{\"A_boot\":{\"pass\":true},\"D_render\":{\"pass\":false},"
+ "\"E_live\":{\"pass\":false,\"distinctStates\":1}}}");
String fb = SaaGenNodes.summarizeFailedGuards(v);
assertTrue(fb.contains("D_render"), "真硬门 D_render 失败应回喂救场");
assertFalse(fb.contains("E_live"), "advisory E_live 不应回喂救场");
}
}