배경
PR #870 ([Audit][MODEL-01]) 는 Post/Article 코어에서 인바운드 ActivityPub 파싱과 마크다운 렌더링을 각각 Posts::FederationIngest 개념(concern)과 Articles::Markdown 함수 모듈로 동작 보존(byte-equivalent) 리팩터링으로 추출했다.
멀티 에이전트 리뷰 결과, 아래 항목들은 이번 PR이 만든 회귀가 아니라 추출로 인해 격리되어 이제 고치기 쉬워진 pre-existing 결함이다. byte-equivalence 보증을 깨지 않기 위해 PR에서 분리하여 후속 이슈로 정리한다.
관련: PR #870 리뷰 요약, 신규 직접 테스트(test/functions/articles/markdown_test.rb, test/models/concerns/posts/federation_ingest_test.rb)가 현재 동작을 핀 고정해 둠.
🟡 정리 대상
1. article_id 값 타입 불일치 (String vs Integer)
app/models/concerns/posts/federation_ingest.rb:58-63
/articles/(\d+) 분기는 정규식 캡처 결과인 String 을, 페더레이션 부모 분기는 .pick / parent.article_id 로 Integer/nil 을 반환한다. 쿼리에서 Rails가 강제 변환해 지금은 동작하지만 Hash[Symbol, untyped] 시그니처가 이를 숨긴다.
→ 추출 시점에 Integer로 정규화(article_id.to_i)하고 시그니처를 좁힌다.
2. /posts/(\d+) 로컬 분기의 dangling parent_id
federation_ingest.rb:60-61
페더레이션 부모 분기(find_by 로 가드)와 달리, 로컬 numeric 분기는 parent_id: post_id 를 무조건 설정한 뒤 article_id 를 별도 조회한다. 매칭과 조회 사이에 로컬 post가 삭제되면 parent_id 가 없는 행을 가리켜 저장 시 FK 위반이 발생한다.
→ Post.find_by(id: post_id) 로 가드.
3. Orphan / 예외 경로가 조용히 삼켜짐 (로깅 부재/부족)
silent-failure 지적:
federation_ingest.rb:66 — 해소 실패한 inReplyTo 를 logger.debug 로만 남긴다. 프로덕션에서 debug는 꺼져 있어 orphan이 top-level post로 조용히 재분류된다. → warn/info 로 승격.
federation_ingest.rb:88 — URI::InvalidURIError rescue가 로깅 없이 삼킨다.
federation_ingest.rb:20 — hash[\"id\"] 가 nil인 경우 로깅 없이 통과.
🟢 폴리시/문서 개선
4. display_summary_key String-vs-Array 잠재 버그
app/functions/articles/markdown.rb:31
.map 이 Array를 가정한다. display_summary_key 가 비어있지 않은 String을 반환하면 .map 이 문자 단위로 순회한다(형제 summary_key_preview 는 양쪽을 처리함).
→ LocalizedDisplay#display_summary_key 를 Array? 로 좁히거나 Array(key).flat_map 사용.
5. local_reply_target? 가 bool 술어인데 로깅 부작용을 가짐
federation_ingest.rb:86 — :local | :remote | :misconfigured 같은 타입 있는 enum 반환으로 부작용을 타입에 드러내는 것을 검토.
6. body 우선순위 체인 문서화
federation_ingest.rb:92-104 — contentMap → content → summary → 첨부 이름 결합 → I18n sentinel 순서가 파일에서 가장 비직관적인 로직인데 주석이 없다.
❌ 리뷰 지적 중 제외 (검증 결과 결함 아님)
리뷰의 "discard된 부모가 답글을 조용히 orphan화한다" 는 전제는 사실이 아님을 런타임으로 확인했다. Post 에는 kept default_scope가 없어(Post.default_scopes == []) Post.find_by/exists? 가 soft-deleted 행을 그대로 반환한다. 따라서 discard된 부모에 대한 답글은 정상적으로 parent_id/article_id 로 해소된다. 현재 동작은 federation_ingest_test.rb 에 핀 고정됨. (별도 조치 불필요)
🤖 Generated with Claude Code
배경
PR #870 (
[Audit][MODEL-01]) 는Post/Article코어에서 인바운드 ActivityPub 파싱과 마크다운 렌더링을 각각Posts::FederationIngest개념(concern)과Articles::Markdown함수 모듈로 동작 보존(byte-equivalent) 리팩터링으로 추출했다.멀티 에이전트 리뷰 결과, 아래 항목들은 이번 PR이 만든 회귀가 아니라 추출로 인해 격리되어 이제 고치기 쉬워진 pre-existing 결함이다. byte-equivalence 보증을 깨지 않기 위해 PR에서 분리하여 후속 이슈로 정리한다.
관련: PR #870 리뷰 요약, 신규 직접 테스트(
test/functions/articles/markdown_test.rb,test/models/concerns/posts/federation_ingest_test.rb)가 현재 동작을 핀 고정해 둠.🟡 정리 대상
1.
article_id값 타입 불일치 (String vs Integer)app/models/concerns/posts/federation_ingest.rb:58-63/articles/(\d+)분기는 정규식 캡처 결과인 String 을, 페더레이션 부모 분기는.pick/parent.article_id로 Integer/nil 을 반환한다. 쿼리에서 Rails가 강제 변환해 지금은 동작하지만Hash[Symbol, untyped]시그니처가 이를 숨긴다.→ 추출 시점에 Integer로 정규화(
article_id.to_i)하고 시그니처를 좁힌다.2.
/posts/(\d+)로컬 분기의 danglingparent_idfederation_ingest.rb:60-61페더레이션 부모 분기(
find_by로 가드)와 달리, 로컬 numeric 분기는parent_id: post_id를 무조건 설정한 뒤article_id를 별도 조회한다. 매칭과 조회 사이에 로컬 post가 삭제되면parent_id가 없는 행을 가리켜 저장 시 FK 위반이 발생한다.→
Post.find_by(id: post_id)로 가드.3. Orphan / 예외 경로가 조용히 삼켜짐 (로깅 부재/부족)
silent-failure지적:federation_ingest.rb:66— 해소 실패한inReplyTo를logger.debug로만 남긴다. 프로덕션에서 debug는 꺼져 있어 orphan이 top-level post로 조용히 재분류된다. →warn/info로 승격.federation_ingest.rb:88—URI::InvalidURIErrorrescue가 로깅 없이 삼킨다.federation_ingest.rb:20—hash[\"id\"]가 nil인 경우 로깅 없이 통과.🟢 폴리시/문서 개선
4.
display_summary_keyString-vs-Array 잠재 버그app/functions/articles/markdown.rb:31.map이 Array를 가정한다.display_summary_key가 비어있지 않은 String을 반환하면.map이 문자 단위로 순회한다(형제summary_key_preview는 양쪽을 처리함).→
LocalizedDisplay#display_summary_key를Array?로 좁히거나Array(key).flat_map사용.5.
local_reply_target?가 bool 술어인데 로깅 부작용을 가짐federation_ingest.rb:86—:local | :remote | :misconfigured같은 타입 있는 enum 반환으로 부작용을 타입에 드러내는 것을 검토.6. body 우선순위 체인 문서화
federation_ingest.rb:92-104—contentMap → content → summary → 첨부 이름 결합 → I18n sentinel순서가 파일에서 가장 비직관적인 로직인데 주석이 없다.❌ 리뷰 지적 중 제외 (검증 결과 결함 아님)
리뷰의 "discard된 부모가 답글을 조용히 orphan화한다" 는 전제는 사실이 아님을 런타임으로 확인했다.
Post에는keptdefault_scope가 없어(Post.default_scopes == [])Post.find_by/exists?가 soft-deleted 행을 그대로 반환한다. 따라서 discard된 부모에 대한 답글은 정상적으로parent_id/article_id로 해소된다. 현재 동작은federation_ingest_test.rb에 핀 고정됨. (별도 조치 불필요)🤖 Generated with Claude Code