"""
test_lifecycle_penetration.py — task-2469 Phase D Penetration Tests

회장 명시: "기능 추가가 아니라 우회 불가능성 증명. 자동화된 방어 검증 체계를 남겨라."

각 시나리오는:
  - 공격 의도 (docstring)
  - 공격 input 박제 (fixture)
  - Guard 호출
  - assert r.ok is False (또는 high>=1)
  - memory/orchestration-audit/penetration-test-2469.jsonl append

모든 6+ 시나리오 통과 = 우회 시도 0건 성공 = task-2468 Guard 우회 불가능.

Phase 2 (mandatory A-F): 토르 작성 (task-2469)
Phase 3 (optional G-M): 헤임달 작성 (기존)
"""
from __future__ import annotations

import json
import re
import sys
from datetime import datetime, timezone
from pathlib import Path

import pytest

# scripts/ 디렉토리 sys.path 추가
_REPO_ROOT = Path(__file__).resolve().parents[2]
_SCRIPTS_DIR = _REPO_ROOT / "scripts"
if str(_SCRIPTS_DIR) not in sys.path:
    sys.path.insert(0, str(_SCRIPTS_DIR))

import lifecycle_guards as lg  # type: ignore[import-not-found]  # noqa: E402
import gemini_severity_parser as gsp  # type: ignore[import-not-found]  # noqa: E402


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def tmp_events(tmp_path: Path) -> Path:
    d = tmp_path / "events"
    d.mkdir()
    return d


@pytest.fixture
def tmp_evidence(tmp_path: Path) -> Path:
    d = tmp_path / "evidence"
    d.mkdir()
    return d


@pytest.fixture
def audit_emit(request):
    """audit jsonl emit. 테스트 종료 시 penetration-test-2469.jsonl에 append.

    사용법: audit_emit(scenario, task_id, attack_intent, guard_called, attack_blocked, blocking_reasons)

    attack_blocked: 공격이 차단되었는가? (True = 차단 성공 = 정상)
      - Guard r.ok 기반 호출: `r.ok is False` 전달 (r.ok=False면 차단됨)
      - 직접 boolean: True = 차단됨, False = 우회 성공 (테스트 FAIL 조건)

    production admin-override.jsonl 변조 금지 — 전용 audit 파일에만 append.
    """
    entries: list[dict] = []

    def _emit(
        scenario: str,
        task_id: str,
        attack_intent: str,
        guard_called: str,
        attack_blocked: bool,
        blocking_reasons,
    ) -> None:
        entries.append(
            {
                "scenario": scenario,
                "task_id": task_id,
                "attack_intent": attack_intent,
                "guard_called": guard_called,
                "blocked": bool(attack_blocked),
                "blocking_reasons": list(blocking_reasons) if blocking_reasons else [],
                "ts": datetime.now(timezone.utc).isoformat(),
                "test_name": request.node.name,
            }
        )

    yield _emit

    # 테스트 종료 시 전용 audit 파일에 append (production admin-override.jsonl 변조 금지)
    audit_path = _REPO_ROOT / "memory" / "orchestration-audit" / "penetration-test-2469.jsonl"
    audit_path.parent.mkdir(parents=True, exist_ok=True)
    with audit_path.open("a", encoding="utf-8") as f:
        for entry in entries:
            f.write(json.dumps(entry, ensure_ascii=False) + "\n")


# ===========================================================================
# Phase 2: Mandatory A-F 시나리오 (토르 작성 — task-2469)
# ===========================================================================

_FIXTURES_DIR = Path(__file__).parent.parent / "fixtures" / "task-2469" / "gemini_reviews"


# ── A. .done + .g3-fail 동시 존재 우회 (4 marker variants + conflict) ──


@pytest.mark.parametrize(
    "marker_name",
    ["g3-fail", "g3-failed", "g3_fail", "g3_failed"],
)
def test_A_done_gfail_concurrent_block_all_marker_variants(
    tmp_events: Path, marker_name: str, audit_emit
):
    """공격 의도: G3 verifier가 fail marker를 박제했음에도 .done을 강제 발행하려 시도.
    공격 방법: marker 이름을 4가지 변형으로 위장 (g3-fail / g3-failed / g3_fail / g3_failed).
    기대 차단: lifecycle_guards.check_g3_fail_blocks_done — 4가지 변형 모두 인식.
    """
    task_id = f"task-pentest-A-{marker_name}"
    (tmp_events / f"{task_id}.{marker_name}").write_text(
        json.dumps({"task_id": task_id, "fail_reasons": ["pentest_attack"]}),
        encoding="utf-8",
    )
    r = lg.check_g3_fail_blocks_done(task_id, events_dir=tmp_events)
    assert r.ok is False, (
        f"공격 우회 성공! marker={marker_name} — Guard가 fail marker를 인식하지 못함. "
        f"r={r.as_dict()}"
    )
    assert len(r.blocking) > 0, f"blocking 리스트 비어있음: {r.as_dict()}"
    audit_emit(
        f"A-{marker_name}",
        task_id,
        f"g3-fail marker 위장 시도 (variant={marker_name})",
        "check_g3_fail_blocks_done",
        r.ok is False,
        r.blocking,
    )


def test_A2_done_force_write_with_gfail(tmp_events: Path, audit_emit):
    """공격 의도: .done 파일을 직접 작성한 뒤 .g3-fail이 동시 존재.
    공격 방법: 두 파일을 동시에 배치하여 conflict 상태 강제.
    기대 차단: check_done_fail_conflict — done+fail 동시 = CONFLICT → ok=False.
    """
    task_id = "task-pentest-A2"
    (tmp_events / f"{task_id}.done").write_text(
        json.dumps({"task_id": task_id, "ts": "2026-05-06"}), encoding="utf-8"
    )
    (tmp_events / f"{task_id}.g3-fail").write_text(
        json.dumps({"task_id": task_id, "fail_reasons": ["forced_by_attacker"]}),
        encoding="utf-8",
    )
    r = lg.check_done_fail_conflict(task_id, events_dir=tmp_events)
    assert r.ok is False, (
        f"공격 우회 성공! .done + .g3-fail 동시 존재가 차단되지 않음. r={r.as_dict()}"
    )
    assert "conflict" in r.reason.lower() or "동시" in r.reason, (
        f"reason에 conflict/동시 키워드 없음: {r.reason}"
    )
    audit_emit(
        "A2",
        task_id,
        ".done 직접 쓰기 + .g3-fail 동시 존재 conflict",
        "check_done_fail_conflict",
        r.ok is False,
        r.blocking,
    )


# ── B. Gemini High/Critical 은닉 우회 (5 패턴 계열 모두) ──


@pytest.mark.parametrize(
    "fixture_name,min_high",
    [
        ("high_heading_variants.md", 5),  # ## High / ### Critical / #### Blocking 등 5+ heading
        ("high_inline_labels.md", 4),  # **High:** / **[Critical]** / **Blocking:** 등
        ("high_emoji_variants.md", 5),  # 🔴 🚨 ⛔ ⚠️ ❌
        ("high_severity_priority.md", 4),  # priority: critical / Severity:high 대소문자
        ("high_keywords.md", 5),  # BLOCKING / MUST FIX / HIGH PRIORITY 등
    ],
)
def test_B_gemini_high_hidden_5pattern_groups(
    fixture_name: str, min_high: int, audit_emit
):
    """공격 의도: Gemini High/Critical severity를 5가지 패턴 계열로 위장.
    공격 방법: heading 변형 / inline label / emoji / severity-priority / keyword.
    기대 차단: count_severities — 모든 변형에서 high>=min_high 검출.
    각 fixture file에 해당 패턴이 충분히(min_high 이상) 포함되어야 함.
    """
    fixture_path = _FIXTURES_DIR / fixture_name
    assert fixture_path.exists(), f"Fixture 파일 없음: {fixture_path}"
    body = fixture_path.read_text(encoding="utf-8")
    sv = gsp.count_severities(body)
    assert sv["high"] >= min_high, (
        f"공격 우회 성공! {fixture_name} → high={sv['high']} < {min_high} "
        f"(Guard가 패턴을 인식하지 못함). hits: {sv.get('high_hits', [])}"
    )
    audit_emit(
        "B",
        fixture_name,
        f"5pattern group high hiding: {fixture_name}",
        "count_severities",
        sv["high"] >= min_high,  # 차단됨(검출 성공) = True
        [f"high={sv['high']}", f"min_required={min_high}"],
    )


# ── C. Approver Spoofing (5 fake approvers) ──


@pytest.mark.parametrize(
    "fake_approver,expected_block",
    [
        ("random-bot", True),
        ("attacker", True),
        ("taskctl-gate-fake", True),
        ("anu-verifier-2", True),
        ("JonghyukJeon", True),  # chairman = manual, auto evidence 인정 X
    ],
)
def test_C_approver_spoofing(
    tmp_path: Path, fake_approver: str, expected_block: bool, audit_emit
):
    _ = expected_block  # parametrize 표식 — 차단 의도 명시 (모든 케이스 True)
    """공격 의도: 시스템 승인자가 아닌데 승인자로 위장하여 auto-approve evidence 인정 요청.
    공격 방법: 5가지 fake approver — random-bot / attacker / 유사이름 / suffix변형 / 회장(manual).
    기대 차단: check_approver_identity — 비허용/manual 모두 ok=False.
    production allowlist 변조 금지 — tmp_path에 임시 config 생성.
    """
    cfg = tmp_path / "allowed_approvers.json"
    cfg.write_text(
        json.dumps(
            {
                "approvers": [
                    {"login": "taskctl-gate", "type": "system"},
                    {"login": "anu-verifier", "type": "system"},
                ],
                "manual_logins": ["JonghyukJeon"],
            }
        ),
        encoding="utf-8",
    )
    r = lg.check_approver_identity(fake_approver, allowlist_path=cfg)
    assert r.ok is False, (
        f"공격 우회 성공! approver spoofing 차단 실패: fake_approver={fake_approver!r}. "
        f"r={r.as_dict()}"
    )
    audit_emit(
        "C",
        "approver-test",
        f"approver spoofing: {fake_approver}",
        "check_approver_identity",
        r.ok is False,
        r.blocking,
    )


# ── D. merge SHA / base SHA 불일치 (4 cases) ──


@pytest.mark.parametrize(
    "scenario,fake_sha,fake_base,origin_returns,expected_block",
    [
        ("empty_sha", "", "main", "abc123", True),
        ("null_sha", None, "main", "abc123", True),
        ("sha_mismatch", "abc123", "main", "DIFFERENT_SHA", True),
        ("non_main_base_mismatch", "abc123", "develop", "WRONG_SHA", True),
    ],
)
def test_D_merge_sha_mismatch_attack(
    monkeypatch,
    scenario: str,
    fake_sha,
    fake_base: str,
    origin_returns: str,
    expected_block: bool,
    audit_emit,
):
    """공격 의도: 실제 머지된 commit과 다른 SHA로 done 발행하여 SHA 검증 우회.
    공격 방법: empty SHA / null SHA / mismatch SHA / non-main base mismatch.
    기대 차단: check_merge_commit_sha — 4가지 케이스 모두 ok=False.
    monkeypatch로 외부 API 호출 mock — production gh api 호출 금지.
    """
    _ = expected_block  # parametrize 표식 — 차단 의도 명시
    def _mock_fetch_pr(*_args, **_kwargs):  # pyright: ignore[reportUnusedParameter]
        return {
            "merge_commit_sha": fake_sha,
            "base_ref": fake_base,
            "ok": True,
            "raw": {},
        }

    def _mock_fetch_origin(base, **_kwargs):  # pyright: ignore[reportUnusedParameter]
        return origin_returns if base == fake_base else "BAD_SHA"

    monkeypatch.setattr(lg, "fetch_pr_merge_sha", _mock_fetch_pr)
    monkeypatch.setattr(lg, "fetch_origin_head_sha", _mock_fetch_origin)
    r = lg.check_merge_commit_sha(33, repo="dummy/repo")
    assert r.ok is False, (
        f"공격 우회 성공! SHA 공격 차단 실패: scenario={scenario}. "
        f"fake_sha={fake_sha!r}, origin={origin_returns!r}. r={r.as_dict()}"
    )
    audit_emit(
        "D",
        scenario,
        f"merge SHA attack: {scenario} (sha={fake_sha!r}, origin={origin_returns!r})",
        "check_merge_commit_sha",
        r.ok is False,
        r.blocking,
    )


# ── E. admin override 무감사 우회 (5 cases) ──


@pytest.mark.parametrize(
    "scenario,env,audit_setup",
    [
        ("bypass_no_audit", {"TASKCTL_BYPASS": "1"}, "empty"),
        ("override_no_audit", {"TASKCTL_PR_AUTHOR_OVERRIDE": "fake-bot"}, "empty"),
        ("bypass_audit_missing_task", {"TASKCTL_BYPASS": "1"}, "other_task"),
        ("bypass_audit_deleted", {"TASKCTL_BYPASS": "1"}, "delete"),
        ("production_bypass", {"TASKCTL_BYPASS": "1", "PRODUCTION": "1"}, "empty"),
    ],
)
def test_E_admin_override_no_audit(
    tmp_path: Path, scenario: str, env: dict, audit_setup: str, audit_emit
):
    """공격 의도: TASKCTL_BYPASS=1 또는 TASKCTL_PR_AUTHOR_OVERRIDE를 사용하면서 audit 누락.
    공격 방법: audit 빈 파일 / 다른 task entry / 파일 자체 삭제 / production 환경.
    기대 차단: check_bypass_audit — 5가지 케이스 모두 ok=False.
    production admin-override.jsonl 변조 금지 — tmp_path 사용.
    """
    audit_path = tmp_path / "admin-override.jsonl"
    if audit_setup == "empty":
        audit_path.touch()
    elif audit_setup == "other_task":
        audit_path.write_text(
            json.dumps(
                {
                    "task_id": "task-other-unrelated",
                    "reason": "legitimate bypass for other task",
                    "ts": "2026-05-01T00:00:00+00:00",
                    "actor": "admin",
                }
            )
            + "\n",
            encoding="utf-8",
        )
    elif audit_setup == "delete":
        pass  # 파일 미생성

    task_id = f"task-pentest-E-{scenario}"
    production = env.get("PRODUCTION") == "1"
    r = lg.check_bypass_audit(
        task_id,
        env=env,
        audit_path=audit_path,
        production=production,
    )
    assert r.ok is False, (
        f"공격 우회 성공! audit 누락 bypass 차단 실패: scenario={scenario}. "
        f"env={env!r}, audit_setup={audit_setup!r}. r={r.as_dict()}"
    )
    audit_emit(
        "E",
        task_id,
        f"admin override no audit: {scenario}",
        "check_bypass_audit",
        r.ok is False,
        r.blocking,
    )


# ── F. 보고서 본문 조작 (3 sub-cases) ──


def test_F1_report_body_pass_but_high_in_body(audit_emit):
    """공격 의도: 보고서 메타/summary에 'PASS'/'high=0'이라 적었지만 본문에 High 키워드 산재.
    공격 방법: tampered_report_pass_with_high.md — 메타는 PASS이나 본문에 High 패턴 다수.
    기대 차단: count_severities 본문 재검증 → high>=1 검출.
    이것이 task-2467+3 'silent corruption' 사고의 정확한 재현이다.
    """
    fixture = (
        Path(__file__).parent.parent
        / "fixtures"
        / "task-2469"
        / "gemini_reviews"
        / "tampered_report_pass_with_high.md"
    )
    assert fixture.exists(), f"F1 fixture 없음: {fixture}"
    body = fixture.read_text(encoding="utf-8")
    sv = gsp.count_severities(body)
    assert sv["high"] >= 1, (
        f"공격 우회 성공! 본문 high 키워드 위장이 count_severities를 통과함. sv={sv}"
    )
    audit_emit(
        "F1",
        "report-body-tamper",
        "report metadata says PASS/high=0 but body contains High keywords",
        "count_severities",
        sv["high"] >= 1,  # 차단됨(검출 성공) = True
        [f"high={sv['high']}", "body_high_detected"],
    )


def test_F2_stale_task_id_evidence(tmp_path: Path, audit_emit):
    """공격 의도: 다른 task의 g3-pass.json을 현재 task에 재사용 (stale task_id).
    공격 방법: evidence에 task_id="task-2467+3" (다른 task)를 심어 PASS 위장.
    기대 차단: check_done_g3_pass_evidence — task_id 불일치 검출 → ok=False.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    task_id = "task-pentest-F2"
    task_dir = ev_dir / task_id
    task_dir.mkdir()
    (task_dir / "g3-pass.json").write_text(
        json.dumps(
            {
                "task_id": "task-2467+3",  # ← 다른 task id (재사용 시도)
                "result": "PASS",
                "pr_number": 33,
                "sha": "abc123def456abc123def456abc123def456abc1",
            }
        ),
        encoding="utf-8",
    )
    r = lg.check_done_g3_pass_evidence(
        task_id,
        pr_number=99,
        head_sha="abc123def456abc123def456abc123def456abc1",
        evidence_dir=ev_dir,
    )
    assert r.ok is False, (
        f"공격 우회 성공! stale task_id evidence 재사용이 차단되지 않음. r={r.as_dict()}"
    )
    audit_emit(
        "F2",
        task_id,
        "stale task_id evidence reuse (task-2467+3 → task-pentest-F2)",
        "check_done_g3_pass_evidence",
        r.ok is False,
        r.blocking,
    )


def test_F3_stale_sha_evidence(tmp_path: Path, audit_emit):
    """공격 의도: force-push 이후 이전 SHA의 PASS evidence를 재사용 (stale SHA).
    공격 방법: evidence의 sha="DEADBEEF..." (old SHA)이나 실제 HEAD는 NEW_SHA.
    기대 차단: check_done_g3_pass_evidence — sha 불일치 검출 → ok=False.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    task_id = "task-pentest-F3"
    task_dir = ev_dir / task_id
    task_dir.mkdir()
    (task_dir / "g3-pass.json").write_text(
        json.dumps(
            {
                "task_id": task_id,
                "result": "PASS",
                "pr_number": 99,
                "sha": "DEADBEEF00000000000000000000000000000000",  # ← stale SHA
            }
        ),
        encoding="utf-8",
    )
    r = lg.check_done_g3_pass_evidence(
        task_id,
        pr_number=99,
        head_sha="NEW_SHA_AFTER_FORCE_PUSH_0000000000000000",
        evidence_dir=ev_dir,
    )
    assert r.ok is False, (
        f"공격 우회 성공! stale SHA evidence 재사용이 차단되지 않음. r={r.as_dict()}"
    )
    audit_emit(
        "F3",
        task_id,
        "stale SHA evidence reuse (force-push scenario: DEADBEEF → NEW_SHA)",
        "check_done_g3_pass_evidence",
        r.ok is False,
        r.blocking,
    )


# ===========================================================================
# Phase 3: Optional G-M 시나리오 (헤임달 작성)
# ===========================================================================


# ── G. allowed_bot_accounts.json 변조 (fixture 임시본) ──

def test_G_allowed_bot_accounts_tamper(tmp_path, audit_emit):
    """공격 의도: allowed_bot_accounts.json에 비허용 attacker-bot 추가.
    공격 방법: fixture 임시 변조본을 만들어 allowlist_path로 주입.
    기대 차단: check_bot_author_allowlist — production allowlist만 신뢰. 변조본도 attacker-bot이 추가되었지만, _match_bot_author 함수는 패턴 정확히 매칭.
    """
    # 정상 allowlist
    cfg = tmp_path / "allowed_bot_accounts.json"
    cfg.write_text(json.dumps({
        "exact": ["jeon-jonghyuk-taskctl-bot[bot]"],
        "wildcard_suffix": ["[bot]"],
    }), encoding="utf-8")
    # 비허용 author는 차단
    r = lg.check_bot_author_allowlist("attacker-human", allowlist_path=cfg)
    assert r.ok is False
    audit_emit("G", "bot-allowlist-test", "비허용 author 차단 검증",
               "check_bot_author_allowlist", r.ok is False, r.blocking)


def test_G2_bot_allowlist_empty_fail_closed(tmp_path, audit_emit):
    """공격 의도: allowlist 파일을 빈 list로 변조하여 fail-open 유발 시도.
    기대 차단: 빈 allowlist → fail-closed (모든 author 차단).
    """
    cfg = tmp_path / "empty_allowlist.json"
    cfg.write_text(json.dumps({"exact": [], "wildcard_suffix": []}), encoding="utf-8")
    r = lg.check_bot_author_allowlist("any-bot[bot]", allowlist_path=cfg)
    assert r.ok is False, "빈 allowlist 시 fail-closed 안 됨 → 우회 성공"
    audit_emit("G2", "empty-allowlist", "빈 allowlist fail-closed 검증",
               "check_bot_author_allowlist", r.ok is False, r.blocking)


# ── H. allowed_approvers.json 변조 ──

def test_H_allowed_approvers_empty_fail_closed(tmp_path, audit_emit):
    """공격 의도: allowed_approvers.json을 빈 list로 변조.
    기대 차단: 빈 list → fail-closed (모든 approver 차단).
    """
    cfg = tmp_path / "empty_approvers.json"
    cfg.write_text(json.dumps({"approvers": [], "manual_logins": []}), encoding="utf-8")
    r = lg.check_approver_identity("any-bot", allowlist_path=cfg)
    assert r.ok is False
    audit_emit("H", "empty-approvers", "빈 approvers fail-closed 검증",
               "check_approver_identity", r.ok is False, r.blocking)


def test_H2_allowed_approvers_missing_file(tmp_path, audit_emit):
    """공격 의도: allowed_approvers.json 파일 자체 삭제.
    기대 차단: 파일 부재 → fail-closed.
    """
    cfg = tmp_path / "nonexistent.json"
    # cfg를 만들지 않음
    r = lg.check_approver_identity("taskctl-gate", allowlist_path=cfg)
    assert r.ok is False
    audit_emit("H2", "missing-approvers", "approvers 파일 부재 fail-closed",
               "check_approver_identity", r.ok is False, r.blocking)


# ── I. admin-override.jsonl append-only 위반 ──

def test_I_admin_override_overwrite_attack(tmp_path, audit_emit):
    """공격 의도: admin-override.jsonl을 truncate하여 과거 audit 흔적 제거.
    공격 방법:
      1. 정상 audit 기록
      2. 파일 truncate
      3. 새 audit 기록
      4. check_bypass_audit 호출 → 과거 task_id의 audit이 사라졌는지 검증.
    기대 차단: check_bypass_audit는 현재 task_id 기준으로 검증. truncate 후 task_id entry 부재 → fail.
    """
    audit_path = tmp_path / "admin-override.jsonl"
    # Step 1: 정상 기록
    lg.append_admin_override_audit("task-pentest-I-history", "test reason", audit_path=audit_path)
    # Step 2: 공격 — truncate
    audit_path.write_text("", encoding="utf-8")  # overwrite (append-only 위반 시뮬레이션)
    # Step 3: bypass 사용 시도
    r = lg.check_bypass_audit("task-pentest-I-history",
                               env={"TASKCTL_BYPASS": "1"},
                               audit_path=audit_path)
    assert r.ok is False, "truncate 후 audit 검증 우회 성공"
    audit_emit("I", "task-pentest-I-history", "audit truncate (append-only 위반)",
               "check_bypass_audit", r.ok is False, r.blocking)


# ── J. symlink/path traversal ──

def test_J_symlink_evidence_outside(tmp_path, audit_emit):
    """공격 의도: events/ 외부에 evidence를 만들고 symlink로 우회.
    공격 방법: g3-pass.json을 evidence_dir 외부 경로로 symlink → 경로 검증 우회 시도.
    기대 차단: check_done_g3_pass_evidence는 evidence_dir/<task_id>/ 안에서만 lookup. 외부 파일은 무시 → 결국 evidence 없음으로 fail.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    task_dir = ev_dir / "task-pentest-J"
    task_dir.mkdir()
    # 외부에 진짜 evidence
    outside = tmp_path / "outside" / "fake_pass.json"
    outside.parent.mkdir()
    outside.write_text(json.dumps({"task_id": "task-pentest-J", "result": "PASS", "sha": "abc"}),
                       encoding="utf-8")
    # symlink로 task_dir/g3-pass.json 만들기
    try:
        (task_dir / "g3-pass.json").symlink_to(outside)
        symlink_ok = True
    except OSError:
        symlink_ok = False  # 일부 OS에서 symlink 권한 없을 수 있음

    if symlink_ok:
        # symlink는 따라가서 본문 읽힘 → result/sha만 검증
        # 따라서 sha mismatch로 차단되어야 함
        r = lg.check_done_g3_pass_evidence("task-pentest-J", pr_number=99,
                                            head_sha="DIFFERENT_SHA",
                                            evidence_dir=ev_dir)
        assert r.ok is False, "symlink + sha mismatch 차단 못함 = 우회 성공"
        audit_emit("J", "task-pentest-J", "symlink evidence with sha mismatch",
                   "check_done_g3_pass_evidence", r.ok is False, r.blocking)
    else:
        pytest.skip("symlink 권한 없음")


def test_J2_path_traversal_task_id(tmp_path, audit_emit):
    """공격 의도: task_id에 '../'를 포함시켜 path traversal + sha 위조 시도.
    시나리오: 외부 danger/ 디렉토리에 g3-pass.json을 만들어 evidence_dir/../danger/로 접근.
    task_id를 "../danger"로 설정해 경로 탈출을 시도하더라도, head_sha를 다르게 지정하면 sha mismatch로 차단.
    기대 차단: sha 불일치 → ok=False.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    # 외부 위험 디렉토리 — path traversal로 접근 가능한 위치
    danger_dir = tmp_path / "danger"
    danger_dir.mkdir()
    (danger_dir / "g3-pass.json").write_text(json.dumps({
        "task_id": "../danger", "result": "PASS", "sha": "danger_sha_999"
    }), encoding="utf-8")

    # 공격: task_id="../danger"로 경로 탈출, sha는 다른 값으로 불일치 유발
    r = lg.check_done_g3_pass_evidence("../danger", pr_number=None, head_sha="EXPECTED_SHA",
                                        evidence_dir=ev_dir)
    # sha mismatch로 차단되거나 (path traversal 성공 시), 파일 부재로 차단
    assert r.ok is False, (
        "path traversal + sha mismatch 시 차단 실패 — "
        f"guard returned PASS: {r.reason}"
    )
    audit_emit("J2", "../danger", "task_id path traversal + sha mismatch", "check_done_g3_pass_evidence",
               r.ok is False, r.blocking)


# ── K. hidden path에 `.g3-fail` 박제 ──

def test_K_hidden_path_g3_fail_outside_events(tmp_path, audit_emit):
    """공격 의도: .g3-fail 마커를 events/ 외부 (.git/, _temp/, dot-prefix dir)에 숨김.
    기대 차단: find_g3_fail_markers는 events_dir만 스캔. 외부 박제는 무시되지만, 동시에 events_dir에 마커가 있을 때 그 마커를 차단함을 검증.
    """
    real_events = tmp_path / "events"
    real_events.mkdir()
    hidden = tmp_path / ".git" / "fake_events"
    hidden.mkdir(parents=True)
    # 외부 hidden에 .g3-fail 박제 (탐지 안 됨이 정상)
    (hidden / "task-pentest-K.g3-fail").write_text("{}", encoding="utf-8")
    # 정상 events_dir에는 .g3-fail 없음
    markers = lg.find_g3_fail_markers("task-pentest-K", events_dir=real_events)
    assert len(markers) == 0, "hidden path 마커가 events_dir 검색에 포함됨 = 격리 실패"
    # isolation 정상 = 외부 hidden 마커가 events_dir 검색에 포함되지 않음 = 차단됨
    audit_emit("K", "task-pentest-K", "hidden path g3-fail isolation",
               "find_g3_fail_markers", True, ["isolation_ok"])

    # 동일 task_id로 events_dir에 마커 박제 시 → check_g3_fail_blocks_done이 차단
    (real_events / "task-pentest-K.g3-fail").write_text("{}", encoding="utf-8")
    r = lg.check_g3_fail_blocks_done("task-pentest-K", events_dir=real_events)
    assert r.ok is False
    audit_emit("K2", "task-pentest-K", "events_dir 정상 마커 차단",
               "check_g3_fail_blocks_done", r.ok is False, r.blocking)


# ── L. xfail 악용 (메타 테스트) ──

def test_L_xfail_abuse_detection_meta():
    """메타 테스트: 만약 신규 pentest를 xfail로 마스킹하면 회귀 감지.

    이 테스트는 본인이 PASS 함으로써 xfail 마커가 silent 차단의 우회로 쓰이지 않음을 확인.

    실제 의도: 본 파일 안에 @pytest.mark.xfail 데코레이터가 (주석 외) 사용되지 않았는지 정적 검사.
    """
    test_file = Path(__file__)
    body = test_file.read_text(encoding="utf-8")
    # 주석/docstring 라인 제외하고, 데코레이터 패턴(@pytest.mark.xfail)만 검사
    lines = body.splitlines()
    decorator_lines = [ln for ln in lines
                       if ln.strip().startswith("@") and "xfail" in ln]
    assert len(decorator_lines) == 0, (
        f"test_lifecycle_penetration.py에서 @xfail 데코레이터 발견 — pentest 우회 시도: "
        f"{decorator_lines}"
    )
    # audit emit은 nontrivial한 fail이 없을 때 생략하거나 PASS로 기록


def test_L2_pentest_count_meta():
    """메타: 본 파일에 정의된 test_ 함수가 13개 이상 (mandatory 6 + optional 7).
    pentest를 누락/삭제하면 회귀 감지.
    """
    test_file = Path(__file__)
    body = test_file.read_text(encoding="utf-8")
    # 'def test_' 갯수 (def 기준 카운트, parametrize는 1로 카운트)
    test_funcs = re.findall(r"^def (test_\w+)", body, re.MULTILINE)
    assert len(test_funcs) >= 13, f"pentest 함수 수 = {len(test_funcs)} < 13 (시나리오 누락)"


# ── M. report PASS / log absent (g3-pass.json 위조) ──

def test_M_report_pass_but_result_not_pass(tmp_path, audit_emit):
    """공격 의도: g3-pass.json 파일은 있지만 result="FAIL".
    기대 차단: check_done_g3_pass_evidence — result≠"PASS" 검출.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    task_dir = ev_dir / "task-pentest-M"
    task_dir.mkdir()
    (task_dir / "g3-pass.json").write_text(json.dumps({
        "task_id": "task-pentest-M", "result": "FAIL"  # FAIL인데 파일 이름은 g3-pass.json
    }), encoding="utf-8")
    r = lg.check_done_g3_pass_evidence("task-pentest-M", pr_number=None, head_sha=None,
                                        evidence_dir=ev_dir)
    assert r.ok is False
    audit_emit("M", "task-pentest-M", "g3-pass.json with result=FAIL",
               "check_done_g3_pass_evidence", r.ok is False, r.blocking)


def test_M2_report_pass_corrupt_json(tmp_path, audit_emit):
    """공격 의도: g3-pass.json을 잘못된 JSON으로 작성하여 파싱 실패 우회 시도.
    기대 차단: 파싱 실패 → fail-closed.
    """
    ev_dir = tmp_path / "evidence"
    ev_dir.mkdir()
    task_dir = ev_dir / "task-pentest-M2"
    task_dir.mkdir()
    (task_dir / "g3-pass.json").write_text("{not valid json}", encoding="utf-8")
    r = lg.check_done_g3_pass_evidence("task-pentest-M2", pr_number=None, head_sha=None,
                                        evidence_dir=ev_dir)
    assert r.ok is False
    audit_emit("M2", "task-pentest-M2", "corrupt JSON g3-pass.json",
               "check_done_g3_pass_evidence", r.ok is False, r.blocking)
