4.0 KiB
4.0 KiB
title, source_type, status, related_branches, related_projects, tags, created, status_label
| title | source_type | status | related_branches | related_projects | tags | created | status_label | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| error / ulid-fixture-crockford-u-self-inconsistency-2026-06-01 | error-note | raw |
|
|
|
2026-06-01 | resolved |
error: ulid-fixture-crockford-u-self-inconsistency-2026-06-01
Layer:
raw/errors/— 작업 중 마주친 단일 실패·트러블슈팅 기록. 원본은 raw에 영구 보관한다.
Parent / 부모
- raw/branch-notes/feature-resource-identifier-contract — D19 sample-portfolio
WorkLogIdreference fixture 값이 자기 자신의 D2 charset / D19 regex와 모순되어 빌드 불가였다. - raw/project-notes/ca-skeleton-operational-contract — ca-tmpl sample fixture(§17/§22)가 cross-cite하는 값이라 cross-document 정합 이슈다.
증상 / Symptom
- 에러 메시지 (원문 그대로):
UlidCodecTest > normalize_is_identity_on_canonical_input() FAILED java.lang.IllegalArgumentException at UlidCodecTest.java:20 - 발생 컨텍스트:
cd src && ./gradlew :adapter-outbound:test실행 중, fixture 값01HRGC7K2N4F6P8Q0R2S4T6U8V를Ulid.from(...)/WorkLogId.of(...)로 파싱하는 모든 테스트가 실패. - 발생 시점: 2026-06-01 (구현 중 1차 빌드)
- 재현 가능 여부:
always— 해당 fixture 문자열을 ULID 파서/검증기에 넣으면 항상 실패.
재현 절차 / Reproduction
WorkLogId.of("01HRGC7K2N4F6P8Q0R2S4T6U8V")또는Ulid.from("01HRGC7K2N4F6P8Q0R2S4T6U8V")호출.- 기대 결과: canonical ULID로 수용.
- 실제 결과:
IllegalArgumentException(23번째 문자U가 Crockford base32 alphabet에 없음).
근본 원인 / Root cause
- 직접 원인: fixture 문자열
...T6**U**8V의U는 Crockford base32 alphabet(0123456789ABCDEFGHJKMNPQRSTVWXYZ)에서 제외된 문자(I/L/O/U)다. ULID 파서와 D19 regex^[0-9A-HJKMNP-TV-Z]{26}$(U 미포함) 모두 거부한다. - 근본 원인: spec 문서가 D2(charset 결정 = Crockford, I/L/O/U 제외)와 D19(구체 fixture 값)를 따로 작성하면서, fixture 예시 값을 직접 검증하지 않아 self-inconsistency가 남았다. 사람이 손으로 만든 "ULID처럼 보이는" placeholder가 실제로는 유효하지 않았다.
- 트리거 조건: 구현체가 placeholder가 아닌 실제 파서(
ulid-creator의Ulid.from)로 fixture를 검증하는 순간 노출.
해결 / Resolution
- 적용한 조치: fixture 값을 ULID spec 공식 예제값
01ARZ3NDEKTSV4RRFFQ69G5FAV(I/L/O/U 부재, 외부 검증 가능)로 교체. 문서(D19/§5/§8/Decision map/Redis 예시/OpenAPI example)와 코드(SamplePortfolioFixture+ 9개 테스트 파일) 전부 일괄 치환. - 검증 방법:
cd src && ./gradlew :adapter-outbound:test :sample-portfolio:test성공.WorkLogIdTest가 canonical 값 수용 + I/L/O/U 포함 값 거부를 모두 단언.
- 잔여 위험 / 후속 작업: baseline branch + project-note §17/§22의 cross-cite 값도 동일하게 갱신됐는지 확인 필요.
회고 / Lessons
- 빨리 감지하는 신호: "ULID/Crockford 문자열인데
IllegalArgumentException" → 먼저 I/L/O/U 포함 여부와 길이(26)를 점검. - 예방 체크리스트: 문서에 박는 예시 식별자 값은 실제 라이브러리 파서로 1회 검증한 값만 사용한다. charset 결정(D2)과 구체 예시(D19)는 같은 alphabet으로 교차 검증한다.
- 일반화된 교훈: "spec이 자기 자신과 모순될 수 있다." 예시 값/정규식/charset을 별도 섹션에 쓰면 사람이 어긋낸다 — 구현이 곧 spec의 단위테스트다.
Related / 관련
- 관련 branch note: raw/branch-notes/feature-resource-identifier-contract
- 관련 형제 branch: raw/branch-notes/feature-api-contract-baseline (URL path variable 예시로 동일 fixture cite)
- 파생 blog 글감: raw/blog-topics/ulid-crockford-base32-excluded-letters-2026-06-01