@chrono-meta/fh-gate 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.claude/registry/agent_cards.json +1 -1
  2. package/.claude/rules/fh_4axis_gate.md +25 -0
  3. package/.claude-plugin/marketplace.json +3 -3
  4. package/AGENTS.md +2 -2
  5. package/CATALOG.md +4 -4
  6. package/CHEATSHEET.md +1 -1
  7. package/CLAUDE.md +4 -4
  8. package/docs/OUTPUT_EVIDENCE.md +1 -1
  9. package/docs/STANDARDS_ALIGNMENT.md +1 -1
  10. package/docs/codex-compat.md +1 -1
  11. package/knowledge/shared/harness-core/agents_md_runtime_details.md +2 -2
  12. package/knowledge/shared/harness-core/field_verdict_crossfamily_gate.md +30 -3
  13. package/knowledge/shared/harness-core/iso_ai_standards_crosswalk.md +1 -1
  14. package/knowledge/shared/harness-core/skill_quality_rubric.md +1 -1
  15. package/knowledge/shared/learnings/subagent_invocations_log.yaml +31 -0
  16. package/knowledge/shared/rules/modes_and_value.md +2 -2
  17. package/package.json +2 -1
  18. package/plugins/fh-commons/.claude-plugin/plugin.json +1 -1
  19. package/plugins/fh-commons/README.md +38 -0
  20. package/plugins/fh-meta/.claude-plugin/plugin.json +1 -1
  21. package/plugins/fh-meta/CHANGELOG.md +95 -0
  22. package/plugins/fh-meta/skills/agent-composer/SKILL.md +2 -2
  23. package/plugins/fh-meta/skills/auto-decorrelation/SKILL.md +40 -0
  24. package/plugins/fh-meta/skills/frontier-digest/SKILL.md +1 -1
  25. package/plugins/fh-meta/skills/frontier-digest/SKILL_detail.md +43 -5
  26. package/plugins/fh-meta/skills/{hub-cc-pr-reviewer → harness-pr-reviewer}/SKILL.md +75 -3
  27. package/plugins/fh-meta/skills/{hub-cc-pr-reviewer → harness-pr-reviewer}/SKILL_detail.md +2 -2
  28. package/plugins/fh-meta/skills/harvest-loop/SKILL_detail.md +2 -2
  29. package/plugins/fh-meta/skills/install-doctor/SKILL.md +1 -1
  30. package/plugins/fh-meta/skills/install-wizard/SKILL.md +1 -1
  31. package/plugins/fh-meta/skills/meta-prompt-builder/SKILL.md +1 -1
  32. package/plugins/fh-meta/skills/pipeline-conductor/SKILL.md +1 -1
  33. package/plugins/fh-meta/skills/plugin-recommender/SKILL.md +1 -1
  34. package/plugins/fh-meta/skills/sim-conductor/SKILL.md +1 -1
  35. package/plugins/fh-qp/.claude-plugin/plugin.json +1 -1
  36. package/scripts/finding_fleet.sh +391 -13
  37. package/scripts/finding_pipeline.sh +370 -11
  38. package/scripts/finding_verifier.sh +31 -2
  39. package/scripts/finding_verify.py +198 -13
  40. package/scripts/frontier_digest_autopilot.sh +3 -3
  41. package/scripts/gate_shape_scan.sh +16 -2
  42. package/scripts/test_finding_pipeline_lanes.sh +1556 -4
  43. package/scripts/test_gate_shape_scan_lanes.sh +11 -0
  44. package/scripts/test_marker_crossfamily_lanes.sh +75 -3
  45. package/scripts/test_marker_standpoint_lanes.sh +31 -6
  46. package/templates/.git-hooks/pre-commit +152 -6
  47. package/templates/local_fh_context.md +1 -1
  48. package/templates/regression_guard.sh +1 -1
@@ -246,12 +246,18 @@ bash "$PIPE" "$D/tgt.py" --out "$D/pipe22" --fleet "$D/fleet0.tbl" >"$D/p22" 2>&
246
246
  MUTD="$D/mut"; mkdir -p "$MUTD"
247
247
  cp "$HERE/finding_fleet.sh" "$HERE/finding_verify.py" "$HERE/finding_verifier.sh" "$MUTD/"
248
248
  MUTP="$MUTD/finding_pipeline.sh"
249
- /usr/bin/python3 - "$PIPE" "$MUTP" <<'PY'
249
+ MUT_ERR="$D/mut23.err"
250
+ /usr/bin/python3 - "$PIPE" "$MUTP" 2>"$MUT_ERR" <<'PY'
250
251
  import sys
251
252
  src, dst = sys.argv[1], sys.argv[2]
252
253
  s = open(src, encoding="utf-8").read()
253
- key = ' while IFS= read -r f; do [ -n "$f" ] && [ "$f" != "$p" ] && supported "$f" && { echo "$f"; return; }; done < "$OUT/families.txt"'
254
- assert key in s, "pick_verifier body moved the revert probe is pinned to a line that no longer exists"
254
+ # 🟥 2026-09-10: 검증자 후보 풀이 families.txt roster(함대 명부) 바뀌면서 핀이 빗나갔다.
255
+ # 그때 레인은 «L20 실물을 잰다» 라는 **틀린 진단**을 냈다 핀이 빗나간 것과 대상이
256
+ # 빨간 것은 다른 사건인데 출력이 같았다. 그래서 이제 핀 실패는 HARNESS-ERROR 로 «따로» 운다.
257
+ key = ' while IFS= read -r f; do [ -n "$f" ] && [ "$f" != "$p" ] && supported "$f" && { echo "$f"; return; }; done < "$ROSTER"'
258
+ if key not in s:
259
+ sys.stderr.write("HARNESS-ERROR: pick_verifier body moved — the revert probe is pinned to a line that no longer exists\n")
260
+ sys.exit(9)
255
261
  s = s.replace(key, ' echo "$p"') # 자기 계열을 검증기로 고른다
256
262
  open(dst, "w", encoding="utf-8").write(s)
257
263
  PY
@@ -260,7 +266,9 @@ FH_CODEX_BIN="$D/fake_conf.sh" FH_AGY_BIN="$D/fake_conf.sh" \
260
266
  M23=$(grep '^PIPELINE ' "$D/p23" | grep -c 'unverified=2')
261
267
  [ "$M23" = 1 ] \
262
268
  && ok "L23 되돌림: 검증기를 자기 계열로 바꾸면 전부 unverified 로 떨어진다 (L20 은 장식이 아니다)" \
263
- || no "L23 되돌림" "뮤턴트인데 unverified=2 가 안 나왔다 — L20 이 실물을 안 잰다 · $(grep '^PIPELINE ' "$D/p23")"
269
+ || no "L23 되돌림" "$(if /usr/bin/grep -q HARNESS-ERROR "$MUT_ERR" 2>/dev/null; then \
270
+ /usr/bin/grep -m1 HARNESS-ERROR "$MUT_ERR"; echo " (계기 실패 — 대상은 무판정이다)"; \
271
+ else echo "뮤턴트인데 unverified=2 가 안 나왔다 — L20 이 실물을 안 잰다"; fi) · $(grep '^PIPELINE ' "$D/p23")"
264
272
 
265
273
  # L24 — G6: fleet 프롬프트가 defeater 를 «요구» 하는가 (축③ 을 잴 수 있는 최소 조건)
266
274
  { grep -q '"defeater"' "$FLEET" && grep -q 'defeater. is required' "$FLEET"; } \
@@ -454,6 +462,1550 @@ R38=$(_pf_run '{"id":"z1","title":"t","producer_family":"gamma"}' diff)
454
462
  && ok "L38 producer_family != 검증자 계열 → 정상 통과 (과차단 아님)" \
455
463
  || no "L38 과차단 없음" "got=$R38 want=0|VERIFIED"
456
464
 
465
+ # ── L39~L45 커버리지 기록 + 씨앗 통제 (2026-09-09, 거버넌스 루프 R1) ─────────────
466
+ # 🟥 오류율은 «혼자 인용될 수 없는 수» 로 만든다. 판정된 것만 분모에 넣고 기권을 빼면 그 비율은
467
+ # 말하지 않은 동작점에서의 값이고, 기권율이 다른 두 팔을 나란히 놓는 순간 비교가 성립하지
468
+ # 않는다. 이건 선택적 예측 문헌이 이미 «흔한 결함» 으로 명명한 형태이고(arXiv:2407.01032),
469
+ # 우리 5팔 표가 그 실례다. 그래서 coverage 는 DROPS 줄과 같이 «무조건» 나간다.
470
+ # 🟥 그리고 씨앗 통제는 known-pair 규율을 «필터» 에 적용한 것이다 — 이 레포는 그 규율을
471
+ # 스캐너에만 적용해 왔고 삭제 단계에는 한 번도 적용한 적이 없다. 필터도 계기다.
472
+ cat > "$D/sv_keep.sh" <<'EOS'
473
+ #!/bin/sh
474
+ cat >/dev/null
475
+ echo '{"id":"z1","verdict":"confirmed","why":"ok"}'
476
+ echo '{"id":"z2","verdict":"confirmed","why":"ok"}'
477
+ EOS
478
+ cat > "$D/sv_drop.sh" <<'EOS'
479
+ #!/bin/sh
480
+ cat >/dev/null
481
+ echo '{"id":"z1","verdict":"confirmed","why":"ok"}'
482
+ echo '{"id":"z2","verdict":"false-positive","why":"nope"}'
483
+ EOS
484
+ chmod +x "$D/sv_keep.sh" "$D/sv_drop.sh"
485
+ printf '%s\n%s\n' '{"id":"z1","title":"a","producer_family":"gamma"}' \
486
+ '{"id":"z2","title":"b","producer_family":"gamma"}' > "$D/sf.jsonl"
487
+ # 하나가 자기 계열이라 unverified 가 되는 입력 — coverage 가 1.0 아래로 내려가야 한다
488
+ printf '%s\n%s\n' '{"id":"z1","title":"a","producer_family":"beta"}' \
489
+ '{"id":"z2","title":"b","producer_family":"gamma"}' > "$D/sf_partial.jsonl"
490
+ _sv() { # $1 out이름, $2 verifier, $3 입력, 나머지 = 추가 인자 → "rc|FINDINGS|SEEDED"
491
+ local n="$1" v="$2" f="$3"; shift 3
492
+ local o rc; o=$(python3 "$HERE/finding_verify.py" "$D/$f" --out "$D/sv_$n" \
493
+ --verifier "sh $D/$v" --family beta "$@" 2>&1); rc=$?
494
+ printf '%s|%s|%s' "$rc" "$(printf '%s' "$o" | /usr/bin/grep -o 'coverage=[0-9]*/[0-9]*')" \
495
+ "$(printf '%s' "$o" | sed -n 's/^SEEDED .*status=\([A-Z_]*\).*/\1/p')"
496
+ }
497
+ R39=$(_sv full sv_keep.sh sf.jsonl)
498
+ [ "$R39" = "0|coverage=2/2|NOT_PROVIDED" ] \
499
+ && ok "L39 전부 판정되면 coverage=2/2 · 씨앗 미제공은 NOT_PROVIDED (후방호환: rc 불변)" \
500
+ || no "L39 coverage 만점 + 후방호환" "got=$R39 want=0|coverage=2/2|NOT_PROVIDED"
501
+ R40=$(_sv part sv_keep.sh sf_partial.jsonl)
502
+ [ "$R40" = "3|coverage=1/2|NOT_PROVIDED" ] \
503
+ && ok "L40 🟥 미판정이 있으면 coverage 가 1.0 아래로 내려간다 (기권이 분모 밖으로 숨지 않는다)" \
504
+ || no "L40 coverage 가 기권을 드러낸다" "got=$R40 want=3|coverage=1/2|NOT_PROVIDED"
505
+ R41=$(_sv seed_ok sv_keep.sh sf.jsonl --seeded z2)
506
+ [ "$R41" = "0|coverage=2/2|CLEAN" ] \
507
+ && ok "L41 씨앗 생존 → CLEAN · rc 불변" || no "L41 씨앗 생존" "got=$R41 want=0|coverage=2/2|CLEAN"
508
+ R42=$(_sv seed_bad sv_drop.sh sf.jsonl --seeded z2)
509
+ [ "$R42" = "5|coverage=2/2|DEGRADED" ] \
510
+ && ok "L42 🟥 씨앗이 지워지면 DEGRADED·rc=5 — 삭제로 정밀도를 산 «직접 증거»" \
511
+ || no "L42 씨앗 삭제 검출" "got=$R42 want=5|coverage=2/2|DEGRADED"
512
+ R43=$(_sv seed_none sv_keep.sh sf.jsonl --seeded zzz)
513
+ [ "$R43" = "5|coverage=2/2|ABSENT" ] \
514
+ && ok "L43 🟥 씨앗이 입력에 없으면 ABSENT·rc=5 — 안 돈 컨트롤은 통과한 컨트롤이 아니다" \
515
+ || no "L43 죽은 컨트롤 검출" "got=$R43 want=5|coverage=2/2|ABSENT"
516
+ # L44 되돌림 — coverage 필드를 지우면 L39·L40 이 빨개져야 한다(앵커가 장식이 아님)
517
+ MUT2="$D/verify_mut2.py"
518
+ MUTERR2=$(python3 - "$VERIFY" "$MUT2" 2>&1 <<'PYX'
519
+ import sys
520
+ src, dst = sys.argv[1], sys.argv[2]
521
+ s = open(src, encoding="utf-8").read()
522
+ key = 'coverage={}/{} ({}%) '
523
+ assert key in s, "ANCHOR-MOVED: coverage field format string not found"
524
+ open(dst, "w", encoding="utf-8").write(s.replace(key, ''))
525
+ PYX
526
+ ); MUTRC2=$?
527
+ if [ "$MUTRC2" -ne 0 ] || [ ! -s "$MUT2" ]; then
528
+ no "L44 되돌림 (뮤턴트 생성 실패)" "$(printf '%s' "$MUTERR2" | tail -1) — 앵커가 «움직인» 것이지 «장식» 이 아니다"
529
+ else
530
+ MO=$(python3 "$MUT2" "$D/sf.jsonl" --out "$D/sv_mut" --verifier "sh $D/sv_keep.sh" --family beta 2>&1); MRC=$?
531
+ # 🟥 «coverage= 가 없다» 만 보면 **크래시한 뮤턴트도 통과**한다 — 없는 이유가 «지웠기 때문» 인지
532
+ # «안 돌았기 때문» 인지 안 갈린다(cross-family 지적 2026-09-09, 항목 5). 그래서 뮤턴트가
533
+ # «정상 종료 + FINDINGS 줄을 냈다» 를 먼저 요구하고, 그 다음에 coverage 부재를 본다.
534
+ if [ "$MRC" -ne 0 ] || ! printf '%s' "$MO" | /usr/bin/grep -q '^FINDINGS '; then
535
+ no "L44 되돌림" "뮤턴트가 정상 실행되지 않았다(rc=$MRC) — coverage 부재를 «지웠기 때문» 으로 읽을 수 없다"
536
+ elif printf '%s' "$MO" | /usr/bin/grep -q 'coverage='; then
537
+ no "L44 되돌림" "coverage 를 지웠는데도 출력에 남아 있다 — 앵커가 다른 곳을 잰다"
538
+ else
539
+ ok "L44 되돌림: 뮤턴트는 정상 실행되고 coverage 만 사라진다 (레인이 실물을 잰다)"
540
+ fi
541
+ fi
542
+ # L45 씨앗 id 는 검증자 프롬프트에 «들어가지 않는다» — 새면 통제가 아니라 힌트가 된다
543
+ cat > "$D/sv_echo.sh" <<'EOS'
544
+ #!/bin/sh
545
+ cat > "$SEEN_FILE"
546
+ echo '{"id":"z1","verdict":"confirmed","why":"ok"}'
547
+ echo '{"id":"z2","verdict":"confirmed","why":"ok"}'
548
+ EOS
549
+ chmod +x "$D/sv_echo.sh"
550
+ # 🟥 초판은 리터럴 `seeded` 를 grep 했다 — 다른 변수명으로 새면 그대로 통과하고, 반대로 finding
551
+ # 본문에 그 낱말이 있으면 거짓 실패다(cross-family 지적 2026-09-09, 항목 5). 누출의 정의는
552
+ # «씨앗 선택이 검증자 입력을 바꾸는가» 이므로, **선택을 바꿔 두 입력을 바이트 비교**한다.
553
+ SEEN_FILE="$D/seen_a.txt" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/sv_leak_a" \
554
+ --verifier "sh $D/sv_echo.sh" --family beta --seeded z1 >/dev/null 2>&1
555
+ SEEN_FILE="$D/seen_b.txt" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/sv_leak_b" \
556
+ --verifier "sh $D/sv_echo.sh" --family beta --seeded z2 >/dev/null 2>&1
557
+ SEEN_FILE="$D/seen_n.txt" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/sv_leak_n" \
558
+ --verifier "sh $D/sv_echo.sh" --family beta >/dev/null 2>&1
559
+ if [ ! -s "$D/seen_a.txt" ] || [ ! -s "$D/seen_b.txt" ] || [ ! -s "$D/seen_n.txt" ]; then
560
+ no "L45 씨앗 누출" "검증자 입력을 못 캡처했다 — 미측정(통과 아님)"
561
+ elif cmp -s "$D/seen_a.txt" "$D/seen_b.txt" && cmp -s "$D/seen_a.txt" "$D/seen_n.txt"; then
562
+ ok "L45 씨앗 «선택» 을 바꿔도 검증자 입력이 바이트 동일 (통제이지 힌트가 아니다)"
563
+ else
564
+ no "L45 씨앗 누출" "씨앗 선택에 따라 검증자 입력이 달라진다 — 멤버십이 샌다"
565
+ fi
566
+
567
+ # L46 — coverage 가 «드라이버» 요약 줄에도 실린다. verify 에만 있으면 읽는 사람에게는 없는 것이다.
568
+ FH_CODEX_BIN="$D/fake_smart.sh" FH_AGY_BIN="$D/fake_smart.sh" \
569
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipeCOV" --fleet "$D/fleetT.tbl" >"$D/pCOV" 2>&1
570
+ SUMC=$(grep -m1 '^PIPELINE ' "$D/pCOV")
571
+ if printf '%s' "$SUMC" | grep -qE 'coverage=[0-9]+/[0-9]+ \([0-9]+%\)'; then
572
+ ok "L46 PIPELINE 요약 줄이 coverage 를 나른다 (verify 에만 있으면 소비처 0 이다)"
573
+ else
574
+ no "L46 드라이버 coverage" "PIPELINE 줄에 coverage 없음 → $SUMC"
575
+ fi
576
+
577
+ # ── L47~L50 cross-family 라운드가 잡은 넷 (2026-09-09) — 수리마다 앵커 하나 ────────
578
+ cat > "$D/sv_drop_all.sh" <<'EOS'
579
+ #!/bin/sh
580
+ cat >/dev/null
581
+ echo '{"id":"z1","verdict":"false-positive","why":"no"}'
582
+ echo '{"id":"z2","verdict":"false-positive","why":"no"}'
583
+ EOS
584
+ cat > "$D/sa_reinstate.sh" <<'EOS'
585
+ #!/bin/sh
586
+ cat >/dev/null
587
+ echo '{"id":"z1","verdict":"correct-drop","why":"fine"}'
588
+ echo '{"id":"z2","verdict":"wrong-drop","why":"actually real"}'
589
+ EOS
590
+ cat > "$D/sv_debate.sh" <<'EOS'
591
+ #!/bin/sh
592
+ cat >/dev/null
593
+ echo '{"id":"z1","verdict":"needs-debate","why":"hm"}'
594
+ echo '{"id":"z2","verdict":"needs-debate","why":"hm"}'
595
+ EOS
596
+ chmod +x "$D/sv_drop_all.sh" "$D/sa_reinstate.sh" "$D/sv_debate.sh"
597
+
598
+ # L47 🟥 A급 — 감사자가 씨앗을 복권시켜도 «필터는 지웠다». 복권은 산출을 고치지 통제를 통과시키지 않는다
599
+ O47=$(python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l47" --verifier "sh $D/sv_drop_all.sh" \
600
+ --family beta --audit-verifier "sh $D/sa_reinstate.sh" --audit-family delta --seeded z2 2>&1); R47=$?
601
+ # 🟥 rc=5/DEGRADED 만 보면 «감사 자체가 안 돈» 경우도 통과한다 — 그러면 이 레인은 A1 회귀를
602
+ # 못 잡는다(cross-family R2 지적). 그러므로 «복권이 실제로 일어났다» 를 먼저 세운다.
603
+ _L47_REINSTATED=0
604
+ printf '%s' "$O47" | /usr/bin/grep -q 'reinstated=1' && /usr/bin/grep -q '"id": *"z2"' "$D/l47/confirmed.jsonl" 2>/dev/null && /usr/bin/grep -q '"reinstated": *true' "$D/l47/confirmed.jsonl" 2>/dev/null && ! /usr/bin/grep -q '"id": *"z2"' "$D/l47/dropped.jsonl" 2>/dev/null && _L47_REINSTATED=1
605
+ if [ "$_L47_REINSTATED" -ne 1 ]; then
606
+ no "L47 전제 미성립" "복권이 실제로 안 일어났다 — 이 레인은 A1 회귀를 못 잡는다. $(printf '%s' "$O47" | /usr/bin/grep '^DROPS')"
607
+ elif [ "$R47" -eq 5 ] && printf '%s' "$O47" | /usr/bin/grep -q 'SEEDED .*status=DEGRADED'; then
608
+ ok "L47 🟥 감사자가 씨앗을 «실제로 복권시켰는데도» DEGRADED·rc=5 (통제는 산출이 아니라 필터를 잰다)"
609
+ else
610
+ no "L47 감사 복권이 통제를 지우지 못한다" "rc=$R47 · $(printf '%s' "$O47" | /usr/bin/grep '^SEEDED')"
611
+ fi
612
+ # L48 needs-debate 는 «판정» 이 아니다 — 전부 debate 면 coverage 는 0 이어야 한다
613
+ O48=$(python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l48" --verifier "sh $D/sv_debate.sh" \
614
+ --family beta 2>&1); R48=$?
615
+ # 🟥 coverage 문자열만 보면 «0% 인데 rc=0» 이 통과한다 — 실제로 그랬다(cross-family R4). 판정
616
+ # 0건인 런은 «완주» 가 아니므로 종료코드까지 같이 단언한다.
617
+ if [ "$R48" -eq 3 ] && printf '%s' "$O48" | /usr/bin/grep -q 'coverage=0/2 (0%)'; then
618
+ ok "L48 전부 needs-debate → coverage=0/2 **그리고 rc=3** (판정 0건은 완주가 아니다)"
619
+ else
620
+ no "L48 debate 는 커버 아님" "rc=$R48 want=3 · $(printf '%s' "$O48" | /usr/bin/grep '^FINDINGS')"
621
+ fi
622
+ # L49 빈 씨앗 파일 = «비활성화된 통제» 지 «없는 통제» 가 아니다 → 설정 오류로 rc=2
623
+ : > "$D/seed_empty.txt"
624
+ python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l49" --verifier "sh $D/sv_keep.sh" \
625
+ --family beta --seeded-file "$D/seed_empty.txt" >/dev/null 2>&1; R49=$?
626
+ [ "$R49" -eq 2 ] && ok "L49 빈 --seeded-file → rc=2 (조용히 통제가 꺼지지 않는다)" \
627
+ || no "L49 빈 통제 파일" "rc=$R49 want=2"
628
+ # L50 불량 UTF-8 은 «완주» 코드로 새면 안 된다 (초판은 UnicodeDecodeError 가 exit 1 로 샜다)
629
+ printf '\377\376\n' > "$D/seed_bad.bin"
630
+ python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l50" --verifier "sh $D/sv_keep.sh" \
631
+ --family beta --seeded-file "$D/seed_bad.bin" >/dev/null 2>&1; R50=$?
632
+ [ "$R50" -eq 2 ] && ok "L50 불량 UTF-8 씨앗 파일 → rc=2 (rc=1 «완주» 로 새지 않는다)" \
633
+ || no "L50 통제 파일 디코드 오류" "rc=$R50 want=2"
634
+
635
+ # ── L49b~L53 cross-family R2 가 지적한 레인 약점 + 새 앵커 (2026-09-09) ───────────
636
+ # 🟥 L49/L50 은 rc=2 만 봤다 — 옵션 이름을 지워도 argparse 가 2 를 내므로 «무관한 실패» 로도
637
+ # 통과했고, 파싱을 다시 뒤로 옮겨도 통과했다. 셋을 더한다: 유효 파일 성공 컨트롤 · 의도한
638
+ # 진단 문구 · **검증자가 아예 호출되지 않았음**(출력물 부재로 판정).
639
+ cat > "$D/sv_touch.sh" <<'EOS'
640
+ #!/bin/sh
641
+ cat >/dev/null
642
+ : > "$TOUCHED"
643
+ echo '{"id":"z1","verdict":"confirmed","why":"ok"}'
644
+ echo '{"id":"z2","verdict":"confirmed","why":"ok"}'
645
+ EOS
646
+ chmod +x "$D/sv_touch.sh"
647
+ printf 'z2\n' > "$D/seed_good.txt"
648
+ # (a) 유효 파일 컨트롤 — 성공해야 한다. 이게 없으면 «전부 막는 픽스» 와 구분 불가
649
+ TOUCHED="$D/t_ok" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l49a" \
650
+ --verifier "sh $D/sv_touch.sh" --family beta --seeded-file "$D/seed_good.txt" >/dev/null 2>&1; R49A=$?
651
+ # (b) 빈 파일 — rc=2 · 진단 문구 · 검증자 미호출 · 출력 미생성
652
+ E49=$(TOUCHED="$D/t_empty" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l49b" \
653
+ --verifier "sh $D/sv_touch.sh" --family beta --seeded-file "$D/seed_empty.txt" 2>&1); R49B=$?
654
+ if [ "$R49A" -ne 0 ]; then
655
+ no "L49b 유효 통제 파일 컨트롤" "정상 씨앗 파일인데 rc=$R49A — 픽스가 과차단이다"
656
+ elif [ "$R49B" -ne 2 ]; then
657
+ no "L49b 빈 통제 파일" "rc=$R49B want=2"
658
+ elif ! printf '%s' "$E49" | /usr/bin/grep -q 'yielded no ids'; then
659
+ no "L49b 진단 문구" "rc=2 는 맞지만 의도한 진단이 아니다 — 무관한 usage 실패와 구분 불가: $E49"
660
+ elif [ -e "$D/t_empty" ] || [ -e "$D/l49b/confirmed.jsonl" ]; then
661
+ no "L49b 조기 거부" "검증자가 호출됐거나 출력이 생겼다 — 검증 «전» 에 안 막았다"
662
+ else
663
+ ok "L49b 빈 통제 파일: rc=2 · 의도한 진단 · 검증자 미호출 · 출력 미생성 (유효 파일 컨트롤 통과)"
664
+ fi
665
+ # (c) 불량 UTF-8 — 같은 세 조건
666
+ E50=$(TOUCHED="$D/t_bad" python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l50b" \
667
+ --verifier "sh $D/sv_touch.sh" --family beta --seeded-file "$D/seed_bad.bin" 2>&1); R50B=$?
668
+ if [ "$R50B" -eq 2 ] && printf '%s' "$E50" | /usr/bin/grep -q 'unusable' \
669
+ && [ ! -e "$D/t_bad" ] && [ ! -e "$D/l50b/confirmed.jsonl" ]; then
670
+ ok "L50b 불량 UTF-8: rc=2 · 의도한 진단 · 검증자 미호출 (rc=1 «완주» 로 새지 않는다)"
671
+ else
672
+ no "L50b 통제 파일 디코드 오류" "rc=$R50B · touched=$([ -e "$D/t_bad" ] && echo yes || echo no) · $E50"
673
+ fi
674
+ # L51 🟥 반올림 회귀 — 201건 중 1건만 미판정이면 100% 가 아니라 99% 여야 한다
675
+ : > "$D/big.jsonl"
676
+ i=1; while [ "$i" -le 201 ]; do
677
+ if [ "$i" -eq 1 ]; then fam=beta; else fam=gamma; fi
678
+ printf '{"id":"b%s","title":"t","producer_family":"%s"}\n' "$i" "$fam" >> "$D/big.jsonl"
679
+ i=$((i+1))
680
+ done
681
+ cat > "$D/sv_all.sh" <<'EOS'
682
+ #!/bin/sh
683
+ python3 -c '
684
+ import sys, json
685
+ for l in sys.stdin:
686
+ l = l.strip()
687
+ if not l: continue
688
+ try: d = json.loads(l)
689
+ except Exception: continue
690
+ print(json.dumps({"id": d.get("id"), "verdict": "confirmed", "why": "ok"}))'
691
+ EOS
692
+ chmod +x "$D/sv_all.sh"
693
+ O51=$(python3 "$HERE/finding_verify.py" "$D/big.jsonl" --out "$D/l51" --verifier "sh $D/sv_all.sh" --family beta 2>&1)
694
+ if printf '%s' "$O51" | /usr/bin/grep -q 'coverage=200/201 (99%)'; then
695
+ ok "L51 🟥 200/201 은 99% 로 찍힌다 (반올림하면 미판정 1건이 100% 뒤에 숨는다)"
696
+ else
697
+ no "L51 바닥 내림" "$(printf '%s' "$O51" | /usr/bin/grep '^FINDINGS')"
698
+ fi
699
+ # L52 드라이버도 debate 를 커버로 안 센다 (verify 만 고치고 드라이버를 안 고친 반쪽-픽스 회귀 앵커)
700
+ cat > "$D/m_deb.sh" <<'EOS'
701
+ #!/bin/sh
702
+ echo '{"id":"d1","title":"debatable thing","file":"tgt.py","line":1,"severity":"B"}'
703
+ EOS
704
+ chmod +x "$D/m_deb.sh"
705
+ # 🟥 초판 픽스처는 stdin 을 버리고 고정 id 하나를 뱉었다 — 실제 id 와 안 맞아 verify 가 «판정 없음
706
+ # → unverified» 로 처리했고, 레인은 「전제 미성립」으로 정직하게 실패했다(통과 아님). 실제로
707
+ # debate 를 만들려면 «받은 id 를 그대로» 되돌려줘야 한다. 형태는 fake_smart.sh 와 같이 간다.
708
+ cat > "$D/fake_debate.sh" <<'EOS'
709
+ #!/bin/sh
710
+ IN=$(cat)
711
+ [ -n "$IN" ] || IN="$*"
712
+ case "$IN" in *"correct-drop"*) MODE=audit ;; *) MODE=verify ;; esac
713
+ printf '%s\n' "$IN" | tr ',' '\n' | sed -n 's/.*"id": *"\([^"]*\)".*/\1/p' | sort -u | while read -r id; do
714
+ if [ "$MODE" = audit ]; then
715
+ echo "{\"id\":\"$id\",\"verdict\":\"correct-drop\",\"why\":\"checked\"}"
716
+ else
717
+ echo "{\"id\":\"$id\",\"verdict\":\"needs-debate\",\"why\":\"unclear from this file alone\"}"
718
+ fi
719
+ done
720
+ EOS
721
+ chmod +x "$D/fake_debate.sh"
722
+ printf 'codex|logic|%s\ngemini|security|%s\n' "sh $D/m_deb.sh" "sh $D/m_deb.sh" > "$D/fleetD.tbl"
723
+ FH_CODEX_BIN="$D/fake_debate.sh" FH_AGY_BIN="$D/fake_debate.sh" \
724
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipeDEB" --fleet "$D/fleetD.tbl" >"$D/pDEB" 2>&1
725
+ SUMD=$(grep -m1 '^PIPELINE ' "$D/pDEB")
726
+ # 🟥 `coverage=0/` 만 보면 «분모도 0» 이거나 «numerator 를 상수 0 으로 바꾼» 구현도 통과한다
727
+ # (cross-family R3 지적). 분모를 정확히 요구한다 — 이 픽스처는 계열 2개 × finding 1개 = 2건.
728
+ if printf '%s' "$SUMD" | grep -qE 'debate=[1-9]' && printf '%s' "$SUMD" | grep -q 'coverage=0/2 (0%)'; then
729
+ ok "L52 드라이버도 needs-debate 를 커버로 안 센다 (verify 만 고친 반쪽-픽스 회귀 앵커)"
730
+ elif printf '%s' "$SUMD" | grep -qE 'debate=0'; then
731
+ no "L52 전제 미성립" "이 픽스처가 debate 를 만들지 못했다 — 미측정(통과 아님): $SUMD"
732
+ else
733
+ no "L52 드라이버 debate" "$SUMD"
734
+ fi
735
+
736
+ # ── L53~L55 cross-family R3: «출력에 안 나타난 것» 이 판정으로 세어지면 안 된다 ────────
737
+ # 🟥 초판 드라이버는 판정 수를 «입력 − 기권» 으로 구했다. 그러면 건너뛴 파티션·읽기 실패·깨진
738
+ # JSON 처럼 **출력에 아예 안 나타난 것이 판정된 것으로** 계수되고 coverage 가 100% 로 찍힌다.
739
+ # 지금은 «해결된 verdict 를 실제로 들고 있는 행» 을 양으로 센다. 그 셋을 각각 박는다.
740
+ # L53 단일 계열이라 라우팅이 건너뛴다 → 판정 0. coverage 가 100% 면 안 된다
741
+ printf 'codex|logic|%s\n' "sh $D/m_codex.sh" > "$D/fleetS53.tbl"
742
+ FH_CODEX_BIN="$D/fake_smart.sh" bash "$PIPE" "$D/tgt.py" --out "$D/pipe53" --fleet "$D/fleetS53.tbl" >"$D/p53" 2>&1; R53=$?
743
+ SUM53=$(grep -m1 '^PIPELINE ' "$D/p53")
744
+ if [ -z "$SUM53" ]; then
745
+ no "L53 전제 미성립" "PIPELINE 요약 줄이 없다 — 미측정(통과 아님)"
746
+ elif printf '%s' "$SUM53" | grep -qE 'coverage=[1-9][0-9]*/[0-9]+ \(100%\)'; then
747
+ no "L53 건너뛴 파티션" "판정이 0인데 coverage 가 100% — 안 나타난 것을 «판정됨» 으로 셌다: $SUM53"
748
+ elif printf '%s' "$SUM53" | grep -q 'coverage=0/'; then
749
+ ok "L53 🟥 라우팅이 건너뛴 파티션은 «판정» 으로 안 세어진다 (coverage 0, rc=$R53)"
750
+ else
751
+ no "L53 건너뛴 파티션" "$SUM53"
752
+ fi
753
+ # L54 양의 컨트롤 — 정상 런은 분모까지 정확히 맞아야 한다. 없으면 «항상 0» 구현이 L53 을 통과한다
754
+ FH_CODEX_BIN="$D/fake_smart.sh" FH_AGY_BIN="$D/fake_smart.sh" \
755
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipe54" --fleet "$D/fleetT.tbl" >"$D/p54" 2>&1
756
+ SUM54=$(grep -m1 '^PIPELINE ' "$D/p54")
757
+ if printf '%s' "$SUM54" | grep -q 'coverage=2/2 (100%)'; then
758
+ ok "L54 정상 런은 coverage=2/2 (100%) — «항상 0» 구현이 L53 을 통과하지 못하게 하는 양의 컨트롤"
759
+ else
760
+ no "L54 양의 컨트롤" "정상 런인데 분모/분자가 안 맞는다: $SUM54"
761
+ fi
762
+ # L55 🟥 이 레인은 «장식» 이었다 — 인라인 파이썬 스니펫이 예외를 던지는지만 봤고, 출하되는
763
+ # `_count_json` 도 `finding_pipeline.sh` 도 한 줄도 안 불렀다. 드라이버의 계측 가드를 통째로
764
+ # 지워도 통과했다(cross-family R4, gemini 지목). 이제 **출하 스크립트에서 함수를 뽑아** 돌린다.
765
+ _L55_FN=$(sed -n '/^_count_json() {/,/^}/p' "$PIPE")
766
+ if [ -z "$_L55_FN" ]; then
767
+ no "L55 계측 실패 규율" "_count_json 을 $PIPE 에서 못 뽑았다 — 미측정(통과 아님)"
768
+ else
769
+ printf 'NOT JSON AT ALL\n' > "$D/broken.jsonl"
770
+ printf '{"id":"z1","verdict":"confirmed"}\n' > "$D/okrows.jsonl"
771
+ _L55_OUT=$(eval "$_L55_FN"; _count_json "$D/broken.jsonl" verdict confirmed >/dev/null 2>&1; echo "broken=$?";
772
+ _count_json "$D/okrows.jsonl" verdict confirmed >/dev/null 2>&1; echo "ok=$?";
773
+ _count_json "$D/does_not_exist.jsonl" verdict confirmed 2>/dev/null; echo "missing_rc=$?")
774
+ case "$_L55_OUT" in
775
+ *"broken=9"*) : ;;
776
+ *) no "L55 계측 실패 규율" "깨진 JSONL 인데 출하 함수가 비-영을 안 냈다 → $_L55_OUT"; _L55_BAD=1 ;;
777
+ esac
778
+ if [ "${_L55_BAD:-0}" -eq 0 ]; then
779
+ case "$_L55_OUT" in
780
+ *"ok=0"*) ok "L55 출하되는 _count_json: 깨진 산출물 → rc=9 · 정상 산출물 → rc=0 (양의 컨트롤 동반)" ;;
781
+ *) no "L55 계측 실패 규율" "정상 JSONL 인데 비-영 — 과차단이다 → $_L55_OUT" ;;
782
+ esac
783
+ fi
784
+ fi
785
+
786
+ # ── L56~L59 cross-family R4 (gemini 계열) 가 잡은 것들 ──────────────────────────
787
+ # L56 🟥 «안 지워졌다» 는 «통과» 가 아니다 — 씨앗에 기권하면 INCONCLUSIVE·rc=5
788
+ # 🟥 픽스처는 «씨앗에만» 기권해야 한다. 전부 기권시키면 «판정 0건 → rc=3» 이 먼저 걸려서
789
+ # rc=5 를 가리고, 그러면 이 레인은 자기가 주장하는 것을 안 재게 된다(첫 작성에서 실제로 그랬다).
790
+ cat > "$D/sv_debate_seed.sh" <<'EOS'
791
+ #!/bin/sh
792
+ cat >/dev/null
793
+ echo '{"id":"z1","verdict":"confirmed","why":"ok"}'
794
+ echo '{"id":"z2","verdict":"needs-debate","why":"unclear"}'
795
+ EOS
796
+ chmod +x "$D/sv_debate_seed.sh"
797
+ O56=$(python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l56" --verifier "sh $D/sv_debate_seed.sh" \
798
+ --family beta --seeded z2 2>&1); R56=$?
799
+ if [ "$R56" -eq 5 ] && printf '%s' "$O56" | /usr/bin/grep -q 'SEEDED .*status=INCONCLUSIVE'; then
800
+ ok "L56 🟥 검증자가 씨앗에 기권하면 INCONCLUSIVE·rc=5 (삭제 말고 «안 정하기» 로도 못 산다)"
801
+ else
802
+ no "L56 기권으로 통제 우회" "rc=$R56 · $(printf '%s' "$O56" | /usr/bin/grep '^SEEDED')"
803
+ fi
804
+ # L57 정수 id 씨앗 — 문자열 비교 강제 전에는 present=0 → ABSENT rc=5 라는 거짓 경보였다
805
+ printf '%s\n' '{"id":101,"title":"a","producer_family":"gamma"}' > "$D/intid.jsonl"
806
+ cat > "$D/sv_101.sh" <<'EOS'
807
+ #!/bin/sh
808
+ cat >/dev/null
809
+ echo '{"id":"101","verdict":"confirmed","why":"ok"}'
810
+ EOS
811
+ chmod +x "$D/sv_101.sh"
812
+ O57=$(python3 "$HERE/finding_verify.py" "$D/intid.jsonl" --out "$D/l57" --verifier "sh $D/sv_101.sh" \
813
+ --family beta --seeded 101 2>&1); R57=$?
814
+ if [ "$R57" -eq 0 ] && printf '%s' "$O57" | /usr/bin/grep -q 'SEEDED .*status=CLEAN'; then
815
+ ok "L57 정수 id 도 씨앗으로 잡힌다 (문자열 비교 — 거짓 ABSENT 경보 없음)"
816
+ else
817
+ no "L57 정수 id 씨앗" "rc=$R57 · $(printf '%s' "$O57" | /usr/bin/grep '^SEEDED')"
818
+ fi
819
+ # L58 복권된 행은 판정으로 세어진다 — verify 쪽 verdict 가 confirmed 로 바뀌었나
820
+ O58=$(python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l58" --verifier "sh $D/sv_drop_all.sh" \
821
+ --family beta --audit-verifier "sh $D/sa_reinstate.sh" --audit-family delta 2>&1)
822
+ if /usr/bin/grep -q '"reinstated": *true' "$D/l58/confirmed.jsonl" 2>/dev/null \
823
+ && /usr/bin/grep -q '"verdict": *"confirmed"' "$D/l58/confirmed.jsonl" 2>/dev/null \
824
+ && /usr/bin/grep -q '"pre_audit_verdict": *"false-positive"' "$D/l58/confirmed.jsonl" 2>/dev/null; then
825
+ ok "L58 복권된 행이 verdict=confirmed 를 달고 이전 값을 pre_audit_verdict 로 보존한다 (계수에서 증발 안 함)"
826
+ else
827
+ no "L58 복권 행 verdict" "$(head -c 300 "$D/l58/confirmed.jsonl" 2>/dev/null)"
828
+ fi
829
+ # L59 🟥 S급 앵커 — 드라이버가 split 의 exit 5 를 삼키지 않는다.
830
+ # 초판은 `sed -n '/^rank_of() {/,/^}/p'` 로 함수를 뽑았는데 **rank_of 는 한 줄짜리라 범위가
831
+ # 안 끝났고**, sed 가 135줄을 뱉었고 `head -20` 이 파이썬 스크립트 중간을 잘랐고, `eval` 이
832
+ # 드라이버 코드 스무 줄을 실제로 실행했다(파일명 too long 오류까지 냈다). 레인이 통과한 이유는
833
+ # 첫 줄의 함수 정의가 크래시 «전» 에 평가됐기 때문이다 — 통과의 출처가 의도와 달랐다.
834
+ # (cross-family round 5, gemini 계열.) 이제 **한 줄 정의만** 뽑고, 뽑은 것이 완결됐는지 검사한다.
835
+ _R59_DEF=$(/usr/bin/grep -m1 -E '^rank_of\(\) \{.*\}$' "$PIPE")
836
+ if [ -z "$_R59_DEF" ]; then
837
+ no "L59 exit 5 순위" "rank_of 를 한 줄 형태로 못 뽑았다 — 정의가 여러 줄로 바뀌었으면 추출을 고쳐라(미측정)"
838
+ else
839
+ _R59_5=$( eval "$_R59_DEF"; rank_of 5 )
840
+ _R59_3=$( eval "$_R59_DEF"; rank_of 3 )
841
+ _R59_0=$( eval "$_R59_DEF"; rank_of 0 )
842
+ if [ "${_R59_5:-0}" -gt "${_R59_3:-0}" ] && [ "${_R59_0:-9}" -eq 0 ]; then
843
+ ok "L59 🟥 출하 rank_of: 5 > 3 이고 0 은 0 (드라이버가 씨앗 통제 실패를 삼키지 않는다)"
844
+ else
845
+ no "L59 exit 5 순위" "rank_of(5)=$_R59_5 rank_of(3)=$_R59_3 rank_of(0)=$_R59_0 — 5 가 3 보다 커야 하고 0 은 0 이어야 한다"
846
+ fi
847
+ fi
848
+ # L60 종단간 — 드라이버 `--seeded` 로 씨앗을 지우는 검증자를 태우면 파이프라인 rc 가 5 여야 한다
849
+ cat > "$D/m_seed.sh" <<'EOS'
850
+ #!/bin/sh
851
+ echo '{"id":"s1","title":"seeded known-true","file":"tgt.py","line":1,"severity":"A"}'
852
+ echo '{"id":"s2","title":"ordinary","file":"tgt.py","line":2,"severity":"B"}'
853
+ EOS
854
+ chmod +x "$D/m_seed.sh"
855
+ # 🟥 R6 #3: 두 계열이 같은 member id(s1)를 내면 «--seeded s1» 은 정당하게 AMBIGUOUS 다 — 양의 컨트롤은
856
+ # 계열마다 다른 별칭을 내야 한다(gemini 는 g1/g2). 종전 픽스처는 그 모호성을 split 이 가려 주는 데 기대고 있었다.
857
+ cat > "$D/m_seed_g.sh" <<'EOS'
858
+ #!/bin/sh
859
+ echo '{"id":"g1","title":"gemini seeded known-true","file":"tgt.py","line":3,"severity":"A"}'
860
+ echo '{"id":"g2","title":"gemini ordinary","file":"tgt.py","line":4,"severity":"B"}'
861
+ EOS
862
+ chmod +x "$D/m_seed_g.sh"
863
+ # 🟥 검증자는 fleet 이 «재번호한» id 를 받는다 — 그래서 픽스처는 원래 멤버 id(`member_id`)를
864
+ # 봐야 «호출자 어휘로 선언한 씨앗» 을 지울 수 있다. 이게 이 레인이 재려는 경로 그 자체다.
865
+ cat > "$D/fake_kill_seed.sh" <<'EOS'
866
+ #!/bin/sh
867
+ # 🟥 gemini 경로는 프롬프트를 stdin 이 아니라 `-p` 인자로 받고 stdin 을 /dev/null 로 닫는다.
868
+ # stdin 만 읽는 픽스처는 그 split 에서 아무 판정도 못 내고 «미판정» 을 만든다 — 레인이
869
+ # 측정하려던 것과 다른 것을 재게 된다. 둘 다 받는다(fake_smart.sh 와 같은 형태).
870
+ IN=$(cat)
871
+ [ -n "$IN" ] || IN="$*"
872
+ printf '%s' "$IN" | python3 -c '
873
+ import sys, json
874
+ rows = []
875
+ for l in sys.stdin:
876
+ l = l.strip()
877
+ if not l.startswith("{"): continue
878
+ try: rows.append(json.loads(l))
879
+ except Exception: pass
880
+ audit = any("drop_verdict" in r or r.get("verdict") == "false-positive" for r in rows)
881
+ for r in rows:
882
+ rid = r.get("id")
883
+ if audit:
884
+ print(json.dumps({"id": rid, "verdict": "correct-drop", "why": "checked"}))
885
+ elif str(r.get("member_id")) == "s1":
886
+ print(json.dumps({"id": rid, "verdict": "false-positive", "why": "deleting the control"}))
887
+ else:
888
+ print(json.dumps({"id": rid, "verdict": "confirmed", "why": "holds"}))'
889
+ EOS
890
+ chmod +x "$D/fake_kill_seed.sh"
891
+ printf 'codex|logic|%s\ngemini|security|%s\n' "sh $D/m_seed.sh" "sh $D/m_seed_g.sh" > "$D/fleetSEED.tbl"
892
+ # 🟥 fleet 이 id 를 재번호하므로 호출자 어휘(`s1`)로 선언할 수 있어야 한다 — member_id 보존이
893
+ # 그것을 가능하게 한다. 이 레인이 그 종단간 경로를 통째로 잰다.
894
+ FH_CODEX_BIN="$D/fake_kill_seed.sh" FH_AGY_BIN="$D/fake_kill_seed.sh" \
895
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipeSEED" --fleet "$D/fleetSEED.tbl" --seeded s1 >"$D/pSEED" 2>&1; R60=$?
896
+ if [ "$R60" -eq 5 ]; then
897
+ ok "L60 🟥 종단간: 드라이버 --seeded s1 (호출자 어휘) 로 씨앗을 지우면 파이프라인 rc=5"
898
+ else
899
+ no "L60 종단간 씨앗" "rc=$R60 want=5 · $(grep -m1 '^PIPELINE ' "$D/pSEED") · $(tail -2 "$D/pSEED")"
900
+ fi
901
+ # L61 배선 검사(행동 아님, 그렇게 라벨한다) — COUNT_ERR 이 실제로 소비되나
902
+ if /usr/bin/grep -q 'COUNT_ERR' "$PIPE" && /usr/bin/grep -A6 'if \[ "\$COUNT_ERR" -ne 0 \]' "$PIPE" | /usr/bin/grep -q 'note_rc'; then
903
+ ok "L61 배선: 계측 오류 플래그가 실제로 note_rc 로 소비된다 (🟡 배선 검사지 행동 검사가 아니다)"
904
+ else
905
+ no "L61 배선" "COUNT_ERR 이 설정만 되고 rc 에 반영되지 않는다 — 조용한 계측 실패다"
906
+ fi
907
+ # ── L62~L65 cross-family R6 (gemini) 가 잡은 것들 ────────────────────────────────
908
+ # L62 드라이버 `confirmed=` 가 debate 를 포함하면 안 된다 — verify 만 고친 반쪽-픽스의 **세 번째** 재발
909
+ FH_CODEX_BIN="$D/fake_debate.sh" FH_AGY_BIN="$D/fake_debate.sh" \
910
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipe62" --fleet "$D/fleetD.tbl" >"$D/p62" 2>&1
911
+ SUM62=$(grep -m1 '^PIPELINE ' "$D/p62")
912
+ if printf '%s' "$SUM62" | grep -qE 'confirmed=0 ' && printf '%s' "$SUM62" | grep -qE 'debate=[1-9]'; then
913
+ ok "L62 전부 기권이면 드라이버도 confirmed=0 (한 줄 안에서 세 숫자가 서로 안 어긋난다)"
914
+ else
915
+ no "L62 드라이버 confirmed" "$SUM62"
916
+ fi
917
+ # L63 🟥 씨앗이 두 행에 걸리면 AMBIGUOUS — «비관적으로 하나로 묶어» 거짓 DEGRADED 를 내면 안 된다
918
+ printf '%s\n%s\n' '{"id":"r1","member_id":"1","title":"a","producer_family":"gamma"}' \
919
+ '{"id":"r2","member_id":"1","title":"b","producer_family":"gamma"}' > "$D/amb.jsonl"
920
+ cat > "$D/sv_amb.sh" <<'EOS'
921
+ #!/bin/sh
922
+ cat >/dev/null
923
+ echo '{"id":"r1","verdict":"confirmed","why":"ok"}'
924
+ echo '{"id":"r2","verdict":"false-positive","why":"nope"}'
925
+ EOS
926
+ chmod +x "$D/sv_amb.sh"
927
+ O63=$(python3 "$HERE/finding_verify.py" "$D/amb.jsonl" --out "$D/l63" --verifier "sh $D/sv_amb.sh" \
928
+ --family beta --seeded 1 2>&1); R63=$?
929
+ if [ "$R63" -eq 5 ] && printf '%s' "$O63" | /usr/bin/grep -q 'status=AMBIGUOUS' \
930
+ && printf '%s' "$O63" | /usr/bin/grep -q 'locally unique'; then
931
+ ok "L63 🟥 씨앗이 여러 행에 걸리면 AMBIGUOUS·rc=5 + 사유 (거짓 DEGRADED 로 접지 않는다)"
932
+ else
933
+ no "L63 씨앗 모호성" "rc=$R63 · $(printf '%s' "$O63" | /usr/bin/grep -E 'status=|locally')"
934
+ fi
935
+ # L64 검증기가 «안 돌았다» 는 통제 모호성이 아니라 실행 실패 — rc=3 이어야 한다
936
+ O64=$(python3 "$HERE/finding_verify.py" "$D/sf.jsonl" --out "$D/l64" \
937
+ --verifier "sh $D/does_not_exist_at_all.sh" --family beta --seeded z2 2>&1); R64=$?
938
+ if [ "$R64" -eq 3 ]; then
939
+ ok "L64 검증기 실행 실패 → rc=3 (씨앗 INCONCLUSIVE 로 오보하지 않는다)"
940
+ else
941
+ no "L64 실행 실패 vs 통제 모호성" "rc=$R64 want=3 · $(printf '%s' "$O64" | /usr/bin/grep -E '^(FINDINGS|SEEDED)')"
942
+ fi
943
+ # L65 양의 컨트롤 — 씨앗이 살아남으면 드라이버는 0 을 낸다. 없으면 «--seeded 면 무조건 5» 도 L60 을 통과한다
944
+ cat > "$D/fake_keep_seed.sh" <<'EOS'
945
+ #!/bin/sh
946
+ # 🟥 gemini 경로는 프롬프트를 stdin 이 아니라 `-p` 인자로 받고 stdin 을 /dev/null 로 닫는다.
947
+ # stdin 만 읽는 픽스처는 그 split 에서 아무 판정도 못 내고 «미판정» 을 만든다 — 레인이
948
+ # 측정하려던 것과 다른 것을 재게 된다. 둘 다 받는다(fake_smart.sh 와 같은 형태).
949
+ IN=$(cat)
950
+ [ -n "$IN" ] || IN="$*"
951
+ printf '%s' "$IN" | python3 -c '
952
+ import sys, json
953
+ rows = []
954
+ for l in sys.stdin:
955
+ l = l.strip()
956
+ if not l.startswith("{"): continue
957
+ try: rows.append(json.loads(l))
958
+ except Exception: pass
959
+ audit = any("drop_verdict" in r or r.get("verdict") == "false-positive" for r in rows)
960
+ for r in rows:
961
+ v = "correct-drop" if audit else "confirmed"
962
+ print(json.dumps({"id": r.get("id"), "verdict": v, "why": "ok"}))'
963
+ EOS
964
+ chmod +x "$D/fake_keep_seed.sh"
965
+ FH_CODEX_BIN="$D/fake_keep_seed.sh" FH_AGY_BIN="$D/fake_keep_seed.sh" \
966
+ bash "$PIPE" "$D/tgt.py" --out "$D/pipeKEEP" --fleet "$D/fleetSEED.tbl" --seeded s1 >"$D/pKEEP" 2>&1; R65=$?
967
+ if [ "$R65" -eq 0 ]; then
968
+ ok "L65 양의 컨트롤: 씨앗이 살아남으면 --seeded 여도 rc=0 («--seeded 면 무조건 5» 를 배제)"
969
+ else
970
+ no "L65 씨앗 양의 컨트롤" "rc=$R65 want=0 · $(grep -m1 '^PIPELINE ' "$D/pKEEP") · $(tail -2 "$D/pKEEP")"
971
+ fi
972
+
973
+ # ── L66~L68 생성시점 탈상관(--round2) — B-2 사전등록의 ARM ────────────────────────
974
+ # 🟥 논지: 계열을 «걸러낼 때» 만나게 하면 수확을 지불한다(실측 8→6). «쓸 때» 만나게 하면 안 낸다.
975
+ # 그래서 2차 패스는 수락/거부가 아니라 «자기 목록 다시 쓰기» 다. 세 가지를 박는다.
976
+ cat > "$D/r2_a.sh" <<'EOS'
977
+ #!/bin/sh
978
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
979
+ case "$IN" in
980
+ *"YOUR OWN ROUND-1 FINDINGS"*)
981
+ echo '{"title":"A1 revised","file":"t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.8}'
982
+ echo '{"title":"A2 new","file":"t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.6}'
983
+ echo 'DROPPED: none' ;;
984
+ *) echo '{"title":"A1","file":"t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
985
+ esac
986
+ EOS
987
+ cat > "$D/r2_b.sh" <<'EOS'
988
+ #!/bin/sh
989
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
990
+ case "$IN" in
991
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"title":"B1 kept","file":"t.py","line":2,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.7}' ;;
992
+ *) echo '{"title":"B1","file":"t.py","line":2,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.7}' ;;
993
+ esac
994
+ EOS
995
+ cat > "$D/r2_dead.sh" <<'EOS'
996
+ #!/bin/sh
997
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
998
+ case "$IN" in
999
+ *"YOUR OWN ROUND-1 FINDINGS"*) : ;; # 2차에서 아무것도 안 낸다
1000
+ *) echo '{"title":"only round1","file":"t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1001
+ esac
1002
+ EOS
1003
+ chmod +x "$D/r2_a.sh" "$D/r2_b.sh" "$D/r2_dead.sh"
1004
+ printf 'def f(x):\n return x\n' > "$D/r2t.py"
1005
+ FLEET_SH2="$HERE/finding_fleet.sh"
1006
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/r2_a.sh" "$D/r2_b.sh" > "$D/fleetR2.tbl"
1007
+ O66=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/fr2" --fleet "$D/fleetR2.tbl" --round2 2>&1)
1008
+ if printf '%s' "$O66" | /usr/bin/grep -q 'FLEET round2 r1=2 r2=3 delta=1'; then
1009
+ ok "L66 🟥 2차 패스가 수확을 «늘린다» (r1=2 → r2=3) — 선별이 아니라 생성이라는 증거"
1010
+ else
1011
+ no "L66 생성시점 탈상관" "$(printf '%s' "$O66" | /usr/bin/grep '^FLEET round2')"
1012
+ fi
1013
+ # L67 peer 파일이 «자기» 와 «남» 을 실제로 가른다 — 안 가르면 무엇을 고칠지 모른다
1014
+ if /usr/bin/grep -q 'YOUR OWN ROUND-1' "$D"/fr2/peer_codex_*.txt 2>/dev/null \
1015
+ && /usr/bin/grep -q 'THE OTHER FAMILY' "$D"/fr2/peer_codex_*.txt 2>/dev/null \
1016
+ && /usr/bin/grep -A2 'YOUR OWN ROUND-1' "$D"/fr2/peer_codex_*.txt | /usr/bin/grep -q '"A1"' \
1017
+ && /usr/bin/grep -A2 'THE OTHER FAMILY' "$D"/fr2/peer_codex_*.txt | /usr/bin/grep -q '"B1"'; then
1018
+ ok "L67 peer 브리핑이 자기 것과 남의 것을 갈라서 보여준다"
1019
+ else
1020
+ no "L67 peer 브리핑" "$(head -8 "$D"/fr2/peer_codex_*.txt 2>/dev/null)"
1021
+ fi
1022
+ # L68 🟥 2차가 아무것도 못 내면 1차를 «대체» 하면 안 된다 — 그러면 탈상관이 아니라 삭제다
1023
+ printf 'codex|logic|sh %s\n' "$D/r2_dead.sh" > "$D/fleetR2d.tbl"
1024
+ O68=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/fr2d" --fleet "$D/fleetR2d.tbl" --round2 2>&1)
1025
+ N68=$(/usr/bin/wc -l < "$D/fr2d/findings.jsonl" 2>/dev/null | tr -d ' ')
1026
+ # R2 #4 이후: 단독 멤버가 2차 빈손이면 «멤버별 폴백»(ZERO_EMPTY → 자기 1차) 이 먼저 잡는다 — 둘 다 «1차 보존» 이다
1027
+ if [ "${N68:-0}" -ge 1 ] && printf '%s' "$O68" | /usr/bin/grep -qE 'keeping round-1 findings|keeping ITS round-1 findings'; then
1028
+ ok "L68 🟥 2차가 빈손이면 1차를 유지한다 (실패한 2차가 1차를 삭제하지 않는다)"
1029
+ else
1030
+ no "L68 2차 실패 시 1차 보존" "findings=$N68 · $(printf '%s' "$O68" | /usr/bin/grep -E 'round2')"
1031
+ fi
1032
+
1033
+ # ── L69~L72 검증자 풀 = 함대 명부 (측정이 스스로 찾아낸 결함, 2026-09-10) ──────────────
1034
+ # 🟥 논지: families.txt 는 «발견을 낸 계열» 이라서 0건을 낸 계열은 검증자 후보에서 사라진다.
1035
+ # 그러면 멀쩡히 돌아간 검증자가 있는데도 발견 전체가 미검증으로 떨어진다(coverage 0/N).
1036
+ # 실측: F_typed 팔 case_g02.py r1~r3 — gemini 가 안전필터로 «차단»(rc=0!) 되어 0건 → codex 2건 전량 미검증.
1037
+ cat > "$D/one_find.sh" <<'EOS'
1038
+ #!/bin/sh
1039
+ cat >/dev/null
1040
+ echo '{"title":"solo A","file":"pool_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.8}'
1041
+ echo '{"title":"solo B","file":"pool_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.6}'
1042
+ EOS
1043
+ cat > "$D/zero_prose.sh" <<'EOS'
1044
+ #!/bin/sh
1045
+ cat >/dev/null
1046
+ echo 'No issues found in this file.'
1047
+ EOS
1048
+ cat > "$D/zero_dead.sh" <<'EOS'
1049
+ #!/bin/sh
1050
+ cat >/dev/null
1051
+ echo 'ERROR: quota exhausted' >&2
1052
+ exit 1
1053
+ EOS
1054
+ # 검증자 스텁 — 받은 모든 행을 confirmed 로 판정. 감사 패스면 correct-drop.
1055
+ # 🟥 gemini 경로는 프롬프트를 stdin 이 아니라 `-p` 인자로 주고 stdin 을 닫는다(L65 의 교훈).
1056
+ cat > "$D/ver_ok.sh" <<'EOS'
1057
+ #!/bin/sh
1058
+ IN=$(cat)
1059
+ [ -n "$IN" ] || IN="$*"
1060
+ printf '%s' "$IN" | /usr/bin/python3 -c '
1061
+ import sys, json
1062
+ rows=[]
1063
+ for l in sys.stdin:
1064
+ l=l.strip()
1065
+ if not l.startswith("{"): continue
1066
+ try: rows.append(json.loads(l))
1067
+ except Exception: pass
1068
+ audit=any("drop_verdict" in r or r.get("verdict")=="false-positive" for r in rows)
1069
+ for r in rows:
1070
+ v="correct-drop" if audit else "confirmed"
1071
+ print(json.dumps({"id": r.get("id"), "verdict": v, "why": "stub"}))'
1072
+ EOS
1073
+ chmod +x "$D/one_find.sh" "$D/zero_prose.sh" "$D/zero_dead.sh" "$D/ver_ok.sh"
1074
+ printf 'def p(x):\n return x\n' > "$D/pool_t.py"
1075
+
1076
+ # L69 함대는 «응답은 있는데 계약 출력 0» 을 ZERO_NONJSON 으로 타입한다 (차단이 이 얼굴이다)
1077
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/one_find.sh" "$D/zero_prose.sh" > "$D/fleetP.tbl"
1078
+ O69=$(bash "$HERE/finding_fleet.sh" "$D/pool_t.py" --out "$D/fp1" --fleet "$D/fleetP.tbl" 2>&1)
1079
+ if printf '%s\n' "$O69" | /usr/bin/grep -q 'family=gemini .*findings=0 status=ZERO_NONJSON' \
1080
+ && printf '%s\n' "$O69" | /usr/bin/grep -q 'family=codex .*findings=2 status=OK'; then
1081
+ ok "L69 🟥 rc=0 인 «응답 있음·계약 0» 이 ZERO_NONJSON 으로 기록된다 (known-negative: 발견 낸 쪽은 OK)"
1082
+ else
1083
+ no "L69 ZERO_NONJSON 타입" "$(printf '%s\n' "$O69" | /usr/bin/grep '^MEMBER')"
1084
+ fi
1085
+
1086
+ # L70 🟥 본 결함 — 0건을 낸 계열이 «검증자로 쓰인다». 수리 전에는 coverage 0/2 rc=3 이었다.
1087
+ O70=$(FH_CODEX_BIN="$D/ver_ok.sh" FH_AGY_BIN="$D/ver_ok.sh" \
1088
+ bash "$PIPE" "$D/pool_t.py" --out "$D/pp1" --fleet "$D/fleetP.tbl" 2>&1); RC70=$?
1089
+ L70=$(printf '%s\n' "$O70" | /usr/bin/grep '^PIPELINE ')
1090
+ if [ "$RC70" -eq 0 ] && printf '%s' "$L70" | /usr/bin/grep -q 'coverage=2/2 (100%)' \
1091
+ && printf '%s' "$L70" | /usr/bin/grep -q 'roster=[a-z,]*codex' \
1092
+ && printf '%s' "$L70" | /usr/bin/grep -q 'roster=[a-z,]*gemini' \
1093
+ && printf '%s' "$L70" | /usr/bin/grep -q '(fleet)' \
1094
+ && printf '%s' "$L70" | /usr/bin/grep -q 'blocked_members=1'; then
1095
+ ok "L70 🟥 0건 계열이 검증자 풀에 남는다 — coverage 2/2 rc=0 (수리 전: 0/2 rc=3)"
1096
+ else
1097
+ no "L70 검증자 풀=함대 명부" "rc=$RC70 · $L70"
1098
+ fi
1099
+
1100
+ # L71 반대 방향 — 상대 멤버가 «실제로 실패» 하면 풀에 들어가면 안 된다. 미검증이 정직한 답이다.
1101
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/one_find.sh" "$D/zero_dead.sh" > "$D/fleetQ.tbl"
1102
+ O71=$(FH_CODEX_BIN="$D/ver_ok.sh" FH_AGY_BIN="$D/ver_ok.sh" \
1103
+ bash "$PIPE" "$D/pool_t.py" --out "$D/pp2" --fleet "$D/fleetQ.tbl" 2>&1); RC71=$?
1104
+ L71=$(printf '%s\n' "$O71" | /usr/bin/grep '^PIPELINE ')
1105
+ if [ "$RC71" -eq 3 ] && printf '%s' "$L71" | /usr/bin/grep -q 'roster=codex(fleet)' \
1106
+ && printf '%s' "$L71" | /usr/bin/grep -q 'coverage=0/2 (0%)'; then
1107
+ ok "L71 🟥 죽은 멤버는 검증자가 못 된다 — rc=3·roster=codex (과교정 방지: 풀이 무조건 넓어지지 않는다)"
1108
+ else
1109
+ no "L71 죽은 멤버 배제" "rc=$RC71 · $L71"
1110
+ fi
1111
+
1112
+ # L72 되돌림 프로브 — 풀 소스를 families.txt 로 되돌리면 L70 이 «정확히» 빨개지나
1113
+ # 🟥 사본의 HERE 는 사본이 있는 디렉터리다 — 형제 스크립트를 같이 옮기지 않으면 «missing …
1114
+ # skipped, NOT passed» 로 exit 3 이 나서 rc 만 맞는 거짓 초록이 된다(초판이 그랬고, 아래
1115
+ # coverage 단언이 그것을 잡았다 — rc 하나만 봤으면 통과했다).
1116
+ /bin/cp "$HERE/finding_fleet.sh" "$HERE/finding_verify.py" "$HERE/finding_verifier.sh" "$D/" 2>/dev/null
1117
+ /usr/bin/sed 's|done < "$ROSTER"|done < "$OUT/families.txt"|g' "$PIPE" > "$D/pipe_rev.sh"
1118
+ NREV=$(/usr/bin/grep -c 'done < "\$OUT/families.txt"' "$D/pipe_rev.sh" 2>/dev/null); NREV=${NREV:-0}
1119
+ if [ "$NREV" -ge 2 ]; then
1120
+ RCVOUT=$(FH_CODEX_BIN="$D/ver_ok.sh" FH_AGY_BIN="$D/ver_ok.sh" \
1121
+ bash "$D/pipe_rev.sh" "$D/pool_t.py" --out "$D/pp3" --fleet "$D/fleetP.tbl" 2>&1); RCV_RC=$?
1122
+ if [ "$RCV_RC" -eq 3 ] && printf '%s\n' "$RCVOUT" | /usr/bin/grep -q 'coverage=0/2 (0%)'; then
1123
+ ok "L72 되돌림 — 풀을 families.txt 로 돌리면 결함이 정확히 재발한다 (L70 앵커가 장식이 아니다)"
1124
+ else
1125
+ no "L72 되돌림 프로브" "rc=$RCV_RC want=3 · $(printf '%s\n' "$RCVOUT" | /usr/bin/grep '^PIPELINE ') · $(printf '%s\n' "$RCVOUT" | /usr/bin/grep -m1 -i 'missing\|skipped')"
1126
+ fi
1127
+ else
1128
+ no "L72 되돌림 프로브" "치환 적용 $NREV 곳 (>=2 필요) — HARNESS-ERROR, 대상 무판정"
1129
+ fi
1130
+
1131
+ # ── L73~L78 cross-family 라운드가 연 구멍들 (codex, 2026-09-10) ────────────────────
1132
+ # 🟥 여섯 건 전부 «내 수리가 만든» 결함이다 — 측정이 ①②를 물어왔고, 적대검증이 그 수리를 물었다.
1133
+ FL="$HERE/finding_fleet.sh"
1134
+ printf 'def q(x):\n return x\n' > "$D/x_t.py"
1135
+ # 2차에서: codex 는 비고(glob 첫 파일) gemini 는 찬다
1136
+ cat > "$D/x_c_empty2.sh" <<'EOS'
1137
+ #!/bin/sh
1138
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1139
+ case "$IN" in
1140
+ *"YOUR OWN ROUND-1 FINDINGS"*) : ;;
1141
+ *) echo '{"title":"C1 round1 only","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1142
+ esac
1143
+ EOS
1144
+ cat > "$D/x_g_full2.sh" <<'EOS'
1145
+ #!/bin/sh
1146
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1147
+ case "$IN" in
1148
+ *"YOUR OWN ROUND-1 FINDINGS"*)
1149
+ echo '{"title":"G1 revised","file":"x_t.py","line":2,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.8}'
1150
+ echo '{"title":"G2 new","file":"x_t.py","line":3,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.6}' ;;
1151
+ *) echo '{"title":"G1","file":"x_t.py","line":2,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.7}' ;;
1152
+ esac
1153
+ EOS
1154
+ # 2차에서 일부만 내고 죽는다 (rc=1)
1155
+ cat > "$D/x_c_die2.sh" <<'EOS'
1156
+ #!/bin/sh
1157
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1158
+ case "$IN" in
1159
+ *"YOUR OWN ROUND-1 FINDINGS"*)
1160
+ echo '{"title":"C1 partial","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1161
+ exit 1 ;;
1162
+ *)
1163
+ echo '{"title":"C1 round1","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1164
+ echo '{"title":"C2 round1 ONLY — must survive","file":"x_t.py","line":9,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1165
+ esac
1166
+ EOS
1167
+ # 2차에서 «전부 스스로 내림» 만 낸다 (계약이 요구한 출력)
1168
+ cat > "$D/x_selfdrop2.sh" <<'EOS'
1169
+ #!/bin/sh
1170
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1171
+ case "$IN" in
1172
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo 'DROPPED: S1 — checked the caller; my claim was wrong' ;;
1173
+ *) echo '{"title":"S1","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1174
+ esac
1175
+ EOS
1176
+ # 발견을 내고 «죽는다» (rc=1) — 1차에서
1177
+ cat > "$D/x_emit_die.sh" <<'EOS'
1178
+ #!/bin/sh
1179
+ cat >/dev/null
1180
+ echo '{"title":"E1 emitted then died","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1181
+ exit 1
1182
+ EOS
1183
+ chmod +x "$D"/x_*.sh
1184
+
1185
+ # L73 🟥 glob 첫 파일이 비었다고 «2차가 아무것도 못 냈다» 로 보고하면 안 된다
1186
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/x_c_empty2.sh" "$D/x_g_full2.sh" > "$D/fx1.tbl"
1187
+ O73=$(bash "$FL" "$D/x_t.py" --out "$D/fx1" --fleet "$D/fx1.tbl" --round2 2>&1)
1188
+ if ! printf '%s\n' "$O73" | /usr/bin/grep -q 'produced nothing' \
1189
+ && /usr/bin/grep -q 'G2 new' "$D/fx1/findings.jsonl" 2>/dev/null; then
1190
+ ok "L73 🟥 glob 첫 파일이 비어도 «2차 빈손» 으로 오보하지 않는다 (멤버별 병합)"
1191
+ else
1192
+ no "L73 glob-첫파일 편향" "$(printf '%s\n' "$O73" | /usr/bin/grep -E '^FLEET round2')"
1193
+ fi
1194
+
1195
+ # L74 🟥 2차에서 죽은 멤버의 «1차 발견» 이 삭제되면 안 된다 — 조용한 수확 손실
1196
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/x_c_die2.sh" "$D/x_g_full2.sh" > "$D/fx2.tbl"
1197
+ O74=$(bash "$FL" "$D/x_t.py" --out "$D/fx2" --fleet "$D/fx2.tbl" --round2 2>&1)
1198
+ if /usr/bin/grep -q 'C2 round1 ONLY' "$D/fx2/findings.jsonl" 2>/dev/null \
1199
+ && printf '%s\n' "$O74" | /usr/bin/grep -q 'fallback=1' \
1200
+ && /usr/bin/grep -q 'G2 new' "$D/fx2/findings.jsonl" 2>/dev/null; then
1201
+ ok "L74 🟥 2차에서 죽은 멤버는 «자기 1차» 로 되돌아가고, 성공한 멤버는 2차로 간다 (멤버별)"
1202
+ else
1203
+ no "L74 2차 실패 멤버별 폴백" "$(printf '%s\n' "$O74" | /usr/bin/grep -E 'fallback|FLEET round2') · $(wc -l < "$D/fx2/findings.jsonl" 2>/dev/null)"
1204
+ fi
1205
+
1206
+ # L75 🟥 «빈 명부» 는 성공이 아니다 — 출하되는 명부 빌더를 «뽑아서» 직접 잰다.
1207
+ # 초판 픽스처는 이 경로에 못 닿았다(멤버가 전부 죽으면 함대가 더 앞에서 rc≠0 으로 끝난다).
1208
+ # 그래서 L55 와 같은 형태로 **출하 코드를 추출해** 양방향으로 돌린다 — 장식 레인 방지.
1209
+ /usr/bin/python3 - "$PIPE" "$D/roster_builder.py" <<'PY'
1210
+ import sys, re
1211
+ src, dst = sys.argv[1], sys.argv[2]
1212
+ s = open(src, encoding="utf-8").read()
1213
+ m = re.search(r"/usr/bin/python3 -c '\n(import re,sys\n.*?)' \"\$OUT/fleet/members\.txt\"", s, re.S)
1214
+ if not m:
1215
+ sys.stderr.write("HARNESS-ERROR: roster builder snippet not found in the shipped driver\n"); sys.exit(9)
1216
+ open(dst, "w", encoding="utf-8").write(m.group(1))
1217
+ PY
1218
+ if [ -s "$D/roster_builder.py" ]; then
1219
+ printf 'MEMBER family=codex role=logic rc=1 findings=0 status=FAILED\n' > "$D/mem_bad.txt"
1220
+ printf 'MEMBER family=codex role=logic review rc=0 findings=1 status=OK\nMEMBER family=gemini role=security rc=0 findings=1 status=OK\n' > "$D/mem_space.txt"
1221
+ R75A=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem_bad.txt" 2>/dev/null); A=$?
1222
+ R75B=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem_space.txt" 2>/dev/null); B=$?
1223
+ NB=$(printf '%s\n' "$R75B" | /usr/bin/grep -c .)
1224
+ # 🟥 R2: `sys.exit(4)`→`raise` 로 바꿔도 «비영» 이라 통과했다 — 의도한 실패와 크래시가 같은 얼굴. 코드 4 를 «정확히» 요구.
1225
+ E75A=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem_bad.txt" 2>&1 >/dev/null)
1226
+ if [ "$A" -eq 4 ] && [ -z "$R75A" ] && printf '%s' "$E75A" | /usr/bin/grep -q '0 usable families' && [ "$B" -eq 0 ] && [ "$NB" = 2 ]; then
1227
+ ok "L75 🟥 출하 명부 빌더: 0계열이면 «exit 4 + 사유»(크래시와 구분) · 공백 낀 role 도 2계열로 파싱 (양방향)"
1228
+ else
1229
+ no "L75 명부 빌더" "빈명부 rc=$A(4 기대) out='$R75A' err='$E75A' · 공백role rc=$B n=$NB(0·2 기대)"
1230
+ fi
1231
+ else
1232
+ no "L75 명부 빌더" "HARNESS-ERROR: 출하 스니펫 추출 실패 — 대상 무판정"
1233
+ fi
1234
+ # L75b 배선 — 드라이버가 «바이트» 가 아니라 «계열 수» 로 판정하나
1235
+ if /usr/bin/grep -q '_ROSTER_N=$(/usr/bin/grep -c . "$ROSTER"' "$PIPE" \
1236
+ && /usr/bin/grep -q '\[ "$_ROSTER_N" -eq 0 \]' "$PIPE" \
1237
+ && ! /usr/bin/grep -q '! -s "$ROSTER"' "$PIPE"; then
1238
+ ok "L75b 드라이버가 명부를 «계열 수» 로 판정한다 (-s 가 사라졌다 — 🟡 배선 검사)"
1239
+ else
1240
+ no "L75b 명부 판정 배선" "$(/usr/bin/grep -n 'ROSTER_N\|-s \"\$ROSTER\"' "$PIPE" | head -3)"
1241
+ fi
1242
+
1243
+ # L76 🟥 «전부 스스로 내림» 은 계약 준수지 «차단» 이 아니다
1244
+ printf 'codex|logic|sh %s\n' "$D/x_selfdrop2.sh" > "$D/fx4.tbl"
1245
+ O76=$(bash "$FL" "$D/x_t.py" --out "$D/fx4" --fleet "$D/fx4.tbl" --round2 2>&1)
1246
+ if printf '%s\n' "$O76" | /usr/bin/grep -q 'MEMBER2 .*status=ZERO_SELFDROPPED' \
1247
+ && ! printf '%s\n' "$O76" | /usr/bin/grep -q 'MEMBER2 .*status=ZERO_NONJSON'; then
1248
+ ok "L76 🟥 DROPPED 만 낸 2차 멤버는 ZERO_SELFDROPPED (규약을 지킨 멤버가 blocked 로 안 세어진다)"
1249
+ else
1250
+ no "L76 자기드롭 분류" "$(printf '%s\n' "$O76" | /usr/bin/grep '^MEMBER2')"
1251
+ fi
1252
+
1253
+ # L77 🟥 blocked_members 는 «마지막 필드» 에 결박된다 — role 값이 그 자리를 흉내내면 안 된다
1254
+ # 🟥 R2: 초판은 «자기 리터럴» 을 검사해서 프로덕션의 `$` 를 지워도 초록이었다. 출하 패턴을 뽑아 쓴다.
1255
+ _P77=$(/usr/bin/sed -n "s/^BLOCKED_MEMBERS=\$(grep -c '\(.*\)' \"\$OUT\/fleet_run.log\".*/\1/p" "$PIPE" | head -1)
1256
+ [ -n "$_P77" ] || _P77='__EXTRACT_FAILED__'
1257
+ _L77=$(printf 'MEMBER family=gemini role=status=ZERO_NONJSON rc=0 findings=1 status=OK\n' | /usr/bin/grep -c "$_P77")
1258
+ _L77b=$(printf 'MEMBER family=gemini role=security rc=0 findings=0 status=ZERO_NONJSON\n' | /usr/bin/grep -c "$_P77")
1259
+ if [ "$_L77" = 0 ] && [ "$_L77b" = 1 ]; then
1260
+ ok "L77 «출하» blocked_members 패턴이 role 흉내를 안 세고 진짜 status 는 센다 (프로덕션 추출, known-pair)"
1261
+ else
1262
+ no "L77 blocked 패턴 결박" "role흉내=$_L77(0 기대) 진짜=$_L77b(1 기대)"
1263
+ fi
1264
+
1265
+ # L78 새 쓰기 경로가 심링크 가드 목록에 있나 (배선 검사 — 그렇게 라벨한다)
1266
+ # 🟥 R2: 같은 문자열이 쓰기 줄에도 있어서 가드에서 빼도 초록이었다. «for _p in» 가드 줄 안에서만 찾는다.
1267
+ if /usr/bin/grep -E '^for _p in .*"\$OUT/roster.err"' "$PIPE" >/dev/null && /usr/bin/grep -E '^\s*for _p in .*"\$out/peer_\$\{fam\}_\$\{role\}.txt"' "$FL" >/dev/null; then
1268
+ ok "L78 새 출력 경로(roster.err · peer_<fam>.txt)가 심링크 가드에 등재됨 (🟡 배선 검사)"
1269
+ else
1270
+ no "L78 심링크 가드 등재" "roster.err=$(/usr/bin/grep -c '"\$OUT/roster.err"' "$PIPE") peer=$(/usr/bin/grep -c 'peer_\${fam}_\${role}.txt"' "$FL")"
1271
+ fi
1272
+
1273
+ # L79 🟥 검증 패스의 CLI stderr(토큰 회계)가 버려지지 않는다 — mktemp cleanup 이 지우던 것
1274
+ cat > "$D/ver_acct.sh" <<'EOS'
1275
+ #!/bin/sh
1276
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1277
+ echo "tokens used" >&2; echo "12345" >&2
1278
+ printf '%s' "$IN" | /usr/bin/python3 -c '
1279
+ import sys,json
1280
+ for l in sys.stdin:
1281
+ l=l.strip()
1282
+ if l.startswith("{"):
1283
+ d=json.loads(l); print(json.dumps({"id":d["id"],"verdict":"confirmed","why":"x"}))'
1284
+ EOS
1285
+ chmod +x "$D/ver_acct.sh"
1286
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/one_find.sh" "$D/one_find.sh" > "$D/fleetK.tbl"
1287
+ FH_CODEX_BIN="$D/ver_acct.sh" FH_AGY_BIN="$D/ver_acct.sh" \
1288
+ bash "$PIPE" "$D/pool_t.py" --out "$D/pk" --fleet "$D/fleetK.tbl" >/dev/null 2>&1
1289
+ # 🟥 R2: 두 split 이 첫 디렉터리로 몰려도 «하나만 매치» 라 통과했다. split 마다 «자기» keep 을 요구한다.
1290
+ _N79=$(ls -d "$D"/pk/split_* 2>/dev/null | wc -l | tr -d ' ')
1291
+ _K79=$(for d in "$D"/pk/split_*; do /usr/bin/grep -lq 'tokens used' "$d"/err.txt "$d"/keep_verify/err.txt 2>/dev/null && echo 1; done | wc -l | tr -d ' ')
1292
+ if [ "$_N79" -ge 2 ] && [ "$_K79" = "$_N79" ]; then
1293
+ ok "L79 검증 패스 stderr 가 «split 마다» 남는다 ($_K79/$_N79 — 첫 디렉터리로 몰리지 않는다)"
1294
+ else
1295
+ no "L79 검증 stderr 보존" "split=$_N79 keep=$_K79 · $(ls "$D"/pk/split_* 2>/dev/null | head -3)"
1296
+ fi
1297
+
1298
+ # ── L80~L84 cross-family R2 (codex, 2026-09-10 21:45) — 2차 병합·명부 파서의 남은 구멍 ─────────
1299
+ # 🟥 F_gen 실측: case_h01.rs r1 에서 gemini 2차 차단(ZERO_NONJSON) → 1차 발견 4건이 최종에서 삭제됐다.
1300
+ # 2차가 차단·빈응답인 멤버 (R2 #4)
1301
+ cat > "$D/r2_blocked.sh" <<'EOS'
1302
+ #!/bin/sh
1303
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1304
+ case "$IN" in
1305
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo "This request was blocked by filters." ;;
1306
+ *) echo '{"title":"B1 round1 — must survive block","file":"x_t.py","line":1,"severity":"A","category":"s","detail":"x","defeater":"y","confidence":0.7}' ;;
1307
+ esac
1308
+ EOS
1309
+ # 전원 자기 철회 (R2 #5)
1310
+ cat > "$D/r2_drop_all.sh" <<'EOS'
1311
+ #!/bin/sh
1312
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1313
+ case "$IN" in
1314
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo 'DROPPED: W1 — re-read; not a defect' ;;
1315
+ *) echo '{"title":"W1","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1316
+ esac
1317
+ EOS
1318
+ chmod +x "$D/r2_blocked.sh" "$D/r2_drop_all.sh"
1319
+
1320
+ # L80 🟥 #4: 2차에서 차단된 멤버의 1차 발견은 «자기 1차» 로 폴백된다 (삭제되지 않는다)
1321
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/x_g_full2.sh" "$D/r2_blocked.sh" > "$D/f80.tbl"
1322
+ O80=$(bash "$FL" "$D/x_t.py" --out "$D/f80" --fleet "$D/f80.tbl" --round2 2>&1)
1323
+ if /usr/bin/grep -q 'B1 round1' "$D/f80/findings.jsonl" 2>/dev/null && printf '%s\n' "$O80" | /usr/bin/grep -q 'gemini/security ZERO_NONJSON — keeping ITS round-1'; then
1324
+ ok "L80 🟥 2차 차단(ZERO_NONJSON) 멤버는 자기 1차로 폴백 — 발견이 삭제되지 않는다 (F_gen h01 r1 재현 차단)"
1325
+ else
1326
+ no "L80 2차 차단 폴백" "$(printf '%s\n' "$O80" | /usr/bin/grep -E 'round2') · B1=$(/usr/bin/grep -c 'B1 round1' "$D/f80/findings.jsonl" 2>/dev/null)"
1327
+ fi
1328
+
1329
+ # L81 🟥 #5: 전원이 «정당하게» 철회하면 결과는 빈 것이고, 1차로 되돌리지 않는다
1330
+ printf 'codex|logic|sh %s\n' "$D/r2_drop_all.sh" > "$D/f81.tbl"
1331
+ O81=$(bash "$FL" "$D/x_t.py" --out "$D/f81" --fleet "$D/f81.tbl" --round2 2>&1)
1332
+ N81=$(/usr/bin/grep -c . "$D/f81/findings.jsonl" 2>/dev/null); N81=${N81:-0}
1333
+ if [ "$N81" -eq 0 ] && printf '%s\n' "$O81" | /usr/bin/grep -q 'all members withdrew'; then
1334
+ ok "L81 🟥 전원 자기철회 → 최종 0건 (정당한 철회가 «1차 유지» 로 무효화되지 않는다)"
1335
+ else
1336
+ no "L81 전원 철회" "final=$N81(0 기대) · $(printf '%s\n' "$O81" | /usr/bin/grep -E 'round2')"
1337
+ fi
1338
+
1339
+ # L82 🟥 #3: 멤버 키 매칭이 리터럴 — role `logic.review` 가 `logicXreview` 의 상태를 집지 않는다
1340
+ T82=$(mktemp -d); mkdir -p "$T82"
1341
+ printf 'MEMBER2 family=codex role=logic.review rc=1 findings=1 status=FAILED\nMEMBER2 family=codex role=logicXreview rc=0 findings=1 status=OK\n' > "$T82/members.txt"
1342
+ _ST82=$(/usr/bin/awk -v f="codex" -v r="logic.review" '$1=="MEMBER2" && $2=="family="f && $3=="role="r {for(i=4;i<=NF;i++) if($i ~ /^status=/) v=substr($i,8)} END{print v}' "$T82/members.txt")
1343
+ _SED82=$(/usr/bin/sed -n "s/^MEMBER2 family=codex role=logic.review .*status=\([A-Z_]*\).*/\1/p" "$T82/members.txt" | tail -1)
1344
+ if [ "$_ST82" = "FAILED" ] && [ "$_SED82" = "OK" ] && /usr/bin/grep -q '_FAM="\$_fam" _ROLE="\$_role" /usr/bin/awk' "$FL"; then
1345
+ ok "L82 🟥 멤버 키 리터럴 매칭 (awk) — 보간 sed 는 OK 를 오집(known-negative 동반), 출하본은 awk"
1346
+ else
1347
+ no "L82 멤버 키 매칭" "awk=$_ST82(FAILED 기대) sed=$_SED82(OK=오집 재현) 출하=$(/usr/bin/grep -c '_FAM="\$_fam" _ROLE=' "$FL")"
1348
+ fi
1349
+ /bin/rm -rf "$T82"
1350
+
1351
+ # L83 🟥 #6: 명부 파서가 role 안의 rc= 를 안 본다
1352
+ printf 'MEMBER family=gemini role=rc=1 rc=0 findings=1 status=OK\nMEMBER family=codex role=rc=0 rc=1 findings=0 status=FAILED\n' > "$D/mem83.txt"
1353
+ R83=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem83.txt" 2>/dev/null | tr '\n' ',')
1354
+ if [ "$R83" = "gemini," ]; then
1355
+ ok "L83 🟥 명부 파서가 role 의 rc= 에 안 속는다 (gemini 포함·codex 배제)"
1356
+ else
1357
+ no "L83 명부 rc 파싱" "got='$R83' want='gemini,'"
1358
+ fi
1359
+
1360
+ # L84 🟥 #7: 검증 keep 과 감사 keep 이 «다른» 디렉터리 — 드롭이 있는 런에서 감사가 검증 err 를 안 덮는다
1361
+ cat > "$D/ver_drop_acct.sh" <<'EOS'
1362
+ #!/bin/sh
1363
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1364
+ # 🟥 원문 grep 은 두 방향 다 틀렸다(검증 프롬프트에 false-positive 리터럴 有 · 감사 프롬프트에 drop_verdict 리터럴 無).
1365
+ # «행» 을 파싱해 verdict==false-positive 를 가진 행이 있으면 감사다 — 드롭 행만 verdict 를 달고 온다(fake_kill_seed 와 동형).
1366
+ if printf '%s' "$IN" | /usr/bin/python3 -c '
1367
+ import sys,json
1368
+ rows=[json.loads(l) for l in sys.stdin if l.strip().startswith("{")]
1369
+ sys.exit(0 if any(r.get("verdict")=="false-positive" for r in rows) else 1)'; then echo "tokens used AUDIT" >&2; else echo "tokens used VERIFY" >&2; fi
1370
+ printf '%s' "$IN" | /usr/bin/python3 -c '
1371
+ import sys,json
1372
+ rows=[json.loads(l) for l in sys.stdin if l.strip().startswith("{")]
1373
+ audit=any("drop_verdict" in r or r.get("verdict")=="false-positive" for r in rows)
1374
+ for r in rows:
1375
+ v="correct-drop" if audit else ("false-positive" if r.get("member_id")=="d1" else "confirmed")
1376
+ print(json.dumps({"id":r.get("id"),"verdict":v,"why":"x"}))'
1377
+ EOS
1378
+ chmod +x "$D/ver_drop_acct.sh"
1379
+ cat > "$D/m_drop.sh" <<'EOS'
1380
+ #!/bin/sh
1381
+ cat >/dev/null
1382
+ echo '{"id":"d1","title":"will be dropped","file":"pool_t.py","line":1,"severity":"B","category":"d","detail":"x","defeater":"y"}'
1383
+ echo '{"id":"k1","title":"kept","file":"pool_t.py","line":2,"severity":"A","category":"d","detail":"x","defeater":"y"}'
1384
+ EOS
1385
+ chmod +x "$D/m_drop.sh"
1386
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/m_drop.sh" "$D/m_drop.sh" > "$D/f84.tbl"
1387
+ FH_CODEX_BIN="$D/ver_drop_acct.sh" FH_AGY_BIN="$D/ver_drop_acct.sh" bash "$PIPE" "$D/pool_t.py" --out "$D/f84" --fleet "$D/f84.tbl" >/dev/null 2>&1
1388
+ V84=$(/usr/bin/grep -l 'tokens used VERIFY' "$D"/f84/split_*/keep_verify/err.txt 2>/dev/null | wc -l | tr -d ' ')
1389
+ A84=$(/usr/bin/grep -l 'tokens used AUDIT' "$D"/f84/split_*/keep_audit/err.txt 2>/dev/null | wc -l | tr -d ' ')
1390
+ if [ "$V84" -ge 1 ] && [ "$A84" -ge 1 ]; then
1391
+ ok "L84 🟥 검증 keep($V84) 과 감사 keep($A84) 이 갈라져 있다 — 드롭 런에서 검증 토큰 회계가 살아남는다"
1392
+ else
1393
+ no "L84 keep 분리" "verify=$V84 audit=$A84 · $(ls -d "$D"/f84/split_*/keep_* 2>/dev/null | head -3)"
1394
+ fi
1395
+
1396
+ # ══ R3 (cross-family, codex 2026-09-11 03:00) — 9 findings, each with a known pair ══
1397
+ _FX='"producer_family":"alpha","title":"t","file":"x_t.py","line":1,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7'
1398
+ # L85 🟥 R3 #1: id 101 과 "101" 은 판정 키(str) 에서 한 키 — 입력 유일성도 같은 형태로 본다
1399
+ printf '{"id":101,%s}\n{"id":"101",%s}\n' "$_FX" "$_FX" > "$D/f85.jsonl"
1400
+ E85=$(python3 "$VERIFY" "$D/f85.jsonl" --out "$D/o85" --verifier "sh $D/v_ok.sh" --family beta 2>&1 >/dev/null); R85=$?
1401
+ cat > "$D/v85.sh" <<'EOS'
1402
+ #!/bin/sh
1403
+ cat >/dev/null
1404
+ echo '{"id":"101","verdict":"false-positive","why":"x"}'
1405
+ echo '{"id":102,"verdict":"confirmed","why":"x"}'
1406
+ EOS
1407
+ chmod +x "$D/v85.sh"
1408
+ printf '{"id":101,%s}\n{"id":"102",%s}\n' "$_FX" "$_FX" > "$D/f85b.jsonl"
1409
+ python3 "$VERIFY" "$D/f85b.jsonl" --out "$D/o85b" --verifier "sh $D/v85.sh" --family beta >/dev/null 2>&1
1410
+ C85=$(/usr/bin/grep -c . "$D/o85b/confirmed.jsonl" 2>/dev/null); D85=$(/usr/bin/grep -c . "$D/o85b/dropped.jsonl" 2>/dev/null)
1411
+ if [ "$R85" -eq 2 ] && printf '%s' "$E85" | /usr/bin/grep -q 'duplicate id' && [ "${C85:-0}" = 1 ] && [ "${D85:-0}" = 1 ]; then
1412
+ ok "L85 🟥 R3#1 101/\"101\" 충돌은 거부 · 컨트롤(101·\"102\", 판정 키 형태 교차) 은 confirmed=1 dropped=1"
1413
+ else
1414
+ no "L85 id 정규화 유일성" "rc=$R85 err='$(printf '%s' "$E85" | head -1)' ctrl c=$C85 d=$D85"
1415
+ fi
1416
+
1417
+ # L86 🟥 R3 #7: 감사 조회도 str(id) — 정수 id 의 wrong-drop 이 복구된다
1418
+ cat > "$D/v86.sh" <<'EOS'
1419
+ #!/bin/sh
1420
+ cat >/dev/null; echo '{"id":"101","verdict":"false-positive","why":"x"}'
1421
+ EOS
1422
+ cat > "$D/a86.sh" <<'EOS'
1423
+ #!/bin/sh
1424
+ cat >/dev/null; echo '{"id":101,"verdict":"wrong-drop","why":"x"}'
1425
+ EOS
1426
+ chmod +x "$D/v86.sh" "$D/a86.sh"
1427
+ printf '{"id":101,%s}\n' "$_FX" > "$D/f86.jsonl"
1428
+ O86=$(python3 "$VERIFY" "$D/f86.jsonl" --out "$D/o86" --verifier "sh $D/v86.sh" --family beta --audit-verifier "sh $D/a86.sh" --audit-family gamma 2>&1); R86=$?
1429
+ C86=$(/usr/bin/grep -c . "$D/o86/confirmed.jsonl" 2>/dev/null)
1430
+ if [ "$R86" -eq 0 ] && [ "${C86:-0}" = 1 ] && printf '%s' "$O86" | /usr/bin/grep -q 'reinstated=1'; then
1431
+ ok "L86 🟥 R3#7 정수 id 의 wrong-drop 감사가 복구된다 (reinstated=1 rc=0)"
1432
+ else
1433
+ no "L86 감사 조회 정규화" "rc=$R86 confirmed=$C86 · $(printf '%s' "$O86" | /usr/bin/grep -E 'VERIFY|audit' | head -2)"
1434
+ fi
1435
+
1436
+ # L87 🟥 R3 #2: 선언 씨앗 중 «하나라도» 없으면 ABSENT — 부분 부재가 CLEAN 이 되지 않는다
1437
+ cat > "$D/v87.sh" <<'EOS'
1438
+ #!/bin/sh
1439
+ cat >/dev/null; echo '{"id":"z1","verdict":"confirmed","why":"x"}'
1440
+ EOS
1441
+ chmod +x "$D/v87.sh"
1442
+ printf '{"id":"z1",%s}\n' "$_FX" > "$D/f87.jsonl"
1443
+ O87a=$(python3 "$VERIFY" "$D/f87.jsonl" --out "$D/o87a" --verifier "sh $D/v87.sh" --family beta --seeded z1,missing 2>&1); R87a=$?
1444
+ O87b=$(python3 "$VERIFY" "$D/f87.jsonl" --out "$D/o87b" --verifier "sh $D/v87.sh" --family beta --seeded z1 2>&1); R87b=$?
1445
+ if [ "$R87a" -ne 0 ] && printf '%s' "$O87a" | /usr/bin/grep -q 'SEEDED declared=2 present=1.*status=ABSENT' && [ "$R87b" -eq 0 ] && printf '%s' "$O87b" | /usr/bin/grep -q 'status=CLEAN'; then
1446
+ ok "L87 🟥 R3#2 씨앗 부분 부재 → ABSENT rc≠0 · 전원 present 컨트롤 → CLEAN rc=0"
1447
+ else
1448
+ no "L87 씨앗 부분 부재" "a rc=$R87a '$(printf '%s' "$O87a" | /usr/bin/grep SEEDED)' · b rc=$R87b '$(printf '%s' "$O87b" | /usr/bin/grep SEEDED)'"
1449
+ fi
1450
+
1451
+ # L88 🟥 R3 #3: --keep 루트가 심링크면 모드 디렉터리를 붙이기 «전에» 거부한다
1452
+ mkdir -p "$D/keep_real"; ln -s "$D/keep_real" "$D/keep_link"
1453
+ E88=$(bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_link" 2>&1 </dev/null); R88=$?
1454
+ E88b=$(FH_CODEX_BIN=/usr/bin/false bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_real" 2>&1 </dev/null); R88b=$?
1455
+ if [ "$R88" -eq 2 ] && printf '%s' "$E88" | /usr/bin/grep -q 'keep root is a symlink' && ! printf '%s' "$E88b" | /usr/bin/grep -q 'keep root is a symlink'; then
1456
+ ok "L88 🟥 R3#3 keep 심링크 루트 거부(rc=2) · 실디렉터리 컨트롤은 그 메시지 없음"
1457
+ else
1458
+ no "L88 keep 루트 심링크" "link rc=$R88 '$(printf '%s' "$E88" | head -1)' · real rc=$R88b"
1459
+ fi
1460
+
1461
+ # L89 🟥 R3 #4: 프로덕션 awk 를 «추출해 실행» — 공백 낀 role 이 자기 status 를 집는다 (R3-B: L82 는 접두만 봤다)
1462
+ _AWK89=$(/usr/bin/python3 -c '
1463
+ import sys,re
1464
+ src=open(sys.argv[1],encoding="utf-8").read()
1465
+ m=re.search(r"_st=\$\((.*?) \"\$OUT/members\.txt\"\)", src, re.S)
1466
+ print(m.group(1) if m else "")' "$FL")
1467
+ T89=$(mktemp -d); printf 'MEMBER2 family=codex role=logic review rc=0 findings=1 self_dropped=0 status=OK\nMEMBER2 family=codex role=logic rc=1 findings=0 self_dropped=0 status=FAILED\nMEMBER2 family=codex role=logic rc=99 rc=0 findings=1 self_dropped=0 status=OK\n' > "$T89/members.txt"
1468
+ if [ -n "$_AWK89" ]; then
1469
+ _S89a=$(_fam=codex _role="logic review" OUT="$T89" eval "$_AWK89 \"\$OUT/members.txt\"")
1470
+ _S89b=$(_fam=codex _role="logic" OUT="$T89" eval "$_AWK89 \"\$OUT/members.txt\"")
1471
+ _S89c=$(_fam=codex _role="logic.review" OUT="$T89" eval "$_AWK89 \"\$OUT/members.txt\"")
1472
+ _S89d=$(_fam=codex _role="logic rc=99" OUT="$T89" eval "$_AWK89 \"\$OUT/members.txt\"")
1473
+ if [ "$_S89a" = OK ] && [ "$_S89b" = FAILED ] && [ -z "$_S89c" ] && [ "$_S89d" = OK ]; then
1474
+ ok "L89 🟥 R3#4/R4#4 출하 awk 실행: 'logic review'→OK · 'logic'→FAILED(접두 'logic rc=99' 오집 없음) · 'logic.review'→부재 · 'logic rc=99'→OK"
1475
+ else
1476
+ no "L89 role 공백 매칭" "a='$_S89a'(OK) b='$_S89b'(FAILED) c='$_S89c'(빈) d='$_S89d'(OK)"
1477
+ fi
1478
+ else
1479
+ no "L89 role 공백 매칭" "HARNESS-ERROR: 출하 awk 추출 실패"
1480
+ fi
1481
+ /bin/rm -rf "$T89"
1482
+
1483
+ # L90 🟥 R3 #5: 멤버별 병합이 «구성됐으면» 빈 것도 권위 — 남의 빈 폴백이 내 철회를 되살리지 않는다
1484
+ cat > "$D/r2_silent.sh" <<'EOS'
1485
+ #!/bin/sh
1486
+ cat >/dev/null
1487
+ EOS
1488
+ cat > "$D/r2_g_r1only.sh" <<'EOS'
1489
+ #!/bin/sh
1490
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1491
+ case "$IN" in
1492
+ *"YOUR OWN ROUND-1 FINDINGS"*) : ;;
1493
+ *) echo '{"title":"G1 keep","file":"x_t.py","line":2,"severity":"B","category":"s","detail":"x","defeater":"y","confidence":0.7}' ;;
1494
+ esac
1495
+ EOS
1496
+ chmod +x "$D/r2_silent.sh" "$D/r2_g_r1only.sh"
1497
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/r2_drop_all.sh" "$D/r2_silent.sh" > "$D/f90.tbl"
1498
+ O90=$(bash "$FL" "$D/x_t.py" --out "$D/f90" --fleet "$D/f90.tbl" --round2 2>&1)
1499
+ N90=$(/usr/bin/grep -c . "$D/f90/findings.jsonl" 2>/dev/null); N90=${N90:-0}
1500
+ printf 'codex|logic|sh %s\ngemini|security|sh %s\n' "$D/r2_drop_all.sh" "$D/r2_g_r1only.sh" > "$D/f90b.tbl"
1501
+ O90b=$(bash "$FL" "$D/x_t.py" --out "$D/f90b" --fleet "$D/f90b.tbl" --round2 2>&1)
1502
+ N90b=$(/usr/bin/grep -c . "$D/f90b/findings.jsonl" 2>/dev/null); N90b=${N90b:-0}
1503
+ if [ "$N90" -eq 0 ] && [ -f "$D/f90/INTENTIONAL_EMPTY" ] && [ "$N90b" -eq 1 ] && /usr/bin/grep -q 'G1 keep' "$D/f90b/findings.jsonl" && ! /usr/bin/grep -q '"W1"' "$D/f90b/findings.jsonl"; then
1504
+ ok "L90 🟥 R3#5 codex 철회 + gemini 빈 폴백 → 0건(마커) · gemini 1차 있음 → G1 만(W1 부활 없음)"
1505
+ else
1506
+ no "L90 빈 병합 권위" "a=$N90(0) marker=$([ -f "$D/f90/INTENTIONAL_EMPTY" ] && echo y || echo n) b=$N90b(1) · $(printf '%s\n' "$O90" | /usr/bin/grep -E 'round2 (all|produced)')"
1507
+ fi
1508
+
1509
+ # L91 🟥 R3 #6: rc 는 «레코드 끝» 에서 — role 이 `rc=0 findings=x` 를 흉내내도 안 속는다 (L83 의 뒤집힌 짝)
1510
+ printf 'MEMBER family=codex role=rc=0 findings=x rc=1 findings=0 status=FAILED\nMEMBER family=gemini role=rc=1 findings=x rc=0 findings=1 status=OK\n' > "$D/mem91.txt"
1511
+ R91=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem91.txt" 2>/dev/null | tr '\n' ',')
1512
+ if [ "$R91" = "gemini," ]; then
1513
+ ok "L91 🟥 R3#6 role 이 'rc=N findings=' 를 흉내내도 명부는 끝 레코드를 읽는다 (gemini 포함·codex 배제)"
1514
+ else
1515
+ no "L91 명부 접미 앵커" "got='$R91' want='gemini,'"
1516
+ fi
1517
+
1518
+ # L92 🟥 R3 #8: 전원 철회는 드라이버에서 WITHDRAWN rc=0 — UNREVIEWED rc=3 과 갈린다 (typed 마커)
1519
+ O92=$(FH_CODEX_BIN=/usr/bin/false FH_AGY_BIN=/usr/bin/false bash "$PIPE" "$D/x_t.py" --out "$D/p92" --fleet "$D/f90.tbl" --round2 2>&1); R92=$?
1520
+ printf 'codex|logic|sh %s\n' "$D/no_such_stub_92.sh" > "$D/f92c.tbl"
1521
+ O92c=$(FH_CODEX_BIN=/usr/bin/false bash "$PIPE" "$D/x_t.py" --out "$D/p92c" --fleet "$D/f92c.tbl" 2>&1); R92c=$?
1522
+ if [ "$R92" -eq 0 ] && printf '%s' "$O92" | /usr/bin/grep -q 'status=WITHDRAWN' && [ "$R92c" -eq 3 ] && printf '%s' "$O92c" | /usr/bin/grep -q 'status=UNREVIEWED'; then
1523
+ ok "L92 🟥 R3#8 전원 철회 → PIPELINE status=WITHDRAWN rc=0 · 죽은 fleet 컨트롤 → UNREVIEWED rc=3"
1524
+ else
1525
+ no "L92 WITHDRAWN vs UNREVIEWED" "rc=$R92 '$(printf '%s' "$O92" | /usr/bin/grep PIPELINE)' · ctrl rc=$R92c"
1526
+ fi
1527
+
1528
+ # L93 🟥 R3 #9: peer 브리핑은 멤버별 파일 — 같은 계열 두 role 이 한 파일을 놓고 경합하지 않는다
1529
+ printf 'codex|logic|sh %s\ncodex|review|sh %s\n' "$D/x_g_full2.sh" "$D/x_g_full2.sh" > "$D/f93.tbl"
1530
+ bash "$FL" "$D/x_t.py" --out "$D/f93" --fleet "$D/f93.tbl" --round2 >/dev/null 2>&1
1531
+ if [ -s "$D/f93/peer_codex_logic.txt" ] && [ -s "$D/f93/peer_codex_review.txt" ] && [ ! -e "$D/f93/peer_codex.txt" ]; then
1532
+ ok "L93 🟥 R3#9 peer_<fam>_<role>.txt 둘 다 실재 · 공유 peer_<fam>.txt 부재"
1533
+ else
1534
+ no "L93 peer 멤버별" "$(ls "$D/f93" 2>/dev/null | /usr/bin/grep peer | tr '\n' ' ')"
1535
+ fi
1536
+
1537
+ # L94 R3 레인 강화: L75 (seen.append 상수화) · L77 (MEMBER2 접두 되돌림) 를 각각 잡는 짝
1538
+ printf 'MEMBER family=gemini role=security rc=0 findings=1 status=OK\nMEMBER family=codex role=logic rc=0 findings=2 status=OK\n' > "$D/mem94.txt"
1539
+ R94=$(/usr/bin/python3 "$D/roster_builder.py" "$D/mem94.txt" 2>/dev/null | sort -u | tr '\n' ',')
1540
+ _L94=$(printf 'MEMBER2 family=gemini role=security rc=0 findings=0 self_dropped=0 status=ZERO_NONJSON\n' | /usr/bin/grep -c "$_P77")
1541
+ if [ "$R94" = "codex,gemini," ] && [ "$_L94" = 1 ]; then
1542
+ ok "L94 명부는 두 계열 «신원» 을 낸다(codex,gemini — 상수화 뮤턴트 배제) · blocked 패턴이 MEMBER2 줄도 센다"
1543
+ else
1544
+ no "L94 L75/L77 강화" "roster='$R94'(codex,gemini,) member2=$_L94(1)"
1545
+ fi
1546
+
1547
+ # ══ R4 (cross-family, codex 2026-09-11 07:47→08:0x) — S3·A5, each with a known pair ══
1548
+ # L95 🟥 R4 #1: WITHDRAWN 경로도 선언 씨앗을 본다 — 씨앗이 들어온 적 없으면 ABSENT rc=5
1549
+ O95=$(FH_CODEX_BIN=/usr/bin/false FH_AGY_BIN=/usr/bin/false bash "$PIPE" "$D/x_t.py" --out "$D/p95" --fleet "$D/f90.tbl" --round2 --seeded zz1 2>&1); R95=$?
1550
+ if [ "$R95" -eq 5 ] && printf '%s' "$O95" | /usr/bin/grep -q 'SEEDED declared=1 present=0 .*status=ABSENT' && printf '%s' "$O95" | /usr/bin/grep -q 'status=WITHDRAWN.*rc=5'; then
1551
+ ok "L95 🟥 R4#1 전원 철회 + 선언 씨앗 → ABSENT rc=5 (L92 의 무씨앗 컨트롤은 rc=0)"
1552
+ else
1553
+ no "L95 WITHDRAWN 씨앗" "rc=$R95 · $(printf '%s' "$O95" | /usr/bin/grep -E 'SEEDED|PIPELINE' | head -2)"
1554
+ fi
1555
+
1556
+ # L96 🟥 R4 #2: --keep 루트 심링크는 뒤에 / 를 붙여도 거부된다
1557
+ E96=$(bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_link/" 2>&1 </dev/null); R96=$?
1558
+ E96b=$(bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_link//" 2>&1 </dev/null); R96b=$?
1559
+ if [ "$R96" -eq 2 ] && [ "$R96b" -eq 2 ] && printf '%s' "$E96" | /usr/bin/grep -q 'keep root is a symlink'; then
1560
+ ok "L96 🟥 R4#2 'link/' · 'link//' 둘 다 rc=2 거부 (L88 의 실디렉터리 컨트롤 동반)"
1561
+ else
1562
+ no "L96 keep trailing slash" "rc=$R96/$R96b '$(printf '%s' "$E96" | head -1)'"
1563
+ fi
1564
+
1565
+ # L97 🟥 R4 #6: 재사용 --out 에서 WITHDRAWN 이 옛 집계를 남기지 않는다
1566
+ mkdir -p "$D/p97"; printf '{"id":"stale","verdict":"confirmed"}\n' > "$D/p97/confirmed.jsonl"; printf 'stale\n' > "$D/p97/splits.txt"
1567
+ FH_CODEX_BIN=/usr/bin/false FH_AGY_BIN=/usr/bin/false bash "$PIPE" "$D/x_t.py" --out "$D/p97" --fleet "$D/f90.tbl" --round2 >/dev/null 2>&1; R97=$?
1568
+ if [ "$R97" -eq 0 ] && [ -f "$D/p97/confirmed.jsonl" ] && [ ! -s "$D/p97/confirmed.jsonl" ] && [ ! -s "$D/p97/splits.txt" ]; then
1569
+ ok "L97 🟥 R4#6 WITHDRAWN 이 재사용 --out 의 confirmed.jsonl·splits.txt 를 비운다 (stale 행 0)"
1570
+ else
1571
+ no "L97 WITHDRAWN 집계 리셋" "rc=$R97 confirmed=$(wc -c < "$D/p97/confirmed.jsonl" 2>/dev/null)B splits=$(wc -c < "$D/p97/splits.txt" 2>/dev/null)B"
1572
+ fi
1573
+
1574
+ # L98 🟥 R4 #7: 입력 행의 reinstated:true 는 버려진다 — 생산자가 감사 메타데이터를 미리 쓸 수 없다
1575
+ cat > "$D/v98.sh" <<'EOS'
1576
+ #!/bin/sh
1577
+ cat >/dev/null
1578
+ echo '{"id":"c1","verdict":"confirmed","why":"x"}'
1579
+ echo '{"id":"d1","verdict":"needs-debate","why":"x"}'
1580
+ EOS
1581
+ chmod +x "$D/v98.sh"
1582
+ printf '{"id":"c1",%s}\n{"id":"d1","reinstated":true,"drop_verdict":"wrong-drop",%s}\n' "$_FX" "$_FX" > "$D/f98.jsonl"
1583
+ python3 "$VERIFY" "$D/f98.jsonl" --out "$D/o98" --verifier "sh $D/v98.sh" --family beta >/dev/null 2>&1
1584
+ N98=$(/usr/bin/grep -c '"reinstated": true' "$D/o98/confirmed.jsonl" 2>/dev/null); N98=${N98:-0}
1585
+ N98b=$(/usr/bin/grep -c '"drop_verdict"' "$D/o98/confirmed.jsonl" 2>/dev/null); N98b=${N98b:-0}
1586
+ C98=$(/usr/bin/grep -c . "$D/o98/confirmed.jsonl" 2>/dev/null)
1587
+ if [ "$N98" = 0 ] && [ "$N98b" = 0 ] && [ "${C98:-0}" = 2 ]; then
1588
+ ok "L98 🟥 R4#7 입력의 reinstated/drop_verdict 가 출력에 없다 (행 2 는 살아 있음 — 삭제가 아니라 필드 제거)"
1589
+ else
1590
+ no "L98 감사 메타 패스스루" "reinstated=$N98 drop_verdict=$N98b rows=$C98"
1591
+ fi
1592
+
1593
+ # L99 🟥 R4 #8: peer 브리핑의 «YOUR OWN» 은 멤버(family+role) 단위 — 같은 계열 다른 role 은 별 절
1594
+ if [ -s "$D/f93/peer_codex_logic.txt" ]; then
1595
+ _OWN99=$(/usr/bin/sed -n '/YOUR OWN ROUND-1/,/=====/p' "$D/f93/peer_codex_logic.txt")
1596
+ _KIN99=$(/usr/bin/sed -n '/OTHER MEMBERS OF YOUR OWN FAMILY/,/THE OTHER FAMILY/p' "$D/f93/peer_codex_logic.txt")
1597
+ if printf '%s' "$_OWN99" | /usr/bin/grep -q '"id": "codex-logic-1"' && ! printf '%s' "$_OWN99" | /usr/bin/grep -q 'codex-review' && [ -n "$_KIN99" ]; then
1598
+ ok "L99 🟥 R4#8 peer_codex_logic: YOUR OWN 에 codex-logic-1 만(id 동반) · codex-review 는 «같은 계열 다른 멤버» 절"
1599
+ else
1600
+ no "L99 멤버별 briefing" "own='$(printf '%s' "$_OWN99" | /usr/bin/grep -o '"id": "[^"]*"' | tr '\n' ' ')' kin=$(printf '%s' "$_KIN99" | /usr/bin/grep -c .)"
1601
+ fi
1602
+ else
1603
+ no "L99 멤버별 briefing" "HARNESS-ERROR: L93 산출 부재"
1604
+ fi
1605
+
1606
+ # L100 🟥 R4 #5: KEEP 한 발견은 1차 id 를 유지한다 — 남의 id 를 사칭하면 새 id 를 받는다
1607
+ cat > "$D/r2_keepid.sh" <<'EOS'
1608
+ #!/bin/sh
1609
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1610
+ case "$IN" in
1611
+ *"YOUR OWN ROUND-1 FINDINGS"*)
1612
+ echo '{"id":"codex-logic-1","title":"K1 kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1613
+ echo '{"id":"gemini-security-1","title":"K2 impersonated","file":"x_t.py","line":2,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1614
+ *) echo '{"title":"K1","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1615
+ esac
1616
+ EOS
1617
+ chmod +x "$D/r2_keepid.sh"
1618
+ printf 'codex|logic|sh %s\n' "$D/r2_keepid.sh" > "$D/f100.tbl"
1619
+ bash "$FL" "$D/x_t.py" --out "$D/f100" --fleet "$D/f100.tbl" --round2 >/dev/null 2>&1
1620
+ _I100=$(/usr/bin/grep -o '"id": "[^"]*"' "$D/f100/findings.jsonl" 2>/dev/null | tr '\n' ' ')
1621
+ if printf '%s' "$_I100" | /usr/bin/grep -q '"id": "codex-logic-1"' && ! printf '%s' "$_I100" | /usr/bin/grep -q 'gemini-security-1' && printf '%s' "$_I100" | /usr/bin/grep -q 'codex-logic-r2-'; then
1622
+ ok "L100 🟥 R4#5 KEEP 은 codex-logic-1 유지 · 남의 id 사칭은 codex-logic-r2-N 으로 재발급"
1623
+ else
1624
+ no "L100 id 이월" "ids='$_I100'"
1625
+ fi
1626
+
1627
+ # ══ R5 (cross-family, codex 2026-09-11 08:12→08:18) — A3, each with a known pair ══
1628
+ # L101 🟥 R5 #1: member_id 별칭은 «신뢰된 1차 행» 에서만 — 사칭한 남의 id 는 별칭으로도 안 남는다
1629
+ _M101=$(/usr/bin/grep -o '"member_id": "[^"]*"' "$D/f100/findings.jsonl" 2>/dev/null | tr '\n' ' ')
1630
+ cat > $D/r2_keepid_alias.sh <<'EOS'
1631
+ #!/bin/sh
1632
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1633
+ case "$IN" in
1634
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"K1 kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1635
+ *) echo '{"id":"seed-original","title":"K1","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1636
+ esac
1637
+ EOS
1638
+ chmod +x $D/r2_keepid_alias.sh
1639
+ printf 'codex|logic|sh %s\n' "$D/r2_keepid_alias.sh" > "$D/f101.tbl"
1640
+ bash "$FL" "$D/x_t.py" --out "$D/f101" --fleet "$D/f101.tbl" --round2 >/dev/null 2>&1
1641
+ _A101=$(/usr/bin/grep -o '"member_id": "[^"]*"' "$D/f101/findings.jsonl" 2>/dev/null | tr '\n' ' ')
1642
+ if ! printf '%s' "$_M101" | /usr/bin/grep -q 'gemini-security-1' && printf '%s' "$_A101" | /usr/bin/grep -q '"member_id": "seed-original"'; then
1643
+ ok "L101 🟥 R5#1 사칭 id 는 member_id 로도 안 남고 · KEEP 한 발견은 1차 별칭 seed-original 을 되찾는다"
1644
+ else
1645
+ no "L101 별칭 신뢰" "f100 member_ids='$_M101' · f101='$_A101'"
1646
+ fi
1647
+
1648
+ # L102 🟥 R5 #2: 깨진 생존자 + DROPPED 는 «전원 철회» 가 아니라 ZERO_NONJSON(폴백)
1649
+ cat > $D/r2_malformed_plus_drop.sh <<'EOS'
1650
+ #!/bin/sh
1651
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1652
+ case "$IN" in
1653
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"A retained",}'; echo 'DROPPED: B withdrawn' ;;
1654
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1655
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1656
+ esac
1657
+ EOS
1658
+ chmod +x $D/r2_malformed_plus_drop.sh
1659
+ printf 'codex|logic|sh %s\n' "$D/r2_malformed_plus_drop.sh" > "$D/f102.tbl"
1660
+ O102=$(bash "$FL" "$D/x_t.py" --out "$D/f102" --fleet "$D/f102.tbl" --round2 2>&1)
1661
+ N102=$(/usr/bin/grep -c . "$D/f102/findings.jsonl" 2>/dev/null); N102=${N102:-0}
1662
+ N81=$(/usr/bin/grep -c . "$D/f81/findings.jsonl" 2>/dev/null); N81=${N81:-0}
1663
+ if [ "$N102" -eq 2 ] && printf '%s' "$O102" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ ! -f "$D/f102/INTENTIONAL_EMPTY" ] && [ "$N81" -eq 0 ]; then
1664
+ ok "L102 🟥 R5#2 깨진 생존자+DROPPED → ZERO_NONJSON 폴백(1차 2건 보존, 마커 없음) · 순수 철회 컨트롤(L81) 은 0건"
1665
+ else
1666
+ no "L102 철회 우선순위" "final=$N102(2) marker=$([ -f "$D/f102/INTENTIONAL_EMPTY" ] && echo y || echo n) · $(printf '%s' "$O102" | /usr/bin/grep -o 'status=[A-Z_]*' | head -2 | tr '\n' ' ') · L81=$N81"
1667
+ fi
1668
+
1669
+ # L103 🟥 R5 #3: 비-객체 JSON 행([] · null) 은 스키마 오류 rc=2 — «생존 0» 의 rc=1 이 아니다
1670
+ R103a=$(printf '[]\n' | python3 "$VERIFY" - --out "$D/o103a" --verifier "sh $D/v_ok.sh" --family beta 2>&1 >/dev/null); Ra=$?
1671
+ R103b=$(printf 'null\n' | python3 "$VERIFY" - --out "$D/o103b" --verifier "sh $D/v_ok.sh" --family beta 2>&1 >/dev/null); Rb=$?
1672
+ if [ "$Ra" -eq 2 ] && [ "$Rb" -eq 2 ] && printf '%s' "$R103a" | /usr/bin/grep -q 'not a JSON object' && ! printf '%s' "$R103a" | /usr/bin/grep -q 'AttributeError'; then
1673
+ ok "L103 🟥 R5#3 [] · null 행 → rc=2 «not a JSON object» (AttributeError 없음)"
1674
+ else
1675
+ no "L103 비-객체 행" "rc=$Ra/$Rb '$(printf '%s' "$R103a" | tail -1)'"
1676
+ fi
1677
+
1678
+ # ══ R6 (cross-family, codex 2026-09-11 12:59) — A5, each with a known pair ══
1679
+ # L104 🟥 R6 #1: «정상 A + 깨진 B» 응답은 OK 가 아니다 — 부분 응답이 멤버의 1차 목록을 대체하지 않는다
1680
+ cat > $D/r2_partial_bad.sh <<'EOS'
1681
+ #!/bin/sh
1682
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1683
+ case "$IN" in
1684
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"A retained","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'; echo '{"id":"codex-logic-2","title":"B retained",}' ;;
1685
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1686
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1687
+ esac
1688
+ EOS
1689
+ cat > $D/r2_notitle_drop.sh <<'EOS'
1690
+ #!/bin/sh
1691
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1692
+ case "$IN" in
1693
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","titel":"A"}'; echo 'DROPPED: B withdrawn' ;;
1694
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1695
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1696
+ esac
1697
+ EOS
1698
+ chmod +x $D/r2_partial_bad.sh $D/r2_notitle_drop.sh
1699
+ printf 'codex|logic|sh %s\n' "$D/r2_partial_bad.sh" > "$D/f104.tbl"; O104=$(bash "$FL" "$D/x_t.py" --out "$D/f104" --fleet "$D/f104.tbl" --round2 2>&1)
1700
+ printf 'codex|logic|sh %s\n' "$D/r2_notitle_drop.sh" > "$D/f104b.tbl"; O104b=$(bash "$FL" "$D/x_t.py" --out "$D/f104b" --fleet "$D/f104b.tbl" --round2 2>&1)
1701
+ N104=$(/usr/bin/grep -c . "$D/f104/findings.jsonl" 2>/dev/null); N104b=$(/usr/bin/grep -c . "$D/f104b/findings.jsonl" 2>/dev/null)
1702
+ if [ "${N104:-0}" -eq 2 ] && printf '%s' "$O104" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ "${N104b:-0}" -eq 2 ] && printf '%s' "$O104b" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ ! -f "$D/f104b/INTENTIONAL_EMPTY" ]; then
1703
+ ok "L104 🟥 R6#1 «정상 A+깨진 B» → ZERO_NONJSON 폴백(1차 2건 보존) · «titel+DROPPED» 도 폴백(전원 철회 아님)"
1704
+ else
1705
+ no "L104 부분 응답" "a=$N104(2) st=$(printf '%s' "$O104" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1) · b=$N104b(2) st=$(printf '%s' "$O104b" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)"
1706
+ fi
1707
+ # L105 🟥 R6 #2: 새 2차 id 가 다른 role 의 1차 id 와 충돌하지 않는다 (role «logic-r2» 의 1차 = codex-logic-r2-1)
1708
+ # role «logic-r2» 는 2차에서 자기 1차 id(codex-logic-r2-1)를 KEEP 한다 — role «logic» 이 새로 찍는 첫 id 와 같은 문자열
1709
+ cat > $D/r2_keep_r2role.sh <<'EOS'
1710
+ #!/bin/sh
1711
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1712
+ case "$IN" in
1713
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-r2-1","title":"K kept","file":"x_t.py","line":5,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1714
+ *) echo '{"title":"K","file":"x_t.py","line":5,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1715
+ esac
1716
+ EOS
1717
+ chmod +x $D/r2_keep_r2role.sh
1718
+ printf 'codex|logic|sh %s\ncodex|logic-r2|sh %s\n' "$D/x_g_full2.sh" "$D/r2_keep_r2role.sh" > "$D/f105.tbl"
1719
+ bash "$FL" "$D/x_t.py" --out "$D/f105" --fleet "$D/f105.tbl" --round2 >/dev/null 2>&1
1720
+ _IDS105=$(/usr/bin/grep -o '"id": "[^"]*"' "$D/f105/findings.jsonl" 2>/dev/null | sort); _DUP105=$(printf '%s\n' "$_IDS105" | uniq -d | /usr/bin/grep -c .)
1721
+ if [ "$_DUP105" -eq 0 ] && [ "$(printf '%s\n' "$_IDS105" | /usr/bin/grep -c .)" -eq 3 ] && printf '%s' "$_IDS105" | /usr/bin/grep -q '"id": "codex-logic-r2-1"'; then
1722
+ ok "L105 🟥 R6#2 role logic + logic-r2 의 2차 병합에 중복 id 0 (전체 id 집합 대비 할당)"
1723
+ else
1724
+ no "L105 id 충돌" "dup=$_DUP105 ids=$(printf '%s' "$_IDS105" | tr '\n' ' ')"
1725
+ fi
1726
+ # L106 🟥 R6 #3: 두 계열에 같은 member_id 가 있으면 파이프라인도 AMBIGUOUS rc=5 (split 이 모호성을 가리지 않는다)
1727
+ printf 'codex|logic|%s\ngemini|security|%s\n' "sh $D/m_seed.sh" "sh $D/m_seed.sh" > "$D/f106.tbl"
1728
+ FH_CODEX_BIN="$D/fake_kill_seed.sh" FH_AGY_BIN="$D/fake_kill_seed.sh" bash "$PIPE" "$D/tgt.py" --out "$D/p106" --fleet "$D/f106.tbl" --seeded s1 > "$D/p106.out" 2>&1; R106=$?
1729
+ if [ "$R106" -eq 5 ] && /usr/bin/grep -q 'status=SEED_AMBIGUOUS seeds=s1' "$D/p106.out"; then
1730
+ ok "L106 🟥 R6#3 계열 둘이 같은 member_id s1 → SEED_AMBIGUOUS rc=5 (L65 의 계열별 별칭 컨트롤은 rc=0)"
1731
+ else
1732
+ no "L106 전역 모호성" "rc=$R106 · $(/usr/bin/grep -m1 '^PIPELINE' "$D/p106.out")"
1733
+ fi
1734
+ # L107 🟥 R6 #4: 1차 응답의 member_id 는 신뢰되지 않는다 — id 없이 member_id 만 보내도 별칭이 안 남는다
1735
+ cat > $D/m_alias_only.sh <<'EOS'
1736
+ #!/bin/sh
1737
+ cat >/dev/null; echo '{"title":"ordinary","member_id":"seed-never-emitted","file":"x_t.py","line":1,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1738
+ EOS
1739
+ chmod +x $D/m_alias_only.sh
1740
+ printf 'codex|logic|sh %s\n' "$D/m_alias_only.sh" > "$D/f107.tbl"; bash "$FL" "$D/x_t.py" --out "$D/f107" --fleet "$D/f107.tbl" >/dev/null 2>&1
1741
+ if [ -s "$D/f107/findings.jsonl" ] && ! /usr/bin/grep -q 'seed-never-emitted' "$D/f107/findings.jsonl"; then
1742
+ ok "L107 🟥 R6#4 응답의 member_id 는 버려진다(발견은 살아 있음)"
1743
+ else
1744
+ no "L107 member_id 신뢰" "$(cat "$D/f107/findings.jsonl" 2>/dev/null | head -1 | cut -c1-120)"
1745
+ fi
1746
+ # L108 🟥 R6 #5: keep 루트 `link/.` · `link//.` 도 거부
1747
+ E108=$(bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_link/." 2>&1 </dev/null); R108=$?
1748
+ E108b=$(bash "$VERIFIER" --family codex --target "$D/x_t.py" --keep "$D/keep_link//." 2>&1 </dev/null); R108b=$?
1749
+ if [ "$R108" -eq 2 ] && [ "$R108b" -eq 2 ] && printf '%s' "$E108" | /usr/bin/grep -q 'keep root is a symlink'; then
1750
+ ok "L108 🟥 R6#5 'link/.' · 'link//.' 둘 다 rc=2 거부"
1751
+ else
1752
+ no "L108 trailing dot" "rc=$R108/$R108b '$(printf '%s' "$E108" | head -1)'"
1753
+ fi
1754
+
1755
+ # ══ R7 (cross-family, codex 2026-09-11 17:57) — A1·B1 ══
1756
+ # L109 🟥 R7 #1: 비-객체 JSON 줄(`[…]` · `null`)은 잡담이 아니라 깨진 생존자 — 배열 응답은 폴백, null+DROPPED 는 «전원 철회» 아님
1757
+ cat > $D/r2_array.sh <<'EOS'
1758
+ #!/bin/sh
1759
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1760
+ case "$IN" in
1761
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"A kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'; echo '[{"id":"codex-logic-2","title":"B kept"}]' ;;
1762
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1763
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1764
+ esac
1765
+ EOS
1766
+ cat > $D/r2_null_drop.sh <<'EOS'
1767
+ #!/bin/sh
1768
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1769
+ case "$IN" in
1770
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo 'null'; echo 'DROPPED: B withdrawn' ;;
1771
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1772
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1773
+ esac
1774
+ EOS
1775
+ chmod +x $D/r2_array.sh $D/r2_null_drop.sh
1776
+ printf 'codex|logic|sh %s\n' "$D/r2_array.sh" > "$D/f109.tbl"; O109=$(bash "$FL" "$D/x_t.py" --out "$D/f109" --fleet "$D/f109.tbl" --round2 2>&1)
1777
+ printf 'codex|logic|sh %s\n' "$D/r2_null_drop.sh" > "$D/f109b.tbl"; O109b=$(bash "$FL" "$D/x_t.py" --out "$D/f109b" --fleet "$D/f109b.tbl" --round2 2>&1)
1778
+ N109=$(/usr/bin/grep -c . "$D/f109/findings.jsonl" 2>/dev/null); N109b=$(/usr/bin/grep -c . "$D/f109b/findings.jsonl" 2>/dev/null)
1779
+ if [ "${N109:-0}" -eq 2 ] && printf '%s' "$O109" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ "${N109b:-0}" -eq 2 ] && printf '%s' "$O109b" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ ! -f "$D/f109b/INTENTIONAL_EMPTY" ]; then
1780
+ ok "L109 🟥 R7#1 배열 생존자 → ZERO_NONJSON 폴백(2건 보존) · null+DROPPED → 폴백(전원 철회 아님)"
1781
+ else
1782
+ no "L109 비객체 줄" "a=$N109(2) $(printf '%s' "$O109" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1) · b=$N109b(2) $(printf '%s' "$O109b" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)"
1783
+ fi
1784
+ # L110 🟥 R7 #2: 같은 1차 id 를 두 번 KEEP 한 응답은 무효(폴백) — 한 id 두 행이 verify 를 rc=2 로 죽이지 않는다
1785
+ cat > $D/r2_dup_keep.sh <<'EOS'
1786
+ #!/bin/sh
1787
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1788
+ case "$IN" in
1789
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"K kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'; echo '{"id":"codex-logic-1","title":"K kept again","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1790
+ *) echo '{"title":"K","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1791
+ esac
1792
+ EOS
1793
+ chmod +x $D/r2_dup_keep.sh
1794
+ printf 'codex|logic|sh %s\n' "$D/r2_dup_keep.sh" > "$D/f110.tbl"; O110=$(bash "$FL" "$D/x_t.py" --out "$D/f110" --fleet "$D/f110.tbl" --round2 2>&1)
1795
+ _DUP110=$(/usr/bin/grep -o '"id": "[^"]*"' "$D/f110/findings.jsonl" 2>/dev/null | sort | uniq -d | /usr/bin/grep -c .)
1796
+ if [ "$_DUP110" -eq 0 ] && [ "$(/usr/bin/grep -c . "$D/f110/findings.jsonl")" -eq 1 ] && printf '%s' "$O110" | /usr/bin/grep -q 'status=ZERO_NONJSON'; then
1797
+ ok "L110 🟥 R7#2 중복 KEEP → ZERO_NONJSON 폴백(1차 1건 유지, 중복 id 0)"
1798
+ else
1799
+ no "L110 중복 KEEP" "dup=$_DUP110 rows=$(/usr/bin/grep -c . "$D/f110/findings.jsonl") $(printf '%s' "$O110" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)"
1800
+ fi
1801
+
1802
+ # ══ R8 (cross-family, codex 2026-09-11 23:01) — A3 ══
1803
+ # L111 🟥 R8 #1: 1차 전부가 설명돼야 성공 — «A 만 KEEP» 과 «DROPPED: B 만» 은 불완전 응답(폴백), «KEEP A + DROPPED B» 는 OK
1804
+ cat > $D/r2_keep_a_only.sh <<'EOS'
1805
+ #!/bin/sh
1806
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1807
+ case "$IN" in
1808
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"A kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1809
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1810
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1811
+ esac
1812
+ EOS
1813
+ cat > $D/r2_drop_b_only.sh <<'EOS'
1814
+ #!/bin/sh
1815
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1816
+ case "$IN" in
1817
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo 'DROPPED: codex-logic-2 — B withdrawn' ;;
1818
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1819
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1820
+ esac
1821
+ EOS
1822
+ cat > $D/r2_keep_a_drop_b.sh <<'EOS'
1823
+ #!/bin/sh
1824
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1825
+ case "$IN" in
1826
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '{"id":"codex-logic-1","title":"A kept","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'; echo 'DROPPED: codex-logic-2 — B withdrawn' ;;
1827
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1828
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1829
+ esac
1830
+ EOS
1831
+ chmod +x $D/r2_keep_a_only.sh $D/r2_drop_b_only.sh $D/r2_keep_a_drop_b.sh
1832
+ run111(){ printf 'codex|logic|sh %s\n' "$D/$1" > "$D/f111_$2.tbl"; O=$(bash "$FL" "$D/x_t.py" --out "$D/f111_$2" --fleet "$D/f111_$2.tbl" --round2 2>&1); N=$(/usr/bin/grep -c . "$D/f111_$2/findings.jsonl" 2>/dev/null); echo "${N:-0} $(printf '%s' "$O" | /usr/bin/grep -E '^MEMBER2 ' | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)"; }
1833
+ R111a=$(run111 r2_keep_a_only.sh a); R111b=$(run111 r2_drop_b_only.sh b); R111c=$(run111 r2_keep_a_drop_b.sh c)
1834
+ if [ "$R111a" = "2 status=ZERO_NONJSON" ] && [ "$R111b" = "2 status=ZERO_NONJSON" ] && [ "$R111c" = "1 status=OK" ]; then
1835
+ ok "L111 🟥 R8#1 «A 만 KEEP»·«DROPPED B 만» → 폴백(1차 2건 보존) · «KEEP A + DROPPED B» → OK(1건)"
1836
+ else
1837
+ no "L111 1차 전부 설명" "a=[$R111a](2 ZERO_NONJSON) b=[$R111b](2 ZERO_NONJSON) c=[$R111c](1 OK)"
1838
+ fi
1839
+ # L112 🟥 R8 #2: 이중 인코딩된 발견(JSON 문자열) + DROPPED → 폴백(전원 철회 아님)
1840
+ cat > $D/r2_strline.sh <<'EOS'
1841
+ #!/bin/sh
1842
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1843
+ case "$IN" in
1844
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '"{\"id\":\"codex-logic-1\",\"title\":\"A kept\"}"'; echo 'DROPPED: codex-logic-2 — B withdrawn' ;;
1845
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}'
1846
+ echo '{"title":"B","file":"x_t.py","line":2,"severity":"B","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1847
+ esac
1848
+ EOS
1849
+ chmod +x $D/r2_strline.sh; printf 'codex|logic|sh %s\n' "$D/r2_strline.sh" > "$D/f112.tbl"; O112=$(bash "$FL" "$D/x_t.py" --out "$D/f112" --fleet "$D/f112.tbl" --round2 2>&1); N112=$(/usr/bin/grep -c . "$D/f112/findings.jsonl" 2>/dev/null)
1850
+ # 둘째 팔 — #2 고유 몫: 1차 A 하나 · 응답 = «새 발견을 JSON 문자열로» + DROPPED A(설명 완료). #1 은 만족되므로
1851
+ # #2 가 없으면 ZERO_SELFDROPPED(«의도된 빈 결과») 로 새 발견이 사라진다 → 문자열 줄은 깨진 생존자, 폴백
1852
+ cat > $D/r2_strline_new.sh <<'EOS'
1853
+ #!/bin/sh
1854
+ IN=$(cat); [ -n "$IN" ] || IN="$*"
1855
+ case "$IN" in
1856
+ *"YOUR OWN ROUND-1 FINDINGS"*) echo '"{\"title\":\"NEW X\",\"file\":\"x_t.py\",\"line\":9}"'; echo 'DROPPED: codex-logic-1 — A withdrawn' ;;
1857
+ *) echo '{"title":"A","file":"x_t.py","line":1,"severity":"A","category":"d","detail":"x","defeater":"y","confidence":0.7}' ;;
1858
+ esac
1859
+ EOS
1860
+ chmod +x $D/r2_strline_new.sh; printf 'codex|logic|sh %s\n' "$D/r2_strline_new.sh" > "$D/f112b.tbl"; O112b=$(bash "$FL" "$D/x_t.py" --out "$D/f112b" --fleet "$D/f112b.tbl" --round2 2>&1)
1861
+ _S112b=$(printf '%s' "$O112b" | /usr/bin/grep -E '^MEMBER2 ' | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)
1862
+ if [ "${N112:-0}" -eq 2 ] && printf '%s' "$O112" | /usr/bin/grep -q 'status=ZERO_NONJSON' && [ ! -f "$D/f112/INTENTIONAL_EMPTY" ] && [ "$_S112b" = "status=ZERO_NONJSON" ] && [ ! -f "$D/f112b/INTENTIONAL_EMPTY" ]; then
1863
+ ok "L112 🟥 R8#2 JSON 문자열 줄 + DROPPED → ZERO_NONJSON 폴백(2건 보존) · 문자열 «새 발견»+완전 철회 → 폴백(SELFDROPPED 아님)"
1864
+ else
1865
+ no "L112 JSON 문자열 줄" "n=$N112 $(printf '%s' "$O112" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1) · b=$_S112b(ZERO_NONJSON)"
1866
+ fi
1867
+ # L113 🟥 R8 #3: 별칭 없는 행은 씨앗 "None" 과 안 맞는다 — 유일한 "None" 별칭은 CLEAN, 별칭 없는 행이 있어도 AMBIGUOUS 아님
1868
+ cat > "$D/v113.sh" <<'EOS'
1869
+ #!/bin/sh
1870
+ cat >/dev/null; echo '{"id":"n1","verdict":"confirmed","why":"x"}'; echo '{"id":"o1","verdict":"confirmed","why":"x"}'
1871
+ EOS
1872
+ chmod +x "$D/v113.sh"
1873
+ printf '{"id":"n1","member_id":"None",%s}\n{"id":"o1",%s}\n' "$_FX" "$_FX" > "$D/f113.jsonl"
1874
+ O113=$(python3 "$VERIFY" "$D/f113.jsonl" --out "$D/o113" --verifier "sh $D/v113.sh" --family beta --seeded None 2>&1); R113=$?
1875
+ if [ "$R113" -eq 0 ] && printf '%s' "$O113" | /usr/bin/grep -q 'SEEDED declared=1 present=1 .*status=CLEAN'; then
1876
+ ok "L113 🟥 R8#3 씨앗 \"None\" 이 별칭 없는 행과 안 섞인다 → CLEAN rc=0"
1877
+ else
1878
+ no "L113 None 별칭" "rc=$R113 '$(printf '%s' "$O113" | /usr/bin/grep SEEDED)'"
1879
+ fi
1880
+
1881
+
1882
+ # ── L114 --round2-blind (F_r2ctrl 컨트롤) — 상대 계열 목록이 2차 프롬프트에 «없어야» 하고, --round2 에는 «있어야» 한다 ──
1883
+ O114=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/fr2blind" --fleet "$D/fleetR2.tbl" --round2-blind 2>&1)
1884
+ P114="$D/fr2blind/prompt2_codex_logic.txt"; P114c="$D/fr2/prompt2_codex_logic.txt"
1885
+ if [ -s "$P114" ] && ! /usr/bin/grep -q 'THE OTHER FAMILY ROUND-1 FINDINGS' "$P114" && ! /usr/bin/grep -q '"B1"' "$P114" \
1886
+ && /usr/bin/grep -q 'YOUR OWN ROUND-1 FINDINGS' "$P114" && /usr/bin/grep -q '"A1"' "$P114" \
1887
+ && /usr/bin/grep -q '"B1"' "$P114c" && printf '%s' "$O114" | /usr/bin/grep -q 'blind=1'; then
1888
+ ok "L114 --round2-blind: 2차 프롬프트에 상대(B1) 없음·자기(A1) 있음 · --round2 컨트롤엔 B1 있음 · blind=1 표시"
1889
+ else
1890
+ no "L114 round2-blind" "blind_has_B1=$(/usr/bin/grep -c '"B1"' "$P114" 2>/dev/null) ctrl_has_B1=$(/usr/bin/grep -c '"B1"' "$P114c" 2>/dev/null) $(printf '%s' "$O114" | /usr/bin/grep '^FLEET round2 members')"
1891
+ fi
1892
+
1893
+ # ── L115~L121 짝지음(paired) 설계 — 공유 round-1 을 한 번 만들고 round-2 만 두 번 가른다 ──
1894
+ # 🟥 존재 이유: F_gen(146) 과 F_r2ctrl(136) 이 round-1 부터 다른 실행이어서 r1→r2 증가율 비교가
1895
+ # «한 변수 비교» 가 아니었다(RESULT §15 최대 잔여). 이 플래그들이 그 잔여를 구조로 없앤다.
1896
+
1897
+ # L115 --r1-only: 1차만 돌고 끝난다 — 2차 산출(part2_/peer_/raw2_)이 «하나도» 없어야 한다
1898
+ O115=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/pr1" --fleet "$D/fleetR2.tbl" --r1-only 2>&1); R115=$?
1899
+ _N115=$(/usr/bin/grep -c . "$D/pr1/findings.jsonl" 2>/dev/null || echo 0)
1900
+ _R2ART=$(ls "$D/pr1"/part2_*.jsonl "$D/pr1"/peer_*.txt "$D/pr1"/raw2_*.txt 2>/dev/null | /usr/bin/wc -l | /usr/bin/tr -d ' ')
1901
+ if [ "$R115" -eq 0 ] && [ "$_N115" -gt 0 ] && [ "$_R2ART" -eq 0 ] && printf '%s' "$O115" | /usr/bin/grep -q 'FLEET r1-only'; then
1902
+ ok "L115 --r1-only: rc=0 · 1차 $_N115 건 · 2차 산출 0개"
1903
+ else
1904
+ no "L115 --r1-only" "rc=$R115 r1=$_N115 r2_artifacts=$_R2ART out='$(printf '%s' "$O115" | /usr/bin/tail -1)'"
1905
+ fi
1906
+
1907
+ # L116 짝지음 불변식: 두 분기의 findings_r1.jsonl 이 공유 소스와 «바이트 동일» — 이게 설계의 전부다
1908
+ bash "$FLEET_SH2" "$D/r2t.py" --out "$D/pA" --reuse-r1 "$D/pr1" --round2 >/dev/null 2>&1
1909
+ bash "$FLEET_SH2" "$D/r2t.py" --out "$D/pB" --reuse-r1 "$D/pr1" --round2-blind >/dev/null 2>&1
1910
+ if /usr/bin/cmp -s "$D/pr1/findings.jsonl" "$D/pA/findings_r1.jsonl" \
1911
+ && /usr/bin/cmp -s "$D/pr1/findings.jsonl" "$D/pB/findings_r1.jsonl" \
1912
+ && /usr/bin/grep -q '"B1"' "$D/pA/prompt2_codex_logic.txt" \
1913
+ && ! /usr/bin/grep -q '"B1"' "$D/pB/prompt2_codex_logic.txt"; then
1914
+ ok "L116 짝지음: 두 분기가 같은 r1(바이트 동일) 위에서 갈린다 — informed 는 상대 보임, blind 는 안 보임"
1915
+ else
1916
+ no "L116 짝지음 불변식" "A_same=$(/usr/bin/cmp -s "$D/pr1/findings.jsonl" "$D/pA/findings_r1.jsonl" && echo y || echo n) B_same=$(/usr/bin/cmp -s "$D/pr1/findings.jsonl" "$D/pB/findings_r1.jsonl" && echo y || echo n)"
1917
+ fi
1918
+
1919
+ # L117 🟥 인자가 실제로 «잡히는가» — known-pair. 파이썬 이스케이프 사고로 REUSE_R1 에 리터럴
1920
+ # '${2:-}' 이 박혔던 적이 있고 `bash -n` 은 그걸 못 잡았다. 오류문에 «내가 준 경로» 가 나와야 한다.
1921
+ O117=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/p117" --reuse-r1 "$D/ZZnope" --round2 2>&1); R117=$?
1922
+ if [ "$R117" -eq 2 ] && printf '%s' "$O117" | /usr/bin/grep -q 'ZZnope' && ! printf '%s' "$O117" | /usr/bin/grep -q '{2:-}'; then
1923
+ ok "L117 🟥 --reuse-r1 인자 포착: 없는 경로를 «그 경로 이름으로» 거부(rc=2) · 리터럴 \${2:-} 아님"
1924
+ else
1925
+ no "L117 인자 포착" "rc=$R117 (2 기대) out='$(printf '%s' "$O117" | /usr/bin/tail -1)'"
1926
+ fi
1927
+
1928
+ # L118 fail-closed: --reuse-r1 인데 --round2 가 없으면 «아무것도 안 하는» 실행이 되므로 거부
1929
+ O118=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/p118" --reuse-r1 "$D/pr1" 2>&1); R118=$?
1930
+ # 컨트롤: 같은 소스에 --round2 를 붙이면 통과한다(거부가 «항상» 이 아님을 증명)
1931
+ bash "$FLEET_SH2" "$D/r2t.py" --out "$D/p118c" --reuse-r1 "$D/pr1" --round2 >/dev/null 2>&1; R118c=$?
1932
+ if [ "$R118" -eq 2 ] && [ "$R118c" -eq 0 ]; then
1933
+ ok "L118 --reuse-r1 단독 → rc=2 · 컨트롤(--round2 동반) → rc=0"
1934
+ else
1935
+ no "L118 reuse-r1 단독 거부" "alone=$R118 (2 기대) with_round2=$R118c (0 기대)"
1936
+ fi
1937
+
1938
+ # L119 소스와 --out 이 같으면 자기 소스를 덮는다 → 거부
1939
+ O119=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/pr1" --reuse-r1 "$D/pr1" --round2 2>&1); R119=$?
1940
+ if [ "$R119" -eq 2 ] && printf '%s' "$O119" | /usr/bin/grep -q 'same as --out'; then
1941
+ ok "L119 소스 == --out → rc=2 (자기 소스 덮기 거부)"
1942
+ else
1943
+ no "L119 소스==out" "rc=$R119 (2 기대) out='$(printf '%s' "$O119" | /usr/bin/tail -1)'"
1944
+ fi
1945
+
1946
+ # L120 «빈 1차» 재사용 거부 — 빈 것을 측정된 것처럼 쓰면 부재가 0 으로 렌더된다
1947
+ /bin/mkdir -p "$D/pempty"; : > "$D/pempty/findings.jsonl"; cp "$D/fleetR2.tbl" "$D/pempty/fleet.txt"
1948
+ O120=$(bash "$FLEET_SH2" "$D/r2t.py" --out "$D/p120" --reuse-r1 "$D/pempty" --round2 2>&1); R120=$?
1949
+ # 컨트롤: part_ 는 있고 findings 만 빈 경우도 같이 막히는가 → 위와 같은 분기라 한 번만 확인
1950
+ if [ "$R120" -eq 2 ] && printf '%s' "$O120" | /usr/bin/grep -q 'empty round-1'; then
1951
+ ok "L120 빈 1차 재사용 → rc=2 (미측정을 0 으로 렌더하지 않는다)"
1952
+ else
1953
+ no "L120 빈 1차" "rc=$R120 (2 기대) out='$(printf '%s' "$O120" | /usr/bin/tail -1)'"
1954
+ fi
1955
+
1956
+ # L121 옛 2차 잔재 제거 — 재사용한 r1 위에 남의 2차가 섞이면 그 자리는 측정이 아니라 잔재다
1957
+ /bin/mkdir -p "$D/p121"
1958
+ printf '{"title":"STALE2","file":"x","line":1,"severity":"S","category":"c","detail":"d"}\n' > "$D/p121/part2_codex_logic.jsonl"
1959
+ printf 'STALEPEER\n' > "$D/p121/peer_codex_logic.txt"
1960
+ bash "$FLEET_SH2" "$D/r2t.py" --out "$D/p121" --reuse-r1 "$D/pr1" --round2 >/dev/null 2>&1
1961
+ if ! /usr/bin/grep -rq 'STALE2' "$D/p121/findings.jsonl" 2>/dev/null \
1962
+ && ! /usr/bin/grep -q 'STALEPEER' "$D/p121/peer_codex_logic.txt" 2>/dev/null; then
1963
+ ok "L121 옛 2차 잔재(part2_·peer_)가 재사용 시점에 제거된다 — 결과에 안 섞인다"
1964
+ else
1965
+ no "L121 옛 2차 잔재" "stale_in_findings=$(/usr/bin/grep -c 'STALE2' "$D/p121/findings.jsonl" 2>/dev/null) stale_peer=$(/usr/bin/grep -c 'STALEPEER' "$D/p121/peer_codex_logic.txt" 2>/dev/null)"
1966
+ fi
1967
+
1968
+ # ── L122~L123 파이프라인 층 짝지음 통과 ──────────────────────────────────────────
1969
+ # L122 --r1-only: status=R1_ONLY rc=0 · 🟥 씨앗을 선언하면 rc=2 (검증이 안 도는데 컨트롤이
1970
+ # «통과» 로 읽히는 것을 막는다 — fail-closed known-pair: 씨앗 없으면 통과, 있으면 거부)
1971
+ O122=$(bash "$PIPE" "$D/r2t.py" --out "$D/pp122" --fleet "$D/fleetR2.tbl" --r1-only 2>&1); R122=$?
1972
+ O122s=$(bash "$PIPE" "$D/r2t.py" --out "$D/pp122s" --fleet "$D/fleetR2.tbl" --r1-only --seeded s1 2>&1); R122s=$?
1973
+ if [ "$R122" -eq 0 ] && printf '%s' "$O122" | /usr/bin/grep -q 'status=R1_ONLY' \
1974
+ && [ "$R122s" -eq 2 ] && printf '%s' "$O122s" | /usr/bin/grep -q 'would never be judged'; then
1975
+ ok "L122 파이프라인 --r1-only: rc=0 status=R1_ONLY · 씨앗 동반 → rc=2 (미판정 컨트롤 거부)"
1976
+ else
1977
+ no "L122 파이프라인 r1-only" "rc=$R122 (0) seeded_rc=$R122s (2) '$(printf '%s' "$O122" | /usr/bin/grep -o 'status=[A-Z_]*' | head -1)'"
1978
+ fi
1979
+
1980
+ # L123 --reuse-r1 이 파이프라인 out 을 주면 그 안 fleet/ 로 «내려가고», 두 분기가 같은 r1 해시를 찍는다
1981
+ O123a=$(FH_CODEX_BIN="$D/fake_ok.sh" FH_AGY_BIN="$D/fake_ok.sh" bash "$PIPE" "$D/r2t.py" --out "$D/pp123a" --fleet "$D/fleetR2.tbl" --reuse-r1 "$D/pp122" --round2 2>&1)
1982
+ O123b=$(FH_CODEX_BIN="$D/fake_ok.sh" FH_AGY_BIN="$D/fake_ok.sh" bash "$PIPE" "$D/r2t.py" --out "$D/pp123b" --fleet "$D/fleetR2.tbl" --reuse-r1 "$D/pp122" --round2-blind 2>&1)
1983
+ _H123a=$(printf '%s' "$O123a" | /usr/bin/grep -o 'reuse_r1 resolved=[^ ]* sha256=[0-9a-f]*' | head -1)
1984
+ _H123b=$(printf '%s' "$O123b" | /usr/bin/grep -o 'sha256=[0-9a-f]*' | head -1)
1985
+ _H123c=$(printf '%s' "$O123a" | /usr/bin/grep -o 'sha256=[0-9a-f]*' | head -1)
1986
+ if printf '%s' "$_H123a" | /usr/bin/grep -q '/fleet sha256=' && [ -n "$_H123b" ] && [ "$_H123b" = "$_H123c" ]; then
1987
+ ok "L123 파이프라인 --reuse-r1: out/fleet 으로 해소 · 두 분기가 같은 r1 해시($_H123b)"
1988
+ else
1989
+ no "L123 파이프라인 reuse-r1" "resolved='$_H123a' openA='$_H123c' blindB='$_H123b'"
1990
+ fi
1991
+
1992
+ # ── L124 🟥 --r1-only 에서 «빈 1차» 가 «사용 가능» 으로 통과하던 fail-OPEN (cross-family codex 적발) ──
1993
+ # 기전: `n=$(grep -c . f || echo 0)` — grep -c 는 빈 파일에서 «0 을 찍고 rc=1» 이라 폴백이 줄을
1994
+ # 하나 더 붙여 n="0\n0" 이 되고, `[ "$n" -eq 0 ]` 이 구문오류로 **거짓**이 되어 R1_UNUSABLE 분기를
1995
+ # 건너뛴다. 픽스처는 «뚫리는 표기» 그대로 — 아무것도 안 뱉는 멤버.
1996
+ printf '#!/bin/sh\ncat >/dev/null\nexit 0\n' > "$D/m_silent.sh"; chmod +x "$D/m_silent.sh"
1997
+ printf 'codex|logic|sh %s\n' "$D/m_silent.sh" > "$D/f124.tbl"
1998
+ O124=$(bash "$PIPE" "$D/r2t.py" --out "$D/pp124" --fleet "$D/f124.tbl" --r1-only 2>&1); R124=$?
1999
+ # 컨트롤: 발견을 내는 멤버면 같은 경로가 rc=0 R1_ONLY 로 간다(거부가 «항상» 이 아님)
2000
+ O124c=$(bash "$PIPE" "$D/r2t.py" --out "$D/pp124c" --fleet "$D/fleetR2.tbl" --r1-only 2>&1); R124c=$?
2001
+ if [ "$R124" -eq 3 ] && printf '%s' "$O124" | /usr/bin/grep -q 'status=R1_UNUSABLE' \
2002
+ && ! printf '%s' "$O124" | /usr/bin/grep -q 'findings=0$(printf "\n")0' \
2003
+ && [ "$R124c" -eq 0 ] && printf '%s' "$O124c" | /usr/bin/grep -q 'status=R1_ONLY'; then
2004
+ ok "L124 🟥 빈 1차 → rc=3 status=R1_UNUSABLE · 컨트롤(발견 있음) → rc=0 R1_ONLY"
2005
+ else
2006
+ no "L124 빈 1차 fail-open" "empty_rc=$R124 (3 기대) '$(printf '%s' "$O124" | /usr/bin/grep -o 'status=[A-Z_0-9]*' | head -1)' ctrl_rc=$R124c (0 기대)"
2007
+ fi
2008
+
457
2009
  /bin/rm -rf "$D"
458
2010
  echo "PASS=$PASS FAIL=$FAIL"
459
2011
  [ "$FAIL" -eq 0 ] && { echo "FAILED=0"; exit 0; } || { echo "FAILED=1"; exit 1; }