Skip to content

feat(planning): add data-rights contributor v1 - #179

Merged
seonghobae merged 23 commits into
mainfrom
feat/planning-data-rights-contributor-v1
Aug 10, 2026
Merged

feat(planning): add data-rights contributor v1#179
seonghobae merged 23 commits into
mainfrom
feat/planning-data-rights-contributor-v1

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Buyer/privacy outcome

Advance #55 with the first concrete owning-service contributor: Planning must expose the versioned data-rights lifecycle from its own runtime without cross-service database access.

Test-first state

This Draft intentionally starts with a RED runtime-composition contract. The test requires PlanningRuntime to expose one service-owned data-rights contributor; protected main currently has no such runtime boundary. After exact-head CI proves that failure, the branch will add the smallest production composition, then add separate RED behavioral tests for deterministic tenant-scoped export, fail-closed preflight, replay-safe erasure and post-erasure verification before implementing those behaviors.

Scope

Planning only. The slice consumes the protected life-os.data-rights-contributor.v1 contract from #159 and will not let Identity read or mutate Planning tables directly. Other contributors, artifact delivery and whole-right orchestration remain separate #55 work.

Refs #55 and #159.

Summary by CodeRabbit

  • 새로운 기능

    • Planning 데이터에 대한 내보내기, 삭제 사전 점검, 삭제 완료 검증 기능을 추가했습니다.
    • 내보낸 데이터에 스키마 버전과 무결성 해시를 포함해 신뢰성을 높였습니다.
    • 삭제 요청 처리 결과를 영수증으로 기록하고, 중복 요청에도 일관된 결과를 제공합니다.
  • 버그 수정

    • 대규모 데이터 내보내기 시 페이지 제한을 초과해도 전체 레코드를 반환하도록 개선했습니다.
    • 삭제 저장소가 준비되지 않은 경우 작업을 안전하게 차단하고 상태를 안내합니다.

@coderabbitai

coderabbitai Bot commented Aug 10, 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: 21 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: fd1c29e6-7c8b-446d-b8df-3a179a7a9efc

📥 Commits

Reviewing files that changed from the base of the PR and between f5bbb6b and 8ea67f5.

📒 Files selected for processing (1)
  • apps/planning-service/src/planning-data-rights.test.ts
📝 Walkthrough

Walkthrough

Planning 서비스에 데이터 권리 contributor를 추가했다. Planning 및 Today 데이터를 내보내고 삭제한다. 삭제 영수증과 멱등성을 PostgreSQL에 저장한다. 삭제 완료 검증과 단위·통합 테스트를 추가했다.

Changes

Planning 데이터 권리 처리

Layer / File(s) Summary
계약, 정규화 및 영수증 저장소
apps/planning-service/src/planning-data-rights.ts, apps/planning-service/migrations/0004_data_rights_erasure_receipts.sql
데이터 권리 요청·응답 타입과 입력 검증을 추가했다. canonical JSON SHA-256을 계산한다. 삭제 영수증 테이블과 인덱스를 추가했다.
요청 처리, 내보내기 및 사전 점검
apps/planning-service/src/planning-data-rights.ts
요청 작업을 분배한다. 다섯 Planning 및 Today 테이블을 1,000건 단위로 내보낸다. 영수증 저장소 상태를 사전 점검한다.
멱등 삭제와 삭제 검증
apps/planning-service/src/planning-data-rights.ts
워크스페이스 advisory lock과 idempotency key를 사용해 삭제한다. 삭제 영수증을 저장한다. 기존 요청 충돌과 잔여 레코드를 검증한다.
런타임 wiring과 단위 검증
apps/planning-service/src/planning-runtime.ts, apps/planning-service/src/planning-data-rights.test.ts
PlanningRuntime에 contributor를 주입한다. 런타임 구성, 대규모 export, 영수증 저장소 사전 점검을 검증한다.
PostgreSQL lifecycle 통합 검증
apps/planning-service/tests/postgres-data-rights.integration.test.ts
임시 PostgreSQL 데이터베이스에서 마이그레이션, fixture, export, erase, 멱등 재실행, 충돌, 삭제 검증과 정리를 검증한다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PlanningRuntime
  participant PlanningDataRightsContributor
  participant PostgreSQL
  Client->>PlanningRuntime: 데이터 권리 요청
  PlanningRuntime->>PlanningDataRightsContributor: handle(request)
  PlanningDataRightsContributor->>PostgreSQL: export 또는 erase 실행
  PostgreSQL-->>PlanningDataRightsContributor: 데이터, 삭제 수 또는 영수증
  PlanningDataRightsContributor-->>PlanningRuntime: 작업별 응답
  PlanningRuntime-->>Client: 데이터 권리 결과
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 서비스에 데이터 권리 contributor v1을 추가하는 변경 사항을 정확하고 간결하게 설명합니다.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/planning-data-rights-contributor-v1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Comment thread apps/planning-service/src/planning-data-rights.test.ts Fixed
Comment thread apps/planning-service/src/planning-data-rights.test.ts Fixed
@seonghobae
seonghobae marked this pull request as ready for review August 10, 2026 18:53

@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: 5

🧹 Nitpick comments (5)
apps/planning-service/src/planning-data-rights.ts (3)

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

동적 테이블 식별자 대신 정적 문장을 사용하십시오.

countDeletedERASURE_TABLES 허용 목록으로 검증하므로 현재 주입 위험은 없습니다. 다만 코딩 가이드라인은 SQL 구조를 정적으로 유지하도록 요구합니다. 테이블별로 완전한 정적 DELETE 문장을 상수 맵으로 정의하고, 워크스페이스 값만 바인딩하십시오. 그러면 문자열 보간이 완전히 사라집니다.

가이드라인 근거: "Keep SQL structure static and parameterize dynamic values."

🤖 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-data-rights.ts` around lines 288 - 306,
Update countDeleted to eliminate dynamic table interpolation by introducing a
static SQL DELETE statement map keyed by the validated ERASURE_TABLES values.
Select the appropriate statement after allowlist validation, keep workspaceId as
the only bound parameter, and preserve the existing deletion result counting and
validation behavior.

Source: Coding guidelines


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

사전 점검 쿼리의 다섯 EXISTS 결과가 사용되지 않습니다.

다섯 개의 EXISTS 식에는 별칭이 없고, 반환 결과도 읽지 않습니다. PostgreSQL은 이 열들에 모두 exists라는 같은 이름을 부여합니다. 따라서 값이 서로 덮여 쓰이고 판단에 쓰이지 않습니다. ready는 영수증 테이블 존재 여부만 반영합니다.

데이터 존재 여부를 blocker에 반영하지 않을 계획이면 이 서브쿼리를 제거하십시오. 반영할 계획이면 각 식에 별칭을 부여하고 blockers에 포함하십시오.

♻️ 불필요한 서브쿼리 제거 예시
     const result = await this.client.query<{ erasure_receipts_ready: unknown }>(
       `SELECT
-         EXISTS (SELECT 1 FROM planning.goals WHERE workspace_id = $1),
-         EXISTS (SELECT 1 FROM planning.projects WHERE workspace_id = $1),
-         EXISTS (SELECT 1 FROM planning.tasks WHERE workspace_id = $1),
-         EXISTS (SELECT 1 FROM planning.today_aggregates WHERE workspace_id = $1),
-         EXISTS (
-           SELECT 1 FROM planning.today_idempotency_records WHERE workspace_id = $1
-         ),
-         to_regclass('planning.data_rights_erasure_receipts') IS NOT NULL
+         to_regclass('planning.data_rights_erasure_receipts') IS NOT NULL
            AS erasure_receipts_ready`,
       [workspaceId],
     );

참고: $1을 사용하는 다른 서브쿼리를 모두 제거하면 바인딩 값은 그대로 두어도 무해하지만, 사용되지 않는 매개변수를 남기지 않도록 쿼리와 값 배열을 함께 정리하십시오.

🤖 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-data-rights.ts` around lines 441 - 455,
Remove the five unused workspace data EXISTS subqueries from the preflight query
in the erasure-receipts readiness flow, since only erasure_receipts_ready is
consumed. Remove the now-unneeded workspaceId parameter from the query values
array as well, while preserving the to_regclass check and erasureReceiptsReady
assignment.

474-478: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

지원 PostgreSQL 버전을 문서화하십시오. PostgreSQL 17.10에는 hashtextextended(text, bigint)가 존재합니다. 현재 호출에서 $1text로 추론되고 0bigint로 변환되므로 타입 오류는 없습니다. 다만 이 함수는 공식 SQL 문서에 공개 함수로 문서화되지 않은 내부 함수입니다. 최소 지원 버전과 업그레이드 호환성 검사를 운영 계약에 명시하십시오.

🤖 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-data-rights.ts` around lines 474 - 478,
planning-data-rights의 advisory-lock 쿼리와 관련된 운영 계약에 최소 지원 PostgreSQL 버전을 명시하고,
hashtextextended 의존성이 있는 업그레이드 호환성 검사를 추가하십시오. PostgreSQL 17.10에서의 text, bigint
시그니처 지원을 기준으로 문서화하되, 해당 내부 함수가 업그레이드 후에도 사용 가능한지 확인하는 절차를 포함하십시오.
apps/planning-service/migrations/0004_data_rights_erasure_receipts.sql (1)

1-19: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

영수증 테이블에 COMMENT ON과 보존 정책을 문서화하십시오.

이 테이블은 requested_by_user_id를 무기한 보존합니다. 데이터 권리 삭제 이후에도 사용자 식별자가 남습니다. 보존 기간과 근거를 명시하십시오. COMMENT ON TABLE과 주요 열에 COMMENT ON COLUMN을 추가하면 계약 의도가 스키마에 남습니다.

🤖 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/migrations/0004_data_rights_erasure_receipts.sql`
around lines 1 - 19, Document the retention contract for
data_rights_erasure_receipts by adding COMMENT ON TABLE and COMMENT ON COLUMN
statements, especially for requested_by_user_id and created_at. State the
retention period and legal or operational basis, and clarify how long user
identifiers remain after erasure; cover the key receipt, request, identity,
timestamp, and digest columns without changing the table definition.
apps/planning-service/tests/postgres-data-rights.integration.test.ts (1)

38-48: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

유휴 확인과 DROP DATABASE 사이에 경합이 남습니다. 데이터베이스 이름도 중복 정의됩니다.

두 가지를 지적합니다.

  1. 카운트 조회와 DROP DATABASE 사이에 새 연결이 생기면 삭제가 database is being accessed by other users로 실패합니다. PostgreSQL 13 이상에서는 WITH (FORCE)가 이 경합을 없앱니다.
  2. DROP DATABASE IF EXISTS life_os_data_rights_test는 이름을 문자열로 직접 씁니다. 조회는 TEMPORARY_DATABASE_NAME을 사용합니다. 상수를 바꾸면 두 문장이 어긋납니다. DROP DATABASE는 파라미터를 받지 않으므로, 상수를 검증한 뒤 보간하십시오.
♻️ 제안
     if (activeConnections.rows[0]?.count === 0) {
-      await adminPool.query('DROP DATABASE IF EXISTS life_os_data_rights_test');
+      await adminPool.query(
+        `DROP DATABASE IF EXISTS "${TEMPORARY_DATABASE_NAME}" WITH (FORCE)`,
+      );
       return;
     }

TEMPORARY_DATABASE_NAME은 이 파일의 리터럴 상수입니다. 외부 입력이 아니므로 주입 위험은 없습니다. 대상 PostgreSQL 버전이 13 미만이면 WITH (FORCE) 대신 pg_terminate_backend를 사용하십시오.

🤖 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/tests/postgres-data-rights.integration.test.ts` around
lines 38 - 48, Update the database cleanup loop around TEMPORARY_DATABASE_NAME
to use PostgreSQL 13+ DROP DATABASE ... WITH (FORCE), eliminating the race
between the activity count and drop. Remove the hardcoded database name,
validate TEMPORARY_DATABASE_NAME as the known literal before safely
interpolating it into the DROP DATABASE statement, and preserve the existing
return behavior after successful cleanup.
🤖 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/planning-data-rights.test.ts`:
- Around line 60-72: Update the test around createPlanningRuntime and
dataRightsContributor to invoke handle with a valid export or erase_preflight
request, then assert the contractually expected response rather than only
checking field presence and function type. Keep runtime.close cleanup and use
the inert pool setup so the test verifies contributor wiring and the SQL-client
execution path through the real runtime.

In `@apps/planning-service/src/planning-data-rights.ts`:
- Around line 379-423: Update the export flow that builds the normalized data
object so large workspaces are handled without passing more than
MAXIMUM_ARRAY_ITEMS to normalizeJson at once. Add pagination or chunked
retrieval for each relevant table query, aggregate all chunks into the export
while preserving recordCount and existing validation, or expose and enforce an
explicit per-table limit with a clear follow-up path instead of silently
producing a partial export.
- Around line 21-29: planning-data-rights.ts의 DataRightsJsonPrimitive,
DataRightsJsonArray, DataRightsJsonObject, DataRightsJsonValue,
DataRightsContributorRequest, DataRightsContributorResponse 및 공개 메서드 handle에 계약을
설명하는 docstring을 추가하십시오. 각 연산의 의미와 실패 조건, requestId가 요청에서 응답·오류로 전달되는 규칙을 새 기여자가
구현을 확인하지 않아도 이해할 수 있도록 문서화하고, 타입 정의와 handle의 동작은 변경하지 마십시오.

In `@apps/planning-service/src/planning-runtime.ts`:
- Line 203: PlanningRuntime의 공개 선언인 dataRightsContributor에 TSDoc을 추가하세요. 문서에는 이
contributor가 Planning 소유 데이터 권리를 담당하며 요청 처리 범위에서 사용되고 공유 SQL client를 함께 사용한다는
계약을 설명하고, 새 기여자가 구현을 추적하지 않고도 이해할 수 있도록 작성하세요.

In `@apps/planning-service/tests/postgres-data-rights.integration.test.ts`:
- Around line 264-279: Remove the cleanupError throw from the finally block in
the surrounding test cleanup flow. Preserve cleanup failures in a flag or
variable, then throw the AggregateError after the try/finally completes, while
retaining the existing primaryFailure cause handling and cleanup behavior.

---

Nitpick comments:
In `@apps/planning-service/migrations/0004_data_rights_erasure_receipts.sql`:
- Around line 1-19: Document the retention contract for
data_rights_erasure_receipts by adding COMMENT ON TABLE and COMMENT ON COLUMN
statements, especially for requested_by_user_id and created_at. State the
retention period and legal or operational basis, and clarify how long user
identifiers remain after erasure; cover the key receipt, request, identity,
timestamp, and digest columns without changing the table definition.

In `@apps/planning-service/src/planning-data-rights.ts`:
- Around line 288-306: Update countDeleted to eliminate dynamic table
interpolation by introducing a static SQL DELETE statement map keyed by the
validated ERASURE_TABLES values. Select the appropriate statement after
allowlist validation, keep workspaceId as the only bound parameter, and preserve
the existing deletion result counting and validation behavior.
- Around line 441-455: Remove the five unused workspace data EXISTS subqueries
from the preflight query in the erasure-receipts readiness flow, since only
erasure_receipts_ready is consumed. Remove the now-unneeded workspaceId
parameter from the query values array as well, while preserving the to_regclass
check and erasureReceiptsReady assignment.
- Around line 474-478: planning-data-rights의 advisory-lock 쿼리와 관련된 운영 계약에 최소 지원
PostgreSQL 버전을 명시하고, hashtextextended 의존성이 있는 업그레이드 호환성 검사를 추가하십시오. PostgreSQL
17.10에서의 text, bigint 시그니처 지원을 기준으로 문서화하되, 해당 내부 함수가 업그레이드 후에도 사용 가능한지 확인하는 절차를
포함하십시오.

In `@apps/planning-service/tests/postgres-data-rights.integration.test.ts`:
- Around line 38-48: Update the database cleanup loop around
TEMPORARY_DATABASE_NAME to use PostgreSQL 13+ DROP DATABASE ... WITH (FORCE),
eliminating the race between the activity count and drop. Remove the hardcoded
database name, validate TEMPORARY_DATABASE_NAME as the known literal before
safely interpolating it into the DROP DATABASE statement, and preserve the
existing return behavior after successful cleanup.
🪄 Autofix

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: e7c43201-6394-40f2-9bde-cc8d3ac46741

📥 Commits

Reviewing files that changed from the base of the PR and between be05311 and 2351c75.

📒 Files selected for processing (6)
  • apps/planning-service/migrations/0004_data_rights_erasure_receipts.sql
  • apps/planning-service/src/planning-controller-authority.test.ts
  • apps/planning-service/src/planning-data-rights.test.ts
  • apps/planning-service/src/planning-data-rights.ts
  • apps/planning-service/src/planning-runtime.ts
  • apps/planning-service/tests/postgres-data-rights.integration.test.ts

Comment thread apps/planning-service/src/planning-data-rights.test.ts Outdated
Comment thread apps/planning-service/src/planning-data-rights.ts
Comment thread apps/planning-service/src/planning-data-rights.ts Outdated
Comment thread apps/planning-service/src/planning-runtime.ts

@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: 1

🤖 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/planning-data-rights.test.ts`:
- Around line 148-156: Update the export assertions in the planning data-rights
test to validate response.data.goals directly: assert its length equals
LARGE_EXPORT_GOAL_COUNT, and verify the first and last goal IDs match the
expected ordered export results. Keep the existing operation, recordCount, hash,
and query-count assertions.
🪄 Autofix

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: 2a50a45b-e83d-404f-a7b8-1b36001d74cc

📥 Commits

Reviewing files that changed from the base of the PR and between 2351c75 and f5bbb6b.

📒 Files selected for processing (4)
  • apps/planning-service/src/planning-data-rights.test.ts
  • apps/planning-service/src/planning-data-rights.ts
  • apps/planning-service/src/planning-runtime.ts
  • apps/planning-service/tests/postgres-data-rights.integration.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/planning-service/src/planning-runtime.ts
  • apps/planning-service/src/planning-data-rights.ts

Comment thread apps/planning-service/src/planning-data-rights.test.ts
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.

2 participants