Skip to content

feat: add tenant-safe planning domain slice - #2

Merged
seonghobae merged 16 commits into
mainfrom
develop
Aug 2, 2026
Merged

feat: add tenant-safe planning domain slice#2
seonghobae merged 16 commits into
mainfrom
develop

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the first usable Planning bounded-context slice.

Included

  • Goal → Project → Task domain model
  • workspace-scoped repository contract and in-memory implementation
  • tenant isolation and hierarchy validation tests
  • Planning REST endpoints under /v1
  • PostgreSQL migration with composite tenant-aware foreign keys and indexes
  • opaque UUIDv4 internal identifiers and HTTP-boundary error mapping

Security model

Every read and parent lookup is scoped by workspaceId. The SQL schema includes workspace_id in parent-child foreign keys, preventing cross-workspace Goal/Project/Task references at the database layer. Numeric-only internal identifiers are rejected.

Validation

  • CI: passed
  • SAST Semgrep: passed
  • Security Scan: passed
  • CodeRabbit findings were fixed and review threads resolved

Merged after required checks and review verification.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6dc3c59b-191e-4fb3-a696-919b64972b78

📥 Commits

Reviewing files that changed from the base of the PR and between c130b0d and f255407.

📒 Files selected for processing (7)
  • apps/planning-service/src/http-boundary.test.ts
  • apps/planning-service/src/http-boundary.ts
  • apps/planning-service/src/main.ts
  • apps/planning-service/src/planning-domain.test.ts
  • apps/planning-service/src/planning-domain.ts
  • docs/adr/0001-opaque-non-numeric-identifiers.md
  • docs/superpowers/plans/2026-08-02-planning-domain-slice.md

📝 Walkthrough

Walkthrough

Planning 서비스에 Goal, Project, Task 도메인과 workspace 범위 검증을 추가했습니다. 인메모리 API와 PostgreSQL 초기 스키마를 구현했습니다. 생성, 조회, 계층 참조, workspace 격리 검증을 테스트했습니다.

Changes

Planning 도메인

Layer / File(s) Summary
도메인 계약과 계층 검증
apps/planning-service/src/planning-domain.ts, apps/planning-service/src/planning-domain.test.ts, docs/superpowers/plans/2026-08-02-planning-domain-slice.md
Goal, Project, Task 데이터 구조와 저장소 계약을 추가했습니다. 인메모리 저장소가 workspace 및 상위 엔터티 기준으로 데이터를 조회합니다. 서비스가 UUID, 생성 시각, 제목 정규화, 상위 엔터티 검증을 처리합니다. 도메인 테스트가 계층 생성·조회, UUIDv4, workspace 격리와 입력 검증을 확인합니다.
워크스페이스 범위 REST API
apps/planning-service/src/main.ts, apps/planning-service/src/http-boundary.ts, apps/planning-service/src/http-boundary.test.ts
x-workspace-id 헤더와 요청 제목을 검증합니다. Goal, Project, Task 생성·조회 엔드포인트를 등록합니다. 도메인 오류를 HTTP 예외로 변환하고 전역 경로 접두사를 v1로 설정합니다.
PostgreSQL 스키마와 마이그레이션
apps/planning-service/migrations/0001_initial_planning.sql, apps/planning-service/migrations/README.md
planning 스키마와 Goal, Project, Task 테이블을 추가했습니다. workspace 외래 키, 제목·상태 제약, 삭제 연쇄 규칙, 조회 인덱스를 정의했습니다. 마이그레이션 적용 순서를 문서화했습니다.
식별자 정책과 구현 범위 기록
docs/adr/0001-opaque-non-numeric-identifiers.md, docs/superpowers/plans/2026-08-02-planning-domain-slice.md
UUIDv4 기반 불투명 식별자 정책과 Planning 도메인의 구현 범위를 문서화했습니다.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PlanningController
  participant PlanningService
  participant InMemoryPlanningRepository
  Client->>PlanningController: workspace 헤더와 Goal, Project 또는 Task 요청
  PlanningController->>PlanningController: workspace ID와 제목 검증
  PlanningController->>PlanningService: 검증된 입력 전달
  PlanningService->>InMemoryPlanningRepository: workspace 범위 저장 또는 조회
  InMemoryPlanningRepository-->>PlanningService: 엔터티 또는 목록 반환
  PlanningService-->>PlanningController: 결과 반환
  PlanningController-->>Client: HTTP 응답 반환
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 테넌트 안전성을 포함한 Planning 도메인 슬라이스 추가라는 PR의 주요 변경 사항을 간결하고 정확하게 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as ready for review August 2, 2026 13:22

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/planning-service/src/main.ts (1)

21-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

PlanningService를 Nest DI provider로 등록하는 방식을 고려하십시오.

planningService는 모듈 스코프에서 new로 생성된 싱글턴입니다. AppModule은 이를 provider로 등록하지 않습니다. 이 방식은 동작하지만, Nest의 관례를 벗어나며 테스트 격리와 향후 저장소 구현 교체(예: PostgreSQL)를 어렵게 만듭니다. PlanningService@Injectable()로 선언하고 providers 배열에 등록한 뒤, 컨트롤러 생성자에서 주입하십시오.

Also applies to: 92-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/planning-service/src/main.ts` at line 21, Replace the module-scoped
PlanningService construction with Nest dependency injection: mark
PlanningService as `@Injectable`(), register it in AppModule.providers, and inject
it through the controller constructor. Preserve the InMemoryPlanningRepository
dependency through the provider configuration so future repository substitutions
remain possible.
apps/planning-service/src/planning-domain.test.ts (1)

23-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

테넌트 격리 테스트 범위를 Project·Task 계층까지 확장하십시오.

현재 테스트는 listGoals의 workspace 격리와 project 생성 시 cross-tenant 거부만 검증합니다. listProjects, listTasks의 workspace 격리와 task 생성 시 cross-tenant 거부는 검증하지 않습니다. PR 목표는 계층 전체의 테넌트 격리를 증명하는 것입니다. 누락된 경로에도 동일한 패턴의 테스트를 추가하십시오.

♻️ 제안: 추가 테스트 예시
   it('rejects a project whose goal belongs to another workspace', () => {
     ...
   });
+
+  it('does not expose projects or tasks from another workspace', () => {
+    const service = new PlanningService(new InMemoryPlanningRepository());
+    const goal = service.createGoal('workspace-a', { title: 'Goal A' });
+    const project = service.createProject('workspace-a', { goalId: goal.id, title: 'Project A' });
+    service.createTask('workspace-a', { projectId: project.id, title: 'Task A' });
+
+    expect(service.listProjects('workspace-b', goal.id)).toEqual([]);
+    expect(service.listTasks('workspace-b', project.id)).toEqual([]);
+  });
+
+  it('rejects a task whose project belongs to another workspace', () => {
+    const service = new PlanningService(new InMemoryPlanningRepository());
+    const goal = service.createGoal('workspace-a', { title: 'Goal A' });
+    const project = service.createProject('workspace-a', { goalId: goal.id, title: 'Project A' });
+
+    expect(() =>
+      service.createTask('workspace-b', { projectId: project.id, title: 'Cross-tenant task' }),
+    ).toThrowError('Project not found');
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/planning-service/src/planning-domain.test.ts` around lines 23 - 41,
planning-domain.test.ts의 테넌트 격리 테스트를 확장하여 다른 workspace의 프로젝트가 listProjects에서
노출되지 않는 경우와 다른 workspace의 태스크가 listTasks에서 노출되지 않는 경우를 검증하십시오. 또한 다른 workspace의
goal 또는 project를 참조하는 createTask 호출이 기존 cross-tenant 프로젝트 생성 테스트와 동일하게 “Task not
found” 또는 실제 도메인 계약의 오류를 발생시키는지 테스트를 추가하십시오.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/planning-service/src/main.ts`:
- Around line 38-44: Update PlanningController methods createGoal,
createProject, and createTask to validate request bodies like
requireWorkspaceId, rejecting missing titles with BadRequestException before
calling the service. Catch the PlanningService domain errors and translate
required-title failures to BadRequestException and missing goal/project failures
to NotFoundException, importing both exceptions from `@nestjs/common` while
preserving successful service calls.

In `@docs/superpowers/plans/2026-08-02-planning-domain-slice.md`:
- Line 12: Update the checklist item “Run CI, SAST, and security scan; fix all
actionable findings” to reflect the current PR status, leaving it unchecked
until CI, SAST, security scans, and actionable review findings are actually
completed.

---

Nitpick comments:
In `@apps/planning-service/src/main.ts`:
- Line 21: Replace the module-scoped PlanningService construction with Nest
dependency injection: mark PlanningService as `@Injectable`(), register it in
AppModule.providers, and inject it through the controller constructor. Preserve
the InMemoryPlanningRepository dependency through the provider configuration so
future repository substitutions remain possible.

In `@apps/planning-service/src/planning-domain.test.ts`:
- Around line 23-41: planning-domain.test.ts의 테넌트 격리 테스트를 확장하여 다른 workspace의
프로젝트가 listProjects에서 노출되지 않는 경우와 다른 workspace의 태스크가 listTasks에서 노출되지 않는 경우를
검증하십시오. 또한 다른 workspace의 goal 또는 project를 참조하는 createTask 호출이 기존 cross-tenant
프로젝트 생성 테스트와 동일하게 “Task not found” 또는 실제 도메인 계약의 오류를 발생시키는지 테스트를 추가하십시오.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d5bc9e13-0b36-4378-ab4a-b81cc7d6905c

📥 Commits

Reviewing files that changed from the base of the PR and between ad11c67 and c130b0d.

📒 Files selected for processing (6)
  • apps/planning-service/migrations/0001_initial_planning.sql
  • apps/planning-service/migrations/README.md
  • apps/planning-service/src/main.ts
  • apps/planning-service/src/planning-domain.test.ts
  • apps/planning-service/src/planning-domain.ts
  • docs/superpowers/plans/2026-08-02-planning-domain-slice.md

Comment thread apps/planning-service/src/main.ts
Comment thread docs/superpowers/plans/2026-08-02-planning-domain-slice.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant