Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-d22e5336.md
T

7.8 KiB

Issue #64 코드 리뷰 — 읽기 전용 에이전트 DB 조회

판정 요약

head b0eccc530149bc6e9211727e19921665c7b80492의 변경은 조회 전용 SQLite 경로를 공용 _ro_connect로 일관되게 바꾸며, 의도된 DELETE 경로와 MAM 자체 DB 쓰기는 보존합니다. 다른 워크스페이스 agy DB에 WAL/SHM이 생긴 원인 경로도 코드와 격리 재현 테스트로 확인했습니다.

검증은 브리프의 검증 트리 /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261019T134017Z-d90c23/1에서만 수행했습니다. identity는 base 3ce5d5fdc108b0435e933a251fe1c1256b5d7973, head b0eccc530149bc6e9211727e19921665c7b80492, tree f5a412a3ee96465db7c4b3223d2d76e95716ddfa이며, git status --short는 비어 있습니다. 소스 워크트리는 실행하거나 수정하지 않았습니다.

원인

원인은 reconcile.sh의 agy drift C 경로입니다. reconcile.sh:786에서 대화 DB 디렉터리를 설정하고, reconcile.sh:800에서 해당 홈의 모든 *.db를 훑은 뒤 reconcile.sh:816에서 각 UUID에 verify_session_uuid(..., mode="discover")를 호출합니다. 이는 adapter의 verify_artifact로 이어집니다. agy의 agy.py:74-76는 이전에 DB를 쓰기 가능 모드로 열어 steps를 조회했습니다. 캐시에 다른 workspace가 기록된 UUID도 sibling claim에 막히지 않으면 조회 대상이 될 수 있었고, macOS SQLite가 새 -wal 및 -shm을 남겨 #55 홈 가드에 걸릴 수 있었습니다. 연결이 이미 닫혀 프로세스가 없던 관찰과도 맞습니다.

신규 테스트 test_unpinned_agy_reconcile_leaves_other_workspace_clean_db_without_sidecars는 workspace A의 미고정 agy 행과 workspace B에 매핑된 DB를 임시 HOME에 만들고 verify_session_uuid가 그 DB를 조회하는 상황을 재현합니다. head에서는 조회 결과를 유지하면서 디렉터리 항목이 바뀌지 않습니다. 이번 변경은 다른 workspace 후보를 선택할 수 있는 기존 정책 자체는 바꾸지 않습니다.

변경 범위와 코드 리뷰

  • base.py:111-134에 기존 opencode _ro_connect를 공용화했습니다. WAL/SHM 쌍은 mode=ro, WAL이 없으면 mode=ro&immutable=1, WAL만 있으면 DB와 WAL의 임시 사본을 메모리에 backup합니다. base.py:137-145의 db_rows_naming 기본 연결도 helper를 사용하며 사용자 지정 connect 인자는 유지됩니다.
  • agy.py:74: steps 검증 조회. discover도 이 검증을 거칩니다.
  • codex.py:80,195: rollout 조회와 state DB 검색. residual 조회는 db_rows_naming의 기본 helper를 사용합니다.
  • hermes.py:51,100: artifact 검증과 discover 조회.
  • status.sh:90-132,290-332: JSON/text 양쪽 hermes·codex 및 opencode 분기를 전환했습니다. agy status는 기존처럼 DB를 열지 않고 파일 존재만 확인합니다.
  • reconcile.sh:423,865,954,1048: 공용 helper를 import하고 hermes, opencode, codex drift 조회에 사용합니다. agy 경로의 verify_artifact 전환도 포함됩니다.
  • 신규 tests/test_issue64_ro_connect.py: helper 공유, agy/codex/hermes 조회, 4가지 WAL 상태, status/reconcile 정적·런타임 동작, 다른 workspace agy 재현을 확인합니다.

제외한 쓰기 연결은 그대로입니다: codex.py:152-155의 purge DELETE, hermes.py:74-78의 purge DELETE, opencode.py:193-199의 legacy-row DELETE. lib.sh, lib_py/state.py, atomic_yaml.py, verify_session.py, loop runner의 연결은 MAM 자체 상태/트랜잭션 용도입니다. Claude/grok adapter에는 대화 SQLite 조회가 없습니다. helper 내부의 read-only URI와 메모리 DB, 임시 사본 연결도 의도된 구현입니다.

변경된 조회 호출은 기존 쿼리·정렬·필터 조건을 보존하면서 helper를 경유합니다. git diff --check base..head와 bash -n으로 두 셸 스크립트 문법도 확인했습니다. 커밋은 8개 파일, 461 insertions / 56 deletions이고 검증 트리는 clean입니다. 버전 파일과 문서는 바뀌지 않았습니다.

테스트 결과

  • Python 3.9.6 / pytest 8.4.2, PYTHONDONTWRITEBYTECODE=1, -p no:cacheprovider; basetemp는 검증 트리 밖을 사용했습니다. 임시 경로와 fixture로만 DB를 만들었으며 실제 agent home DB는 열지 않았습니다.
  • tests/test_issue64_ro_connect.py: 43 passed (11.59초).
  • 관련 회귀 묶음 test_issue60_linux_fixes.py, test_issue60_opencode_v2.py, test_a4_adapter_contract.py, test_issue31_purge_residual.py, test_issue50_dispatch.py: 216 passed (148.47초).
  • 이번 리뷰에서 base 별도 checkout을 만들지는 않았습니다. 동일 identity/head/base를 사용한 독립 리뷰 .mam/jobs/366a4d58/grok-reports/report-final.md에 기록된 base 실행은 새 테스트 23 failed, 20 passed였습니다. 실패에는 helper 이동, clean/WAL-only 조회 뒤 sidecar 생성, status/reconcile helper 부재, 다른 workspace agy 재현이 포함되며, 남은 20개는 sidecar가 이미 있던 상태입니다. 이 수치는 그 독립 리뷰의 결과이지 이번 실행에서 재현한 수치는 아닙니다.

남은 위험

  1. helper가 -wal 존재 여부를 확인한 뒤 immutable 연결을 여는 사이 writer가 WAL을 만들면 해당 연결은 새 WAL을 보지 못할 수 있습니다. 기존 구현에서 온 경쟁 조건입니다.
  2. stale WAL/SHM을 동반한 파일은 mode=ro 분기로 처리됩니다. 파일명 생성·삭제는 막지만 SQLite가 기존 공유 메모리 영역을 업데이트할 가능성은 남습니다.
  3. reconcile의 agy drift C가 다른 workspace DB를 후보로 보는 정책은 그대로입니다. 수정은 해당 검사가 원본 sidecar를 만들지 않게 하며 후보 귀속 규칙은 변경하지 않습니다.
  4. 브리프가 제외한 purge 쓰기는 WAL/SHM을 만들거나 변경할 수 있습니다.

[VERDICT: PASS]