Skip to content

feat(habit): add recurring domain and immutable history - #37

Merged
seonghobae merged 20 commits into
mainfrom
feat/habit-recurring-domain
Aug 3, 2026
Merged

feat(habit): add recurring domain and immutable history#37
seonghobae merged 20 commits into
mainfrom
feat/habit-recurring-domain

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the first reviewable slice of #36.

  • defines daily and weekly recurring-habit contracts with bounded intervals and normalized ISO weekdays
  • validates opaque tenant identifiers, UUIDv4 idempotency keys, titles, IANA timezones, calendar dates, timestamps, and bounded date ranges
  • generates occurrences from local calendar dates so daylight-saving transitions do not introduce elapsed-hour drift
  • adds an asynchronous tenant-safe repository boundary and in-memory adapter with deterministic reads and idempotent completion replay
  • preserves completion history through copy-on-read behavior and append-only domain operations
  • adds parameter-recovery tests across leap day, month/year boundaries, DST dates, recurrence intervals, cross-workspace access, replayed completion commands, and unscheduled dates
  • adds a PostgreSQL schema with UUIDv4 checks, composite workspace ownership, weekly weekday bit masks, deterministic indexes, idempotency uniqueness, and a database append-only trigger
  • documents migration integrity, rollback risk, controlled-erasure requirements, deferred runtime work, and exact validation gates

Deferred

The parameterized PostgreSQL repository, pooled integration tests, validated runtime, NestJS HTTP surface, pause/archive workflows, streak projections, reminders, and controlled data-rights erasure remain separate reviewable slices.

Validation gate

Merge only when CI, AppGuardrail, Semgrep, Security Scan, Commercial Readiness, CodeRabbit, and all human/security review requirements pass on the exact head with no unresolved actionable findings.

Refs #36

Summary by CodeRabbit

  • 새 기능

    • 일간·주간 반복 습관을 생성하고 예정된 발생일을 조회할 수 있습니다.
    • 습관 완료 기록과 완료 이력을 관리하며, 중복 완료를 방지합니다.
    • 날짜, 시간대, UUID 및 입력값을 검증하고 작업 공간별 데이터를 격리합니다.
    • 완료 이력의 변경·삭제를 제한해 데이터 무결성을 강화했습니다.
  • 문서

    • 마이그레이션 적용 순서, 롤백 절차 및 데이터 보존 정책을 문서화했습니다.
  • 테스트

    • 윤년, 월말, DST, 연도 경계 및 잘못된 완료 요청에 대한 검증을 추가했습니다.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@seonghobae, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5c5b11bf-7420-4427-9694-96c15c17ea5f

📥 Commits

Reviewing files that changed from the base of the PR and between 288ba08 and 248da1d.

📒 Files selected for processing (4)
  • apps/habit-service/migrations/0001_recurring_habit_core.sql
  • apps/habit-service/migrations/README.md
  • apps/habit-service/src/habit-domain.test.ts
  • apps/habit-service/src/habit-domain.ts
📝 Walkthrough

Walkthrough

반복 습관 도메인 모델과 발생일 계산을 추가했습니다. HabitService는 workspace 격리, 입력 검증, 멱등 완료 기록을 제공합니다. PostgreSQL 스키마는 복합 외래 키와 append-only 완료 이력을 적용합니다.

Changes

반복 습관 도메인

Layer / File(s) Summary
도메인 계약과 일정 생성
apps/habit-service/src/habit-domain.ts, docs/superpowers/plans/..., package.json
일간·주간 반복 규칙, 입력 정규화, 로컬 날짜 기반 발생일 생성, 저장소 계약을 추가했습니다. 관련 계획 문서와 포맷 검사 대상도 갱신했습니다.
서비스와 완료 기록
apps/habit-service/src/habit-domain.ts, apps/habit-service/src/habit-domain.test.ts
습관 생성·조회·완료·이력 조회를 추가했습니다. workspace 격리, UUID, 멱등성, 불변 복사, 잘못된 입력과 비예약일을 테스트합니다.
PostgreSQL 무결성 스키마
apps/habit-service/migrations/0001_recurring_habit_core.sql, apps/habit-service/migrations/README.md
습관 정의와 완료 이벤트 테이블, 인덱스, 복합 외래 키, 중복 방지 제약, append-only 트리거를 추가했습니다. 마이그레이션 적용 및 롤백 절차를 문서화했습니다.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HabitService
  participant InMemoryHabitRepository
  Client->>HabitService: completeHabit 요청
  HabitService->>InMemoryHabitRepository: 습관 및 예약일 확인
  HabitService->>InMemoryHabitRepository: 완료 이벤트 저장
  InMemoryHabitRepository-->>HabitService: 기존 또는 신규 이벤트 반환
  HabitService-->>Client: 완료 이벤트 반환
Loading

Possibly related issues

Possibly related PRs

  • ContextualWisdomLab/life-os#2 — 다른 서비스와 엔터티를 대상으로 workspace 격리 저장소, UUID 검증, PostgreSQL 스키마, 테스트를 추가한 점이 코드 수준에서 연결됩니다.
🚥 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 제목은 반복 습관 도메인과 불변 완료 이력 추가라는 변경의 핵심을 정확하고 간결하게 설명합니다.
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 feat/habit-recurring-domain

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

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 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: 4

🧹 Nitpick comments (1)
apps/habit-service/migrations/0001_recurring_habit_core.sql (1)

74-81: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

listCompletions 정렬용 인덱스를 추가하세요.

apps/habit-service/src/habit-domain.ts:262-332listCompletions(recordedAt, id) 순서로 결과를 반환합니다. 현재 인덱스는 scheduled_local_date를 먼저 포함합니다. 날짜 조건이 없으면 PostgreSQL 구현은 해당 정렬을 인덱스만으로 처리할 수 없습니다. (workspace_id, habit_id, recorded_at, id) 인덱스를 추가하고 일정 조회 인덱스는 유지하세요.

제안된 수정
 CREATE INDEX completion_events_workspace_habit_schedule_idx
   ON habit.completion_events (
     workspace_id,
     habit_id,
     scheduled_local_date ASC,
     recorded_at ASC,
     id ASC
   );
+
+CREATE INDEX completion_events_workspace_habit_recorded_idx
+  ON habit.completion_events (
+    workspace_id,
+    habit_id,
+    recorded_at ASC,
+    id ASC
+  );
🤖 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/habit-service/migrations/0001_recurring_habit_core.sql` around lines 74
- 81, Update the migration near completion_events_workspace_habit_schedule_idx
to add a separate index on completion_events using workspace_id, habit_id,
recorded_at ASC, and id ASC for listCompletions ordering. Keep the existing
schedule-date index unchanged.
🤖 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 @.github/workflows/ci.yml:
- Around line 52-58: Remove the write-enabled Prettier invocation from the
“Render habit test formatting diff” workflow step, or restore
apps/habit-service/src/habit-domain.test.ts to its original state before the
“Check formatting” step runs, so pnpm format:check validates the unmodified
file.

In `@apps/habit-service/migrations/0001_recurring_habit_core.sql`:
- Around line 94-97: Update the completion_events append-only protection by
adding a separate BEFORE TRUNCATE FOR EACH STATEMENT trigger on
habit.completion_events, using the existing rejection function or equivalent
statement-level handler. Keep the UPDATE/DELETE trigger intact and ensure the
service role is not granted TRUNCATE permission.

In `@apps/habit-service/src/habit-domain.ts`:
- Around line 71-77: Update requireOpaqueId and the composite-key construction
in entityLookupKey and appendCompletion’s idempotencyLookup to use
delimiter-safe encoding before joining workspaceId, habitId, or entity IDs.
Ensure distinct component combinations always produce distinct keys while
preserving existing validation and lookup behavior.
- Around line 293-314: Update appendCompletion to compare a replayed
completion’s payload with the existing record found through
completionIdempotency, including scheduledLocalDate and completedAt (and any
other persisted completion fields that define the payload). Return the cloned
existing record only when the payload matches; otherwise throw an error for
idempotency-key reuse with conflicting data, while preserving the existing
inconsistency error.

---

Nitpick comments:
In `@apps/habit-service/migrations/0001_recurring_habit_core.sql`:
- Around line 74-81: Update the migration near
completion_events_workspace_habit_schedule_idx to add a separate index on
completion_events using workspace_id, habit_id, recorded_at ASC, and id ASC for
listCompletions ordering. Keep the existing schedule-date index unchanged.
🪄 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: 4f5b7b8f-bf34-4d24-b188-2162d0de1865

📥 Commits

Reviewing files that changed from the base of the PR and between 174590b and 1e58dd8.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • apps/habit-service/migrations/0001_recurring_habit_core.sql
  • apps/habit-service/migrations/README.md
  • apps/habit-service/src/habit-domain.test.ts
  • apps/habit-service/src/habit-domain.ts
  • docs/superpowers/plans/2026-08-04-habit-recurring-domain-slice.md
  • package.json

Comment thread .github/workflows/ci.yml Outdated
Comment thread apps/habit-service/migrations/0001_recurring_habit_core.sql
Comment thread apps/habit-service/src/habit-domain.ts
Comment thread apps/habit-service/src/habit-domain.ts
@seonghobae
seonghobae merged commit f3ea399 into main Aug 3, 2026
31 of 32 checks passed
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