Skip to content

feat(sandbox): restack exact patch quarantine after nanoid - #93

Closed
seonghobae wants to merge 3 commits into
mainfrom
feat/quarantined-patch-validation-on-main
Closed

feat(sandbox): restack exact patch quarantine after nanoid#93
seonghobae wants to merge 3 commits into
mainfrom
feat/quarantined-patch-validation-on-main

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Purpose

Clean protected-main successor for closed stale PR #65 after #76 integrated. This PR starts from current protected main c85d710804139c0697d7ef8fa47d02b1389e6d84 and carries the unique credential-free exact-source patch-quarantine implementation, tests, doctoring, and one current-baseline ## Unreleased changelog entry without replaying #65's stale pre-#76 package/audit state.

Current exact source boundary

  • head branch: feat/quarantined-patch-validation-on-main;
  • exact current head: fe3074a188739705f89ff67b46712ac9d228081d;
  • independently re-resolved protected main: c85d710804139c0697d7ef8fa47d02b1389e6d84;
  • changed-file set: 27 paths, matching feat(sandbox): add quarantined patch validation #65's product/docs/test path set plus a current-baseline CHANGELOG.md entry;
  • fresh changed-file enumeration contains no package, lockfile, workflow, or nanoid files.

PR #65 is already closed unmerged; its checks/reviews are historical and do not transfer.

Preserved product boundary

  • exact repository/base/head/patch-SHA-256/profile binding;
  • allowlisted validation profiles with no caller-provided shell command;
  • strict UTF-8, path, mode, hunk, file-count and governance-surface validation;
  • exact Git HEAD/clean-worktree plus exact-tree/object identity verification;
  • descriptor-safe/no-follow/size-bounded reads and source object-store/alternate isolation;
  • bounded exact-commit archive materialization and extracted-file Git object rehashing;
  • no-network/read-only/non-root container execution without Docker socket or reviewer/model/GitHub write/release/deployment credentials;
  • trusted-host result validation and bounded retained evidence;
  • beginner-readable public docs, realistic hostile-input regressions and reviewer coverage/docstring gates.

Fresh exact-head technical evidence

For unchanged exact head fe3074a188739705f89ff67b46712ac9d228081d:

  • application ci run 31375580944: terminal success;
  • reviewer-ci run 31375580959: terminal success;
  • central Security Scan run 31375580983: terminal success under its own scanner/revision semantics;
  • formal GitHub review submissions: 0;
  • inline review threads: 0;
  • GitHub reports the PR mergeable.

These runs are fresh post-#76 technical evidence. They do not create live ruleset evidence or qualifying independent approval.

Current acceptance sequence

  1. Preserve the proven bounded 27-path implementation set without package/lock/workflow drift.
  2. Keep this exact head stable while current review/governance evidence is evaluated.
  3. Merge only when live chore(governance): protect main and enforce release checks #27/chore(operations): provision and activate hourly maintainer App #29 ruleset/reviewer requirements are actually satisfied; no check/status/model evidence substitutes for formal approval when required.
  4. After protected integration, run exact-source patch-quarantine operational acceptance on main.
  5. Restack only feat(sandbox): build and verify patch-validator image #67's unique validator-image work onto the protected successor lineage rather than retaining the stale feat(sandbox): add quarantined patch validation #65 feature-base stack.

No repair/self-modifying workflow, force push, audit waiver, synthetic approval, version bump, release or license decision is introduced.

Related: #9, #27, #29, #65, #66, #67

Summary by CodeRabbit

  • 새 기능

    • 인증 정보와 네트워크 없이 의심스러운 패치를 격리 검증하는 기능을 추가했습니다.
    • 정확한 커밋, 소스 트리, 패치 내용을 대조해 무결성을 확인합니다.
    • 검증 결과를 성공·실패·차단 상태와 제한된 진단 정보로 제공합니다.
    • 검증 기능과 사용 방법, 보안 경계를 설명하는 문서를 추가했습니다.
  • 보안 강화

    • Docker 샌드박스에서 읽기 전용 파일 시스템, 네트워크 차단, 리소스 제한을 적용합니다.
    • 안전하지 않은 경로·파일 유형·심볼릭 링크·변조된 아카이브를 자동으로 거부합니다.
  • 검증

    • 패치 형식, Git 메타데이터, 아카이브, 결과 파일에 대한 광범위한 경계 조건 테스트를 추가했습니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

정확한 저장소·커밋·패치 다이제스트에 바인딩된 패치 검증기를 추가했다. Git 소스와 exact tree를 검증하고, archive와 추출 결과를 재검증한다. Docker sandbox는 네트워크·권한·자원·결과 채널을 제한한다. 관련 회귀 테스트와 운영 문서를 추가했다.

Changes

패치 격리 검증

Layer / File(s) Summary
요청 계약과 패치 사전 검증
reviewer/noema_reviewer/patch_validation.py, reviewer/noema_reviewer/__init__.py, reviewer/tests/test_patch_validation*.py
요청·결과 모델과 공개 API를 추가했다. UTF-8, 경로, hunk, 파일 모드, 메타데이터, 크기 및 다이제스트를 검증한다.
Git 소스 무결성 사전 검사
reviewer/noema_reviewer/patch_validation.py, reviewer/tests/test_patch_validation_git_*.py, reviewer/tests/test_patch_validation_streaming_*.py
격리된 Git 제어 영역에서 exact HEAD와 clean worktree를 확인한다. bounded stream과 alternate 검증을 적용한다.
Exact tree와 snapshot materialization
reviewer/noema_reviewer/patch_validation.py, reviewer/tests/test_patch_validation_archive_boundaries.py, reviewer/tests/test_patch_validation_exact_tree_*.py, reviewer/tests/test_patch_validation_blob_identity_edges.py
커밋 tree, archive, 추출 결과를 경로·모드·크기·Git blob identity로 대조한다. 링크, 특수 파일, 중복 및 치환을 거부한다.
Docker sandbox와 결과 검증
reviewer/noema_reviewer/patch_validation.py, reviewer/tests/test_patch_validation.py, reviewer/tests/test_patch_validation_hardening.py, reviewer/tests/test_patch_validation_security_boundaries.py, docs/quarantined-patch-validation.md, docs/doctoring/quarantined-patch-validation.md
고정 이미지, 네트워크 차단, 비 root, 읽기 전용 마운트와 자원 제한을 적용한다. 단일 결과 파일을 bounded 방식으로 읽고 요청 identity를 재검증한다.

Estimated code review effort: 5 (Critical) | ~90 minutes

Mergeability Score: 🔵 Low · up to 26aa1

The PR adds exact patch-quarantine validation and currently passes its stated checks, but valid patches containing unquoted spaces may be rejected, permission-related metadata errors may escape the expected error contract, and large archives may incur avoidable quadratic processing. It is mergeable with explicit owner awareness and follow-up for these bounded risks.

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 nanoid 이후 exact patch quarantine 구현을 재정렬하는 주요 변경을 정확히 설명하며, 변경 범위와 일치합니다.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/quarantined-patch-validation-on-main

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (10)
reviewer/tests/test_patch_validation_streaming_edges.py (2)

413-433: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

부분 레코드 조립 결과를 단정하십시오.

이 테스트는 예외가 없는 것만 확인합니다. 반환된 인벤토리의 경로, 모드, 오브젝트 ID, 크기를 단정하면 청크 경계에서 필드가 잘려도 검출됩니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_streaming_edges.py` around lines 413 -
433, Update test_consume_exact_tree_stream_accepts_partial_records to capture
the return value from _consume_exact_tree_stream and assert the reconstructed
inventory’s path, mode, object ID, and size, ensuring fields split across chunk
boundaries are assembled correctly.

436-451: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

무한 반환 가짜 리더를 유한 반복자로 바꾸십시오.

_read_git_stream_chunk 대체 함수가 항상 b"12345"를 반환합니다. 레코드 상한 검사가 회귀하면 _consume_exact_tree_stream이 종료되지 않고 테스트가 CI에서 멈춥니다. 유한 반복자를 쓰면 회귀 시 즉시 실패합니다.

♻️ 제안 수정
     process = _FakeProcess()
     monkeypatch.setattr(patch_validation, "MAX_SOURCE_TREE_RECORD_BYTES", 4)
+    chunks = iter((b"12345",) * 4 + (b"",))
     monkeypatch.setattr(
         patch_validation,
         "_read_git_stream_chunk",
-        lambda *_args, **_kwargs: b"12345",
+        lambda *_args, **_kwargs: next(chunks),
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_streaming_edges.py` around lines 436 -
451, Update test_consume_exact_tree_stream_rejects_unterminated_record_ceiling
so the _read_git_stream_chunk monkeypatch returns data from a finite iterator
rather than always returning b"12345". Keep the first returned chunk sufficient
to trigger the record byte-limit error, and ensure subsequent reads are
exhausted so a regression cannot cause the test to hang.
reviewer/noema_reviewer/patch_validation.py (4)

1402-1415: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

_read_result_payload_completed 매개변수는 사용되지 않습니다.

호출부(Line 1576-1580)는 completed를 전달하지만 함수는 이를 무시합니다. 향후 확장 계획이 없다면 매개변수와 인수를 함께 제거하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/noema_reviewer/patch_validation.py` around lines 1402 - 1415, Remove
the unused _completed parameter from _read_result_payload and update its
callers, including the call that passes completed, so the function signature and
invocation no longer accept or provide this argument.

1455-1458: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

metadata_kind is None 검사가 중복이며 도달할 수 없습니다.

_verify_source_headmetadata_kindNone이면 같은 메시지로 이미 예외를 발생시킵니다(Line 870-871). 따라서 Line 1457-1458은 실행되지 않습니다. 타입 좁히기 목적이라면 주석을 남기고, 그렇지 않으면 제거하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/noema_reviewer/patch_validation.py` around lines 1455 - 1458, Remove
the unreachable metadata_kind is None check after _verify_source_head in the
source validation flow, since _verify_source_head already raises for that case.
If the check is required solely for type narrowing, replace it with an
explanatory comment; otherwise leave the existing _verify_source_head call and
subsequent logic unchanged.

1016-1038: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

_validated_exact_tree_output는 결과를 버리며 스트리밍 검증기와 로직이 중복됩니다.

이 함수는 inventory를 채운 다음 반환하지 않고 폐기합니다. 프로덕션 경로는 _consume_exact_tree_stream만 사용합니다. 두 구현이 같은 한계값과 규칙을 따로 유지하면 향후 한쪽만 수정될 위험이 있습니다. 이 함수가 테스트 전용이라면 스트리밍 검증기를 재사용하도록 정리하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/noema_reviewer/patch_validation.py` around lines 1016 - 1038, Update
_validated_exact_tree_output to reuse _consume_exact_tree_stream rather than
independently splitting records and invoking _validated_exact_tree_record.
Preserve the existing validation limits and rules while returning or exposing
the validated inventory needed by tests, so the test helper does not maintain a
duplicate implementation.

1197-1203: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

빈 디렉터리 검사는 멤버 수에 대해 2차 시간 복잡도로 동작합니다.

declared_directories의 각 항목마다 declared_paths 전체를 다시 순회합니다. 멤버 상한은 MAX_SOURCE_ARCHIVE_MEMBERS = 20_000입니다. 디렉터리가 많은 저장소에서는 최악의 경우 문자열 비교가 수억 회 발생합니다. 부모 경로 집합을 한 번만 만들어 상수 시간으로 조회하십시오.

♻️ 제안 수정
-    for directory in declared_directories:
-        prefix = f"{directory}/"
-        if not any(
-            path != directory and path.startswith(prefix)
-            for path in declared_paths
-        ):
-            raise ValueError("source archive contains an empty gitlink-like directory")
+    populated_directories: set[str] = set()
+    for path in declared_paths:
+        parent = PurePosixPath(path).parent
+        while parent != PurePosixPath("."):
+            populated_directories.add(parent.as_posix())
+            parent = parent.parent
+    for directory in declared_directories:
+        if directory not in populated_directories:
+            raise ValueError("source archive contains an empty gitlink-like directory")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/noema_reviewer/patch_validation.py` around lines 1197 - 1203,
Optimize the empty-directory validation loop in the declared_directories check
by building a set of parent directory paths from declared_paths once, then use
constant-time membership checks for each directory instead of repeatedly
scanning declared_paths. Preserve the existing path-prefix semantics and
ValueError behavior for empty gitlink-like directories.
reviewer/tests/test_patch_validation_streaming_final_edges.py (1)

25-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

subprocess.run을 전역으로 교체하면 테스트 범위가 넓어집니다.

monkeypatch.setattr(patch_validation.subprocess, "run", ...)는 공유된 subprocess 모듈 속성을 바꿉니다. 테스트 실행 중 다른 코드가 subprocess.run을 호출하면 스텁 값을 받습니다. 검증 대상 함수만 감싸도록 좁히십시오. 예를 들어 _verify_source_head가 사용하는 호출만 대체하는 헬퍼를 도입하거나, 반환값 검사를 인자별로 구분하십시오.

Also applies to: 99-103

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_streaming_final_edges.py` around lines
25 - 29, Replace the global subprocess.run monkeypatch in the affected tests
with a narrowly scoped stub for the call used by _verify_source_head. Ensure
only that validation path receives the fake return value, while unrelated
subprocess.run calls retain their real behavior; apply the same change to both
highlighted test locations.
reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py (1)

51-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Git 헬퍼가 다른 테스트 파일과 다르게 부분 실행 경로를 사용합니다.

이 파일은 "git"을 직접 호출하고 timeout을 지정하지 않습니다. 같은 코호트의 test_patch_validation.pytest_patch_validation_security_boundaries.pypatch_validation.TRUSTED_GIT_EXECUTABLE, shell=False, timeout=30을 사용하는 _run_git 헬퍼를 씁니다. Ruff도 S607(부분 실행 경로)로 이 호출들을 표시합니다. 동일한 헬퍼 형태로 통일하십시오.

♻️ 제안 수정
+def _run_git(source: Path, *arguments: str) -> str:
+    """Run one deterministic non-shell Git command for a test repository."""
+    completed = subprocess.run(
+        [patch_validation.TRUSTED_GIT_EXECUTABLE, "-C", str(source), *arguments],
+        check=True,
+        shell=False,
+        text=True,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        timeout=30,
+    )
+    return completed.stdout.strip()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py` around
lines 51 - 78, Update the _git_repository helper to run every Git command
through the established patch_validation.TRUSTED_GIT_EXECUTABLE-based _run_git
pattern, preserving shell=False and timeout=30 for each invocation. Replace
direct "git" subprocess calls and reuse the existing shared helper conventions
used by the related patch-validation tests.

Source: Linters/SAST tools

reviewer/tests/test_patch_validation.py (1)

419-456: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

성공 경로 테스트가 러너의 실제 UID/GID에 의존합니다. DockerPatchValidationRunner.validateos.getuid() 또는 os.getgid()가 0이면 RuntimeError를 발생시킵니다. 루트로 동작하는 컨테이너 러너에서는 아래 테스트가 모두 실패합니다. 각 성공 경로에서 비루트 신원을 고정하십시오.

  • reviewer/tests/test_patch_validation.py#L419-L456: monkeypatch.setattr(patch_validation.os, "getuid", lambda: 1000)getgid 스텁을 추가하십시오. 같은 파일의 Line 693-717에도 적용하십시오.
  • reviewer/tests/test_patch_validation_security_boundaries.py#L307-L345: 동일한 UID/GID 스텁을 추가하십시오. Line 401-418에도 적용하십시오.
  • reviewer/tests/test_patch_validation_git_control_isolation.py#L91-L150: 동일한 UID/GID 스텁을 추가하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation.py` around lines 419 - 456, Fix the
successful validation tests so they do not depend on the process identity by
stubbing patch_validation.os.getuid and getgid to return 1000 before invoking
DockerPatchValidationRunner.validate. Apply this to
reviewer/tests/test_patch_validation.py lines 419-456 and 693-717,
reviewer/tests/test_patch_validation_security_boundaries.py lines 307-345 and
401-418, and reviewer/tests/test_patch_validation_git_control_isolation.py lines
91-150; each listed site requires the same UID/GID stubs.
reviewer/tests/test_patch_validation_hardening.py (1)

44-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

픽스처가 부분 경로 "git"을 사용합니다. patch_validation.TRUSTED_GIT_EXECUTABLE로 통일하십시오. 두 파일의 저장소 픽스처는 "git"을 실행합니다. 같은 cohort의 다른 테스트는 절대 경로 상수를 사용하며, Ruff는 이 두 파일에서만 S607을 보고합니다.

  • reviewer/tests/test_patch_validation_hardening.py#L44-L71: _git_repository의 모든 subprocess.run 호출에서 "git"patch_validation.TRUSTED_GIT_EXECUTABLE로 바꾸십시오.
  • reviewer/tests/test_patch_validation_git_metadata_mask.py#L40-L77: _initialize_repository와 Line 133-136의 worktree add 호출에서 같은 상수를 사용하십시오.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_hardening.py` around lines 44 - 71,
Replace the partial-path "git" executable in every subprocess.run call within
_git_repository in reviewer/tests/test_patch_validation_hardening.py:44-71 with
patch_validation.TRUSTED_GIT_EXECUTABLE. Apply the same replacement in
_initialize_repository and the worktree add invocation at
reviewer/tests/test_patch_validation_git_metadata_mask.py:40-77, preserving all
other command arguments.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@reviewer/noema_reviewer/patch_validation.py`:
- Around line 849-861: Update _git_metadata_kind to catch OSError from os.lstat
after preserving the existing FileNotFoundError handling, then wrap the
remaining filesystem error in RuntimeError so callers consistently receive the
module’s normalized exception type.

In `@reviewer/tests/test_patch_validation_canonical_paths.py`:
- Around line 31-39: Update inspect_patch_bytes to accept unquoted paths
containing spaces in diff --git headers, as well as matching unquoted --- and
+++ headers, instead of treating the resulting extra tokens as malformed. Add a
regression test alongside test_canonical_path_with_spaces_remains_supported
using unquoted src/file name.ts and assert the canonical result is ("src/file
name.ts",).

In `@reviewer/tests/test_patch_validation_prearchive_and_result_channel.py`:
- Around line 99-128: Update fake_run and the test around
_materialize_committed_source to mock the subprocess.Popen streaming path used
by _verify_exact_tree_limits, including the ls-tree output and required process
behavior. Replace the broad RuntimeError regex with a direct assertion for the
oversized-entry/byte-limit validation message, while retaining the assertion
that archive execution never starts.
- Line 58: pytest.raises 호출의 match 인자를 일반 문자열이 아닌 raw 문자열로 변경하여 RUF043 경고를
제거하십시오. test_patch_validation_prearchive_and_result_channel.py의 해당 테스트에서
“result.*must not be empty” 패턴과 동일하게 지적된 세 위치(58, 70, 120행)의 정규식 패턴에 적용하고, 기존 매칭
동작은 유지하십시오.

Apply the same fix in
`@reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py` at line 180:
The same raw-regex-literal remediation applies to this assertion.

In `@reviewer/tests/test_patch_validation_source_integrity.py`:
- Around line 155-208: Use stage-specific exception match patterns in
test_snapshot_materialization_rejects_failed_archive and
test_snapshot_materialization_rejects_invalid_archive so they cannot match the
preflight “source commit snapshot could not be materialized safely” message;
also track archive invocation in each subprocess double and assert it occurred,
while preserving the existing cleanup assertion for the invalid archive case.

---

Nitpick comments:
In `@reviewer/noema_reviewer/patch_validation.py`:
- Around line 1402-1415: Remove the unused _completed parameter from
_read_result_payload and update its callers, including the call that passes
completed, so the function signature and invocation no longer accept or provide
this argument.
- Around line 1455-1458: Remove the unreachable metadata_kind is None check
after _verify_source_head in the source validation flow, since
_verify_source_head already raises for that case. If the check is required
solely for type narrowing, replace it with an explanatory comment; otherwise
leave the existing _verify_source_head call and subsequent logic unchanged.
- Around line 1016-1038: Update _validated_exact_tree_output to reuse
_consume_exact_tree_stream rather than independently splitting records and
invoking _validated_exact_tree_record. Preserve the existing validation limits
and rules while returning or exposing the validated inventory needed by tests,
so the test helper does not maintain a duplicate implementation.
- Around line 1197-1203: Optimize the empty-directory validation loop in the
declared_directories check by building a set of parent directory paths from
declared_paths once, then use constant-time membership checks for each directory
instead of repeatedly scanning declared_paths. Preserve the existing path-prefix
semantics and ValueError behavior for empty gitlink-like directories.

In `@reviewer/tests/test_patch_validation_hardening.py`:
- Around line 44-71: Replace the partial-path "git" executable in every
subprocess.run call within _git_repository in
reviewer/tests/test_patch_validation_hardening.py:44-71 with
patch_validation.TRUSTED_GIT_EXECUTABLE. Apply the same replacement in
_initialize_repository and the worktree add invocation at
reviewer/tests/test_patch_validation_git_metadata_mask.py:40-77, preserving all
other command arguments.

In `@reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py`:
- Around line 51-78: Update the _git_repository helper to run every Git command
through the established patch_validation.TRUSTED_GIT_EXECUTABLE-based _run_git
pattern, preserving shell=False and timeout=30 for each invocation. Replace
direct "git" subprocess calls and reuse the existing shared helper conventions
used by the related patch-validation tests.

In `@reviewer/tests/test_patch_validation_streaming_edges.py`:
- Around line 413-433: Update
test_consume_exact_tree_stream_accepts_partial_records to capture the return
value from _consume_exact_tree_stream and assert the reconstructed inventory’s
path, mode, object ID, and size, ensuring fields split across chunk boundaries
are assembled correctly.
- Around line 436-451: Update
test_consume_exact_tree_stream_rejects_unterminated_record_ceiling so the
_read_git_stream_chunk monkeypatch returns data from a finite iterator rather
than always returning b"12345". Keep the first returned chunk sufficient to
trigger the record byte-limit error, and ensure subsequent reads are exhausted
so a regression cannot cause the test to hang.

In `@reviewer/tests/test_patch_validation_streaming_final_edges.py`:
- Around line 25-29: Replace the global subprocess.run monkeypatch in the
affected tests with a narrowly scoped stub for the call used by
_verify_source_head. Ensure only that validation path receives the fake return
value, while unrelated subprocess.run calls retain their real behavior; apply
the same change to both highlighted test locations.

In `@reviewer/tests/test_patch_validation.py`:
- Around line 419-456: Fix the successful validation tests so they do not depend
on the process identity by stubbing patch_validation.os.getuid and getgid to
return 1000 before invoking DockerPatchValidationRunner.validate. Apply this to
reviewer/tests/test_patch_validation.py lines 419-456 and 693-717,
reviewer/tests/test_patch_validation_security_boundaries.py lines 307-345 and
401-418, and reviewer/tests/test_patch_validation_git_control_isolation.py lines
91-150; each listed site requires the same UID/GID stubs.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b0212ff-2c99-4ab7-a7ce-3d930061f5f5

📥 Commits

Reviewing files that changed from the base of the PR and between e52f802 and 26aa169.

📒 Files selected for processing (27)
  • CHANGELOG.md
  • docs/doctoring/quarantined-patch-validation.md
  • docs/quarantined-patch-validation.md
  • reviewer/noema_reviewer/__init__.py
  • reviewer/noema_reviewer/patch_validation.py
  • reviewer/tests/test_patch_validation.py
  • reviewer/tests/test_patch_validation_archive_boundaries.py
  • reviewer/tests/test_patch_validation_blob_identity_edges.py
  • reviewer/tests/test_patch_validation_canonical_paths.py
  • reviewer/tests/test_patch_validation_coverage_edges.py
  • reviewer/tests/test_patch_validation_exact_tree_and_output.py
  • reviewer/tests/test_patch_validation_exact_tree_archive_binding.py
  • reviewer/tests/test_patch_validation_exact_tree_canonical_metadata.py
  • reviewer/tests/test_patch_validation_git_control_descriptor_edge.py
  • reviewer/tests/test_patch_validation_git_control_isolation.py
  • reviewer/tests/test_patch_validation_git_metadata_mask.py
  • reviewer/tests/test_patch_validation_hardening.py
  • reviewer/tests/test_patch_validation_mode_boundaries.py
  • reviewer/tests/test_patch_validation_object_alternates_boundary.py
  • reviewer/tests/test_patch_validation_path_consistency.py
  • reviewer/tests/test_patch_validation_prearchive_and_result_channel.py
  • reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py
  • reviewer/tests/test_patch_validation_security_boundaries.py
  • reviewer/tests/test_patch_validation_source_integrity.py
  • reviewer/tests/test_patch_validation_streaming_edges.py
  • reviewer/tests/test_patch_validation_streaming_final_edges.py
  • reviewer/tests/test_patch_validation_streaming_git_output.py

Comment on lines +849 to +861
def _git_metadata_kind(source: Path) -> GitMetadataKind | None:
"""Return safe Git-control metadata shape or reject special-file redirection."""
try:
metadata = os.lstat(source / ".git")
except FileNotFoundError:
return None
if stat.S_ISLNK(metadata.st_mode) or not (
stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)
):
raise RuntimeError(
"source Git metadata must not be a symlink and must be a regular file or directory"
)
return "directory" if stat.S_ISDIR(metadata.st_mode) else "file"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

_git_metadata_kindFileNotFoundError 외의 OSError를 그대로 전파합니다.

예를 들어 상위 디렉터리 권한이 없으면 PermissionError가 호출자까지 올라갑니다. 이 모듈의 다른 실패는 모두 RuntimeError로 정규화됩니다. 호출자가 RuntimeError만 처리한다고 가정하면 예외 유형이 어긋납니다. OSErrorRuntimeError로 감싸십시오.

🛡️ 제안 수정
     try:
         metadata = os.lstat(source / ".git")
     except FileNotFoundError:
         return None
+    except OSError as exc:
+        raise RuntimeError("source Git metadata is unavailable") from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _git_metadata_kind(source: Path) -> GitMetadataKind | None:
"""Return safe Git-control metadata shape or reject special-file redirection."""
try:
metadata = os.lstat(source / ".git")
except FileNotFoundError:
return None
if stat.S_ISLNK(metadata.st_mode) or not (
stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)
):
raise RuntimeError(
"source Git metadata must not be a symlink and must be a regular file or directory"
)
return "directory" if stat.S_ISDIR(metadata.st_mode) else "file"
def _git_metadata_kind(source: Path) -> GitMetadataKind | None:
"""Return safe Git-control metadata shape or reject special-file redirection."""
try:
metadata = os.lstat(source / ".git")
except FileNotFoundError:
return None
except OSError as exc:
raise RuntimeError("source Git metadata is unavailable") from exc
if stat.S_ISLNK(metadata.st_mode) or not (
stat.S_ISDIR(metadata.st_mode) or stat.S_ISREG(metadata.st_mode)
):
raise RuntimeError(
"source Git metadata must not be a symlink and must be a regular file or directory"
)
return "directory" if stat.S_ISDIR(metadata.st_mode) else "file"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/noema_reviewer/patch_validation.py` around lines 849 - 861, Update
_git_metadata_kind to catch OSError from os.lstat after preserving the existing
FileNotFoundError handling, then wrap the remaining filesystem error in
RuntimeError so callers consistently receive the module’s normalized exception
type.

Comment on lines +31 to +39
def test_canonical_path_with_spaces_remains_supported() -> None:
"""An exact quoted path with ordinary spaces remains a valid identity."""
patch_bytes = (
b'diff --git "a/src/file name.ts" "b/src/file name.ts"\n'
b'--- "a/src/file name.ts"\n'
b'+++ "b/src/file name.ts"\n'
)

assert inspect_patch_bytes(patch_bytes) == ("src/file name.ts",)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 비인용 공백 경로 처리 경로와 기존 테스트 커버리지를 확인한다.
fd -t f 'patch_validation.py' reviewer --exec rg -n -C 6 'shlex.split|malformed diff header'
rg -n 'file name|unquoted' reviewer/tests

Repository: ContextualWisdomLab/noema

Length of output: 3774


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- AGENTS.md files ---'
find .. -name AGENTS.md -print
for f in $(find .. -name AGENTS.md -print); do
  printf '\n--- %s ---\n' "$f"
  cat "$f"
done
printf '%s\n' '--- patch validator structure ---'
ast-grep outline reviewer/noema_reviewer/patch_validation.py
printf '%s\n' '--- relevant validator code ---'
sed -n '560,620p' reviewer/noema_reviewer/patch_validation.py
sed -n '700,750p' reviewer/noema_reviewer/patch_validation.py
printf '%s\n' '--- relevant tests ---'
sed -n '1,70p' reviewer/tests/test_patch_validation_canonical_paths.py
sed -n '165,215p' reviewer/tests/test_patch_validation.py
sed -n '175,210p' reviewer/tests/test_patch_validation_security_boundaries.py

Repository: ContextualWisdomLab/noema

Length of output: 18950


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
import shlex

cases = [
    "diff --git a/src/file name.ts b/src/file name.ts",
    'diff --git "a/src/file name.ts" "b/src/file name.ts"',
]
for line in cases:
    parts = shlex.split(line)
    print(f"{line!r}")
    print(f"  tokens={len(parts)}: {parts!r}")

source = open("reviewer/noema_reviewer/patch_validation.py", encoding="utf-8").read()
needle = "if len(parts) != 4 or parts[:2] != [\"diff\", \"--git\"]:"
print(f"validator_requires_exactly_four_tokens={needle in source}")
print("existing_unquoted_space_tests:")
for path in (
    "reviewer/tests/test_patch_validation_canonical_paths.py",
    "reviewer/tests/test_patch_validation.py",
    "reviewer/tests/test_patch_validation_security_boundaries.py",
):
    text = open(path, encoding="utf-8").read()
    print(f"  {path}: {sum('diff --git a/src/file name.ts b/src/file name.ts' in line for line in text.splitlines())}")
PY
printf '%s\n' '--- repository references to unquoted Git headers ---'
rg -n -i 'unquoted|diff --git [^"]* [^"]*' reviewer README.md .github 2>/dev/null || true

Repository: ContextualWisdomLab/noema

Length of output: 9372


🌐 Web query:

Git diff --git header unquoted file paths containing spaces official documentation

💡 Result:

In the default output format of git diff, file paths in the diff header (the line starting with diff --git) and extended headers are not inherently "quoted" in the traditional sense, even if they contain spaces [1][2]. Instead, Git handles pathnames with "unusual" characters—including spaces, tabs, and non-printable characters—by applying C-style escaping if the core.quotePath configuration variable is enabled (which it is by default) [1][2][3]. When core.quotePath is true (default), paths containing such characters are enclosed in double quotes, and the special characters within them are escaped (e.g., spaces may be represented literally within the quotes, but characters like backslashes, tabs, or non-ASCII characters will be escaped) [1][3][4]. If you require raw, machine-readable output that avoids this munging of pathnames, you should use the -z option (e.g., git diff -z) with commands like --raw, --name-only, or --name-status [5][6]. The -z option forces Git to output the pathname verbatim, using NUL bytes as terminators instead of newlines, which safely handles any characters within the filename [3][6][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- Git version and read-only path quoting probes ---'
git --version
set +e
git diff --no-index \
  --label='a/src/file name.ts' \
  --label='b/src/file name.ts' \
  /etc/hosts /etc/passwd > /tmp/noema-git-label-diff.txt
status=$?
set -e
printf 'exit_status=%s\n' "$status"
sed -n '1,8p' /tmp/noema-git-label-diff.txt

set +e
git diff --no-index \
  --src-prefix='a/' \
  --dst-prefix='b/' \
  '/etc/hosts' '/etc/passwd' > /tmp/noema-git-prefix-diff.txt
status=$?
set -e
printf 'exit_status=%s\n' "$status"
sed -n '1,8p' /tmp/noema-git-prefix-diff.txt

printf '%s\n' '--- Official Git diff-format wording ---'
curl -L --fail --silent --show-error https://git-scm.com/docs/diff-format \
  | rg -n -i -C 3 'diff --git|quote|unusual|space' | head -80

Repository: ContextualWisdomLab/noema

Length of output: 10576


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- Read-only Git quoting probe with spaces in generated prefixes ---'
set +e
git -c core.quotePath=true diff --no-index \
  --src-prefix='a/src/file name.ts/' \
  --dst-prefix='b/src/file name.ts/' \
  /etc/hosts /etc/passwd > /tmp/noema-git-space-prefix-diff.txt
status=$?
set -e
printf 'exit_status=%s\n' "$status"
sed -n '1,5p' /tmp/noema-git-space-prefix-diff.txt

printf '%s\n' '--- Git quotePath documentation ---'
curl -L --fail --silent --show-error https://git-scm.com/docs/git-config \
  | rg -n -i -C 5 'core\.quotePath|quote.*path' | head -60

printf '%s\n' '--- Git quote implementation references ---'
curl -L --fail --silent --show-error \
  https://raw.githubusercontent.com/git/git/v2.50.1/quote.c \
  | rg -n -C 8 'quote_c_style|needquote|isspace|0x20|32'

Repository: ContextualWisdomLab/noema

Length of output: 6109


비인용 공백 경로를 지원하고 회귀 테스트를 추가하십시오.

Git은 diff --git a/src/file name.ts b/src/file name.ts처럼 공백을 포함한 경로를 인용하지 않을 수 있습니다. 현재 shlex.split(line)은 6개 토큰을 반환하므로 inspect_patch_bytes"patch contains a malformed diff header"로 거부합니다. 동일한 비인용 경로를 ---+++ 헤더에도 사용하고, 반환값이 ("src/file name.ts",)인지 테스트하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_canonical_paths.py` around lines 31 -
39, Update inspect_patch_bytes to accept unquoted paths containing spaces in
diff --git headers, as well as matching unquoted --- and +++ headers, instead of
treating the resulting extra tokens as malformed. Add a regression test
alongside test_canonical_path_with_spaces_remains_supported using unquoted
src/file name.ts and assert the canonical result is ("src/file name.ts",).

result_path.touch(mode=0o600)
completed = SimpleNamespace(returncode=0, stdout='{"status":"passed"}')

with pytest.raises(RuntimeError, match="result.*must not be empty"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use raw-string regex literals for pytest.raises(..., match=...) patterns containing regular-expression metacharacters. This applies to the patterns at lines 58, 70, 120 in this file and the hunk|trailing|syntax pattern in reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py:180 so Ruff RUF043 is not triggered.

📍 Affects 2 files
  • reviewer/tests/test_patch_validation_prearchive_and_result_channel.py#L58-L58 (this comment)
  • reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py#L180-L180
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_prearchive_and_result_channel.py` at
line 58, pytest.raises 호출의 match 인자를 일반 문자열이 아닌 raw 문자열로 변경하여 RUF043 경고를 제거하십시오.
test_patch_validation_prearchive_and_result_channel.py의 해당 테스트에서 “result.*must
not be empty” 패턴과 동일하게 지적된 세 위치(58, 70, 120행)의 정규식 패턴에 적용하고, 기존 매칭 동작은 유지하십시오.

Apply the same fix in
`@reviewer/tests/test_patch_validation_provenance_and_hunk_edges.py` at line 180:
The same raw-regex-literal remediation applies to this assertion.

Source: Linters/SAST tools

Comment on lines +99 to +128
def fake_run(command, **_kwargs):
"""Expose an oversized exact-tree entry and forbid archive execution."""
nonlocal archive_started
command_list = list(command)
if "ls-tree" in command_list:
return SimpleNamespace(
returncode=0,
stdout=(
"100644 blob "
f"{'a' * 40} "
f"{patch_validation.MAX_SOURCE_ARCHIVE_MEMBER_BYTES + 1}"
"\toversized.bin\0"
),
)
if "archive" in command_list:
archive_started = True
return SimpleNamespace(returncode=0)
raise AssertionError(f"unexpected Git command: {command_list}")

monkeypatch.setattr(patch_validation.subprocess, "run", fake_run)

with pytest.raises(RuntimeError, match="tree|byte limit|materialized"):
patch_validation._materialize_committed_source(
source,
"1" * 40,
staging,
"directory",
)

assert archive_started is False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check which subprocess API the exact-tree preflight uses.
set -euo pipefail

fd -t f 'patch_validation.py' reviewer --exec ast-grep run --lang python --pattern 'def _verify_exact_tree_limits($$$):
    $$$'

rg -nP -C3 'subprocess\.(run|Popen)' reviewer/noema_reviewer/patch_validation.py

Repository: ContextualWisdomLab/noema

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- AGENTS.md files ---'
find .. -name AGENTS.md -print

printf '%s\n' '--- candidate files ---'
fd -t f | rg '(^|/)(patch_validation\.py|test_patch_validation_prearchive_and_result_channel\.py)$|^docs/quarantined-patch-validation\.md$'

printf '%s\n' '--- subprocess usage ---'
rg -n -C5 'subprocess\.(run|Popen)|_verify_exact_tree_limits|_materialize_committed_source' reviewer docs 2>/dev/null || true

Repository: ContextualWisdomLab/noema

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- AGENTS.md ---'
cat -n AGENTS.md

printf '%s\n' '--- exact-tree implementation ---'
sed -n '430,490p;1030,1115p;1307,1355p' reviewer/noema_reviewer/patch_validation.py

printf '%s\n' '--- reviewed test ---'
sed -n '1,155p' reviewer/tests/test_patch_validation_prearchive_and_result_channel.py

printf '%s\n' '--- related streaming tests ---'
sed -n '1,120p' reviewer/tests/test_patch_validation_streaming_final_edges.py
sed -n '1,115p' reviewer/tests/test_patch_validation_streaming_git_output.py

printf '%s\n' '--- documentation ---'
sed -n '35,60p' docs/quarantined-patch-validation.md

Repository: ContextualWisdomLab/noema

Length of output: 25986


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

implementation = Path("reviewer/noema_reviewer/patch_validation.py")
test_file = Path("reviewer/tests/test_patch_validation_prearchive_and_result_channel.py")

impl_tree = ast.parse(implementation.read_text())
test_tree = ast.parse(test_file.read_text())

def function(tree, name):
    return next(node for node in ast.walk(tree)
                if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
                and node.name == name)

verify = function(impl_tree, "_verify_exact_tree_limits")
materialize = function(impl_tree, "_materialize_committed_source")
test = function(test_tree, "test_exact_tree_limits_are_checked_before_git_archive")

verify_calls = [
    ast.unparse(node)
    for node in ast.walk(verify)
    if isinstance(node, ast.Call)
]
materialize_calls = [
    ast.unparse(node)
    for node in ast.walk(materialize)
    if isinstance(node, ast.Call)
]
patched_targets = [
    ast.unparse(node)
    for node in ast.walk(test)
    if isinstance(node, ast.Call)
    and isinstance(node.func, ast.Attribute)
    and node.func.attr == "setattr"
]

print("verify subprocess APIs:", [
    call for call in verify_calls
    if "subprocess." in call or "_start_git_stream" in call
])
print("materialize relevant calls:", [
    call for call in materialize_calls
    if "_verify_exact_tree_limits" in call or "subprocess.run" in call
])
print("test monkeypatch targets:", patched_targets)

assert any("_start_git_stream" in call for call in verify_calls)
assert not any("subprocess.run" in call for call in verify_calls)
assert any("patch_validation.subprocess, 'run'" in target
           for target in patched_targets)
PY

Repository: ContextualWisdomLab/noema

Length of output: 1024


_verify_exact_tree_limits의 스트리밍 실행을 테스트에서 대체하십시오. ls-treesubprocess.Popen을 통해 실행되므로 현재 subprocess.run의 분기는 호출되지 않습니다. 빈 control 디렉터리에서 발생한 일반적인 materialization 오류도 현재 정규식과 일치하여 테스트가 잘못 통과할 수 있습니다. 스트리밍 API를 대체하고, 초과 한계 메시지를 직접 검사하십시오.

🧰 Tools
🪛 Ruff (0.16.1)

[warning] 120-120: Pattern passed to match= contains metacharacters but is neither escaped nor raw

(RUF043)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_prearchive_and_result_channel.py` around
lines 99 - 128, Update fake_run and the test around
_materialize_committed_source to mock the subprocess.Popen streaming path used
by _verify_exact_tree_limits, including the ls-tree output and required process
behavior. Replace the broad RuntimeError regex with a direct assertion for the
oversized-entry/byte-limit validation message, while retaining the assertion
that archive execution never starts.

Comment on lines +155 to +208
def failed_archive(command, **_kwargs):
"""Pass exact-tree preflight but fail the subsequent archive command."""
command_list = list(command)
if "ls-tree" in command_list:
return _bounded_tree_result()
if "archive" in command_list:
return SimpleNamespace(returncode=1)
raise AssertionError(f"unexpected Git command: {command_list}")

monkeypatch.setattr(patch_validation.subprocess, "run", failed_archive)

with pytest.raises(RuntimeError, match="snapshot could not be materialized"):
patch_validation._materialize_committed_source(
tmp_path,
"2" * 40,
staging,
"directory",
)


def test_snapshot_materialization_rejects_invalid_archive(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Malformed archive bytes fail closed and the transient archive is removed."""
staging = tmp_path / "staging"
staging.mkdir()
_isolated_control(staging, monkeypatch)

def corrupt_archive(command, **_kwargs):
"""Pass preflight, then write invalid bytes at Git's archive output path."""
command_list = list(command)
if "ls-tree" in command_list:
return _bounded_tree_result()
if "archive" not in command_list:
raise AssertionError(f"unexpected Git command: {command_list}")
output = next(
argument.removeprefix("--output=")
for argument in command_list
if argument.startswith("--output=")
)
Path(output).write_bytes(b"not a tar archive")
return SimpleNamespace(returncode=0)

monkeypatch.setattr(patch_validation.subprocess, "run", corrupt_archive)

with pytest.raises(RuntimeError, match="materialized safely"):
patch_validation._materialize_committed_source(
tmp_path,
"2" * 40,
staging,
"directory",
)
assert not (staging / "source.tar").exists()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

match 패턴이 넓어 archive 단계 검증이 보증되지 않습니다.

Line 166의 패턴 "snapshot could not be materialized"는 preflight 실패 메시지 "source commit snapshot could not be materialized safely"의 부분 문자열입니다. Line 201의 "materialized safely"도 같은 preflight 실패와 일치합니다. ls-tree 더블이 호출되지 않으면 두 테스트는 archive 반환 코드 경로나 tar 파싱 경로를 검증하지 않고도 통과합니다.

패턴을 각 단계의 고유 메시지에 고정하고, 각 더블이 실제로 호출되었는지 assert하십시오. 예로 failed_archive에 호출 플래그를 두고 assert archive_invoked is True를 추가하십시오.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reviewer/tests/test_patch_validation_source_integrity.py` around lines 155 -
208, Use stage-specific exception match patterns in
test_snapshot_materialization_rejects_failed_archive and
test_snapshot_materialization_rejects_invalid_archive so they cannot match the
preflight “source commit snapshot could not be materialized safely” message;
also track archive invocation in each subprocess double and assert it occurred,
while preserving the existing cleanup assertion for the invalid archive case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant