Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
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가 이미 있던 상태입니다. 이 수치는 그 독립 리뷰의 결과이지 이번 실행에서 재현한 수치는 아닙니다.
남은 위험
- helper가
-wal존재 여부를 확인한 뒤 immutable 연결을 여는 사이 writer가 WAL을 만들면 해당 연결은 새 WAL을 보지 못할 수 있습니다. 기존 구현에서 온 경쟁 조건입니다. - stale WAL/SHM을 동반한 파일은
mode=ro분기로 처리됩니다. 파일명 생성·삭제는 막지만 SQLite가 기존 공유 메모리 영역을 업데이트할 가능성은 남습니다. - reconcile의 agy drift C가 다른 workspace DB를 후보로 보는 정책은 그대로입니다. 수정은 해당 검사가 원본 sidecar를 만들지 않게 하며 후보 귀속 규칙은 변경하지 않습니다.
- 브리프가 제외한 purge 쓰기는 WAL/SHM을 만들거나 변경할 수 있습니다.
[VERDICT: PASS]