1485 lines
73 KiB
JSON
1485 lines
73 KiB
JSON
{
|
|
"schemaVersion": 4,
|
|
"project": "keycloak-session-store",
|
|
"ssot": "final/document.md",
|
|
"sourceRepository": {
|
|
"path": "/home/donghyeon/workspace/keycloak-pattern",
|
|
"revision": "cdac9b8178391311d8eca1ebc6cac15bb62d79af",
|
|
"verified": "이 커밋이 A-1 부터 D-4a 까지 실험 전량을 병합한 tip 이고 저장소 HEAD 다. git branch --contains 가 experiment 브랜치 26 개를 모두 낸다 (2026-09-07 확인)"
|
|
},
|
|
"ssotSha256": "0ae5674723f25dc85d5069529890f07be1a11b768b56901103fa3ea6ac3dcf55",
|
|
"sourceRevision": "keycloak-session-lab@2026-09",
|
|
"generatedAt": "2026-09-08",
|
|
"candidateScope": {
|
|
"document": "final/document.md",
|
|
"sections": [
|
|
"코드보다 먼저 드러난 문제",
|
|
"문제를 어렵게 만든 제약",
|
|
"검토한 선택지와 막힌 지점",
|
|
"선택의 이유와 지킨 경계",
|
|
"선택이 코드와 흐름에 반영되는 방식",
|
|
"결정이 지켜지는지 확인하는 방법",
|
|
"얻은 것, 잃은 것, 적용하지 않을 때",
|
|
"결국 지키려던 것은 무엇이었나"
|
|
],
|
|
"excluded": [
|
|
"자료",
|
|
"이 기록에 아직 없는 것"
|
|
],
|
|
"note": "처음부터 한 편으로 쓴 글이라 제2부가 없다. 맨 뒤 두 절은 증거 목록과 그림 제작 메모라 후보 자리가 아니다"
|
|
},
|
|
"note": "이 프로젝트의 글감 전부다. 분해 계약이자 색인이고, 이 파일이 정본이다. 노드의 칸은 사람이 적고 file·publication·status 는 기록 파일에서 읽어 채운다 — python3 scripts/build-tech-log-tree.py keycloak-session-store",
|
|
"contract": {
|
|
"decomposition": [
|
|
"글감을 찾는 입력은 final/document.md 하나다. 거기에 없는 근거는 먼저 SSOT 에 넣는다.",
|
|
"후보 전부는 candidates 에 처분과 함께 남고 PROMOTE 만 topics 로 올라간다.",
|
|
"없애고 관련 Case 나 Concept 의 한 절로 넣어도 이해·결정·재사용성이 그대로라면 독립 기록으로 만들지 않는다.",
|
|
"Topic 은 독자 질문 하나다. 그 물음에 답하지 않는 글감은 다른 Topic 으로 옮긴다.",
|
|
"Concept 은 Case·Decision·Question 을 먼저 고른 뒤 그것을 이해하는 데 필요한 것만 거꾸로 더한다."
|
|
],
|
|
"readinessValues": [
|
|
"READY",
|
|
"OPEN",
|
|
"NEEDS_EVIDENCE",
|
|
"NEEDS_DECISION",
|
|
"BLOCKED"
|
|
],
|
|
"dispositionValues": {
|
|
"PROMOTE": "독립 Tech Log 로 쓴다",
|
|
"MERGE_INTO": "다른 기록의 한 절로 흡수한다",
|
|
"KEEP_IN_SSOT": "분석에는 남기고 독립 기록으로 만들지 않는다 — 정상적인 성공 결과다",
|
|
"NEEDS_EVIDENCE": "주장에 아직 검증이 없다",
|
|
"NEEDS_DECISION": "방향이 그럴듯하지만 프로젝트가 정하지 않았다",
|
|
"BLOCKED": "원본이 불완전하거나 서로 어긋난다"
|
|
}
|
|
},
|
|
"topics": {
|
|
"session-custody-across-nodes": {
|
|
"title": "세션의 거처 — 두 노드가 같은 답을 내는 이유",
|
|
"readerQuestion": "Keycloak 세션은 실제로 어디에 있고, 두 노드가 같은 세션을 쓰는 이유는 무엇인가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "클러스터는 형성됐는데 세션을 나르는 것은 데이터베이스였다",
|
|
"slug": "session-sharing-is-the-database-not-replication",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-전제가-무너졌다",
|
|
"final/document.md#선택의-이유와-지킨-경계-a1"
|
|
],
|
|
"code": [
|
|
"JGROUPS_PING",
|
|
"OFFLINE_USER_SESSION",
|
|
"sessions 캐시"
|
|
],
|
|
"classification": "로그가 가리키는 원인과 실제 경로가 다르다는 것을 노드 B 의 SQL 문장 로깅으로 확정했고, 그 확정이 A-1 의 빗나간 예측까지 설명하며 닫힌다",
|
|
"missing-verification": "Infinispan 복제를 명시적으로 켠 구성에서는 재지 않았다. 이 결론은 persistent-user-sessions 가 켜진 26.7.0 기본값에 한정된다",
|
|
"relations": [
|
|
"concept:persistent-vs-volatile-user-sessions",
|
|
"case:cache-temperature-decides-the-outcome",
|
|
"reference:state-the-version-and-the-setting-with-the-result"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"session-sharing-path",
|
|
"a1-transport-vs-discovery"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a1-jgroups-transport-block__09-cross-node-under-partition.txt",
|
|
"raw/a1-jgroups-transport-block__10-logout-not-propagated.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "같은 설정이 캐시 온도만으로 세 가지 답을 냈다",
|
|
"slug": "cache-temperature-decides-the-outcome",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a7-a7a"
|
|
],
|
|
"code": [
|
|
"CLIENT_SCOPE_CLIENT",
|
|
"REVOKED_TOKEN",
|
|
"DEFAULT_SCOPE"
|
|
],
|
|
"classification": "가설(REVOKED_TOKEN)을 문장 로깅으로 반증하고 실제 조회를 확정한 뒤, 캐시 온도 셋을 모두 재현해 표 하나로 닫히지 않는다는 결론까지 갔다",
|
|
"missing-verification": "캐시가 식는 시간을 재지 않았다. 냉·중간·온 세 상태를 만들어 확인했을 뿐 그 사이의 전이는 관측하지 않았다",
|
|
"relations": [
|
|
"concept:persistent-vs-volatile-user-sessions",
|
|
"reference:state-the-version-and-the-setting-with-the-result"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"cache-temperature-outcomes"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a7a-volatile-cause__01-cause-determined.txt",
|
|
"raw/a7-volatile-comparison__05-a2-rerun-db-loss.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "롤링 재시작은 세션을 남기고 캐시만 지웠다",
|
|
"slug": "rolling-restart-keeps-sessions-drops-cache",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a8"
|
|
],
|
|
"code": [
|
|
"persistent-user-sessions"
|
|
],
|
|
"classification": "재시작 전후의 DB 세션 수와 캐시 수를 따로 세어 무엇이 남고 무엇이 사라지는지 갈랐고, 그 설정을 켜는 이유로 닫힌다",
|
|
"missing-verification": "표본이 적어 무중단 주장을 처음에 과장했다가 고쳤다. 재시작 중 진행 중이던 요청은 재지 않았다",
|
|
"relations": [
|
|
"concept:persistent-vs-volatile-user-sessions",
|
|
"case:session-sharing-is-the-database-not-replication"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"a8-cache-vs-session"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a8-rolling-restart__01-restart-availability.txt",
|
|
"raw/a8-rolling-restart__02-session-survival.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "persistent-user-sessions 가 세션의 거처를 정한다",
|
|
"slug": "persistent-vs-volatile-user-sessions",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-버전-조건"
|
|
],
|
|
"basis-version": "Keycloak 26.7.0 · persistent-user-sessions 기본 활성 · 24 이전과 대조",
|
|
"classification": "세션이 DB 에 있는지 메모리에 있는지를 이 설정이 정하고, 그것을 모르면 A 층 결과 셋이 왜 뒤집히는지 읽을 수 없다",
|
|
"relations": [
|
|
"case:session-sharing-is-the-database-not-replication",
|
|
"case:rolling-restart-keeps-sessions-drops-cache",
|
|
"reference:state-the-version-and-the-setting-with-the-result"
|
|
],
|
|
"kind": "concept",
|
|
"ssot-assets": [
|
|
"version-conditional-results"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "버전과 설정을 결과와 함께 적는다",
|
|
"slug": "state-the-version-and-the-setting-with-the-result",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-버전-조건",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-적용되지-않는-조건"
|
|
],
|
|
"classification": "같은 제품의 같은 실험이 설정 하나로 뒤집히는 것을 세 건에서 확인했고, 그 규칙은 다른 제품의 기본값 변경에도 그대로 적용된다",
|
|
"scope": "기본값이 메이저 버전 사이에 바뀐 제품을 측정해 결과를 적는 자리",
|
|
"exceptions": "측정 대상이 그 설정에 걸리지 않는 경로라면 조건을 달지 않아도 된다. 무엇이 걸리는지를 먼저 확인한다",
|
|
"relations": [
|
|
"concept:persistent-vs-volatile-user-sessions",
|
|
"case:cache-temperature-decides-the-outcome"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"question": [],
|
|
"decision": []
|
|
},
|
|
"topic": "session-custody-across-nodes"
|
|
},
|
|
"losing-a-node-or-the-store": {
|
|
"title": "노드와 저장소를 잃을 때",
|
|
"readerQuestion": "노드나 저장소를 잃으면 무엇이 먼저 깨지고, 복구까지 시간을 쓰는 것은 어느 단계인가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "200 과 토큰을 받은 로그인 네 건이 데이터베이스에 없었다",
|
|
"slug": "four-logins-that-returned-200-and-vanished",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a2-a3"
|
|
],
|
|
"code": [
|
|
"SET LOCAL synchronous_commit TO OFF",
|
|
"wal_writer_delay"
|
|
],
|
|
"classification": "153 건 중 149 건만 남은 것을 세어 확인하고 원인을 커밋 설정에서 확정했으며, 의도된 설계의 대가를 숫자로 닫았다",
|
|
"missing-verification": "synchronous_commit 을 켠 대조군을 돌리지 않았다. 유실이 0 이 되는지와 그때의 지연 비용은 재지 않았다",
|
|
"relations": [
|
|
"case:two-ways-to-lose-a-node",
|
|
"concept:readiness-hides-the-broken-node",
|
|
"reference:most-of-an-outage-is-noticing"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"a3-commit-to-disk-gap"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a3-database-crash__03-loss-measurement.txt",
|
|
"raw/a3-database-crash__07-loss-result.txt",
|
|
"raw/a3-database-crash__08-wal-settings.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "노드를 잃는 두 가지 — 저장소가 같이 죽는 것과 들어갈 길이 없는 것",
|
|
"slug": "two-ways-to-lose-a-node",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a4"
|
|
],
|
|
"code": [
|
|
"node-monitor-grace-period",
|
|
"tolerationSeconds",
|
|
"StatefulSet"
|
|
],
|
|
"classification": "같은 복구 시간을 내는 두 장애의 원인이 다르다는 것을 갈랐고, 축출까지 5분 40초라는 내역으로 닫힌다",
|
|
"missing-verification": "저장소를 두 노드에 나눠 배치한 구성에서는 재지 않았다. 4a 의 결과는 이 실험대의 배치에 걸려 있다",
|
|
"relations": [
|
|
"case:four-logins-that-returned-200-and-vanished",
|
|
"concept:readiness-hides-the-broken-node",
|
|
"reference:most-of-an-outage-is-noticing"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"a4-two-node-losses",
|
|
"lab-topology"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a4-node-loss__02-worker-node-killed.txt",
|
|
"raw/a4-node-loss__03-state-during-loss.txt",
|
|
"raw/a4-node-loss__04-eviction-timing.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "200 밀리초를 넣었더니 응답이 22.2 초가 됐다",
|
|
"slug": "200ms-of-delay-became-22-seconds",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a6"
|
|
],
|
|
"code": [
|
|
"agroal 커넥션 풀",
|
|
"readiness probe"
|
|
],
|
|
"classification": "28 배가 어디서 왔는지를 왕복 누적과 풀 경합 두 단계로 분해했고, 느림이 장애로 승격되는 지점까지 관측해 닫았다",
|
|
"missing-verification": "지연 값을 200ms 한 점에서만 쟀다. 어느 지연부터 readiness 가 실패하는지는 재지 않았다",
|
|
"relations": [
|
|
"concept:readiness-hides-the-broken-node",
|
|
"case:two-ways-to-lose-a-node"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"a6-latency-multiplication"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a6-latency-injection__02-delay-injected.txt",
|
|
"raw/a6-latency-injection__04-pool-under-load.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "readiness 가 깨진 노드를 시야에서 먼저 치운다",
|
|
"slug": "readiness-hides-the-broken-node",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-관측을-어디에"
|
|
],
|
|
"basis-version": "k3s · Kubernetes readiness probe · Keycloak 26.7.0 /health",
|
|
"classification": "밖에서 재면 장애가 보이지 않는 이유가 이 동작이고, 이것을 모르면 A 층 결과 대부분을 아무 일도 없었던 것으로 읽는다",
|
|
"relations": [
|
|
"case:200ms-of-delay-became-22-seconds",
|
|
"case:two-ways-to-lose-a-node",
|
|
"concept:the-up-metric-cannot-see-alive-but-useless"
|
|
],
|
|
"kind": "concept",
|
|
"ssot-assets": [
|
|
"observation-points"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "장애 시간의 대부분은 알아채는 데 걸린다",
|
|
"slug": "most-of-an-outage-is-noticing",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a4"
|
|
],
|
|
"classification": "복구 절차가 아니라 감지 지연이 장애 시간을 정한다는 것을 타이머 두 개의 합으로 확인했고, 다른 오케스트레이터에도 같은 축이 있다",
|
|
"scope": "노드 상실을 오케스트레이터가 감지해 대체를 만드는 구성",
|
|
"exceptions": "진입 경로 자체를 잃은 장애는 감지가 빨라도 복구되지 않는다. 그때는 이중화 지점이 문제다",
|
|
"relations": [
|
|
"case:two-ways-to-lose-a-node",
|
|
"case:four-logins-that-returned-200-and-vanished"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"question": [],
|
|
"decision": []
|
|
},
|
|
"topic": "losing-a-node-or-the-store"
|
|
},
|
|
"where-application-state-lives": {
|
|
"title": "애플리케이션이 서버에 든 상태의 자리",
|
|
"readerQuestion": "로그인 세션과 OAuth 토큰을 서버 어디에 두어야 하고, 저장소를 옮기면 무엇이 따라오지 않는가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "세션만 Redis 로 옮기자 토큰이 따라오지 않았다",
|
|
"slug": "moving-the-session-left-the-tokens-behind",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b1"
|
|
],
|
|
"code": [
|
|
"InMemoryOAuth2AuthorizedClientService",
|
|
"AuthenticatedPrincipalOAuth2AuthorizedClientRepository",
|
|
"SPRING_SESSION_STORE_TYPE"
|
|
],
|
|
"classification": "저장소를 붙이기 전에 자동구성이 무엇을 골랐는지 먼저 읽고, 그 조회 키 차이가 다음 실험의 결과를 만든다는 것까지 이어 닫았다",
|
|
"missing-verification": "Redis 를 붙인 상태에서 토큰 저장소만 따로 두는 조합은 B-2 에서 다시 쟀다. 여기서는 세션 쪽만 확인했다",
|
|
"relations": [
|
|
"concept:two-stores-two-lookup-keys",
|
|
"case:a-primary-key-without-the-session-id",
|
|
"decision:split-the-two-stores-and-design-each"
|
|
],
|
|
"kind": "case",
|
|
"ssot-evidence": [
|
|
"raw/b0-bff-redis-deploy__03-beans-analysis.txt",
|
|
"raw/b1-redis-session-store__03-redis-contents.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "기본키에 세션 id 가 없어서 두 번째 로그인이 첫 토큰을 덮어썼다",
|
|
"slug": "a-primary-key-without-the-session-id",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b2"
|
|
],
|
|
"code": [
|
|
"JdbcOAuth2AuthorizedClientService",
|
|
"PRIMARY KEY (client_registration_id, principal_name)"
|
|
],
|
|
"classification": "저장소를 나눠 옮긴 뒤 네 가지를 확인해 둘은 풀리고 둘은 남는 것을 갈랐고, 남은 둘의 원인이 저장소가 아니라 DDL 한 줄임을 확정했다",
|
|
"missing-verification": "세션 id 를 키에 넣은 스키마로 고쳐서 다시 재지 않았다. 덮어쓰기가 사라지는지는 확인하지 않았다",
|
|
"relations": [
|
|
"concept:two-stores-two-lookup-keys",
|
|
"case:moving-the-session-left-the-tokens-behind",
|
|
"reference:look-at-the-lookup-key-before-moving-the-store",
|
|
"decision:split-the-two-stores-and-design-each"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"b2-primary-key-overwrite"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b0-bff-redis-deploy__02-autoconfiguration.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "회전 경쟁에서 이긴 요청의 토큰도 쓸 수 없었다",
|
|
"slug": "the-winner-of-the-rotation-race-also-loses",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b3"
|
|
],
|
|
"code": [
|
|
"revokeRefreshToken=true",
|
|
"refreshTokenMaxReuse=0"
|
|
],
|
|
"classification": "동시 5 건을 실제로 경합시켜 「하나는 성공한다」가 아니라 전부 못 쓰게 되는 것을 관측했고, 원인을 client session 삭제로 확정했다",
|
|
"missing-verification": "재시도 설계를 붙여서 다시 재지 않았다. 어떤 재시도가 이 상황을 복구하는지는 확인하지 않았다",
|
|
"relations": [
|
|
"case:a-primary-key-without-the-session-id",
|
|
"decision:split-the-two-stores-and-design-each"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"b3-rotation-contention"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b0-bff-redis-deploy__01-deploy.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "볼륨 없는 영속화와 유예 없는 키 회전",
|
|
"slug": "persistence-without-a-volume-and-rotation-without-overlap",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b5-b6"
|
|
],
|
|
"code": [
|
|
"appendonly yes",
|
|
"NimbusJwtDecoder",
|
|
"JWKS kid"
|
|
],
|
|
"classification": "설정만 보면 되어 있는 것으로 읽히는 두 자리를 각각 실제로 죽여 보고 회전시켜 확인했으며, 설정과 매체를 나눠 봐야 한다는 결론으로 닫힌다",
|
|
"missing-verification": "볼륨을 붙인 Redis 로 다시 재지 않았다. 영속화가 실제로 동작하는지는 확인하지 않았다",
|
|
"relations": [
|
|
"case:moving-the-session-left-the-tokens-behind",
|
|
"reference:look-at-the-lookup-key-before-moving-the-store"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"b5-b6-storage-and-keys"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b5-redis-loss__04-persistence.txt",
|
|
"raw/b6-key-rotation__03-old-key-removed.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "세션과 인가된 클라이언트는 조회 키가 다르다",
|
|
"slug": "two-stores-two-lookup-keys",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0"
|
|
],
|
|
"basis-version": "Spring Boot 3 · Spring Security OAuth2 Client · Spring Session Redis",
|
|
"classification": "하나는 세션 id 로 다른 하나는 principal 이름으로 찾는다는 것을 알아야 저장소를 옮긴 결과를 읽을 수 있다",
|
|
"relations": [
|
|
"case:moving-the-session-left-the-tokens-behind",
|
|
"case:a-primary-key-without-the-session-id",
|
|
"decision:split-the-two-stores-and-design-each"
|
|
],
|
|
"kind": "concept",
|
|
"ssot-assets": [
|
|
"bff-store-lookup-keys"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "저장소를 옮기기 전에 조회 키를 본다",
|
|
"slug": "look-at-the-lookup-key-before-moving-the-store",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"classification": "무엇으로 찾는지가 무엇을 옮겨야 하는지를 정한다는 기준이고, 세션과 토큰이 아닌 다른 짝에도 그대로 적용된다",
|
|
"scope": "서버가 든 상태를 외부 저장소로 옮기는 자리",
|
|
"exceptions": "상태가 하나뿐이고 조회 키도 하나면 이 확인이 필요 없다. 다만 자동구성이 무엇을 골랐는지는 그때도 읽는다",
|
|
"relations": [
|
|
"concept:two-stores-two-lookup-keys",
|
|
"case:a-primary-key-without-the-session-id"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"question": [],
|
|
"decision": [
|
|
{
|
|
"title": "세션과 토큰의 저장소를 나눠 각각 설계한다",
|
|
"slug": "split-the-two-stores-and-design-each",
|
|
"readiness": "READY",
|
|
"decision-status": "ADOPTED",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b2",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"decision-evidence": [
|
|
"case:moving-the-session-left-the-tokens-behind",
|
|
"case:a-primary-key-without-the-session-id"
|
|
],
|
|
"grounds": "조회 키가 달라 한쪽을 옮겨도 다른 쪽이 따라오지 않고, 나눠 옮긴 뒤에도 남는 문제의 원인은 저장소가 아니라 스키마였다",
|
|
"classification": "한 저장소로 묶는 대안을 실제로 시도해 반쪽만 옮겨지는 것을 확인한 뒤 나누기로 정했고, 남는 비용을 함께 적었다",
|
|
"relations": [
|
|
"concept:two-stores-two-lookup-keys",
|
|
"case:a-primary-key-without-the-session-id"
|
|
],
|
|
"kind": "decision",
|
|
"publication": "미작성"
|
|
}
|
|
]
|
|
},
|
|
"topic": "where-application-state-lives"
|
|
},
|
|
"trust-handed-over-at-the-edge": {
|
|
"title": "엣지와 IdP 가 건네는 신뢰",
|
|
"readerQuestion": "엣지와 IdP 가 만든 인증 결과는 애플리케이션까지 어떻게 전달되고, 무엇이 그 전달을 끊는가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "nginx 는 자기가 설정하지 않은 헤더를 덮어쓰지 않았다",
|
|
"slug": "nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b4"
|
|
],
|
|
"code": [
|
|
"proxy_set_header X-Auth-Request-Roles",
|
|
"--cookie-refresh"
|
|
],
|
|
"classification": "위조 헤더를 실제로 보내 통과하는 것을 관측하고 원인을 지우지 않은 설정에서 확정했으며, 클레임 변경이 재인증까지 반영되지 않는 것도 같은 실험에서 닫았다",
|
|
"missing-verification": "--cookie-refresh 를 켠 구성에서는 재지 않았다. 클레임 반영 지연이 얼마나 줄어드는지는 확인하지 않았다",
|
|
"relations": [
|
|
"reference:clear-the-header-before-you-trust-it",
|
|
"case:nobody-implemented-backchannel-logout"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"b4-header-trust-boundary"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b4-edge-authorization__01-header-handling.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "백채널 로그아웃은 양쪽 다 없었다",
|
|
"slug": "nobody-implemented-backchannel-logout",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-c층"
|
|
],
|
|
"code": [
|
|
"backchannelLogoutUrl",
|
|
"oidcLogout"
|
|
],
|
|
"classification": "네 가지를 순서대로 확인해 설정 결손과 기능 부재가 한 경로에 겹쳐 있는 것을 갈랐고, 한쪽만 고쳐서는 되지 않는다는 결론으로 닫힌다",
|
|
"missing-verification": "양쪽을 다 구현해서 전파가 되는지 확인하지 않았다. 원인 확정까지가 이 실험의 범위다",
|
|
"relations": [
|
|
"case:nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"reference:clear-the-header-before-you-trust-it"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"c2-backchannel-both-sides"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b4-edge-authorization__01-header-handling.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "쿠키에 세션을 담으면 지울 대상을 잃는다 — TTL 로 되찾은 고아 세션",
|
|
"slug": "orphan-sessions-and-the-ttl-that-finds-them",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b7-b7a"
|
|
],
|
|
"code": [
|
|
"--cookie-secret",
|
|
"--cookie-refresh",
|
|
"refresh:disabled"
|
|
],
|
|
"classification": "secret 교체가 티켓을 못 풀게 만든 상태에서 무엇을 지울지 고르는 방법을 TTL 역산으로 세우고 로그와 1 초 차로 교차검증해 닫았다",
|
|
"missing-verification": "--cookie-refresh 를 켠 구성에서는 이 역산이 성립하지 않는다. 그 경우의 정리 방법은 재지 않았다",
|
|
"relations": [
|
|
"case:nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"reference:clear-the-header-before-you-trust-it"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"b7-cookie-session-tradeoff"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/b7a-orphan-session__01-orphan-lifecycle.txt",
|
|
"raw/b7-cookie-secret__01-deploy.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [],
|
|
"question": [],
|
|
"decision": [],
|
|
"reference": [
|
|
{
|
|
"title": "믿기 전에 그 헤더를 먼저 지운다",
|
|
"slug": "clear-the-header-before-you-trust-it",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"classification": "프록시가 동명 헤더를 자동으로 덮어쓸 것이라는 전제가 틀렸다는 것을 확인했고, 다른 프록시에서도 같은 확인이 필요하다",
|
|
"scope": "엣지가 인증 결과를 헤더로 실어 업스트림에 넘기는 구성",
|
|
"exceptions": "업스트림이 헤더가 아니라 서명된 토큰을 검증하면 이 지우기가 필요 없다",
|
|
"relations": [
|
|
"case:nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"case:orphan-sessions-and-the-ttl-that-finds-them"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
]
|
|
},
|
|
"topic": "trust-handed-over-at-the-edge"
|
|
},
|
|
"operations-that-report-success": {
|
|
"title": "성공이라고 적으면서 실패하는 운영 절차",
|
|
"readerQuestion": "운영 절차가 성공이라고 보고하는데도 의도한 일이 일어나지 않는 자리는 어디이고, 무엇으로 판정하는가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "새 인증서가 디스크에 있고 38분 25초 동안 옛 인증서가 나갔다",
|
|
"slug": "the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4"
|
|
],
|
|
"code": [
|
|
"certbot-renew.service ExecStartPost",
|
|
"/etc/letsencrypt/renewal-hooks/",
|
|
"nginx 마스터·워커 PID"
|
|
],
|
|
"classification": "디스크 기록 시각과 실제 서빙 시각을 따로 재어 공백을 확정하고 원인 셋이 모두 비어 있는 것을 확인했으며, 88 일 잠복한다는 성질까지 닫았다",
|
|
"missing-verification": "실제 만료가 임박한 상태를 만들지 않았다. 이 결함이 만료로 드러나는 경로는 재지 않았다",
|
|
"relations": [
|
|
"case:a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"decision:put-the-reload-in-a-deploy-hook",
|
|
"question:does-the-renewal-timer-actually-renew"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"renewal-to-serving-gap"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/d4-certificate-renewal__07-renewal-hook-missing.txt",
|
|
"raw/d4-certificate-renewal__09-serial-timeline.txt",
|
|
"raw/d4-certificate-renewal__12-certbot-state.txt",
|
|
"raw/d4-certificate-renewal__13-verdict.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "deploy 훅 하나가 그 공백을 1~2초로 줄였다",
|
|
"slug": "a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"code": [
|
|
"renewal-hooks/deploy/",
|
|
"types_hash 경고"
|
|
],
|
|
"classification": "처방을 실제로 넣고 전후를 같은 방법으로 재어 확인했고, 훅 로그의 error 문구가 실패가 아니라는 것까지 갈라 닫았다",
|
|
"missing-verification": "실제 갱신 주기에서 훅이 도는 것은 확인하지 않았다. 강제 갱신으로만 검증했다",
|
|
"relations": [
|
|
"case:the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"decision:put-the-reload-in-a-deploy-hook",
|
|
"reference:judge-a-reload-by-the-worker-pid-not-the-log"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"d4a-hook-effect"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/d4a-deploy-hook__01-hook-verified.txt",
|
|
"raw/d4a-deploy-hook__02-certbot-with-hook.txt",
|
|
"raw/d4a-deploy-hook__03-after-state.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "되돌리기를 막은 것은 체크섬이었고 그 판정은 조건부였다",
|
|
"slug": "the-upgrade-that-would-not-roll-back",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d1-d2"
|
|
],
|
|
"code": [
|
|
"liquibase ValidationFailedException",
|
|
"databasechangelog",
|
|
"StatefulSet 롤링 업데이트"
|
|
],
|
|
"classification": "롤백 실패를 관측하고 원인을 체크섬으로 확정한 뒤, 스키마가 움직이지 않은 방향은 실제로 성공하는 것까지 확인해 조건을 붙여 닫았다",
|
|
"missing-verification": "스키마가 크게 바뀌는 메이저 업그레이드에서는 재지 않았다. 두 패치 버전 사이만 확인했다",
|
|
"relations": [
|
|
"reference:judge-a-reload-by-the-worker-pid-not-the-log",
|
|
"decision:put-the-reload-in-a-deploy-hook"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"d2-upgrade-direction"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/d4-certificate-renewal__01-certificate-state.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [],
|
|
"reference": [
|
|
{
|
|
"title": "적용됐는지는 로그 문구가 아니라 상태로 판정한다",
|
|
"slug": "judge-a-reload-by-the-worker-pid-not-the-log",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"classification": "같은 절차에서 로그는 성공이라고 적는데 상태는 바뀌지 않은 자리를 두 번 만났고, 판정을 상태로 옮기는 규칙은 다른 재적재에도 적용된다",
|
|
"scope": "설정이나 인증서를 다시 읽게 하는 절차. reload · rotate · reconcile",
|
|
"exceptions": "절차가 프로세스를 완전히 교체하면 PID 비교가 판정이 되지 않는다. 그때는 적재한 값 자체를 확인한다",
|
|
"relations": [
|
|
"case:a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"case:the-upgrade-that-would-not-roll-back"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"question": [
|
|
{
|
|
"title": "갱신 타이머가 실제 갱신에서도 도는가",
|
|
"slug": "does-the-renewal-timer-actually-renew",
|
|
"readiness": "OPEN",
|
|
"source": [
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-재보지-않은-것",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"known": "강제 갱신에서는 deploy 훅이 1~2 초 만에 reload 를 걸었다. 타이머 자체는 매번 SUCCESS 로 끝나고 있다",
|
|
"unknown": "만료 30 일 전 조건이 성립해 타이머가 실제 갱신을 수행할 때도 같은 훅이 도는가",
|
|
"next-verification": "만료 30 일 전(약 89 일 뒤)에 타이머가 돈 뒤 워커 PID 와 서빙 인증서의 일련번호를 확인한다",
|
|
"decision-criterion": "워커 PID 가 바뀌고 서빙 일련번호가 새 인증서와 같으면 닫는다. 그렇지 않으면 훅이 강제 갱신에서만 도는 것이므로 타이머 유닛 쪽에 훅을 다시 건다",
|
|
"relations": [
|
|
"case:the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"case:a-deploy-hook-closed-the-gap-to-two-seconds"
|
|
],
|
|
"kind": "question",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"decision": [
|
|
{
|
|
"title": "reload 를 사람이 아니라 deploy 훅이 부르게 한다",
|
|
"slug": "put-the-reload-in-a-deploy-hook",
|
|
"readiness": "READY",
|
|
"decision-status": "ADOPTED",
|
|
"source": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"decision-evidence": [
|
|
"case:the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"case:a-deploy-hook-closed-the-gap-to-two-seconds"
|
|
],
|
|
"grounds": "훅이 없을 때 갱신에서 서빙까지 2305 초가 걸렸고 그 reload 를 부른 것은 자동화가 아니라 사람이었다. 훅을 넣자 1~2 초가 됐다",
|
|
"classification": "사람이 치는 절차로 두는 대안을 실제 공백으로 확인한 뒤 훅으로 정했고, 훅 로그가 error 를 찍는다는 비용을 함께 적었다",
|
|
"relations": [
|
|
"case:a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"reference:judge-a-reload-by-the-worker-pid-not-the-log",
|
|
"question:does-the-renewal-timer-actually-renew"
|
|
],
|
|
"kind": "decision",
|
|
"publication": "미작성"
|
|
}
|
|
]
|
|
},
|
|
"topic": "operations-that-report-success"
|
|
},
|
|
"when-the-measurement-lies": {
|
|
"title": "측정이 거짓말하는 자리",
|
|
"readerQuestion": "무엇을 재야 잰 것이 되는가 — 주입과 관측 사이에서 측정은 어디서 거짓말하는가?",
|
|
"kinds": {
|
|
"case": [
|
|
{
|
|
"title": "주입이 아홉 번 조용히 실패했고 전부 아무 일도 없는 것처럼 보였다",
|
|
"slug": "nine-injections-that-silently-did-nothing",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#문제를-어렵게-만든-제약-주입이-먹지-않는다"
|
|
],
|
|
"code": [
|
|
"conntrack",
|
|
"kube-router FORWARD",
|
|
"PID 1 SIGKILL",
|
|
"flannel VXLAN"
|
|
],
|
|
"classification": "아홉 건이 각각 다른 이유로 실패한 것을 하나씩 확정하고, 실패한 주입과 영향 없는 주입이 구별되지 않는다는 공통 원인으로 닫았다",
|
|
"missing-verification": "아홉 건 모두를 고친 뒤 다시 돌린 것은 아니다. 일부는 다른 주입 방법으로 우회했다",
|
|
"relations": [
|
|
"reference:verify-the-injection-landed-separately-from-the-result",
|
|
"case:seventy-six-failures-that-were-not-the-servers",
|
|
"concept:the-up-metric-cannot-see-alive-but-useless"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"injection-verification",
|
|
"a5-partition-asymmetry"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a1-jgroups-transport-block__05-conntrack-problem.txt",
|
|
"raw/a6-latency-injection__03-flannel-injection.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "실패 76 건이 서버 탓이 아니었다 — 대조군이 오보를 막았다",
|
|
"slug": "seventy-six-failures-that-were-not-the-servers",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-대조군-없이는"
|
|
],
|
|
"code": [
|
|
"TCP 연결수 0",
|
|
"50µs"
|
|
],
|
|
"classification": "같은 순간의 대조 폴링과 연결수·소요 시간·재현율 넷으로 귀속을 뒤집었고, 대조군이 없었으면 무엇이 됐을지까지 닫았다",
|
|
"missing-verification": "클라이언트 쪽 원인을 특정하지 않았다. 서버 탓이 아니라는 것까지가 이 확인의 범위다",
|
|
"relations": [
|
|
"reference:verify-the-injection-landed-separately-from-the-result",
|
|
"concept:the-up-metric-cannot-see-alive-but-useless",
|
|
"reference:never-subtract-values-from-two-clocks"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"measurement-control"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/d4-certificate-renewal__05-control-no-injection.txt",
|
|
"raw/d4-certificate-renewal__11-inflight-full.txt"
|
|
],
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "산문으로 적힌 측정 장치를 실행 가능하게 고쳤더니 한 건이 깨졌다",
|
|
"slug": "commands-written-as-prose-do-not-run",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-재현-가능성",
|
|
"final/document.md#검토한-선택지와-막힌-지점-스크립트를-쓰지-않는다"
|
|
],
|
|
"code": [
|
|
"kubectl run --rm -i",
|
|
"상주 탐침"
|
|
],
|
|
"classification": "헤드라인 수치를 만든 명령 넷이 산문이던 것을 셸 표현식으로 바꾸고 실제로 돌려 확인했으며, 그 확인에서 한 건이 깨진 것까지 닫았다",
|
|
"missing-verification": "고친 명령을 다른 환경에서 돌려 보지 않았다. 이 실험대에서만 확인했다",
|
|
"relations": [
|
|
"case:nine-injections-that-silently-did-nothing",
|
|
"reference:verify-the-injection-landed-separately-from-the-result"
|
|
],
|
|
"kind": "case",
|
|
"ssot-assets": [
|
|
"reproducibility-gap"
|
|
],
|
|
"ssot-evidence": [
|
|
"raw/a6-latency-injection__04-pool-under-load.txt"
|
|
],
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"concept": [
|
|
{
|
|
"title": "up 지표는 살아 있지만 쓸모없는 상태를 보지 못한다",
|
|
"slug": "the-up-metric-cannot-see-alive-but-useless",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-관측을-어디에",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-관측-도구는-부분집합"
|
|
],
|
|
"basis-version": "Prometheus up 지표 · Keycloak 26.7.0 /metrics · 이 실험대의 스크레이프 대상",
|
|
"classification": "프로세스가 살아 있고 /metrics 가 응답하기만 하면 1 이 된다는 것을 알아야 503 중에도 1 이던 관측을 읽을 수 있다",
|
|
"relations": [
|
|
"case:seventy-six-failures-that-were-not-the-servers",
|
|
"concept:readiness-hides-the-broken-node",
|
|
"reference:verify-the-injection-landed-separately-from-the-result"
|
|
],
|
|
"kind": "concept",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"reference": [
|
|
{
|
|
"title": "예측을 먼저 적고, 주입이 걸렸는지 결과와 따로 확인하고, 대조군 없이 귀속하지 않는다",
|
|
"slug": "verify-the-injection-landed-separately-from-the-result",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#결국-지키려던-것은-무엇이었나",
|
|
"final/document.md#문제를-어렵게-만든-제약-주입이-먹지-않는다",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-대조군-없이는"
|
|
],
|
|
"classification": "세 규칙이 각각 어겨졌을 때 무엇이 틀렸는지가 이 실험대의 이력으로 남아 있고, 장애 주입을 하는 다른 실험에도 그대로 적용된다",
|
|
"scope": "상태를 일부러 망가뜨리고 그 영향을 재는 모든 실험",
|
|
"exceptions": "주입 없이 평시를 관측하는 측정에는 첫 두 규칙이 걸리지 않는다. 대조군 규칙은 그때도 걸린다",
|
|
"relations": [
|
|
"case:nine-injections-that-silently-did-nothing",
|
|
"case:seventy-six-failures-that-were-not-the-servers",
|
|
"concept:the-up-metric-cannot-see-alive-but-useless"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
},
|
|
{
|
|
"title": "두 시계에서 온 값을 빼지 않는다",
|
|
"slug": "never-subtract-values-from-two-clocks",
|
|
"readiness": "READY",
|
|
"source": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-두-시계"
|
|
],
|
|
"classification": "보정 없이 뺀 값이 자릿수가 아니라 방향까지 틀리는 것을 실제로 만났고, 시각을 다루는 다른 측정에도 같은 확인이 필요하다",
|
|
"scope": "서로 다른 기계에서 온 타임스탬프의 차이를 재는 자리",
|
|
"exceptions": "같은 기계의 단조 시계로만 잰 구간에는 걸리지 않는다",
|
|
"relations": [
|
|
"case:seventy-six-failures-that-were-not-the-servers",
|
|
"case:commands-written-as-prose-do-not-run"
|
|
],
|
|
"kind": "reference",
|
|
"publication": "미작성"
|
|
}
|
|
],
|
|
"question": [],
|
|
"decision": []
|
|
},
|
|
"topic": "when-the-measurement-lies"
|
|
}
|
|
},
|
|
"candidates": [
|
|
{
|
|
"id": "SSOT-session-sharing-is-the-database-not-replication",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-전제가-무너졌다",
|
|
"final/document.md#선택의-이유와-지킨-경계-a1"
|
|
],
|
|
"summary": "클러스터는 형성됐는데 세션을 나르는 것은 데이터베이스였다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:session-sharing-is-the-database-not-replication",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-cache-temperature-decides-the-outcome",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a7-a7a"
|
|
],
|
|
"summary": "같은 설정이 캐시 온도만으로 세 가지 답을 냈다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:cache-temperature-decides-the-outcome",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-rolling-restart-keeps-sessions-drops-cache",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a8"
|
|
],
|
|
"summary": "롤링 재시작은 세션을 남기고 캐시만 지웠다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:rolling-restart-keeps-sessions-drops-cache",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-persistent-vs-volatile-user-sessions",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-버전-조건"
|
|
],
|
|
"summary": "persistent-user-sessions 가 세션의 거처를 정한다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:persistent-vs-volatile-user-sessions",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-state-the-version-and-the-setting-with-the-result",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#코드보다-먼저-드러난-문제-버전-조건",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-적용되지-않는-조건"
|
|
],
|
|
"summary": "버전과 설정을 결과와 함께 적는다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:state-the-version-and-the-setting-with-the-result",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-four-logins-that-returned-200-and-vanished",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a2-a3"
|
|
],
|
|
"summary": "200 과 토큰을 받은 로그인 네 건이 데이터베이스에 없었다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:four-logins-that-returned-200-and-vanished",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-two-ways-to-lose-a-node",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a4"
|
|
],
|
|
"summary": "노드를 잃는 두 가지 — 저장소가 같이 죽는 것과 들어갈 길이 없는 것",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:two-ways-to-lose-a-node",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-200ms-of-delay-became-22-seconds",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a6"
|
|
],
|
|
"summary": "200 밀리초를 넣었더니 응답이 22.2 초가 됐다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:200ms-of-delay-became-22-seconds",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-readiness-hides-the-broken-node",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-관측을-어디에"
|
|
],
|
|
"summary": "readiness 가 깨진 노드를 시야에서 먼저 치운다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:readiness-hides-the-broken-node",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-most-of-an-outage-is-noticing",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a4"
|
|
],
|
|
"summary": "장애 시간의 대부분은 알아채는 데 걸린다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:most-of-an-outage-is-noticing",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-moving-the-session-left-the-tokens-behind",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b1"
|
|
],
|
|
"summary": "세션만 Redis 로 옮기자 토큰이 따라오지 않았다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:moving-the-session-left-the-tokens-behind",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-a-primary-key-without-the-session-id",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b2"
|
|
],
|
|
"summary": "기본키에 세션 id 가 없어서 두 번째 로그인이 첫 토큰을 덮어썼다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:a-primary-key-without-the-session-id",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-the-winner-of-the-rotation-race-also-loses",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b3"
|
|
],
|
|
"summary": "회전 경쟁에서 이긴 요청의 토큰도 쓸 수 없었다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:the-winner-of-the-rotation-race-also-loses",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-persistence-without-a-volume-and-rotation-without-overlap",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b5-b6"
|
|
],
|
|
"summary": "볼륨 없는 영속화와 유예 없는 키 회전",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:persistence-without-a-volume-and-rotation-without-overlap",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-two-stores-two-lookup-keys",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0"
|
|
],
|
|
"summary": "세션과 인가된 클라이언트는 조회 키가 다르다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:two-stores-two-lookup-keys",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-look-at-the-lookup-key-before-moving-the-store",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b0",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"summary": "저장소를 옮기기 전에 조회 키를 본다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:look-at-the-lookup-key-before-moving-the-store",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-split-the-two-stores-and-design-each",
|
|
"kindCandidate": "DECISION",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b2",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"summary": "세션과 토큰의 저장소를 나눠 각각 설계한다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "decision:split-the-two-stores-and-design-each",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b4"
|
|
],
|
|
"summary": "nginx 는 자기가 설정하지 않은 헤더를 덮어쓰지 않았다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:nginx-does-not-overwrite-a-header-it-never-sets",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-nobody-implemented-backchannel-logout",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-c층"
|
|
],
|
|
"summary": "백채널 로그아웃은 양쪽 다 없었다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:nobody-implemented-backchannel-logout",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-orphan-sessions-and-the-ttl-that-finds-them",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b7-b7a"
|
|
],
|
|
"summary": "쿠키에 세션을 담으면 지울 대상을 잃는다 — TTL 로 되찾은 고아 세션",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:orphan-sessions-and-the-ttl-that-finds-them",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-clear-the-header-before-you-trust-it",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-b4",
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-열린-질문-네-개에-대한-답"
|
|
],
|
|
"summary": "믿기 전에 그 헤더를 먼저 지운다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:clear-the-header-before-you-trust-it",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4"
|
|
],
|
|
"summary": "새 인증서가 디스크에 있고 38분 25초 동안 옛 인증서가 나갔다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:the-certificate-that-took-38-minutes-to-reach-the-wire",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"summary": "deploy 훅 하나가 그 공백을 1~2초로 줄였다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:a-deploy-hook-closed-the-gap-to-two-seconds",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-the-upgrade-that-would-not-roll-back",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d1-d2"
|
|
],
|
|
"summary": "되돌리기를 막은 것은 체크섬이었고 그 판정은 조건부였다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:the-upgrade-that-would-not-roll-back",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-judge-a-reload-by-the-worker-pid-not-the-log",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"summary": "적용됐는지는 로그 문구가 아니라 상태로 판정한다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:judge-a-reload-by-the-worker-pid-not-the-log",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-does-the-renewal-timer-actually-renew",
|
|
"kindCandidate": "QUESTION",
|
|
"sourceRefs": [
|
|
"final/document.md#얻은-것-잃은-것-적용하지-않을-때-재보지-않은-것",
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"summary": "갱신 타이머가 실제 갱신에서도 도는가",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "question:does-the-renewal-timer-actually-renew",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-put-the-reload-in-a-deploy-hook",
|
|
"kindCandidate": "DECISION",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d4a"
|
|
],
|
|
"summary": "reload 를 사람이 아니라 deploy 훅이 부르게 한다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "decision:put-the-reload-in-a-deploy-hook",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-nine-injections-that-silently-did-nothing",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-주입이-먹지-않는다"
|
|
],
|
|
"summary": "주입이 아홉 번 조용히 실패했고 전부 아무 일도 없는 것처럼 보였다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:nine-injections-that-silently-did-nothing",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-seventy-six-failures-that-were-not-the-servers",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-대조군-없이는"
|
|
],
|
|
"summary": "실패 76 건이 서버 탓이 아니었다 — 대조군이 오보를 막았다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:seventy-six-failures-that-were-not-the-servers",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-commands-written-as-prose-do-not-run",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-재현-가능성",
|
|
"final/document.md#검토한-선택지와-막힌-지점-스크립트를-쓰지-않는다"
|
|
],
|
|
"summary": "산문으로 적힌 측정 장치를 실행 가능하게 고쳤더니 한 건이 깨졌다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:commands-written-as-prose-do-not-run",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-the-up-metric-cannot-see-alive-but-useless",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-관측을-어디에",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-관측-도구는-부분집합"
|
|
],
|
|
"summary": "up 지표는 살아 있지만 쓸모없는 상태를 보지 못한다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "concept:the-up-metric-cannot-see-alive-but-useless",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-verify-the-injection-landed-separately-from-the-result",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#결국-지키려던-것은-무엇이었나",
|
|
"final/document.md#문제를-어렵게-만든-제약-주입이-먹지-않는다",
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-대조군-없이는"
|
|
],
|
|
"summary": "예측을 먼저 적고, 주입이 걸렸는지 결과와 따로 확인하고, 대조군 없이 귀속하지 않는다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:verify-the-injection-landed-separately-from-the-result",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-never-subtract-values-from-two-clocks",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#결정이-지켜지는지-확인하는-방법-두-시계"
|
|
],
|
|
"summary": "두 시계에서 온 값을 빼지 않는다",
|
|
"disposition": "PROMOTE",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:never-subtract-values-from-two-clocks",
|
|
"reason": "SSOT 가 이 주장을 독립된 관측·규칙·결정·질문으로 담고 있고 독립성 검사를 통과한다"
|
|
},
|
|
{
|
|
"id": "SSOT-a1-transport-vs-discovery",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a1"
|
|
],
|
|
"summary": "7800 을 끊었더니 세션 공유는 안 깨지고 로그아웃 전파가 깨졌다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:session-sharing-is-the-database-not-replication",
|
|
"reason": "SSOT 가 이 결과를 A-0 의 확증으로 적는다. 발견은 DB 를 쓰고 전송은 7800 을 쓴다는 같은 사실의 두 면이다"
|
|
},
|
|
{
|
|
"id": "SSOT-a5-asymmetric-partition",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택의-이유와-지킨-경계-a5"
|
|
],
|
|
"summary": "비대칭 분단은 클러스터를 가르지 못하고 전면 장애 경로도 없다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:two-ways-to-lose-a-node",
|
|
"reason": "결론이 「전면 장애가 없다」라 독립 기록으로 읽을 이유가 약하다. 여기서 나온 주입 실패 셋은 측정 Topic 의 Case 가 담는다"
|
|
},
|
|
{
|
|
"id": "SSOT-c1-sso-across-apps",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-c층"
|
|
],
|
|
"summary": "두 앱이 같은 realm 으로 SSO 되는 것을 확인했다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:nobody-implemented-backchannel-logout",
|
|
"reason": "SSO 가 되는 것 자체는 기대한 동작이고, 이 실험이 남긴 것은 그 옆에서 관측한 로그아웃 미전파다"
|
|
},
|
|
{
|
|
"id": "SSOT-d1-backup-restore",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d1-d2"
|
|
],
|
|
"summary": "백업과 복구 — 본문 RTO 30초와 타임라인 41초가 어긋났다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:seventy-six-failures-that-were-not-the-servers",
|
|
"reason": "SSOT 가 이 건을 문서와 증거가 어긋난 사례 표에 넣었고, 그 표가 측정 Topic 의 근거다"
|
|
},
|
|
{
|
|
"id": "SSOT-d3-secret-base64",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#선택이-코드와-흐름에-반영되는-방식-d3"
|
|
],
|
|
"summary": "kubectl get secret 의 base64 는 암호화가 아니라 인코딩이다",
|
|
"disposition": "KEEP_IN_SSOT",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": null,
|
|
"reason": "널리 알려진 사실이고 이 실험이 더한 것은 두 노출 경로를 확인한 것까지다. 규칙으로 빼면 다른 프로젝트에 줄 것이 없다"
|
|
},
|
|
{
|
|
"id": "SSOT-lab-topology",
|
|
"kindCandidate": "CONCEPT",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-실험대"
|
|
],
|
|
"summary": "베어메탈 한 대 위의 VM 두 대와 인증서 SAN 셋",
|
|
"disposition": "KEEP_IN_SSOT",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": null,
|
|
"reason": "측정 환경의 구성이다. 각 Case 의 검증 환경이 필요한 만큼을 이미 담는다"
|
|
},
|
|
{
|
|
"id": "SSOT-guest-host-sudo",
|
|
"kindCandidate": "CASE",
|
|
"sourceRefs": [
|
|
"final/document.md#문제를-어렵게-만든-제약-sudo"
|
|
],
|
|
"summary": "호스트의 sudo 가 비밀번호를 요구해 조용히 실패했다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "case:nine-injections-that-silently-did-nothing",
|
|
"reason": "아홉 건 표의 8 번이 이 사건이다. 표 밖으로 빼면 같은 이야기가 둘이 된다"
|
|
},
|
|
{
|
|
"id": "SSOT-observation-three-points",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#검토한-선택지와-막힌-지점-관측을-어디에"
|
|
],
|
|
"summary": "관측 지점을 외부·지표·데이터베이스 셋으로 늘렸다",
|
|
"disposition": "MERGE_INTO",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": "reference:verify-the-injection-landed-separately-from-the-result",
|
|
"reason": "주입이 걸렸는지 결과와 따로 확인한다는 규칙이 이 세 지점을 요구한다. 그 규칙의 한 절이다"
|
|
},
|
|
{
|
|
"id": "SSOT-techviz-remake",
|
|
"kindCandidate": "REFERENCE",
|
|
"sourceRefs": [
|
|
"final/document.md#이-기록에-아직-없는-것"
|
|
],
|
|
"summary": "손그림 28 건을 techviz 로 다시 만들며 lint 가 잡은 두 가지",
|
|
"disposition": "KEEP_IN_SSOT",
|
|
"dispositionReview": "CONFIRMED",
|
|
"target": null,
|
|
"reason": "이 저장소의 그림 제작 절차이지 이 주제의 독자 질문에 답하지 않는다. 후보 범위 밖이다"
|
|
}
|
|
],
|
|
"unlisted": [],
|
|
"history": {},
|
|
"counts": {
|
|
"topics": 6,
|
|
"nodes": 33,
|
|
"written": 0,
|
|
"unwritten": 33,
|
|
"unlisted": 0,
|
|
"candidates": 42
|
|
},
|
|
"unassignedSsotAssets": {
|
|
"d3-secret-exposure": "D-3 의 base64 관측은 KEEP_IN_SSOT 로 두어 글감이 없다. 그림도 함께 남긴다",
|
|
"wrong-predictions": "틀린 예측 다섯을 한 자리에 모은 그림이라 특정 Case 에 붙지 않는다. 주제 전체의 그림이다",
|
|
"open-questions-answered": "앞 프로젝트가 남긴 열린 질문 넷에 답한 것을 모은 그림이라 이 프로젝트의 어느 한 글감에 붙지 않는다",
|
|
"not-applicable-conditions": "적용되지 않는 조건을 모은 그림이고 그것을 담는 것은 Reference 다. Reference 에는 본문이 없어 그림을 렌더링할 자리가 없다"
|
|
}
|
|
}
|