diff --git a/specs/004-roompost-domain/checklists/requirements.md b/specs/004-roompost-domain/checklists/requirements.md new file mode 100644 index 00000000..a32a5d89 --- /dev/null +++ b/specs/004-roompost-domain/checklists/requirements.md @@ -0,0 +1,35 @@ +# Specification Quality Checklist: 방 게시글(RoomPost) — 기록·투표 + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-17 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) — Spring AI / JPA / 트랜잭션 등 구현 용어 본문 미포함, 비즈니스 어휘로 기술 +- [x] Focused on user value and business needs — 우선순위(P1~P3)로 사용자 가치 정렬 +- [x] Written for non-technical stakeholders — 한국어 비기술자 친화 서술 +- [x] All mandatory sections completed — User Scenarios / Requirements / Success Criteria 모두 작성 + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain — 1개 마커 해소 (Q1: 전역 평생 누적 5회 / 리셋 없음 / 기록 작성 횟수는 안내용) +- [x] Requirements are testable and unambiguous — 모든 FR이 관찰 가능한 결과로 기술 +- [x] Success criteria are measurable — SC 7건 모두 측정 가능 +- [x] Success criteria are technology-agnostic — 응답시간 절대치/RPS 등 기술 임계치 배제 +- [x] All acceptance scenarios are defined — 5개 User Story 모두 Given-When-Then 보유 +- [x] Edge cases are identified — Edge Cases 섹션 9건 +- [x] Scope is clearly bounded — 오늘의 한마디, 방, 책, 피드, 댓글·좋아요 행위는 명시적 범위 외 +- [x] Dependencies and assumptions identified — Assumptions 섹션 10건 + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria — FR-001~FR-024가 User Story 시나리오와 매핑 +- [x] User scenarios cover primary flows — 기록 작성/투표 작성·참여/목록 조회/핀/AI 모두 커버 +- [x] Feature meets measurable outcomes defined in Success Criteria — SC가 P1~P3 시나리오와 정렬 +- [x] No implementation details leak into specification — 구현 디테일은 Assumptions에서 외부화 + +## Notes + +- 2026-05-17 1차 검증: 마커 해소, 모든 항목 통과. `/speckit-clarify`(선택) 또는 `/speckit-plan`으로 진행 가능. +- 의도적 보존: 총평 조건이 기록(전체 페이지 일치)과 투표(진행률 80%+)에서 다르다는 점은 코드 상의 실제 정책 차이를 그대로 PRD에 명문화한 것임. 의도 외 차이라면 코드 또는 PRD 한쪽 정정 필요. diff --git a/specs/004-roompost-domain/spec.md b/specs/004-roompost-domain/spec.md new file mode 100644 index 00000000..5e026db8 --- /dev/null +++ b/specs/004-roompost-domain/spec.md @@ -0,0 +1,215 @@ +# Feature Specification: 방 게시글(RoomPost) — 기록(Record) · 투표(Vote) + +**Feature Branch**: `004-roompost-domain` + +**Created**: 2026-05-17 + +**Status**: Reviewed (clarifications resolved 2026-05-17) + +**Input**: User description: "RoomPost 기록 투표 기능" + +> 본 문서는 신규 기능 정의가 아닌, 이미 운영 중인 THIP 서비스의 "방 게시글(RoomPost)" 도메인을 사용자 관점에서 역설계해 정형화한 PRD다. 본 PRD가 다루는 RoomPost는 두 종류로 한정한다: **기록(Record)**과 **투표(Vote)**. 같은 컨트롤러에 함께 라우팅되는 "오늘의 한마디(AttendanceCheck)"는 사용자 정의에 따라 별도 도메인으로 분리하여 본 PRD의 범위에서 제외한다. + +## User Scenarios & Testing *(mandatory)* + +THIP의 **방**은 같은 책을 함께 읽는 사용자 모임이며, 방 안에서 참여자들은 두 종류의 게시글을 만든다. 기록은 *자기 진행도에 대한 일지·감상*이고, 투표는 *함께 묻고 답하는 짧은 합의 도구*다. 본 PRD의 사용자 스토리는 우선순위(P1~P3)로 정렬되어 있으며, 각각 독립적으로 검증·배포 가능한 슬라이스다. + +### User Story 1 - 기록(Record) 작성·수정·삭제 (Priority: P1) + +방 참여자는 자신이 읽은 페이지에 대한 기록을 남기고, 작성자 본인 한정으로 본문을 수정하거나 기록을 삭제한다. *총평*으로 표시된 기록은 책의 마지막 페이지에서만 작성될 수 있다. + +**Why this priority**: 기록이 없으면 방은 의미를 잃는다. THIP의 핵심 가치 제안. + +**Independent Test**: 방 참여자 A가 임의 페이지에 기록을 작성·수정·삭제할 수 있다. 방 비참여자는 작성이 거부된다. 총평은 마지막 페이지에서만 작성 가능하고, 총평이 아닌 기록은 1~마지막 페이지 범위 내에서만 작성 가능하다. + +**Acceptance Scenarios**: + +1. **Given** A가 어떤 방의 참여자일 때, **When** A가 본문과 페이지를 지정해 기록 작성을 요청하면, **Then** 새 기록이 저장되고 생성된 기록 식별자가 반환된다. +2. **Given** A가 본인이 작성한 기록을 수정 요청한다, **When** 본문 내용만을 변경한다, **Then** 기록 본문이 갱신된다. 페이지·총평 여부·방·작성자는 수정 대상이 아니다. +3. **Given** B가 A가 작성한 기록을 수정 또는 삭제하려고 시도하면, **Then** 작업이 거부된다. +4. **Given** 기록 작성 요청의 페이지가 1보다 작거나 책 전체 페이지 수를 초과하면, **Then** 페이지 범위 오류로 거부된다. +5. **Given** 총평 플래그가 켜진 기록 작성 요청이 들어왔는데 페이지가 책의 마지막 페이지와 다르면, **Then** 총평 작성 조건 불충족으로 거부된다. +6. **Given** A가 작성한 기록이 존재할 때, **When** A가 동일 방·자신의 기록 ID로 삭제를 요청하면, **Then** 기록이 제거되고 이후 목록·상세에서 노출되지 않는다. +7. **Given** 기록이 어떤 방에 속해 있을 때, **When** 사용자가 *다른 방* 식별자로 동일 기록의 수정·삭제·핀을 요청하면, **Then** 작업이 거부된다(방-기록 소속 검증). + +--- + +### User Story 2 - 투표(Vote) 만들기·수정·삭제 그리고 투표하기 (Priority: P1) + +방 참여자는 짧은 질문과 선택지 묶음으로 투표를 생성하고, 다른 참여자는 그 투표에 참여한다(한 사용자는 한 투표에 정확히 하나의 선택지만 선택; 다른 선택지로 바꾸려면 항목을 변경한다; 더 이상 의견이 없으면 투표를 취소한다). 투표는 *진행 중인 방*에서만 가능하다. + +**Why this priority**: 가벼운 합의 도구는 방의 활성도를 직접적으로 끌어올린다. 기록과 함께 P1로 묶임. + +**Independent Test**: 방 참여자 A가 투표를 만들고, B가 그 투표에 참여·항목 변경·취소를 수행할 때 각 단계마다 항목별 카운트와 비율이 정합하게 유지된다. + +**Acceptance Scenarios**: + +1. **Given** A가 방의 참여자일 때, **When** A가 질문과 2개 이상의 선택지를 지정해 투표 생성을 요청하면, **Then** 새 투표가 저장되고 식별자가 반환된다. +2. **Given** 방이 만료되었거나 진행 중이 아닐 때, **When** 임의 참여자가 투표 참여를 요청하면, **Then** 작업이 거부된다. +3. **Given** B가 어떤 투표에 한 번도 참여하지 않은 상태에서, **When** B가 임의 선택지에 "투표하기"를 요청하면, **Then** 선택지의 카운트가 1 증가하고 B는 그 투표의 참여자로 기록된다. +4. **Given** B가 이미 어떤 선택지에 투표한 상태에서, **When** B가 동일 투표의 *다른* 선택지에 "투표하기"를 요청하면, **Then** 이전 선택지의 카운트가 1 감소하고 새 선택지의 카운트가 1 증가하며, B의 참여 기록은 새 선택지로 갱신된다(중복 카운트 없음). +5. **Given** B가 어떤 선택지에 투표한 상태에서, **When** B가 그 선택지에 대해 "투표 취소"를 요청하면, **Then** 선택지의 카운트가 1 감소하고 B의 참여 기록이 제거된다. +6. **Given** B가 어떤 선택지에 투표하지 않은 상태에서, **When** B가 그 선택지에 대해 "투표 취소"를 요청하면, **Then** 작업이 거부된다. +7. **Given** A가 본인이 만든 투표를 수정한다, **When** 본문 내용만을 변경한다, **Then** 투표 본문이 갱신된다. 페이지·총평 여부·선택지·방·작성자는 수정 대상이 아니다. +8. **Given** B가 A가 만든 투표를 수정·삭제하려고 시도하면, **Then** 작업이 거부된다. +9. **Given** 투표가 *총평*으로 표시되어 생성될 때, **When** 작성 시 책 진행률이 80% 미만이면, **Then** 작성이 거부된다. + +--- + +### User Story 3 - 방의 게시글 목록 둘러보기 (Priority: P1) + +방 참여자는 자신이 속한 방의 게시글(기록·투표)을 한 화면에서 둘러본다. 기본은 "그룹 기록"(다른 참여자들의 게시글 포함), 별도 모드로 "내 기록"만 보기를 선택할 수 있다. 정렬·페이지 범위 필터·총평만 보기 필터·커서 페이지를 지원한다. + +**Why this priority**: 작성된 게시글이 발견되지 않으면 작성자의 동기가 사라진다. 목록은 작성 직후의 첫 소비 진입점. + +**Independent Test**: 방의 게시글이 다수 있을 때 (a) "group"으로 요청하면 자신을 포함한 참여자들의 게시글이, (b) "mine"으로 요청하면 본인 게시글만 반환된다. 정렬 옵션과 페이지 범위·총평 필터 모두 의도된 결과를 보인다. + +**Acceptance Scenarios**: + +1. **Given** 방에 다수의 기록·투표가 있을 때, **When** 사용자가 그룹 모드(`type=group`)로 목록을 요청하면, **Then** 참여자들의 게시글이 정해진 정렬 기준에 따라 커서 페이지로 반환된다. 정렬 기준은 "최신순(기본) / 좋아요 많은 순 / 댓글 많은 순" 중 하나여야 한다. +2. **Given** 사용자가 내 모드(`type=mine`)로 목록을 요청한다, **When** 어떤 요청이든, **Then** 본인의 게시글만 *페이지 높은 순* 고정 정렬로 반환된다(이 모드에서 정렬 옵션은 무시된다). +3. **Given** 사용자가 페이지 범위 필터를 활성화(`isPageFilter=true`)하고 `pageStart`·`pageEnd`를 지정한다, **When** 목록을 요청하면, **Then** 그 페이지 범위에 속하는 게시글만 반환된다. 필터가 비활성일 때는 전체가 대상이다. +4. **Given** 사용자가 총평만 보기(`isOverview=true`)를 선택한다, **When** 목록을 요청하면, **Then** 총평으로 표시된 게시글만 반환된다. +5. **Given** 사용자가 방 비참여자일 때, **When** 그 방의 게시글 목록을 요청하면, **Then** 작업이 거부된다. + +--- + +### User Story 4 - 마음에 든 내 기록을 피드로 옮겨 공유하기 (Priority: P2) + +사용자가 방에 작성한 기록 중 외부(피드)에도 공유하고 싶은 것이 있으면, *작성자 본인 한정*으로 그 기록을 피드에 핀(연결)할 수 있다. 본 PRD는 핀 가능 여부 검증과 핀에 필요한 책 정보 제공 흐름까지를 다루며, *피드 작성 자체*의 상세는 피드 도메인 PRD(#354)를 따른다. + +**Why this priority**: 방-피드를 잇는 사용자 동선. 방 안 콘텐츠가 더 넓은 커뮤니티로 흘러가는 경로. + +**Independent Test**: 사용자가 본인 기록의 핀 진입점에 진입하면 (a) 핀 가능 여부와 (b) 핀에 필요한 책 정보가 함께 응답된다. 본인이 아닌 기록·다른 방 식별자로의 핀 요청은 거부된다. + +**Acceptance Scenarios**: + +1. **Given** A가 본인이 작성한 기록 ID와 그 기록이 속한 방 ID를 지정해 핀 진입을 요청한다, **When** 시스템이 권한·소속을 검증하면, **Then** 핀에 필요한 책 정보(이후 피드 작성 단계에 사용)가 응답된다. +2. **Given** A가 본인이 아닌 사용자의 기록을 핀하려 시도한다, **Then** 작업이 거부된다. +3. **Given** A가 *다른 방*의 ID로 자신의 기록을 핀하려 시도한다, **Then** 방-기록 소속 검증 실패로 거부된다. + +--- + +### User Story 5 - AI 독후감 생성과 사용량 안내 (Priority: P2) + +사용자는 자신이 한 방에서 작성한 기록들을 바탕으로 AI 도움을 받아 독후감 형태의 콘텐츠를 생성한다. 시스템은 사용자별 AI 이용 횟수와 기록 작성 횟수를 화면에 안내해 사용자가 한도를 인지할 수 있게 한다. + +**Why this priority**: AI 보조는 종이를 채우는 마찰을 낮추는 보조 시나리오. 본질적 가치는 P1에 있지만 활용 폭을 넓힌다. + +**Independent Test**: 사용자가 (a) 자신의 AI 사용량/기록 작성 횟수를 조회할 수 있고, (b) 어떤 방의 자신의 기록을 바탕으로 AI 독후감 생성을 요청할 수 있다. 사용량이 한도를 넘으면 추가 요청이 거부된다. + +**Acceptance Scenarios**: + +1. **Given** 사용자가 어떤 방의 참여자일 때, **When** 사용자가 그 방에서의 AI 이용 횟수와 기록 작성 횟수를 조회하면, **Then** 그 두 수치가 응답된다. +2. **Given** 사용자가 AI 한도 내에 있을 때, **When** 사용자가 자신의 기록들에 기반한 AI 독후감 생성을 요청하면, **Then** 생성 결과가 응답되며 AI 사용량이 1 증가한다. +3. **Given** 사용자가 이미 AI 한도에 도달했을 때, **When** 사용자가 추가 AI 독후감 생성을 요청하면, **Then** 작업이 한도 초과로 거부된다. +4. **Given** 사용자가 방 비참여자일 때, **When** 그 방의 AI 독후감 생성 또는 사용량 조회를 요청하면, **Then** 작업이 거부된다. + +**AI 사용량 한도 정책 (확정)**: + +- **한도**: 사용자당 **평생 누적 5회**의 AI 독후감 생성을 허용한다. +- **적용 단위**: **전역**. 사용자가 참여하는 *모든 방을 합산*해 산정한다(방별 별도 한도가 아님). +- **리셋**: **없음**. 한 번 사용한 횟수는 어떤 주기로도 회복되지 않는다. +- **기록 작성 횟수와의 관계**: 기록 작성 횟수는 사용자에게 *안내 정보*로만 노출되며 AI 한도 산정에는 사용되지 않는다(게이트 아님). +- **한도 도달 후**: 추가 AI 독후감 생성 요청은 거부되며, 사용자에게는 한도 도달 사실을 인지할 수 있는 명확한 안내가 전달된다. + +--- + +### Edge Cases + +- **방 비참여자의 작성/조회**: 방에 참여하지 않은 사용자의 기록·투표 작성, 목록 조회, AI 사용은 거부된다. +- **만료/종료된 방**: 방이 진행 중이 아니면 투표 참여가 거부된다(기록 작성·조회·AI 정책은 방 라이프사이클 PRD를 따른다). +- **방-게시글 소속 불일치**: 게시글의 수정·삭제·핀 요청 시 요청에 포함된 방 ID와 실제 게시글의 방 ID가 다르면 작업이 거부된다. +- **페이지 범위 위반**: 1보다 작거나 책 전체 페이지를 초과하는 페이지에 대한 게시글 작성은 거부된다. +- **총평 조건 위반**: *기록*의 총평은 책의 마지막 페이지에서만, *투표*의 총평은 책 진행률 80% 이상일 때만 작성된다. 두 종류 사이에 의도된 조건 차이가 있다. +- **카운트 무결성**: 좋아요·댓글·투표 항목 카운트는 동시 요청 시에도 정합한다(중복 누계·누락 0건). 댓글 수가 0 미만으로 내려가는 일은 없다. +- **빠른 투표 항목 변경**: 사용자가 짧은 시간 안에 한 투표 안에서 항목을 여러 번 바꿔도, 최종 상태는 마지막 선택지에 정확히 1표가 누적된 결과가 된다(이전 선택지의 카운트는 모두 회수되어 있음). +- **존재하지 않는 참여 취소**: 참여 기록이 없는 선택지에 대한 "투표 취소" 요청은 거부된다. +- **삭제 후의 후속 작업**: 삭제된 게시글에 대한 좋아요·댓글·핀·AI 요청은 명확한 오류로 거부된다. + +## Requirements *(mandatory)* + +### Functional Requirements + +#### 공통 (Record · Vote 공히) + +- **FR-001**: 게시글 작성·수정·삭제·핀은 모두 방 참여자만 가능해야 한다. 비참여자의 시도는 거부된다. +- **FR-002**: 게시글의 수정·삭제·핀 요청에서 요청에 포함된 방 ID는 게시글이 실제 속한 방과 일치해야 한다. 불일치 시 작업이 거부된다. +- **FR-003**: 게시글 작성 시 페이지는 1 이상이며 책 전체 페이지 수 이하여야 한다. +- **FR-004**: 게시글의 수정은 본문 변경만을 허용한다. 페이지·총평 여부·방·작성자·연결 책은 수정 대상이 아니다. +- **FR-005**: 게시글의 좋아요·댓글·(투표의 경우) 항목 카운트는 어떤 동시성 시나리오에서도 실제 활성 상태와 일치해야 한다. 댓글 수는 음수가 될 수 없다. + +#### 기록(Record) + +- **FR-006**: 사용자는 방 안에서 본문·페이지·총평 여부를 지정해 기록을 작성할 수 있다. +- **FR-007**: 기록의 총평(`isOverview=true`)은 *책의 마지막 페이지*에서만 작성될 수 있다. 그 외 페이지로 총평을 작성하면 거부된다. +- **FR-008**: 기록의 작성자는 본인 기록의 본문을 수정할 수 있다. 본인이 아니면 거부된다. +- **FR-009**: 기록의 작성자는 본인 기록을 삭제할 수 있다. 본인이 아니면 거부된다. +- **FR-010**: 삭제된 기록은 어떤 사용자에게도 노출되지 않는다. + +#### 투표(Vote) + +- **FR-011**: 사용자는 방 안에서 본문·페이지·총평 여부·복수의 선택지를 지정해 투표를 생성할 수 있다. +- **FR-012**: 투표의 총평(`isOverview=true`)은 *책 진행률이 80% 이상*인 페이지에서만 생성될 수 있다. 그 외 페이지로 총평을 생성하면 거부된다. +- **FR-013**: 투표 참여는 *진행 중인 방*에서만 가능하다. 만료·종료된 방에서의 참여 요청은 거부된다. +- **FR-014**: 한 사용자는 한 투표에 대해 정확히 하나의 선택지에만 투표 상태를 가진다. 다른 선택지에 다시 "투표하기"를 요청하면 이전 선택지의 카운트가 회수되고 새 선택지의 카운트가 증가하며 사용자 참여 기록이 새 선택지로 갱신된다. +- **FR-015**: 투표 취소는 사용자가 *참여한* 선택지에 대해서만 가능하다. 참여하지 않은 선택지의 취소 요청은 거부된다. +- **FR-016**: 투표 생성자는 본인 투표의 본문을 수정·삭제할 수 있다. 본인이 아니면 거부된다. +- **FR-017**: 투표 항목별 카운트는 모든 참여자의 활성 참여 수와 일치해야 한다. 빠른 항목 변경 시에도 중복 누계·누락은 발생하지 않는다. + +#### 목록 조회 + +- **FR-018**: 사용자는 자신이 참여한 방의 게시글(기록·투표) 목록을 단일 진입점에서 조회할 수 있다. 비참여자의 요청은 거부된다. +- **FR-019**: 목록 조회는 다음 모드를 지원해야 한다: (a) `group`(기본; 참여자들의 게시글, 정렬은 최신/좋아요/댓글 중 선택), (b) `mine`(본인 게시글만, *페이지 높은 순* 고정 정렬, 정렬 옵션은 무시). +- **FR-020**: 목록 조회는 페이지 범위 필터(`isPageFilter`, `pageStart`, `pageEnd`)와 총평만 보기 필터(`isOverview`)를 독립적으로 적용할 수 있어야 한다. +- **FR-021**: 목록 조회는 커서 기반 페이지네이션을 사용한다(다른 도메인 목록과 일관). + +#### 피드 핀 + +- **FR-022**: 사용자는 본인이 작성한 기록을 피드로 핀하기 위한 진입점에서, 핀 가능 여부와 핀에 필요한 책 정보를 단일 호출로 받아야 한다. 실제 피드 작성은 피드 도메인 PRD를 따른다. + +#### AI 보조 + +- **FR-023**: 사용자는 자신이 한 방에서 작성한 기록들을 바탕으로 AI 독후감 생성을 요청할 수 있다. 사용자가 평생 누적 AI 사용 한도(5회)에 도달했거나 방 비참여자라면 거부된다. +- **FR-024**: 시스템은 사용자별로 (a) 모든 방을 합산한 *전역 AI 사용 횟수*와 (b) 방 단위 *기록 작성 횟수*를 조회 가능하게 노출해야 한다. AI 한도는 사용자당 평생 누적 5회이며 리셋되지 않는다. 기록 작성 횟수는 안내 정보이며 한도 산정에 사용되지 않는다. +- **FR-025**: 사용자가 AI 한도(5회)에 도달한 경우, 추가 생성 요청은 거부되며 사용자에게 한도 도달 사실을 인지할 수 있는 명확한 안내가 전달되어야 한다. + +### Key Entities + +- **RoomPost (방 게시글)**: 방 안에 작성되는 게시글의 추상. 기록과 투표를 통합한다. 좋아요 수·댓글 수·작성자·방·페이지·총평 여부를 가진다. +- **Record (기록)**: 책의 한 페이지(혹은 마지막 페이지)에 대한 개인 감상·일지. 본문·페이지·총평 여부를 가진다. 마지막 페이지에서만 총평이 가능하다. +- **Vote (투표)**: 방 안에서 짧은 질문에 대한 합의 도구. 본문·페이지·총평 여부·복수의 선택지를 가진다. 진행 중인 방에서만 참여가 가능하고, 진행률 80% 이상에서만 총평이 가능하다. +- **Vote Item (투표 선택지)**: 투표에 속한 선택지. 이름과 카운트를 가진다. +- **Vote Participant (투표 참여자)**: 한 사용자가 한 투표에서 선택한 선택지를 가리키는 연결. (사용자, 투표) 쌍에 대해 최대 1건 존재한다. +- **AI Usage (AI 사용량)**: 사용자의 *전역* AI 독후감 생성 누적 횟수(평생, 리셋 없음)와 *방 단위* 기록 작성 횟수의 집계. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 방 참여자가 기록 또는 투표 작성을 진입한 뒤 평균 60초 이내에 작성을 완료할 수 있다(본문·페이지·총평 여부 선택 기준). +- **SC-002**: 어떤 사용자가 동일 투표에 대해 빠르게 항목을 N회 바꿔도, 최종 항목별 카운트 합은 그 투표의 활성 참여자 수와 항상 일치한다(불일치 사건 0건). +- **SC-003**: 어떤 사용자도 자신이 참여하지 않은 방의 기록·투표 작성·조회·핀·AI 호출에 성공할 수 없다(권한 누수 0건). +- **SC-004**: 비공개 권한 정책 위반(타인 기록/투표 수정·삭제, 방-게시글 소속 불일치)이 발생한 사건이 0건이다. +- **SC-005**: "그룹 기록" 모드와 "내 기록" 모드의 결과는 서로의 정렬 정책을 침범하지 않는다(내 모드는 페이지 높은 순 고정, 그룹 모드는 사용자가 선택한 정렬을 그대로 반영). +- **SC-006**: 사용자가 자신의 *전역* AI 이용 횟수와 방별 기록 작성 횟수를 조회할 수 있는 비율이 100%이다. 한도(5회) 도달 시 사용자는 한도 사실을 명확히 인지할 수 있는 안내를 받는다. +- **SC-007**: 본인 기록을 피드로 핀하려는 사용자의 진입에서, 핀 가능 여부와 책 정보가 한 번의 응답으로 전달되는 비율이 100%이다(추가 호출 없이 화면 구성 가능). + +> 본 PRD는 사용자 경험 차원의 성공 기준만 정의하며, 백엔드 응답 시간/RPS 등 기술 임계치는 헌법(constitution)의 성능 가드 원칙과 별도의 부하 시나리오에서 정의한다. + +## Assumptions + +- **이미 운영 중인 기능의 역설계**: 본 PRD는 신규 기능 정의가 아니라 기존 구현을 사용자 관점으로 정형화한 산출물이다. 요구사항은 "구현이 보장해야 한다(혹은 보장하고 있어야 한다)"의 형태로 읽힌다. +- **인증 전제**: 모든 시나리오는 인증된 사용자를 전제로 한다. 인증/인가의 상세 동작은 별도 PRD가 다룬다. +- **방 도메인은 외부 의존**: 방의 생성·만료·참여자 관리·라이프사이클 상태는 별도 방 도메인 PRD가 정의한다. 본 PRD는 방의 "참여자 여부"와 "진행 중 여부"라는 두 외부 상태만 사용한다. +- **책 도메인은 외부 의존**: 본 PRD가 사용하는 "책의 전체 페이지 수"·"진행률"은 책 도메인 PRD(#356) 및 방-책 연결을 통해 제공된다. 본 PRD는 그 값을 신뢰한다. +- **피드 도메인은 외부 의존**: "기록을 피드로 핀하기"의 핀 이후 피드 작성 상세는 피드 도메인 PRD(#354)를 따른다. +- **오늘의 한마디(AttendanceCheck)는 범위 외**: 같은 컨트롤러에 라우팅되지만 사용자 정의에 따라 본 PRD에서는 별도 도메인으로 처리한다. +- **댓글·좋아요 행위 자체는 범위 외**: 본 PRD는 *카운트 정합성*만 책임진다. 댓글 작성/삭제·좋아요 토글의 사용자 흐름은 댓글/좋아요(또는 Post) 도메인 PRD에서 다룬다. +- **AI 한도의 확장 가능성**: 본 PRD가 정의한 한도(전역 평생 누적 5회·리셋 없음)는 운영 초기 정책이다. 멤버십 등급·유료화·단기 캠페인 등으로 한도를 차등화하는 후속 결정이 발생하면 본 PRD를 개정 트리거로 본다. +- **알림 연동**: 본 PRD는 *본 도메인이 직접 발화하는* 알림 트리거만을 보증한다. 게시글에 대한 댓글·좋아요로 인한 알림 트리거의 발화는 각각 댓글 도메인과 좋아요 공통 도메인이 책임지며, 본 도메인은 그 발화의 *대상이 방 게시글이라는 사실*만 인정한다. 알림 트리거 수신 이후의 저장·표시·푸시 전달 흐름은 알림 도메인 PRD(#359)를 따른다. + + **본 도메인이 직접 발화하는 트리거**: + - **"내가 참여한 방에서 새 기록이 작성됨"**: 사용자가 기록을 작성하는 데 성공한 시점에, 같은 방의 다른 참여자(작성자 본인 제외)에게 알림 트리거가 발화된다. 기록 수정·삭제는 트리거를 발화하지 않는다. + - **"내가 참여한 방에서 새 투표가 시작됨"**: 사용자가 투표를 *생성*하는 데 성공한 시점에, 같은 방의 다른 참여자(작성자 본인 제외)에게 알림 트리거가 발화된다. 투표 참여·취소·항목 변경·수정·삭제는 트리거를 발화하지 않는다(생성 1회당 1건). +- **신고**: 게시글에 대한 신고 흐름은 별도 신고 도메인 PRD가 정의한다(피드 PRD에서 정한 정책과 일관).