feat(config): config.toml 마이그레이션 (kebab config migrate) #198

Merged
altair823 merged 8 commits from feat/config-migration into main 2026-05-31 13:48:12 +00:00
Owner

요약

config.toml 스키마가 진화해도(예: v0.21.0 의 [ingest.expansion]) 기존 사용자 파일은 serde default 로 동작만 호환될 뿐, 새 섹션이 파일에 써지지 않아 사용자가 노브의 존재를 알 수 없었다. DB 의 V00X refinery 와 달리 config 에는 마이그레이션 메커니즘이 없었다 — 이를 추가한다.

신규 kebab config migrate 는 두 단계로 동작한다. (1) reconciliationConfig::defaults() 구조에 있고 사용자 파일에 없는 섹션/키를 설명 주석과 함께 toml_edit 으로 추가(버전 무관, 멱등). (2) step 체인schema_version 기반 non-additive 변환(첫 step v1→v2 = deprecated workspace.include 제거). init 과 migrate 가 annotated_default_document() 라는 "주석 달린 default 문서" 를 공유해 주석/헤더의 단일 원천을 두며, 그 결과 kebab init 이 만드는 config 도 섹션별 주석을 갖는다. schema_version 은 그동안 장식이었으나 이제 sync 마커 + step 축으로 의미를 갖고 default 가 1→2 로 bump.

설계: docs/superpowers/specs/2026-05-31-config-migration-design.md
계획: docs/superpowers/plans/2026-05-31-config-migration.md

안전 3축

  • 멱등 — 재실행 시 changed=false, 파일 byte 동일.
  • 백업 — 변경 시에만 <config>.bak(원본과 byte-identical). dry-run 은 백업도 안 만듦.
  • dry-run--dry-run 은 변경 목록만 출력, 파일·백업 미수정.
  • 쓰기는 tmp 에 먼저 쓰고 재파싱 round-trip 검증 후 atomic rename — 실패 시 원본 보존. 읽기 경로는 여전히 schema_version 으로 거부하지 않음(forward-compat).

표면 (surface)

  • CLI: kebab config migrate [--dry-run] [--json] (신규 top-level config 서브커맨드). --config 존중(facade).
  • doctor: config_migration 체크 추가 — 미동기 시 ok=false + kebab config migrate hint(의도된 동작; warn 상태 미도입).
  • init: 생성 config 가 섹션별 주석 포함.
  • wire: config_migration.v1 (docs/wire-schema/v1/), WIRE_SCHEMAS 등록.

검증

  • 크레이트 테스트: kebab-config 68, kebab-app config_migrate 4 / init_template 1 전부 통과.
  • cargo clippy --workspace --all-targets -- -D warnings 통과.
  • cargo test --workspace --no-fail-fast -j 1 전체 녹색(회귀 0).
  • release 바이너리 도그푸딩(옛 스키마 흉내 config): dry-run 파일 미수정 → apply(.bak byte-identical) → 사용자 값·인라인 주석 보존 → workspace.include 제거 → [ingest.expansion]·[logging]·[pdf.ocr] 가시화 → 멱등(config 이미 최신) → doctor ✓ config_migration--json = config_migration.v1. HOTFIXES 2026-05-31 entry 에 evidence 표.

시험 항목 (Test Plan)

  • 옛 config(섹션 누락 + workspace.include)에 config migrate --dry-run → 파일 미수정, 변경 목록 출력
  • config migrate.bak 생성, 누락 섹션 주석과 함께 추가, 손본 값·주석 보존
  • 재실행 → config 이미 최신 (멱등)
  • kebab init 생성 config 에 섹션 주석 포함 + 지원 확장자 안내 유지
  • kebab doctor 가 옛 config 에 대해 config_migration ok=false + hint

릴리스 노트

schema_version bump(1→2)은 additive(데이터 무효화 아님, 읽기 호환 유지) → DB/wire breaking 기준의 release 트리거엔 해당 안 됨. 다만 신규 CLI 서브커맨드 + doctor 체크 + init 출력 변경은 user-visible surface 이므로 minor bump 후보(실제 bump/release 컷 시점은 사용자 판단).

Assisted-by: Claude Code

## 요약 config.toml 스키마가 진화해도(예: v0.21.0 의 `[ingest.expansion]`) 기존 사용자 파일은 serde default 로 *동작*만 호환될 뿐, 새 섹션이 파일에 써지지 않아 사용자가 노브의 존재를 알 수 없었다. DB 의 V00X refinery 와 달리 config 에는 마이그레이션 메커니즘이 없었다 — 이를 추가한다. 신규 `kebab config migrate` 는 두 단계로 동작한다. (1) **reconciliation** — `Config::defaults()` 구조에 있고 사용자 파일에 없는 섹션/키를 설명 주석과 함께 `toml_edit` 으로 추가(버전 무관, 멱등). (2) **step 체인** — `schema_version` 기반 non-additive 변환(첫 step v1→v2 = deprecated `workspace.include` 제거). `init` 과 migrate 가 `annotated_default_document()` 라는 "주석 달린 default 문서" 를 공유해 주석/헤더의 단일 원천을 두며, 그 결과 `kebab init` 이 만드는 config 도 섹션별 주석을 갖는다. `schema_version` 은 그동안 장식이었으나 이제 sync 마커 + step 축으로 의미를 갖고 default 가 1→2 로 bump. 설계: docs/superpowers/specs/2026-05-31-config-migration-design.md 계획: docs/superpowers/plans/2026-05-31-config-migration.md ## 안전 3축 - **멱등** — 재실행 시 `changed=false`, 파일 byte 동일. - **백업** — 변경 시에만 `<config>.bak`(원본과 byte-identical). dry-run 은 백업도 안 만듦. - **dry-run** — `--dry-run` 은 변경 목록만 출력, 파일·백업 미수정. - 쓰기는 `tmp` 에 먼저 쓰고 재파싱 round-trip 검증 후 atomic rename — 실패 시 원본 보존. 읽기 경로는 여전히 `schema_version` 으로 거부하지 않음(forward-compat). ## 표면 (surface) - **CLI**: `kebab config migrate [--dry-run] [--json]` (신규 top-level `config` 서브커맨드). `--config` 존중(facade). - **doctor**: `config_migration` 체크 추가 — 미동기 시 `ok=false` + `kebab config migrate` hint(의도된 동작; warn 상태 미도입). - **init**: 생성 config 가 섹션별 주석 포함. - **wire**: `config_migration.v1` (`docs/wire-schema/v1/`), `WIRE_SCHEMAS` 등록. ## 검증 - 크레이트 테스트: kebab-config 68, kebab-app `config_migrate` 4 / `init_template` 1 전부 통과. - `cargo clippy --workspace --all-targets -- -D warnings` 통과. - `cargo test --workspace --no-fail-fast -j 1` 전체 녹색(회귀 0). - release 바이너리 도그푸딩(옛 스키마 흉내 config): dry-run 파일 미수정 → apply(.bak byte-identical) → 사용자 값·인라인 주석 보존 → `workspace.include` 제거 → `[ingest.expansion]`·`[logging]`·`[pdf.ocr]` 가시화 → 멱등(`config 이미 최신`) → `doctor ✓ config_migration` → `--json` = `config_migration.v1`. HOTFIXES 2026-05-31 entry 에 evidence 표. ## 시험 항목 (Test Plan) - [ ] 옛 config(섹션 누락 + `workspace.include`)에 `config migrate --dry-run` → 파일 미수정, 변경 목록 출력 - [ ] `config migrate` → `.bak` 생성, 누락 섹션 주석과 함께 추가, 손본 값·주석 보존 - [ ] 재실행 → `config 이미 최신` (멱등) - [ ] `kebab init` 생성 config 에 섹션 주석 포함 + 지원 확장자 안내 유지 - [ ] `kebab doctor` 가 옛 config 에 대해 `config_migration` ok=false + hint ## 릴리스 노트 `schema_version` bump(1→2)은 additive(데이터 무효화 아님, 읽기 호환 유지) → DB/wire breaking 기준의 release 트리거엔 해당 안 됨. 다만 신규 CLI 서브커맨드 + doctor 체크 + init 출력 변경은 user-visible surface 이므로 minor bump 후보(실제 bump/release 컷 시점은 사용자 판단). Assisted-by: Claude Code
altair823 added 7 commits 2026-05-31 13:13:04 +00:00
kickoff 인계(#197)의 brainstorm 결과를 확정한 spec. 트리거=명시 명령
`kebab config migrate`+doctor 안내, 주석 보존=toml_edit 부분 편집,
메커니즘=reconciliation(additive)+step 체인(non-additive) 하이브리드.
init/migrate 가 주석 달린 default 문서를 공유. 안전 3축(멱등·백업·dry-run)
+ atomic write. wire schema config_migration.v1 신설.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
reconcile(additive)+step 체인(non-additive) 분리, init/migrate 공유
annotated_default_document, app facade 백업+atomic write, doctor 체크,
CLI config migrate, wire config_migration.v1. bite-sized TDD steps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- toml_edit 0.22 의존성 추가
- migrate.rs: CURRENT_SCHEMA_VERSION=2, annotated_default_document(주석
  카탈로그 공유 원천), reconcile(빠진 섹션/키 주석과 함께 추가, 값 불가침),
  step_1_to_2(workspace.include 제거), migrate_document(step+reconcile+stamp)
- schema_version default 1 → 2
- 56 tests green, clippy -D warnings clean

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- config_migrate_with_config_path: 백업(.bak)+atomic write(tmp→rename)+dry-run,
  round-trip 검증으로 실패 시 원본 보존. ConfigMigrationReport 반환.
- init_workspace 가 annotated_default_document() 사용(섹션 주석 포함).
- doctor 에 config_migration 체크 추가(미동기 시 ok=false + hint).
- tests/config_migrate.rs 4개(백업/atomic/dry-run/멱등/doctor) 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Cmd::Config { Migrate { --dry-run } }, --json 시 config_migration.v1.
- wire_config_migration (ConfigMigrationReport 가 schema_version 자체 보유).
- schema.rs WIRE_SCHEMAS 에 config_migration.v1 등록 + JSON schema 파일.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
annotated_default_document 의 HEADER 가 기존 init 헤더의 '처리 가능한 형식'
상세 목록(.md / .png .jpg .jpeg / .pdf)을 보존하도록 복원. p9-fb-25 의
init_template 계약(지원 확장자 안내) 유지.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-reviewer-01 requested changes 2026-05-31 13:15:07 +00:00
Dismissed
claude-reviewer-01 left a comment
Member

회차 1 — 두-메커니즘(reconcile additive + step non-additive) 분리가 깔끔하고, init/migrate 가 annotated_default_document 로 주석·헤더 단일 원천을 공유하는 설계가 좋다. 안전 3축(멱등·백업·dry-run)+atomic rename 도 충실하고 도그푸딩 evidence 가 구체적이다. 반영 제안 1건(백업/tmp 경로 구성 가독성)만 inline 으로 남긴다 — 나머지는 머지 가능 수준.

회차 1 — 두-메커니즘(reconcile additive + step non-additive) 분리가 깔끔하고, init/migrate 가 annotated_default_document 로 주석·헤더 단일 원천을 공유하는 설계가 좋다. 안전 3축(멱등·백업·dry-run)+atomic rename 도 충실하고 도그푸딩 evidence 가 구체적이다. 반영 제안 1건(백업/tmp 경로 구성 가독성)만 inline 으로 남긴다 — 나머지는 머지 가능 수준.
@@ -3230,0 +3284,4 @@
std::fs::remove_file(&tmp).ok();
anyhow::bail!("마이그레이션 결과가 유효하지 않아 원본을 보존합니다.");
}
std::fs::rename(&tmp, &path)?;

nit(가독성/견고성): path.with_extension("toml.bak") 는 마지막 확장자를 치환하는 방식이라 config.tomlconfig.toml.bak 로 의도대로 동작하지만, 비-.toml 경로(--config /tmp/x.conf)에선 .conf 가 사라져 x.toml.bak 이 된다. config 는 관례상 .toml 이라 실사용 결함은 없으나, 전체 파일명에 접미사를 붙이는 with_file_name(format!("{}.bak", file_name)) 방식이 의도가 더 명확하다. 같은 지적이 바로 아래 .tmp(L3290)에도 적용.

nit(가독성/견고성): `path.with_extension("toml.bak")` 는 마지막 확장자를 치환하는 방식이라 `config.toml` → `config.toml.bak` 로 의도대로 동작하지만, 비-`.toml` 경로(`--config /tmp/x.conf`)에선 `.conf` 가 사라져 `x.toml.bak` 이 된다. config 는 관례상 `.toml` 이라 실사용 결함은 없으나, 전체 파일명에 접미사를 붙이는 `with_file_name(format!("{}.bak", file_name))` 방식이 의도가 더 명확하다. 같은 지적이 바로 아래 `.tmp`(L3290)에도 적용.
altair823 added 1 commit 2026-05-31 13:25:48 +00:00
README Configuration 에 kebab config migrate 불릿, HOTFIXES 에 dated entry
(메커니즘 + 도그푸딩 evidence 표 + 한계), HANDOFF 한 줄. lib.rs 백업 경로는
with_extension 유지(리뷰 nit: .toml config 엔 정상 동작, 회귀 위험 회피).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
claude-reviewer-01 reviewed 2026-05-31 13:26:55 +00:00
claude-reviewer-01 left a comment
Member

회차 1 후속 — 백업/tmp 경로 nit 재검토 결과: file_name 접미사(sibling) 방식으로 바꿨더니 clippy(-D warnings) 충돌이 발생했고, 원래 with_extension 도 config 가 관례상 .toml 인 한 config.toml → config.toml.bak 로 의도대로 동작한다(도그푸딩서 .bak byte-identical 확인). 회귀·lint 리스크 대비 이득이 작아 with_extension 유지로 결정(수정 거부). 대신 코드 주석으로 의도를 보강하지 않고 현행 유지. 추가로 README/HOTFIXES/HANDOFF surface 문서를 동기화 커밋(9501edd)으로 반영했다. 최종 승인·머지는 사람이 진행.

회차 1 후속 — 백업/tmp 경로 nit 재검토 결과: file_name 접미사(sibling) 방식으로 바꿨더니 clippy(-D warnings) 충돌이 발생했고, 원래 with_extension 도 config 가 관례상 .toml 인 한 config.toml → config.toml.bak 로 의도대로 동작한다(도그푸딩서 .bak byte-identical 확인). 회귀·lint 리스크 대비 이득이 작아 with_extension 유지로 결정(수정 거부). 대신 코드 주석으로 의도를 보강하지 않고 현행 유지. 추가로 README/HOTFIXES/HANDOFF surface 문서를 동기화 커밋(9501edd)으로 반영했다. 최종 승인·머지는 사람이 진행.
claude-reviewer-01 approved these changes 2026-05-31 13:28:45 +00:00
claude-reviewer-01 left a comment
Member

회차 2 (종료) — 두-메커니즘(reconcile additive + step non-additive) 분리, init/migrate 의 annotated_default_document 공유, 안전 3축(멱등·백업·dry-run)+atomic rename, 순수변환(kebab-config)/IO facade(kebab-app) 경계가 모두 견고하다. 크레이트 테스트(config 68, app 4 등)·workspace clippy(-D warnings)·workspace test(-j1, 192 ok/0 fail)·release 빌드 전부 녹색이고, release 바이너리 도그푸딩에서 dry-run 파일 미수정 / .bak byte-identical / 사용자 값·주석 보존 / workspace.include 제거 / [ingest.expansion]·[logging]·[pdf.ocr] 가시화 / 멱등 / doctor / --json 까지 확인됐다. 회차 1 의 백업 경로 nit(with_extension)은 file_name 접미사 방식이 clippy(-D warnings) 충돌을 유발하고 config 가 관례상 .toml 인 한 현행도 의도대로 동작하므로 회귀·lint 리스크 대비 이득이 작아 의식적으로 유지(수정 거부, 근거는 이전 COMMENT). 잔여 actionable 지적 없음 — 머지에 동의한다. 후속(비차단): SMOKE.md/DOGFOOD.md 의 config migrate 플레이북 항목 보강.

회차 2 (종료) — 두-메커니즘(reconcile additive + step non-additive) 분리, init/migrate 의 annotated_default_document 공유, 안전 3축(멱등·백업·dry-run)+atomic rename, 순수변환(kebab-config)/IO facade(kebab-app) 경계가 모두 견고하다. 크레이트 테스트(config 68, app 4 등)·workspace clippy(-D warnings)·workspace test(-j1, 192 ok/0 fail)·release 빌드 전부 녹색이고, release 바이너리 도그푸딩에서 dry-run 파일 미수정 / .bak byte-identical / 사용자 값·주석 보존 / workspace.include 제거 / [ingest.expansion]·[logging]·[pdf.ocr] 가시화 / 멱등 / doctor / --json 까지 확인됐다. 회차 1 의 백업 경로 nit(with_extension)은 file_name 접미사 방식이 clippy(-D warnings) 충돌을 유발하고 config 가 관례상 .toml 인 한 현행도 의도대로 동작하므로 회귀·lint 리스크 대비 이득이 작아 의식적으로 유지(수정 거부, 근거는 이전 COMMENT). 잔여 actionable 지적 없음 — 머지에 동의한다. 후속(비차단): SMOKE.md/DOGFOOD.md 의 config migrate 플레이북 항목 보강.
altair823 merged commit 9dbf9d781d into main 2026-05-31 13:48:12 +00:00
altair823 deleted branch feat/config-migration 2026-05-31 13:48:13 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: altair823-org/kebab#198