Skip to content

fix: publish ownership events after commit - #1478

Merged
kang-heewon merged 1 commit into
trunkfrom
fix/1464-atomic-ownership-transfer
Jul 25, 2026
Merged

fix: publish ownership events after commit#1478
kang-heewon merged 1 commit into
trunkfrom
fix/1464-atomic-ownership-transfer

Conversation

@kang-heewon

@kang-heewon kang-heewon commented Jul 25, 2026

Copy link
Copy Markdown
Member

Outcome

Ownership transfer events now follow the transaction boundary established by the atomic store contract. When a transfer joins an ambient transaction, both membership update events are registered through publishAfterCommit; when the store operation commits independently, the existing typed no-active-transaction signal falls back to immediate publication. Other publication errors remain visible.

This is the remaining event-timing delta on top of #1481, which introduced the atomic in-memory and Drizzle ownership contracts and concurrency enforcement.

Fixes #1464

Verification

  • @croco/membership-core — 79/79 tests
  • Affected package lint — passed
  • Affected package typecheck — passed
  • Changeset-required gate — passed
  • Pre-push workspace tests — 226/226 tasks
  • Pre-push workspace typecheck — 225/225 tasks
  • Independent adversarial review — APPROVE

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

소유권 이전에 원자적 스토어 계약과 입력·결과 타입을 추가했습니다. 인메모리 및 Drizzle 구현체가 역할 변경을 처리하며, Manager와 Service는 커밋 후 멤버십 이벤트를 발행합니다. 실패, 동시성, 재시도, 이벤트 오류 테스트와 API 문서가 갱신되었습니다.

Changes

멤버십 소유권 이전

Layer / File(s) Summary
소유권 이전 계약과 공개 타입
packages/membership-core/src/libs/types.ts, packages/membership-core/src/libs/MembershipStore.ts, packages/membership-core/src/libs/problems/*, packages/docs/src/content/docs/api/..., docs/problem-code-registry.json, public-api-surface.snapshot.json, .changeset/...
OwnershipTransferInputOwnershipTransferResult, 원자적 커밋 계약, 자기 자신 전송 문제 및 관련 공개 문서를 추가했습니다.
인메모리 소유권 이전 구현
packages/membership-core/src/libs/InMemoryMembershipStore.ts, packages/membership-core/src/tests/MembershipStore.spec.ts
소유자를 admin으로 강등하고 대상 멤버를 owner로 승격하며 오류와 owner 수를 검증합니다.
Drizzle 트랜잭션 구현과 예외 처리
packages/membership-drizzle/src/libs/DrizzleMembershipStore.ts, packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts
단일 트랜잭션에서 역할을 변경하고 행 잠금, 멤버십 누락, 경쟁 전송, 승격 실패를 검증합니다.
서비스 위임과 커밋 후 이벤트 발행
packages/membership-core/src/libs/MembershipManager.ts, packages/membership-core/src/libs/MembershipService.ts, packages/membership-core/src/tests/MembershipManager.spec.ts, packages/membership-core/src/tests/MembershipService.spec.ts
스토어 연산을 호출하고 publishAfterCommit을 우선 사용하며 활성 트랜잭션이 없을 때 즉시 발행으로 폴백합니다. 실패·동시성·이벤트 오류 시나리오를 추가했습니다.

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

Sequence Diagram(s)

sequenceDiagram
  participant MembershipManager
  participant MembershipStore
  participant TransactionManager
  participant EventPublisher

  MembershipManager->>MembershipStore: transferOwnership(input)
  MembershipStore->>TransactionManager: 역할 변경 트랜잭션 실행
  TransactionManager-->>MembershipStore: OwnershipTransferResult
  MembershipStore-->>MembershipManager: previousToRole 반환
  MembershipManager->>EventPublisher: publishAfterCommit(MembershipUpdatedEvent)
  EventPublisher-->>MembershipManager: 커밋 후 이벤트 발행 또는 즉시 발행 폴백
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed 원자적 이전, 실패 시 이전 소유자 보존, 동시 전송 1개 성공, 커밋 후 이벤트 발행이 구현과 테스트로 확인됩니다.
Out of Scope Changes check ✅ Passed 변경 대부분이 소유권 이전 원자성, 문제 타입, 테스트, API 문서화에 집중되어 있어 범위를 벗어난 작업은 보이지 않습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 소유권 이전을 원자적으로 처리하고 커밋 후 이벤트를 발행하도록 바꾸는 핵심 변경을 잘 요약합니다.
✨ 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 fix/1464-atomic-ownership-transfer

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.

@github-actions

github-actions Bot commented Jul 25, 2026

Copy link
Copy Markdown

📊 Benchmark Results

✅ All benchmarks passed

Benchmark p75 Threshold Baseline vs Baseline Status Notes
CrocoApp constructor 6.8μs 30.0ms 8.2μs -17.4% -
CrocoApp lambdaHandler (10 controllers) 156.6μs 50.0ms 258.4μs -39.4% -
Lambda cold-start simulation 340.0μs 80.0ms 418.1μs -18.7% -
Lambda cold-start with headers 246.2μs 80.0ms 369.7μs -33.4% -
Lambda cold-start with binary body 208.9μs 80.0ms 339.1μs -38.4% -
Lambda cold-start with query params 186.3μs 80.0ms 301.3μs -38.2% -
Lambda cold-start with authorizer context 190.5μs 80.0ms 299.8μs -36.5% -
Lambda cold-start realistic scenario 189.1μs 80.0ms 299.2μs -36.8% -
EventBusConfig.start (10 handlers) 1.2μs 10.0ms 1.4μs -18.5% -
EventPublisher.publishNow single event 1.4μs 2.0ms 1.7μs -17.5% -
DefaultHandlerResolver.resolve × 10 0.1μs 5.0ms 0.1μs -28.7% -
Container.get singleton (cold) 46.6μs 5.0ms 70.3μs -33.7% -
Container.register × 50 components 2.5ms 10.0ms 3.2ms -22.2% -
Container.validate (50 components) 2.9ms 20.0ms 3.4ms -15.5% -
Container.get singleton (warm) 1.2μs 500.0μs 1.6μs -26.8% -
TelemetryRuntime.init (lambda preset) 1.8μs 200.0ms 1.1ms -99.8% -
lambdaPreset config creation 1.2μs 2.0ms 1.4μs -17.1% -

Updated: 2026-07-25T09:04:40.139Z · Commit: ea111df

@kang-heewon
kang-heewon force-pushed the fix/1464-atomic-ownership-transfer branch from 9cf8fa0 to e99910b Compare July 25, 2026 07:05

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/membership-core/src/libs/MembershipManager.ts (2)

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

transferOwnershippublishAfterCommitOrNow가 두 클래스에 완전히 동일하게 중복 구현되어 있습니다. 원자적 이전 위임(store.transferOwnership)과 커밋-후 이벤트 발행(publishAfterCommit → 실패 시 publishSafely 폴백) 로직 자체는 정확하지만, 이 핵심 정합성 로직이 두 파일에 각각 존재해 한쪽만 수정될 경우 두 구현체 간 실패 시맨틱이 어긋날 위험이 있습니다.

  • packages/membership-core/src/libs/MembershipManager.ts#L115-L193: transferOwnershippublishAfterCommitOrNow를 공유 헬퍼(함수 또는 공통 베이스 클래스)로 추출합니다.
  • packages/membership-core/src/libs/MembershipService.ts#L129-L207: 동일한 공유 헬퍼를 사용하도록 변경합니다.
🤖 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 `@packages/membership-core/src/libs/MembershipManager.ts` around lines 115 -
193, Extract the duplicated ownership-transfer and commit-aware event-publishing
logic from MembershipManager.ts lines 115-193 and MembershipService.ts lines
129-207 into a shared helper or common base abstraction. Update both
MembershipManager.transferOwnership and MembershipService.transferOwnership to
use it, preserving the atomic store.transferOwnership call and
publishAfterCommit fallback to publishSafely semantics in both files.

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

transferOwnership/publishAfterCommitOrNow 로직이 MembershipService.ts와 완전히 동일합니다.

원자적 이전 위임 및 커밋-후 이벤트 발행 흐름 자체는 정확합니다(previousToRole을 스토어에서 받아 oldRole로 사용, EventAfterCommitRequiresActiveTransactionProblem만 폴백). 다만 이 신규 로직이 MembershipService.ts에 바이트 단위로 중복되어 있어, 이후 한쪽만 수정되면 커밋 순서·원자성 보장이 두 구현체 간에 어긋날 위험이 있습니다. 공유 헬퍼(예: 별도 함수 또는 공통 베이스 클래스)로 추출하는 것을 권장합니다.

export async function transferOwnershipAtomic(
  store: MembershipStore,
  eventPublisher: EventPublisher,
  tenantId: string,
  fromUserId: string,
  toUserId: string,
): Promise<void> {
  const { previousToRole } = await store.transferOwnership({ tenantId, fromUserId, toUserId });
  // ...publishAfterCommitOrNow 호출 두 번을 여기로 이동
}

Also applies to: 182-193

🤖 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 `@packages/membership-core/src/libs/MembershipManager.ts` around lines 115 -
139, Extract the duplicated transferOwnership flow from
MembershipManager.transferOwnership and MembershipService.transferOwnership into
a shared helper or common base abstraction. The shared implementation must
retain store.transferOwnership, previousToRole handling, event ordering, and
publishAfterCommitOrNow behavior, with both callers delegating to it so future
changes cannot diverge.
packages/membership-core/src/libs/MembershipService.ts (1)

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

MembershipManager.tstransferOwnership/publishAfterCommitOrNow와 완전히 동일한 로직입니다.

두 클래스가 핵심 원자성/이벤트 발행 로직을 각자 보유하고 있어 향후 변경 시 동기화가 누락될 위험이 있습니다. MembershipManager.ts 쪽 코멘트에서 제안한 공유 헬퍼 추출을 함께 적용하는 것을 권장합니다.

Also applies to: 196-207

🤖 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 `@packages/membership-core/src/libs/MembershipService.ts` around lines 129 -
153, Extract the shared ownership-transfer and event-publication logic used by
MembershipService.transferOwnership and
MembershipManager.transferOwnership/publishAfterCommitOrNow into a common
helper. Update both classes to delegate to that helper while preserving the
existing transaction atomicity and emitted MembershipUpdatedEvent payloads.
🤖 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 @.changeset/atomic-membership-ownership.md:
- Around line 2-3: Update the `@croco/membership-core` version bump in the
changeset from minor to major, reflecting the new abstract
MembershipStore.transferOwnership(...) requirement; leave the
`@croco/membership-drizzle` patch bump unchanged.

In `@packages/membership-core/src/libs/InMemoryMembershipStore.ts`:
- Around line 48-82: Update transferOwnership in InMemoryMembershipStore to
handle input.fromUserId === input.toUserId consistently with MembershipManager,
MembershipService, and the Drizzle store: either reject the transfer using the
established validation/problem or ensure both stores return the same
previousToRole and event payload. Apply the guard before mutating storage so
same-user transfers cannot produce divergent results.

In `@packages/membership-core/src/libs/MembershipManager.ts`:
- Around line 4-7: Update the EventPublisher imports in
packages/membership-core/src/libs/MembershipManager.ts lines 4-7 and
packages/membership-core/src/libs/MembershipService.ts lines 4-7 to use the type
modifier, while keeping EventAfterCommitRequiresActiveTransactionProblem as a
value import.

In `@packages/membership-core/src/libs/MembershipStore.ts`:
- Around line 18-24: Update MembershipStore.transferOwnership to document that
fromUserId and toUserId must differ, and validate this precondition before any
changes in DrizzleMembershipStore.transferOwnership, rejecting equal IDs with
the appropriate Problem subclass. Regenerate or update the API documentation in
packages/docs/src/content/docs/api/membership-core/src/classes/MembershipStore.md
(lines 182-199) and
packages/docs/src/content/docs/api/membership-core/src/classes/InMemoryMembershipStore.md
(lines 202-223) to include the same precondition.

In `@packages/membership-core/src/tests/MembershipManager.spec.ts`:
- Around line 402-430: Update the rollback test around
MembershipManager.transferOwnership and the fake TransactionContext to model an
explicit rollback path that discards registered after-commit hooks without
executing them. Invoke that rollback path after asserting hooks were deferred,
then verify publishCommittedEvent remains uncalled; do not merely clear
afterCommitHooks directly, and extend the context shape only as needed for this
test.

In `@packages/membership-core/src/tests/MembershipService.spec.ts`:
- Around line 49-51: MembershipService.transferOwnership lacks coverage for
atomicity, concurrency, retries, rollback, and post-commit event behavior. Add
corresponding tests in MembershipService.spec.ts, using the scenarios from
MembershipManager.spec.ts around its transfer tests, and exercise
transferOwnership plus publishAfterCommit/publishAfterCommitOrNow to verify
state preservation on store failure, retry behavior, a single winner for
concurrent transfers, event ordering after commit, and event disposal on
rollback.

In `@packages/membership-core/src/tests/MembershipStore.spec.ts`:
- Around line 142-161: Add store-level failure-path tests alongside “should
transfer ownership atomically” in MembershipStore.spec.ts: verify
transferOwnership rejects with OwnershipTransferRequiredProblem when the source
user is not an owner, and rejects with MembershipNotFoundProblem when the target
is missing while preserving the existing owner role. Include setup for each
scenario and assert the relevant error type and unchanged ownership state.

In `@packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts`:
- Around line 2-7: Separate the type-only imports Membership and
MembershipCreateInput from the value imports in DrizzleMembershipStore.spec.ts
by using a dedicated import type declaration, while keeping
MembershipNotFoundProblem and OwnershipTransferRequiredProblem in the regular
import block and preserving the existing import ordering.

---

Outside diff comments:
In `@packages/membership-core/src/libs/MembershipManager.ts`:
- Around line 115-193: Extract the duplicated ownership-transfer and
commit-aware event-publishing logic from MembershipManager.ts lines 115-193 and
MembershipService.ts lines 129-207 into a shared helper or common base
abstraction. Update both MembershipManager.transferOwnership and
MembershipService.transferOwnership to use it, preserving the atomic
store.transferOwnership call and publishAfterCommit fallback to publishSafely
semantics in both files.
- Around line 115-139: Extract the duplicated transferOwnership flow from
MembershipManager.transferOwnership and MembershipService.transferOwnership into
a shared helper or common base abstraction. The shared implementation must
retain store.transferOwnership, previousToRole handling, event ordering, and
publishAfterCommitOrNow behavior, with both callers delegating to it so future
changes cannot diverge.

In `@packages/membership-core/src/libs/MembershipService.ts`:
- Around line 129-153: Extract the shared ownership-transfer and
event-publication logic used by MembershipService.transferOwnership and
MembershipManager.transferOwnership/publishAfterCommitOrNow into a common
helper. Update both classes to delegate to that helper while preserving the
existing transaction atomicity and emitted MembershipUpdatedEvent payloads.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 94b9221b-3f4f-47e4-90da-d0cb1f430eca

📥 Commits

Reviewing files that changed from the base of the PR and between 96b6b80 and e99910b.

📒 Files selected for processing (17)
  • .changeset/atomic-membership-ownership.md
  • packages/docs/src/content/docs/api/membership-core/src/classes/InMemoryMembershipStore.md
  • packages/docs/src/content/docs/api/membership-core/src/classes/MembershipStore.md
  • packages/docs/src/content/docs/api/membership-core/src/type-aliases/OwnershipTransferInput.md
  • packages/docs/src/content/docs/api/membership-core/src/type-aliases/OwnershipTransferResult.md
  • packages/docs/src/content/docs/api/membership-drizzle/src/classes/DrizzleMembershipStore.md
  • packages/membership-core/src/libs/InMemoryMembershipStore.ts
  • packages/membership-core/src/libs/MembershipManager.ts
  • packages/membership-core/src/libs/MembershipService.ts
  • packages/membership-core/src/libs/MembershipStore.ts
  • packages/membership-core/src/libs/types.ts
  • packages/membership-core/src/tests/MembershipManager.spec.ts
  • packages/membership-core/src/tests/MembershipService.spec.ts
  • packages/membership-core/src/tests/MembershipStore.spec.ts
  • packages/membership-drizzle/src/libs/DrizzleMembershipStore.ts
  • packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts
  • public-api-surface.snapshot.json

Comment thread .changeset/atomic-membership-ownership.md Outdated
Comment thread packages/membership-core/src/libs/InMemoryMembershipStore.ts Outdated
Comment thread packages/membership-core/src/libs/MembershipManager.ts
Comment thread packages/membership-core/src/libs/MembershipStore.ts Outdated
Comment thread packages/membership-core/src/tests/MembershipManager.spec.ts Outdated
Comment thread packages/membership-core/src/tests/MembershipService.spec.ts
Comment thread packages/membership-core/src/tests/MembershipStore.spec.ts
Comment thread packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts Outdated
@kang-heewon
kang-heewon force-pushed the fix/1464-atomic-ownership-transfer branch from e99910b to 676f6e6 Compare July 25, 2026 07:33
@kang-heewon

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@kang-heewon
kang-heewon force-pushed the fix/1464-atomic-ownership-transfer branch from 676f6e6 to cf436e1 Compare July 25, 2026 08:20

@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

🤖 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
`@packages/docs/src/content/docs/api/membership-core/src/classes/SameUserOwnershipTransferProblem.md`:
- Around line 7-14: Update the JSDoc for the MembershipProblems.ts
SameUserOwnershipTransferProblem class to describe the
SAME_USER_OWNERSHIP_TRANSFER error and prohibit transferring ownership to the
same user, replacing the copied seat-limit wording. Regenerate
SameUserOwnershipTransferProblem.md so its summary and description reflect the
corrected source documentation.

In `@packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts`:
- Around line 62-64: Update the mockTxManager.run implementation in
DrizzleMembershipStore.spec.ts to emulate transaction isolation and rollback
instead of directly executing against shared state. Ensure failed operations
restore the pre-transaction state, and strengthen the failure tests around the
existing-owner demotion to assert that the owner remains "owner" after promotion
fails.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 4deb11dc-485e-4ab5-b136-da027a0fdcb5

📥 Commits

Reviewing files that changed from the base of the PR and between e99910b and cf436e1.

⛔ Files ignored due to path filters (1)
  • packages/problems-core/src/generated/problem-code-registry.ts is excluded by !**/generated/**
📒 Files selected for processing (23)
  • .changeset/atomic-membership-ownership.md
  • docs/problem-code-registry.json
  • packages/docs/src/content/docs/api/membership-core/src/classes/InMemoryMembershipStore.md
  • packages/docs/src/content/docs/api/membership-core/src/classes/MembershipStore.md
  • packages/docs/src/content/docs/api/membership-core/src/classes/SameUserOwnershipTransferProblem.md
  • packages/docs/src/content/docs/api/membership-core/src/type-aliases/OwnershipTransferInput.md
  • packages/docs/src/content/docs/api/membership-core/src/type-aliases/OwnershipTransferResult.md
  • packages/docs/src/content/docs/api/membership-drizzle/src/classes/DrizzleMembershipStore.md
  • packages/docs/src/content/docs/api/problems-core/src/classes/Problem.md
  • packages/docs/src/content/docs/en/reference/problem-recovery-cookbook.md
  • packages/membership-core/src/index.ts
  • packages/membership-core/src/libs/InMemoryMembershipStore.ts
  • packages/membership-core/src/libs/MembershipManager.ts
  • packages/membership-core/src/libs/MembershipService.ts
  • packages/membership-core/src/libs/MembershipStore.ts
  • packages/membership-core/src/libs/problems/MembershipProblems.ts
  • packages/membership-core/src/libs/types.ts
  • packages/membership-core/src/tests/MembershipManager.spec.ts
  • packages/membership-core/src/tests/MembershipService.spec.ts
  • packages/membership-core/src/tests/MembershipStore.spec.ts
  • packages/membership-drizzle/src/libs/DrizzleMembershipStore.ts
  • packages/membership-drizzle/src/tests/DrizzleMembershipStore.spec.ts
  • public-api-surface.snapshot.json

@kang-heewon
kang-heewon force-pushed the fix/1464-atomic-ownership-transfer branch from cf436e1 to 98d0480 Compare July 25, 2026 08:32
@kang-heewon
kang-heewon force-pushed the fix/1464-atomic-ownership-transfer branch from 98d0480 to f3243fa Compare July 25, 2026 08:42
@kang-heewon kang-heewon changed the title fix: make ownership transfers atomic fix: publish ownership events after commit Jul 25, 2026
@kang-heewon

Copy link
Copy Markdown
Member Author

@coderabbitai resolve

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
✅ Action performed

Comments resolved and changes approved.

@kang-heewon
kang-heewon merged commit 4237d35 into trunk Jul 25, 2026
11 of 15 checks passed
@kang-heewon
kang-heewon deleted the fix/1464-atomic-ownership-transfer branch July 25, 2026 14:34
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.

[membership-core] Make ownership transfer atomic and preserve the current owner on failure

1 participant