# 다이제스트가 덮던 다섯 성분과 그때 같아지던 쌍 *

It omitted several. {@code transition|operationId|ownerToken|attempt|stateRevision} covers who * and when, and nothing about what: a {@code FAIL} recorded with a retryable disposition and one * recorded as abandoned produced identical digests, as did two completions carrying different * responses or different retention. A replay comparing digests concluded "same transition" for two * transitions whose whole difference was the part that mattered. * *

Length-framed, because every component is variable-width text and at least one — the owner * token — is not this platform's to constrain. Joining with a delimiter means two different * component lists can render to one string. */ # 지금 각 호출부가 실제로 넘기는 의미 인자 181: String startDigest = transitionDigest("START", operationId, owner); 220: transitionDigest("RENEW", operationId, owner, Long.toString(processingLeaseTtl.toMillis())); 365: String releaseDigest = transitionDigest("RELEASE", operationId, owner); transitionDigest( "COMPLETE", operationId, owner, responseDigest, Long.toString(replayTtl.toMillis())), response.payload(), String transitionKind = disposition == IdempotencyFailureDisposition.NO_EFFECT_RETRYABLE ? "FAIL_RETRYABLE" : "FAIL_ABANDONED"; String failDigest = transitionDigest( transitionKind, operationId, owner, disposition.name(), Long.toString(retention.toMillis())); # 이어 붙일 때 각 성분 앞에 적는 길이와, 해시가 실제로 먹는 바이트 private static void write(StringBuilder out, String value) { out.append(value.length()).append(':').append(value); 79: MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8))); 30: static final int VERSION = 2; 59: write(framed, "v" + VERSION); # 그런데 완료 재생 분기는 이 다이제스트를 비교하지 않는다 if (row.state() == IdempotencyState.COMPLETED && "COMPLETE".equals(row.lastTransitionKind()) && operationId.value().equals(row.lastTransitionOperationId())) { return responseDigest.equals(row.responseDigest()) ? IdempotencyCompleteOutcome.ALREADY_COMPLETED_SAME_RESULT : IdempotencyCompleteOutcome.RESPONSE_CONFLICT;