Skip to content

feat(admin): config-sync mirror mode + opt-in user sync (ADR 0003, PR 4)#55

Merged
jlc488 merged 1 commit into
mainfrom
feat/config-sync-mirror-users
Jun 3, 2026
Merged

feat(admin): config-sync mirror mode + opt-in user sync (ADR 0003, PR 4)#55
jlc488 merged 1 commit into
mainfrom
feat/config-sync-mirror-users

Conversation

@jlc488

@jlc488 jlc488 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Extends config-sync (ADR 0003) beyond additive merge with mirror mode and opt-in user sync. Both stay gated by ConfigSyncAutoConfiguration (off unless devslab.kit.config-sync.enabled=true) and refused under a production profile.

mirror mode

Makes the target match the bundle exactly. On top of the merge it:

  • reconciles each role's grants — revokes permissions the bundle omits for that role;
  • deletes definitional entities absent from the bundle.

There are no FK constraints between roles / permissions / users (only platform_menu.parent_id cascades), so deletes clean their own join rows and run in a safe order:

  • menus — deleted leaf-first (a child before its parent);
  • roles — a role still assigned to any user is skipped (mirror never strips a user's role); otherwise its permission grants are revoked, then it is deleted;
  • permissions — revoked from the tenant's roles, then deleted. (Permissions are global, so mirror is intended for single-tenant-per-deployment use.)

ImportResult.Section gains deleted and skipped to report this.

user sync (includeUsers, opt-in, default off)

  • export carries users by login id with no password — only email, status and role codes.
  • import is create-only: a missing user is created with no usable password + mustChangePassword, then assigned roles by code. An existing user is never overwritten (reported as skipped).
  • ConfigBundle gains an optional users list; the 5-arg constructor is kept for definitional-only bundles.

Verification

Full suite green locally — 54 tests, 0 failures / 0 errors (./gradlew build, real Postgres via Testcontainers). New tests cover mirror delete/skip/grant-reconcile and user export/create/idempotent/never-overwrite.

Pairs with admin-ui #20 (the Config Sync page renders the new deleted/skipped/users sections and the includeUsers toggle).


요약 (한국어)

config-sync(ADR 0003)를 추가형 merge 너머로 확장 — mirror 모드 + 옵트인 user sync. 둘 다 ConfigSyncAutoConfiguration 으로 게이팅(기본 off: devslab.kit.config-sync.enabled=true 일 때만) 되고 운영 프로파일에서는 기동 거부됩니다.

mirror 모드

대상을 번들과 정확히 일치시킵니다. merge 에 더해:

  • 역할 권한 재조정 — 번들에 없는 권한은 해당 역할에서 회수;
  • 번들에 없는 정의성 엔터티 삭제.

roles/permissions/users 사이엔 FK 제약이 없어서(오직 platform_menu.parent_id 만 cascade), 삭제가 조인 행을 직접 정리하고 안전한 순서로 실행됩니다:

  • 메뉴leaf-first(자식 먼저, 부모 나중);
  • 역할 — 사용자에게 할당된 역할은 skip(mirror 가 사용자 역할을 함부로 떼지 않음), 아니면 권한 회수 후 삭제;
  • 권한 — 테넌트 역할에서 회수 후 삭제. (권한은 전역이라 mirror 는 단일 테넌트 배포 용도.)

ImportResult.Sectiondeleted·skipped 추가.

user sync (includeUsers, 옵트인, 기본 off)

  • export 는 사용자를 login id 기준으로 내보내되 비밀번호 없음 — email·status·role 코드만.
  • import생성 전용: 없는 사용자만 비밀번호 없이 + mustChangePassword 로 생성하고 코드로 역할 할당. 기존 사용자는 절대 덮어쓰지 않음(skipped 보고).
  • ConfigBundle 에 optional users 추가; 정의성-only 번들용 5-arg 생성자 유지.

검증

로컬 전체 그린 — 54 tests, 0 failures / 0 errors (./gradlew build, Testcontainers 실제 Postgres). mirror 삭제/skip/권한 재조정 + 사용자 export/생성/멱등/덮어쓰기-금지 테스트 추가.

admin-ui #20 과 짝(페이지가 새 deleted/skipped/users 섹션 + includeUsers 토글 렌더).

Extends the config-sync import/export beyond additive merge.

mirror mode — makes the target match the bundle exactly. On top of the
merge it reconciles each role's grants (revoking permissions the bundle
omits) and deletes definitional entities absent from the bundle. There
are no FK cascades between roles/permissions/users, so deletes clean
their own join rows and run in a safe order:
  - menus: deleted leaf-first (a child before its parent);
  - roles: a role still assigned to any user is SKIPPED (mirror never
    strips a user's role), else its grants are revoked then it is deleted;
  - permissions: revoked from the tenant's roles, then deleted.
ImportResult.Section gains `deleted` and `skipped` to report this.

user sync (includeUsers, opt-in, default off) — export carries users by
login id with NO password (just email, status, role codes); import is
create-only: a missing user is created with no usable password and
mustChangePassword, then assigned roles by code. An existing user is
never overwritten (reported as skipped). ConfigBundle gains an optional
`users` list (5-arg constructor kept for definitional-only bundles).

Both stay gated by ConfigSyncAutoConfiguration (off unless
devslab.kit.config-sync.enabled=true) and refused under a production
profile. mirror is intended for single-tenant-per-deployment use.

Tests: mirror delete/skip/grant-reconcile and user export/create/
idempotent/never-overwrite, over real Postgres. Full suite green
(54 tests, 0 failures/0 errors).
@jlc488 jlc488 merged commit d8b77df into main Jun 3, 2026
1 check passed
jlc488 added a commit that referenced this pull request Jun 3, 2026
All implementation PRs have merged: export/import (#53), prod fail-fast
gating (#54), mirror mode + opt-in user sync (#55), and the admin-ui
"Config Sync" page (devslab-kit-admin-ui #20). Flip the status to Accepted
(item 6 of the plan) and record the PRs; the standalone how-to guide is
the remaining piece. Both language files.
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