Skip to content

feat(habit): expose trusted data-rights contributor transport - #192

Merged
seonghobae merged 14 commits into
mainfrom
feat/habit-data-rights-http-v1
Aug 11, 2026
Merged

feat(habit): expose trusted data-rights contributor transport#192
seonghobae merged 14 commits into
mainfrom
feat/habit-data-rights-http-v1

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • expose the existing Habit-owned life-os.data-rights-contributor.v1 participant over an internal service-authenticated HTTP boundary
  • bind HMAC authority to workspace, requesting user, request ID, operation/purpose, destructive idempotency key, 60-second lifetime, exact method, and exact route
  • fail closed on absent/forged/stale authority before Habit persistence is invoked
  • add boundary and controller regression tests for cross-operation, cross-resource, and idempotency-key replay

Scope / maturity

This advances #55 but does not claim the end-to-end data-rights workflow is complete. Identity orchestration still needs a production client/registration path for this contributor, followed by the remaining contributor coverage, artifact delivery, lifecycle/audit, and recovery evidence described by the canonical data-rights plan.

Validation

CI and required security/review workflows must validate exact head; no pending/skipped/stale result is treated as passing.

Refs #55

Summary by CodeRabbit

  • 새 기능

    • 데이터 권리 요청을 위한 내부 API를 추가했습니다.
    • 요청 형식, 작업 유형, UUID, 계약 버전 및 삭제 멱등성 키를 검증합니다.
    • 유효한 서명과 요청만 처리하며, 위조·만료·변조된 요청은 거부합니다.
    • 삭제 요청의 증거 재사용을 차단해 중복 처리를 방지합니다.
    • 처리 불가 상황에는 제한된 오류 응답을 제공합니다.
    • 관련 인증 설정을 위한 환경 변수 예시를 추가했습니다.
  • 테스트

    • 정상 요청, 동시 요청, 만료, 중복, 잘못된 형식 및 설정 누락을 검증합니다.

@coderabbitai

coderabbitai Bot commented Aug 11, 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: 3cbabced-dac1-4671-8167-0225e3892a3f

📥 Commits

Reviewing files that changed from the base of the PR and between c64b80d and 4b239c3.

📒 Files selected for processing (2)
  • apps/habit-service/src/habit-data-rights-authority-replay.test.ts
  • apps/habit-service/src/habit-data-rights-http-boundary.test.ts
📝 Walkthrough

Walkthrough

Habit 데이터 권리 요청의 HTTP 신뢰 경계와 HMAC 인증을 추가했습니다. 인증된 요청만 내부 contributor로 전달합니다. Destructive 요청은 PostgreSQL replay guard로 한 번만 처리합니다. 요청 오류와 contributor 오류를 전용 HTTP 예외로 변환합니다. 관련 테스트와 시크릿 설정 예시도 추가했습니다.

Changes

Habit 데이터 권리 요청

Layer / File(s) Summary
요청 계약과 HMAC 검증
apps/habit-service/src/habit-data-rights-http-boundary.ts, apps/habit-service/src/habit-data-rights-http-boundary.test.ts
요청 구조, UUID, 계약 버전, 작업 유형, idempotencyKey, HTTP method/path를 검증합니다. 시크릿, 발급 시각, HMAC-SHA256 서명을 검증합니다.
Authority 증거의 원자적 Replay 소비
apps/habit-service/migrations/0003_data_rights_authority_replay.sql, apps/habit-service/src/habit-data-rights-authority-replay.ts, apps/habit-service/src/habit-data-rights-authority-replay.test.ts, apps/habit-service/src/habit-data-rights-authority-replay.integration.test.ts
PostgreSQL에 authority 증거 digest와 만료 시각을 저장합니다. 만료 레코드를 정리한 뒤 원자적 INSERT로 최초 소비만 허용합니다. 동시 소비와 만료 동작을 검증합니다.
Runtime과 Contributor 엔드포인트 연결
apps/habit-service/src/habit-runtime.ts, apps/habit-service/src/main.ts, apps/habit-service/src/habit-data-rights-controller-authority.test.ts
POST /v1/internal/data-rights/contributor를 등록합니다. 검증된 요청을 contributor 처리기로 전달합니다. Replay, 위조 서명, 시크릿 누락, contributor 오류를 제한된 HTTP 예외로 변환합니다.
신뢰 시크릿 설정
.env.example
HABIT_DATA_RIGHTS_CONTEXT_SECRET 설정 예시를 추가합니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant HabitDataRightsController
  participant HabitDataRightsHttpBoundary
  participant HabitRuntime
  participant PostgreSQL
  participant DataRightsContributor
  Client->>HabitDataRightsController: POST /v1/internal/data-rights/contributor
  HabitDataRightsController->>HabitDataRightsHttpBoundary: 요청 본문과 HMAC 서명 검증
  HabitDataRightsHttpBoundary->>HabitRuntime: destructive 요청의 replay 증거 소비
  HabitRuntime->>PostgreSQL: digest 원자적 소비
  PostgreSQL-->>HabitRuntime: 최초 소비 여부 반환
  HabitRuntime-->>HabitDataRightsHttpBoundary: 소비 결과 반환
  HabitDataRightsHttpBoundary-->>HabitDataRightsController: 정규화된 요청 반환
  HabitDataRightsController->>DataRightsContributor: 검증된 요청 전달
  DataRightsContributor-->>HabitDataRightsController: 처리 결과 반환
  HabitDataRightsController-->>Client: HTTP 응답 반환
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 신뢰된 data-rights contributor 전송 경계를 노출하는 이번 변경의 주요 내용을 간결하고 정확하게 설명합니다.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.
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-data-rights-http-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.

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

🤖 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/habit-service/src/habit-data-rights-http-boundary.ts`:
- Around line 195-239: Update parseTrustedHabitDataRightsRequest in
apps/habit-service/src/habit-data-rights-http-boundary.ts (lines 195-239) to
atomically consume a replay identifier in the habit owner’s persistence store
before destructive erase persistence, rejecting duplicate signed authority. In
apps/habit-service/src/habit-data-rights-http-boundary.test.ts (lines 104-127),
submit the same signed erase request twice and verify the contributor is not
invoked on the second submission.
- Around line 20-25: 문제 응답 계약과 요청 정규화·검증·digest 생성 규칙이 문서화되지 않았습니다.
apps/habit-service/src/habit-data-rights-http-boundary.ts:20-25의
HabitDataRightsProblemDetails에 외부 응답 형식을, :36-41의 NormalizedRequest에 정규화 보장을,
:43-188의 검증 helper와 requestDigest에 실패 조건 및 canonical digest 입력 순서를 설명하는
docstring을 추가하세요.
- Around line 243-245: Update toHabitDataRightsHttpException so only
permission-parsing errors are returned unchanged; do not pass through arbitrary
HttpException instances raised by dataRightsContributor.handle(). Convert every
contributor error, including HttpException, into the bounded credential-free 503
data_rights_unavailable problem response, and add a regression test covering a
contributor that throws HttpException.
🪄 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: 856bfe4a-7f07-4014-9942-abd4787e0086

📥 Commits

Reviewing files that changed from the base of the PR and between 2042348 and d3e6dca.

📒 Files selected for processing (5)
  • .env.example
  • apps/habit-service/src/habit-data-rights-controller-authority.test.ts
  • apps/habit-service/src/habit-data-rights-http-boundary.test.ts
  • apps/habit-service/src/habit-data-rights-http-boundary.ts
  • apps/habit-service/src/main.ts

Comment thread apps/habit-service/src/habit-data-rights-http-boundary.ts
Comment thread apps/habit-service/src/habit-data-rights-http-boundary.ts Outdated
Comment thread apps/habit-service/src/habit-data-rights-http-boundary.ts Outdated

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

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.

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 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 (5)
apps/habit-service/src/habit-data-rights-controller-authority.test.ts (1)

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

정규 HMAC 필드 순서의 테스트 사본을 하나로 합치세요.

signature 헬퍼는 habit-data-rights-http-boundary.test.ts의 서명 헬퍼와 동일한 필드 순서를 반복합니다. production requestDigest까지 포함하면 같은 계약이 세 곳에 존재합니다. 필드 순서가 바뀌면 두 테스트 파일을 모두 수정해야 합니다. 수정 누락 시 한쪽 테스트만 통과합니다.

공유 테스트 헬퍼 모듈로 추출하세요.

🤖 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/src/habit-data-rights-controller-authority.test.ts` around
lines 27 - 45, Extract the duplicated canonical HMAC field ordering from
signature in habit-data-rights-controller-authority.test.ts and the
corresponding helper in habit-data-rights-http-boundary.test.ts into one shared
test helper module, reusing the production requestDigest contract rather than
maintaining separate field lists. Update both test files to call the shared
helper and remove their local signature implementations.
apps/habit-service/src/habit-data-rights-http-boundary.ts (1)

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

오류 원인을 관측 가능하게 남기세요.

toHabitDataRightsHttpException은 모든 오류를 폐기합니다. 응답을 bounded, credential-free로 유지하는 결정은 올바릅니다. 다만 원인이 완전히 사라지면 운영 중 장애 원인 분석이 불가능합니다. 오류 종류와 requestId 같은 비민감 식별자만 구조화 로그로 남기세요. 원문 메시지와 stack trace는 계속 응답과 보존 아티팩트에서 제외하세요.

🤖 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/src/habit-data-rights-http-boundary.ts` around lines 299 -
307, The toHabitDataRightsHttpException function currently discards all error
context; add structured logging there that records only the error type and a
non-sensitive requestId when available. Keep the existing bounded,
credential-free response unchanged, and do not include the original message or
stack trace in the response or preserved artifacts.

Source: Coding guidelines

apps/habit-service/src/habit-data-rights-authority-replay.test.ts (1)

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

형식은 맞지만 존재하지 않는 시각 케이스를 추가하세요.

현재 malformed 케이스는 패턴 자체가 틀린 '2026-08-12'뿐입니다. habit-data-rights-authority-replay.ts 51행의 parsed.toISOString() !== value 분기는 검증되지 않습니다. 패턴은 통과하지만 정규 형식이 아닌 값을 추가하세요. 이 분기는 커버리지 게이트 대상입니다.

💚 제안 변경
     for (const evidence of [
       { evidenceDigest: 'not-a-digest', expiresAt: EXPIRES_AT },
       { evidenceDigest: DIGEST, expiresAt: '2026-08-12' },
+      { evidenceDigest: DIGEST, expiresAt: '2026-02-30T00:00:00.000Z' },
     ]) {
🤖 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/src/habit-data-rights-authority-replay.test.ts` around
lines 58 - 61, Extend the evidence cases in the replay test around the existing
expiresAt validation to include a timestamp that matches the accepted pattern
but fails the canonical ISO representation check, so the parsed.toISOString()
!== value branch in the replay implementation is exercised while retaining the
current malformed cases.

Source: Coding guidelines

apps/habit-service/src/habit-data-rights-authority-replay.ts (1)

78-92: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

만료 정리를 요청 경로에서 분리하는 방안을 검토하세요.

consume는 요청마다 DELETE를 먼저 실행합니다. 정확성은 유지됩니다. INSERTWHERE $2::timestamptz >= now()로 만료를 다시 확인하기 때문입니다. 다만 모든 destructive 요청이 추가 쓰기를 발생시킵니다. 이는 write amplification과 vacuum 부하를 만듭니다.

만료 정리를 주기 작업이나 파티션 삭제로 옮기는 방안을 검토하세요. 요청 경로에는 INSERT만 남기세요.

🤖 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/src/habit-data-rights-authority-replay.ts` around lines 78
- 92, Remove the per-request DELETE cleanup from the consume flow in
habit-data-rights-authority-replay.ts, leaving the INSERT query’s expiresAt
validation and conflict handling unchanged. Move expired-record cleanup to an
appropriate periodic maintenance job or partition-retention mechanism, and
ensure the request path performs only the INSERT operation.
apps/habit-service/src/habit-data-rights-http-boundary.test.ts (1)

147-158: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

응답 본문이 저장소 오류 세부정보를 포함하지 않음을 검증하세요.

이 테스트는 'database detail' 메시지를 가진 오류를 주입합니다. 그러나 상태 코드만 확인합니다. 문제 응답 본문에 해당 문자열이 없음을 함께 검증하세요. 이 검증은 저장소 세부정보 유출 회귀를 직접 차단합니다.

🛡️ 제안 변경
-    await expectHttpStatus(
-      () =>
-        parseTrustedHabitDataRightsRequest(
-          ERASE_REQUEST,
-          { issuedAt, signature },
-          CONTEXT_SECRET,
-          BINDING,
-          NOW_SECONDS,
-          { consume: vi.fn().mockRejectedValue(new Error('database detail')) },
-        ),
-      503,
-    );
+    let thrown: unknown;
+    try {
+      await parseTrustedHabitDataRightsRequest(
+        ERASE_REQUEST,
+        { issuedAt, signature },
+        CONTEXT_SECRET,
+        BINDING,
+        NOW_SECONDS,
+        { consume: vi.fn().mockRejectedValue(new Error('database detail')) },
+      );
+    } catch (error) {
+      thrown = error;
+    }
+    expect(thrown).toBeInstanceOf(HttpException);
+    expect((thrown as HttpException).getStatus()).toBe(503);
+    expect(JSON.stringify((thrown as HttpException).getResponse())).not.toContain(
+      'database detail',
+    );
🤖 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/src/habit-data-rights-http-boundary.test.ts` around lines
147 - 158, Update the test around parseTrustedHabitDataRightsRequest to capture
the full HTTP response from the injected consume rejection, assert status 503,
and additionally verify the response body does not contain “database detail”.

Source: Coding guidelines

🤖 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/habit-service/src/habit-data-rights-authority-replay.integration.test.ts`:
- Around line 71-87: Update the replay-consumption test around guard.consume so
evidenceDigest is deterministically derived from rawSignature using the
production digesting path or equivalent domain helper, then pass that digest to
both consume calls. Keep the assertions verifying the stored digest and absence
of rawSignature, so the test can fail if the original signature is persisted.

In `@apps/habit-service/src/habit-runtime.ts`:
- Line 3: HabitRuntime의 dataRightsAuthorityReplayGuard 필드 타입을 구체 구현체인
PostgresHabitDataRightsAuthorityReplayGuard가 아닌
HabitDataRightsAuthorityReplayGuardPort로 변경하세요.
parseTrustedHabitDataRightsRequest가 요구하는 포트 계약을 사용하도록 관련 import와 타입 참조를 정리하고,
createHabitRuntime에서는 기존처럼 PostgreSQL 구현을 생성해 주입하세요.

---

Nitpick comments:
In `@apps/habit-service/src/habit-data-rights-authority-replay.test.ts`:
- Around line 58-61: Extend the evidence cases in the replay test around the
existing expiresAt validation to include a timestamp that matches the accepted
pattern but fails the canonical ISO representation check, so the
parsed.toISOString() !== value branch in the replay implementation is exercised
while retaining the current malformed cases.

In `@apps/habit-service/src/habit-data-rights-authority-replay.ts`:
- Around line 78-92: Remove the per-request DELETE cleanup from the consume flow
in habit-data-rights-authority-replay.ts, leaving the INSERT query’s expiresAt
validation and conflict handling unchanged. Move expired-record cleanup to an
appropriate periodic maintenance job or partition-retention mechanism, and
ensure the request path performs only the INSERT operation.

In `@apps/habit-service/src/habit-data-rights-controller-authority.test.ts`:
- Around line 27-45: Extract the duplicated canonical HMAC field ordering from
signature in habit-data-rights-controller-authority.test.ts and the
corresponding helper in habit-data-rights-http-boundary.test.ts into one shared
test helper module, reusing the production requestDigest contract rather than
maintaining separate field lists. Update both test files to call the shared
helper and remove their local signature implementations.

In `@apps/habit-service/src/habit-data-rights-http-boundary.test.ts`:
- Around line 147-158: Update the test around parseTrustedHabitDataRightsRequest
to capture the full HTTP response from the injected consume rejection, assert
status 503, and additionally verify the response body does not contain “database
detail”.

In `@apps/habit-service/src/habit-data-rights-http-boundary.ts`:
- Around line 299-307: The toHabitDataRightsHttpException function currently
discards all error context; add structured logging there that records only the
error type and a non-sensitive requestId when available. Keep the existing
bounded, credential-free response unchanged, and do not include the original
message or stack trace in the response or preserved artifacts.
🪄 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: 1421dc98-3cc4-4181-ac63-66135e6b204a

📥 Commits

Reviewing files that changed from the base of the PR and between d3e6dca and d6d1933.

📒 Files selected for processing (9)
  • apps/habit-service/migrations/0003_data_rights_authority_replay.sql
  • apps/habit-service/src/habit-data-rights-authority-replay.integration.test.ts
  • apps/habit-service/src/habit-data-rights-authority-replay.test.ts
  • apps/habit-service/src/habit-data-rights-authority-replay.ts
  • apps/habit-service/src/habit-data-rights-controller-authority.test.ts
  • apps/habit-service/src/habit-data-rights-http-boundary.test.ts
  • apps/habit-service/src/habit-data-rights-http-boundary.ts
  • apps/habit-service/src/habit-runtime.ts
  • apps/habit-service/src/main.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/habit-service/src/main.ts

Comment thread apps/habit-service/src/habit-runtime.ts Outdated
@seonghobae
seonghobae merged commit 95093f5 into main Aug 11, 2026
38 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