결정(2026-09-29): 카톡과 메일 둘 다, 승인은 링크가 아니라 연결된 계정 신원으로.
이 커밋은 발송과 메시지 그리기까지고 [승인] 버튼은 다음 단계(2-2)다.
- blog_jobs._send_one: 메일에 더해 Event API 로 보낸다. 하나라도 나가면 SENT,
아무 데도 안 나가면 SENT 로 표시하지 않아 다음 스윕이 다시 시도한다. 카톡은 채널
친구가 아니거나 차단했으면 실패하므로 메일을 빼지 않는다. send_now 도 같은 경로
- 카톡 params 로 post_id 와 수정용 일회용 코드(edit_token)를 넘긴다. 코드 평문은
발송 시점에만 알아서다. 로그에는 params 의 키만 남기고 값은 남기지 않는다
- channel.approval_notice: 발화자 키 → 사장님 → 그 글이 그 사장님 가게 것·미처리·
기한 전일 때만 본문과 [수정하기] 를 준다. 연결 안 됨·남의 글·처리됨·만료·이상한
ID 는 구분 없이 같은 안내(구분해 주면 글 ID 를 탐색할 수 있다)
- kakao_bot: userRequest.params.post_id 가 있으면 승인 알림 요청으로 처리하고 링크
버튼은 본문과 따로 textCard 로 그린다(카드 설명 길이 제한을 피한다)
- KAKAO_APPROVAL_PUSH_ENABLED(기본 0), KAKAO_APPROVAL_EVENT_NAME 추가 —
오픈빌더 이벤트 블록(스킬 연결)과 배포가 끝나기 전에는 켜지 않는다
test_blog_owner 6건·test_kakao_webhook 6건 추가, 카카오·미니블로그 스위트 119 passed,
인접 스위트 110 passed. 실제 카톡 수신은 콘솔 설정·운영 배포 뒤에 확인한다.
카카오 채널이 주는 발화자 식별자는 **채널 단위 익명 키**라 우리 user_id 와 관계가 없다.
다른 엔드포인트는 전부 place_crud.get_place(s, owner_user_id, place_id) 로 소유자 범위를
지키는데 채널 발화에는 그 owner_user_id 를 줄 근거가 없다 — 매핑이 없으면 채널
진입점만 소유자 범위 밖에 놓이고, 채널에 말을 건 아무나가 남의 가게를 고친다.
- postgres-init: owner_kakao_links(0021 + init.sql). 부분 유니크 셋 중
uq_kakao_link_channel_key(한 카카오 계정 = 한 사장님)가 없으면 "어느 가게
이야기냐" 가 대화가 아니라 DB 에서 갈라진다
- services/kakao_link_service: 일회성은 코드 값이 아니라 WHERE status='PENDING'
CAS 한 문장이 보장한다. 실패는 전부 같은 에러 — 없는 코드·만료·시도초과를
구분해 답하면 6자리의 유효성을 밖에서 탐색할 수 있다
- 코드는 sha256 만 저장. 손으로 치는 짧은 값이라 평문이면 DB 를 읽는 쪽이 곧
연결 권한이다. 글자에서 0·O·1·I·L 제외 — 잘못 읽으면 원인이 화면에 안 보인다
- router/v1/agent/kakao: 셋 다 no-store·no-referrer·noindex.
★ 소비(redeem) 엔드포인트는 일부러 없다 — 웹훅 서명 검증 전에 공개 소비 경로를
열면 누구나 6자리를 대입해 남의 계정에 자기 카톡을 붙인다
- config/agent_config: social_config 와 일부러 가름. SNS 게재는 되돌릴 수 없는
대외 발화, 에이전트는 자기 사이트를 고치는 창구 — 승인 강도가 다르다
- frontend/features/agent: /sites 의 Threads 카드 옆. 연결은 사람 단위라 같은 자리다
- docs/AGENT.md 신설, CLAUDE.md 색인·함정, DEVLOG
test_kakao_link.py 15 passed. 전체 780 passed / 50 failed —
그 50건은 HEAD 에서도 동일(워크트리 대조), 기존 이슈로 이번 변경과 무관.
npm run lint 통과
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>