- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7.9 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | a-digest-that-covered-who-but-not-what | transition digest가 "누가·언제"만 덮고 "무엇"을 덮지 않아 다른 전이를 같다고 보고했다 | owner-safe-state-machines | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:a-digest-that-covered-who-but-not-what | 2026-09-02 |
|
|
|
transition digest가 "누가·언제"만 덮고 "무엇"을 덮지 않아 다른 전이를 같다고 보고했다
전이 다이제스트가 전이 종류와 연산 식별자와 소유자와 시도와 상태 리비전을 덮었다. 누가 언제 했는지는 덮고 무엇을 했는지는 덮지 않아서, 결말이 다른 전이가 같은 값을 냈다.
관계
- digest는 길이 프레이밍하고 버전을 붙인다 이 사례가 만든 규칙이다.
- CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다 같은 저장소의 소유자 안전 규칙이다.
- complete()의 replay 판정이 replayTtl 변경을 무시한다 이 다이제스트가 실제 판정에 쓰이지 않는 경로다.
문제
다이제스트가 덮던 다섯 성분은 전부 누가 몇 번째로 어느 상태에서 했는지를 말한다. 무엇을 했는지를 말하는 성분이 없었다.
그래서 재시도 가능한 실패와 포기한 실패가 같은 값을 냈고, 응답이 다른 두 완료와 보존 기간이 다른 두 완료도 그랬다.
결론
다이제스트를 비교한 재생 판정이 세 쌍을 같은 전이로 결론지었다. 세 쌍에서 갈린 것은 호출자의 행동이 달라지는 자리뿐이었다.
수정은 두 층으로 왔다. 호출부마다 의미 인자를 넘기게 했고, 실패 쪽은 전이 종류 자체를 성향별로 갈랐다.
이어 붙이는 방식도 바뀌었다. 성분마다 앞에 길이를 적고, 버전 상수를 다이제스트 입력에 넣는다.
다만 완료 쪽 재생 분기는 이 다이제스트를 비교하지 않는다. 그래서 응답이 같고 재생 창만 다른 두 완료는 지금도 같은 결과로 판정된다.
검증 환경
OpenJDK : 21.0.12 확인 방식 : 다이제스트 정책과 다섯 호출부, 완료 재생 분기 확인 소스 수정 : x
재현 조건
- 다이제스트 정책의 javadoc 에서 덮던 성분과 충돌한 쌍을 읽는다.
- 전이 다이제스트를 부르는 다섯 지점에서 각각 무엇을 넘기는지 확인한다.
- 실패 쪽 전이 종류가 어떻게 갈라지는지 확인한다.
- 이어 붙이기가 각 성분 앞에 적는 길이와, 해시가 실제로 먹는 바이트를 확인한다.
- 완료 재생 분기가 무엇을 비교하는지 확인한다.
본문
전이 다이제스트가 덮던 것은 전이 종류와 연산 식별자와 소유자 토큰과 시도와 상태 리비전이었다.
결말을 가르는 성분이 하나도 없었다
:::evidence key="a-digest-that-covered-who-but-not-what" alt="코드베이스에서 전이 다이제스트가 덮던 다섯 성분과 그때 같은 값을 내던 쌍을 적은 javadoc, 지금 다섯 호출부가 실제로 넘기는 의미 인자, 각 성분 앞에 적는 길이와 해시가 먹는 바이트, 버전 상수, 그리고 완료 재생 분기가 비교하는 값을 뽑은 출력 49줄. 완료 재생 분기가 전이 다이제스트가 아니라 연산 식별자와 응답 다이제스트만 본다는 것이 그 출력에 그대로 보인다." caption="덮던 다섯과 충돌한 쌍 · 호출부별 의미 인자 · 길이 프레이밍과 해시 입력 · 완료 재생 분기가 비교하는 값 — 49줄 · exit 0" zoom="true" :::
다섯은 전부 누가 몇 번째로 어느 상태에서 했는지를 말한다. 결말에 가장 가까운 것이 전이 종류인데, 그때는 실패가 성향과 무관하게 하나의 FAIL 이었다.
javadoc 이 같은 값을 내던 쌍 셋을 든다. 재시도 가능한 실패와 포기한 실패, 응답이 다른 두 완료, 보존 기간이 다른 두 완료다.
세 쌍이 달랐던 부분은 호출자가 실제로 행동을 바꾸는 부분뿐이었다. 다이제스트를 비교한 재생 판정은 그 셋을 전부 같은 전이라고 답했다.
무엇을 넘길지는 호출부가 정한다
지금은 다섯 호출부가 각자 인자를 넘긴다.
갱신은 처리 임차 유효 기간을 넘긴다. 완료는 응답 다이제스트와 재생 유효 기간을 넘기고, 그 자리 주석이 이유를 적는다 — 응답 다이제스트를 전이 다이제스트 대신 쓰면 같은 응답을 낸 서로 다른 연산의 완료가 구별되지 않고, 완료가 함께 결정하는 재생 창도 잃는다.
실패는 성향과 보존 기간을 넘긴다. 그리고 전이 종류 자체가 FAIL_RETRYABLE 과 FAIL_ABANDONED 로 갈린다. 첫 번째 쌍은 두 겹으로 갈라진 셈이다.
시작과 해제는 아무것도 넘기지 않는다. javadoc 이 예시로 든 코덱 신원은 지금 어느 호출부도 넘기지 않는다.
이어 붙이는 방식이 구별을 잃게 할 수 있다
성분은 전부 가변 폭 텍스트다. 그중 소유자 토큰은 문자 집합을 이 플랫폼이 정하지 않는다 — 소유자 타입이 강제하는 것은 1자 이상 128자 이하와 공백 불가뿐이다.
구분자로 이으면 서로 다른 성분 목록이 하나의 문자열로 렌더링될 수 있다. 그래서 각 성분 앞에 길이를 적고 콜론을 찍은 뒤 값을 적는다.
적는 길이는 자바 문자 수다. 해시가 먹는 것은 UTF-8 바이트다. 같은 저장소의 메시징 파티션 키는 같은 목적에 UTF-8 바이트 수를 쓰고, 비 ASCII 골든 벡터로 그 선택을 고정한다.
버전 상수는 다이제스트 입력 안에 있다
상수 값은 2 이고, 이어 붙이기가 그 값을 v2 로 맨 앞에 쓴다. 형식이 바뀌면 값이 바뀌므로 옛 형식으로 계산된 다이제스트와 섞이지 않는다.
저장소 이력에는 이 파일이 지금 형태 그대로 커밋 하나에 들어와 있다. 값이 언제 몇 번 올랐는지는 남아 있지 않다.
완료 쪽은 아직 이 다이제스트를 보지 않는다
정책이 고쳐진 것과 그 정책이 판정에 쓰이는 것은 다르다.
이미 완료된 동일 연산의 재생 분기는 전이 다이제스트를 비교하지 않는다. 상태가 COMPLETED 인지, 마지막 전이 종류가 COMPLETE 인지, 연산 식별자가 같은지를 보고, 그다음 응답 다이제스트만 비교한다.
그래서 응답은 같고 재생 유효 기간만 다른 두 완료가 같은 결과로 판정된다. 분석 문서가 실제 PostgreSQL 탐침으로 그것을 확인했다 — 첫 호출에 한 시간, 두 번째 호출에 아홉 시간을 넘겼는데 두 번째가 같은 결과로 판정됐고, 저장된 창은 3600초 그대로였다.
확인하지 못한 것
수정 이전의 충돌을 실행으로 재현하지 않았다. 지금 정책이 지켜지는지는 다이제스트 정책 전용 테스트가 고정한다 — 성향 차이, 응답 차이, 재생 창 차이, 길이 프레이밍, 그리고 널 인자와 인자 없음의 구분이다. 완료 쪽이 아직 열려 있다는 것은 분석 문서의 실측 탐침이 확인했다.