W23 · 운영 관측성
23주차 코드 뒤풀이: metric·requestId·DB wait부터 alert·runbook까지
낮은 cardinality metric 설계와 learner-typed Micrometer 코드에서 시작해, requestId 회귀 Gate·disposable PostgreSQL row-lock wait·행동 가능한 alert와 incident runbook까지 하나의 관측 사슬로 읽습니다. 로컬 자동 증거와 dashboard·notification 같은 외부 수동 증거를 분리하고, 교재의 stale 잠금 설명과 정본 source 차이, prompt-only Q37·Q38의 비정본 가정도 그대로 드러냅니다.
01D1 metric catalog — RED와 도메인 신호 5개를 낮은 cardinality로 고정
illustrative/csv/W23-D1-metric-catalog.csv
학습용 예시 · metric catalog template · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F016줄 연결6줄 번역1 chunks
D1 metric catalog — RED와 도메인 신호 5개를 낮은 cardinality로 고정
illustrative/csv/W23-D1-metric-catalog.csv
학습용 예시 · metric catalog template · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F01STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다.
- `metrics=5`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다` 경계와 어떻게 연결되는가?
metrics=5required=5high_cardinality_tags=0rate/error/durationfixed outcome tagsSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
D1 metric catalog — RED와 도메인 신호 5개를 낮은 cardinality로 고정 ― 관측 증거 흐름으로 바꾸기
RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다.
핵심값 metrics=5, required=5, high_cardinality_tags=0, rate/error/duration, fixed outcome tags을 원본 줄로 따라가되, `catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–6줄
1~6줄을 한 덩어리로 읽어 1번째 움직임을 본다. RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다.
- 코드 연결
1~6줄- 비유
- 관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표
- 비유의 끝
- catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다
문제의 첫 장면
-
첫 고정값은 `metrics=5` 맞아?
-
RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다.
-
원문에서 `metrics=5`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다`도 같이 적어.
입력에서 결과까지
-
`required=5`은 언제 생겨?
-
고객 요청 RED 신호를 정한다. → transfer·retry 도메인 신호를 더한다.
-
reconciliation·idempotency 이상 신호를 더한다. → 모든 tag가 LOW cardinality인지 검토한다.
-
관찰값과 `requestId·accountId를 metric tag로 쓰지 않는다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 6줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F01-L01 | metric, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | metric 이름·meter type·허용 tag·의미·cardinality·사용처라는 catalog 열 계약을 연다.
|
| 2줄F01-L02 | http_server_requests, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | 요청 rate·error·duration을 route/status/outcome의 제한된 tag로 관찰하는 RED timer다.
|
| 3줄F01-L03 | transfer_completed, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | 고정 outcome별 완료 건수를 누적하는 domain counter다.
|
| 4줄F01-L04 | transfer_retry, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | SQLSTATE의 제한된 category별 lock retry 시도를 세는 counter다.
|
| 5줄F01-L05 | reconciliation_mismatch, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | 아직 해결되지 않은 대사 불일치 수를 job별로 읽는 gauge다.
|
| 6줄F01-L06 | idempotency_stale, |
관제실 계기판에 속도·고장·지연과 금고 이상 신호를 다섯 칸으로 나누고, 버튼 이름은 몇 개의 고정 선택지만 쓰는 계측표 | stale PROCESSING idempotency record 수를 operation별로 읽는 gauge다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `fixed outcome tags`이야.
-
`threshold는 운영 SLA가 아니다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 1개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
metric,type,tags,meaning,cardinality,alert_or_dashboard
http_server_requests,timer,route|status|outcome,customer request rate error and duration,LOW,RED rate error and p95 dashboard
transfer_completed,counter,outcome,committed transfer outcomes by fixed category,LOW,transfer outcome dashboard
transfer_retry,counter,sqlstate_category,bounded database lock retry attempts,LOW,retry burst alert
reconciliation_mismatch,gauge,job,unresolved reconciliation mismatch count,LOW,ledger mismatch alert
idempotency_stale,gauge,operation,stale processing idempotency records,LOW,stale request alert
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 6줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | metric,type,tags,meaning,cardinality,alert_or_dashboard | metric 이름·meter type·허용 tag·의미·cardinality·사용처라는 catalog 열 계약을 연다. |
| 2 | http_server_requests,timer,route|status|outcome,customer request rate error and duration,LOW,RED rate error and p95 dashboard | 요청 rate·error·duration을 route/status/outcome의 제한된 tag로 관찰하는 RED timer다. |
| 3 | transfer_completed,counter,outcome,committed transfer outcomes by fixed category,LOW,transfer outcome dashboard | 고정 outcome별 완료 건수를 누적하는 domain counter다. |
| 4 | transfer_retry,counter,sqlstate_category,bounded database lock retry attempts,LOW,retry burst alert | SQLSTATE의 제한된 category별 lock retry 시도를 세는 counter다. |
| 5 | reconciliation_mismatch,gauge,job,unresolved reconciliation mismatch count,LOW,ledger mismatch alert | 아직 해결되지 않은 대사 불일치 수를 job별로 읽는 gauge다. |
| 6 | idempotency_stale,gauge,operation,stale processing idempotency records,LOW,stale request alert | stale PROCESSING idempotency record 수를 operation별로 읽는 gauge다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다. 다만 catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다
문법 해부
- CSV 첫 줄은 metric·type·tags·meaning·cardinality·사용처라는 여섯 열 계약이다.
- `route|status|outcome`처럼 tag 후보를 제한하고 고객 ID·requestId 같은 무한 값을 tag로 쓰지 않는다.
실행 순서
- 고객 요청 RED 신호를 정한다.
- transfer·retry 도메인 신호를 더한다.
- reconciliation·idempotency 이상 신호를 더한다.
- 모든 tag가 LOW cardinality인지 검토한다.
W23 조각별 정밀 해설
F01-C01 · 원문 1–6줄
- 문법 해부
- 1~6줄의 `원문 1–6줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `metrics=5`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `metrics=5`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다.
- 착각 방지
- `원문 1–6줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
requestId나 accountId를 metric tag로 넣는다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F01-T01 HTTP | route/status/outcome | timer | rate·error·duration | 실제 dashboard 실행 아님 |
| F01-T02 transfer | 고정 outcome | counter | completed/retry | 개별 고객 tag 금지 |
| F01-T03 integrity | job/operation | gauge | mismatch·stale | threshold는 별도 검증 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `관측 metric 이름·type·tag cardinality 계약`이야.
-
대표 경계는 `외부 관측 UI는 별도 증거가 필요하다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
이름과 tag 조합별 meter를 보관한다.
catalog만으로 application 등록을 증명하지 않는다.tag 조합마다 시계열을 만든다.
LOW 표시는 실제 cardinality 측정값이 아니다.meter를 query와 window에 연결한다.
URL·screenshot이 없으면 external Green이 아니다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ requestId나 accountId를 metric tag로 넣는다
왜 틀리나 관측 metric 이름·type·tag cardinality 계약의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 고객 요청 RED 신호를 정한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ counter와 gauge의 의미를 섞는다
왜 틀리나 관측 metric 이름·type·tag cardinality 계약의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 transfer·retry 도메인 신호를 더한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `requestId·accountId를 metric tag로 쓰지 않는다` 상태인데도 Green을 주장하게 된다.
❌ 5xx와 업무 409를 같은 오류율로 합친다
왜 틀리나 관측 metric 이름·type·tag cardinality 계약의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 reconciliation·idempotency 이상 신호를 더한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `threshold는 운영 SLA가 아니다` 상태인데도 Green을 주장하게 된다.
❌ catalog 5행을 dashboard 실행 증거라 부른다
왜 틀리나 관측 metric 이름·type·tag cardinality 계약의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 모든 tag가 LOW cardinality인지 검토한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `외부 관측 UI는 별도 증거가 필요하다` 상태인데도 Green을 주장하게 된다.
❌ threshold를 운영 SLA로 단정한다
왜 틀리나 관측 metric 이름·type·tag cardinality 계약의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 모든 tag가 LOW cardinality인지 검토한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
catalog는 실제 meter 등록이나 dashboard를 증명하지 않는다
이 책임을 맡는 곳: metric/query grainrequestId·accountId를 metric tag로 쓰지 않는다
이 책임을 맡는 곳: 별도 local/external 실행threshold는 운영 SLA가 아니다
이 책임을 맡는 곳: secret·개인정보·운영 정책외부 관측 UI는 별도 증거가 필요하다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: RED 관점의 필수 metric 5개와 낮은 cardinality tag만 고정하는 D1 학습 catalog다.
2단계 · 코드 조각 재조립
- 원문 1–6줄
3단계 · 파일 전체 다시 쓰기
6개 물리 줄을 원본 순서로 복원하고 SHA-256 3b4d42f8d2037b67091c78d60d8159b10a4b75e8890e3b0e442d71f098fd089e와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
metric,type,tags,meaning,cardinality,alert_or_dashboard
http_server_requests,timer,route|status|outcome,customer request rate error and duration,LOW,RED rate error and p95 dashboard
transfer_completed,counter,outcome,committed transfer outcomes by fixed category,LOW,transfer outcome dashboard
transfer_retry,counter,sqlstate_category,bounded database lock retry attempts,LOW,retry burst alert
reconciliation_mismatch,gauge,job,unresolved reconciliation mismatch count,LOW,ledger mismatch alert
idempotency_stale,gauge,operation,stale processing idempotency records,LOW,stale request alert
02TransferMetrics.java — 성공·업무 거절 counter와 duration을 한 경계에서 기록
illustrative/java/TransferMetrics.java
학습용 예시 · 학습자 작성 Java · 정본 답안 아님 · 실행 결과 아님 · 학습용 예시 · 정본 답안 아님 · W23-F0224줄 연결24줄 번역3 chunks
TransferMetrics.java — 성공·업무 거절 counter와 duration을 한 경계에서 기록
illustrative/java/TransferMetrics.java
학습용 예시 · 학습자 작성 Java · 정본 답안 아님 · 실행 결과 아님 · 학습용 예시 · 정본 답안 아님 · W23-F02STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
- `fcl.transfer`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `reference project packaged production source가 아니다` 경계와 어떻게 연결되는가?
fcl.transfercompletedbusiness_rejectedfcl.transfer.durationfinally stopSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
TransferMetrics.java — 성공·업무 거절 counter와 duration을 한 경계에서 기록 ― 관측 증거 흐름으로 바꾸기
성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
핵심값 fcl.transfer, completed, business_rejected, fcl.transfer.duration, finally stop을 원본 줄로 따라가되, `reference project packaged production source가 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. 성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
- 코드 연결
1~10줄- 비유
- 택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구
- 비유의 끝
- reference project packaged production source가 아니다
원문 11–20줄
11~20줄을 한 덩어리로 읽어 2번째 움직임을 본다. 성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
- 코드 연결
11~20줄- 비유
- 택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구
- 비유의 끝
- RuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다
원문 21–28줄
21~28줄을 한 덩어리로 읽어 3번째 움직임을 본다. 성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
- 코드 연결
21~28줄- 비유
- 택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구
- 비유의 끝
- registry 구현과 export 설정은 별도다
문제의 첫 장면
-
첫 고정값은 `fcl.transfer` 맞아?
-
성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
-
원문에서 `fcl.transfer`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `reference project packaged production source가 아니다`도 같이 적어.
입력에서 결과까지
-
`completed`은 언제 생겨?
-
Timer.Sample을 시작한다. → action을 한 번 호출한다.
-
성공 또는 business_rejected counter를 올린다. → finally에서 duration timer를 멈춘다.
-
관찰값과 `RuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 24줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F02-L01 | package com. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 3줄F02-L03 | import io. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 4줄F02-L04 | import io. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 5줄F02-L05 | import java. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 5번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 6줄F02-L06 | import java. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 8줄F02-L08 | public final class TransferMetrics { |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 8번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 9줄F02-L09 | private final MeterRegistry registry; |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 11줄F02-L11 | public TransferMetrics( |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 12줄F02-L12 | this. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드에서 `this.registry = Objects.requireNonNull(registry)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 13줄F02-L13 | } |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 15줄F02-L15 | public <T> T record( |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 16줄F02-L16 | Timer. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 동작 시작 시각을 잡아 성공·예외 공통 duration 기록의 출발점을 만든다.
|
| 17줄F02-L17 | try { |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다.
|
| 18줄F02-L18 | T result = |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 계측으로 감싼 실제 동작을 정확히 한 번 호출한다.
|
| 19줄F02-L19 | registry. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 성공 분기의 고정 outcome counter를 1 올린다.
|
| 20줄F02-L20 | return result; |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 21줄F02-L21 | } catch ( |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 21번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 22줄F02-L22 | registry. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | RuntimeException 분기의 고정 거절 outcome counter를 1 올린다.
|
| 23줄F02-L23 | throw rejected; |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 24줄F02-L24 | } finally { |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | Micrometer transfer 계측 경계 학습 코드의 24번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 25줄F02-L25 | sample. |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | finally 경로에서 성공·예외 모두 duration timer를 끝낸다.
|
| 26줄F02-L26 | } |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 27줄F02-L27 | } |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 28줄F02-L28 | } |
택배 접수 때 성공·반려 도장을 찍고, 어느 출구로 나가도 스톱워치는 반드시 멈추게 만든 한 개의 계측 창구 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `finally stop`이야.
-
`registry 구현과 export 설정은 별도다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 3개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
package com.example.financialcore.observability;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import java.util.Objects;
import java.util.function.Supplier;
public final class TransferMetrics {
private final MeterRegistry registry;
public TransferMetrics(MeterRegistry registry) {
this.registry = Objects.requireNonNull(registry);
}
public <T> T record(Supplier<T> action) {
Timer.Sample sample = Timer.start(registry);
try {
T result = action.get();
registry.counter("fcl.transfer", "outcome", "completed").increment();
return result;
} catch (RuntimeException rejected) {
registry.counter("fcl.transfer", "outcome", "business_rejected").increment();
throw rejected;
} finally {
sample.stop(registry.timer("fcl.transfer.duration"));
}
}
}
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 24줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | package com.example.financialcore.observability; | Micrometer transfer 계측 경계 학습 코드의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 3 | import io.micrometer.core.instrument.MeterRegistry; | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 4 | import io.micrometer.core.instrument.Timer; | Micrometer transfer 계측 경계 학습 코드의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 5 | import java.util.Objects; | Micrometer transfer 계측 경계 학습 코드의 5번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 6 | import java.util.function.Supplier; | Micrometer transfer 계측 경계 학습 코드의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 8 | public final class TransferMetrics { | Micrometer transfer 계측 경계 학습 코드의 8번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 9 | private final MeterRegistry registry; | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 11 | public TransferMetrics(MeterRegistry registry) { | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 12 | this.registry = Objects.requireNonNull(registry); | Micrometer transfer 계측 경계 학습 코드에서 `this.registry = Objects.requireNonNull(registry)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 13 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 15 | public <T> T record(Supplier<T> action) { | Micrometer transfer 계측 경계 학습 코드의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 16 | Timer.Sample sample = Timer.start(registry); | 동작 시작 시각을 잡아 성공·예외 공통 duration 기록의 출발점을 만든다. |
| 17 | try { | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다. |
| 18 | T result = action.get(); | 계측으로 감싼 실제 동작을 정확히 한 번 호출한다. |
| 19 | registry.counter("fcl.transfer", "outcome", "completed").increment(); | 성공 분기의 고정 outcome counter를 1 올린다. |
| 20 | return result; | Micrometer transfer 계측 경계 학습 코드의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 21 | } catch (RuntimeException rejected) { | Micrometer transfer 계측 경계 학습 코드의 21번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 22 | registry.counter("fcl.transfer", "outcome", "business_rejected").increment(); | RuntimeException 분기의 고정 거절 outcome counter를 1 올린다. |
| 23 | throw rejected; | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 24 | } finally { | Micrometer transfer 계측 경계 학습 코드의 24번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 25 | sample.stop(registry.timer("fcl.transfer.duration")); | finally 경로에서 성공·예외 모두 duration timer를 끝낸다. |
| 26 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 27 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 28 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다. 다만 reference project packaged production source가 아니다
문법 해부
- `Supplier<T>`가 실제 transfer 동작을 감싼다. PDF의 설명은 BusinessException을 말하지만 이 학습 source는 더 넓은 `RuntimeException`을 거절로 센다.
- `try/catch/finally`에서 outcome counter는 분기하고 duration timer는 공통으로 멈춘다. 다만 meter API 자체가 던지면 원래 반환값·예외를 가릴 수 있다.
실행 순서
- Timer.Sample을 시작한다.
- action을 한 번 호출한다.
- 성공 또는 business_rejected counter를 올린다.
- finally에서 duration timer를 멈춘다.
W23 조각별 정밀 해설
F02-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `fcl.transfer`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `fcl.transfer`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: reference project packaged production source가 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: reference project packaged production source가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F02-C02 · 원문 11–20줄
- 문법 해부
- 11~20줄의 `원문 11–20줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `completed`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `completed`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: RuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다.
- 착각 방지
- `원문 11–20줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: RuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F02-C03 · 원문 21–28줄
- 문법 해부
- 21~28줄의 `원문 21–28줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `business_rejected`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `business_rejected`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: registry 구현과 export 설정은 별도다.
- 착각 방지
- `원문 21–28줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: registry 구현과 export 설정은 별도다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
PDF 설명의 BusinessException과 source의 RuntimeException 경계를 같다고 본다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F02-T01 성공 | Supplier returns ok | completed +1 | 원래 값 반환 | commit 자체는 모름 |
| F02-T02 거절 | RuntimeException | business_rejected +1 | 같은 예외 재던짐 | 모든 RuntimeException을 업무 거절로 분류 |
| F02-T03 공통 | 두 경로 | finally stop | duration count +1 | 실제 p95 backend 미실행 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `Micrometer transfer 계측 경계 학습 코드`이야.
-
대표 경계는 `meter 등록만으로 dashboard Green이 아니다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
시작 시각을 잡고 stop 때 duration을 meter에 기록한다.
분산 trace나 DB wait를 자동 설명하지 않으며 stop 실패가 원래 예외를 mask할 수 있다.고정 outcome tag 조합의 누적 횟수를 올린다.
고객·예외 메시지를 tag로 넣지 않으며 registry 실패 처리 정책은 별도다.계측 뒤 RuntimeException을 다시 던진다.
PDF의 BusinessException 설명과 learner source의 RuntimeException catch 범위가 다르다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ PDF 설명의 BusinessException과 source의 RuntimeException 경계를 같다고 본다
왜 틀리나 Micrometer transfer 계측 경계 학습 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 Timer.Sample을 시작한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `reference project packaged production source가 아니다` 상태인데도 Green을 주장하게 된다.
❌ meter increment/stop은 절대 실패하지 않아 원래 결과를 가릴 수 없다고 본다
왜 틀리나 Micrometer transfer 계측 경계 학습 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 action을 한 번 호출한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `RuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다` 상태인데도 Green을 주장하게 된다.
❌ exception message를 tag value로 쓴다
왜 틀리나 Micrometer transfer 계측 경계 학습 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 성공 또는 business_rejected counter를 올린다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `registry 구현과 export 설정은 별도다` 상태인데도 Green을 주장하게 된다.
❌ record 호출을 transfer commit 증거라 부른다
왜 틀리나 Micrometer transfer 계측 경계 학습 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 finally에서 duration timer를 멈춘다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `meter 등록만으로 dashboard Green이 아니다` 상태인데도 Green을 주장하게 된다.
❌ business_rejected를 모두 5xx로 해석한다
왜 틀리나 Micrometer transfer 계측 경계 학습 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 finally에서 duration timer를 멈춘다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `reference project packaged production source가 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
reference project packaged production source가 아니다
이 책임을 맡는 곳: metric/query grainRuntimeException 전체를 업무 거절로 분류하면 운영 분류를 더 세분해야 한다
이 책임을 맡는 곳: 별도 local/external 실행registry 구현과 export 설정은 별도다
이 책임을 맡는 곳: secret·개인정보·운영 정책meter 등록만으로 dashboard Green이 아니다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: 성공·업무 거절 outcome counter와 성공/실패 공통 duration timer를 기록하는 learner-typed Micrometer wrapper다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–20줄
- 원문 21–28줄
3단계 · 파일 전체 다시 쓰기
28개 물리 줄을 원본 순서로 복원하고 SHA-256 30c2a35c6083c287f461599b4565b34d5d3f0b0cba1f50bfa7df68f899aa9d37와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
package com.example.financialcore.observability;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import java.util.Objects;
import java.util.function.Supplier;
public final class TransferMetrics {
private final MeterRegistry registry;
public TransferMetrics(MeterRegistry registry) {
this.registry = Objects.requireNonNull(registry);
}
public <T> T record(Supplier<T> action) {
Timer.Sample sample = Timer.start(registry);
try {
T result = action.get();
registry.counter("fcl.transfer", "outcome", "completed").increment();
return result;
} catch (RuntimeException rejected) {
registry.counter("fcl.transfer", "outcome", "business_rejected").increment();
throw rejected;
} finally {
sample.stop(registry.timer("fcl.transfer.duration"));
}
}
}
03TransferMetricsTest.java — 성공·예외 모두 timer가 멈추는 두 학습 test
illustrative/java/TransferMetricsTest.java
학습용 예시 · 학습자 작성 Java · 정본 답안 아님 · 실행 결과 아님 · 학습용 예시 · 정본 답안 아님 · W23-F0324줄 연결24줄 번역3 chunks
TransferMetricsTest.java — 성공·예외 모두 timer가 멈추는 두 학습 test
illustrative/java/TransferMetricsTest.java
학습용 예시 · 학습자 작성 Java · 정본 답안 아님 · 실행 결과 아님 · 학습용 예시 · 정본 답안 아님 · W23-F03STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
- `tests=2`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다` 경계와 어떻게 연결되는가?
tests=2completed=1business_rejected=1timer.count=1SimpleMeterRegistrySTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
TransferMetricsTest.java — 성공·예외 모두 timer가 멈추는 두 학습 test ― 관측 증거 흐름으로 바꾸기
성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
핵심값 tests=2, completed=1, business_rejected=1, timer.count=1, SimpleMeterRegistry을 원본 줄로 따라가되, `이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. 성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
- 코드 연결
1~10줄- 비유
- 정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표
- 비유의 끝
- 이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다
원문 11–20줄
11~20줄을 한 덩어리로 읽어 2번째 움직임을 본다. 성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
- 코드 연결
11~20줄- 비유
- 정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표
- 비유의 끝
- PDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다
원문 21–28줄
21~28줄을 한 덩어리로 읽어 3번째 움직임을 본다. 성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
- 코드 연결
21~28줄- 비유
- 정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표
- 비유의 끝
- tag cardinality 전체를 검증하지 않는다
문제의 첫 장면
-
첫 고정값은 `tests=2` 맞아?
-
성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
-
원문에서 `tests=2`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다`도 같이 적어.
입력에서 결과까지
-
`completed=1`은 언제 생겨?
-
SimpleMeterRegistry를 새로 만든다. → TransferMetrics를 연결한다.
-
성공 또는 예외 action을 호출한다. → outcome과 timer count를 각각 단언한다.
-
관찰값과 `PDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 24줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F03-L01 | package com. |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 3줄F03-L03 | import io. |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 4줄F03-L04 | import org. |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 6줄F03-L06 | import static org. |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 8줄F03-L08 | class TransferMetricsTest { |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 8번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 9줄F03-L09 | @Test |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 독립적인 JUnit 학습 test method임을 선언한다. 제공 코드이지 실행 결과는 아니다.
|
| 10줄F03-L10 | void success_records_fixed_outcome_and_duration( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 10번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 11줄F03-L11 | var registry = |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 12줄F03-L12 | var metrics = |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드에서 `var metrics = new TransferMetrics(registry);` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 13줄F03-L13 | assertThat( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 13번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 14줄F03-L14 | assertThat( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공 분기의 고정 outcome counter를 1 올린다.
|
| 15줄F03-L15 | assertThat( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 16줄F03-L16 | } |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 18줄F03-L18 | @Test |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 독립적인 JUnit 학습 test method임을 선언한다. 제공 코드이지 실행 결과는 아니다.
|
| 19줄F03-L19 | void rejection_records_fixed_outcome_and_still_stops_timer( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 19번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 20줄F03-L20 | var registry = |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다.
|
| 21줄F03-L21 | var metrics = |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드에서 `var metrics = new TransferMetrics(registry);` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 22줄F03-L22 | assertThatThrownBy( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 거절 action이 원래 예외 type을 호출자에게 다시 전달하는지 확인한다.
|
| 23줄F03-L23 | throw new IllegalArgumentException( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 24줄F03-L24 | })). |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 24번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 25줄F03-L25 | assertThat( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | RuntimeException 분기의 고정 거절 outcome counter를 1 올린다.
|
| 26줄F03-L26 | assertThat( |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 성공·거절 계측 불변식 학습 test 코드의 26번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 27줄F03-L27 | } |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 28줄F03-L28 | } |
정상 문과 반려 문을 각각 통과시켜 도장 1개와 스톱워치 1개가 남는지 확인하는 두 장짜리 검표표 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `SimpleMeterRegistry`이야.
-
`tag cardinality 전체를 검증하지 않는다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 3개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
package com.example.financialcore.observability;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class TransferMetricsTest {
@Test
void success_records_fixed_outcome_and_duration() {
var registry = new SimpleMeterRegistry();
var metrics = new TransferMetrics(registry);
assertThat(metrics.record(() -> "ok")).isEqualTo("ok");
assertThat(registry.get("fcl.transfer").tag("outcome", "completed").counter().count()).isEqualTo(1.0);
assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1);
}
@Test
void rejection_records_fixed_outcome_and_still_stops_timer() {
var registry = new SimpleMeterRegistry();
var metrics = new TransferMetrics(registry);
assertThatThrownBy(() -> metrics.record(() -> {
throw new IllegalArgumentException("rejected");
})).isInstanceOf(IllegalArgumentException.class);
assertThat(registry.get("fcl.transfer").tag("outcome", "business_rejected").counter().count()).isEqualTo(1.0);
assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1);
}
}
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 24줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | package com.example.financialcore.observability; | 성공·거절 계측 불변식 학습 test 코드의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 3 | import io.micrometer.core.instrument.simple.SimpleMeterRegistry; | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 4 | import org.junit.jupiter.api.Test; | 성공·거절 계측 불변식 학습 test 코드의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 6 | import static org.assertj.core.api.Assertions.*; | 성공·거절 계측 불변식 학습 test 코드의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 8 | class TransferMetricsTest { | 성공·거절 계측 불변식 학습 test 코드의 8번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 9 | @Test | 독립적인 JUnit 학습 test method임을 선언한다. 제공 코드이지 실행 결과는 아니다. |
| 10 | void success_records_fixed_outcome_and_duration() { | 성공·거절 계측 불변식 학습 test 코드의 10번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 11 | var registry = new SimpleMeterRegistry(); | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 12 | var metrics = new TransferMetrics(registry); | 성공·거절 계측 불변식 학습 test 코드에서 `var metrics = new TransferMetrics(registry);` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 13 | assertThat(metrics.record(() -> "ok")).isEqualTo("ok"); | 성공·거절 계측 불변식 학습 test 코드의 13번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 14 | assertThat(registry.get("fcl.transfer").tag("outcome", "completed").counter().count()).isEqualTo(1.0); | 성공 분기의 고정 outcome counter를 1 올린다. |
| 15 | assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1); | 성공·거절 계측 불변식 학습 test 코드의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 16 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 18 | @Test | 독립적인 JUnit 학습 test method임을 선언한다. 제공 코드이지 실행 결과는 아니다. |
| 19 | void rejection_records_fixed_outcome_and_still_stops_timer() { | 성공·거절 계측 불변식 학습 test 코드의 19번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 20 | var registry = new SimpleMeterRegistry(); | Micrometer meter를 등록하고 이름·tag로 다시 찾는 registry dependency다. |
| 21 | var metrics = new TransferMetrics(registry); | 성공·거절 계측 불변식 학습 test 코드에서 `var metrics = new TransferMetrics(registry);` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 22 | assertThatThrownBy(() -> metrics.record(() -> { | 거절 action이 원래 예외 type을 호출자에게 다시 전달하는지 확인한다. |
| 23 | throw new IllegalArgumentException("rejected"); | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 24 | })).isInstanceOf(IllegalArgumentException.class); | 성공·거절 계측 불변식 학습 test 코드의 24번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 25 | assertThat(registry.get("fcl.transfer").tag("outcome", "business_rejected").counter().count()).isEqualTo(1.0); | RuntimeException 분기의 고정 거절 outcome counter를 1 올린다. |
| 26 | assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1); | 성공·거절 계측 불변식 학습 test 코드의 26번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 27 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 28 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다. 다만 이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다
문법 해부
- 첫 `@Test`는 반환값·completed counter·duration count를 함께 확인한다.
- 둘째 `@Test`는 예외 type·business_rejected counter·duration count를 함께 확인한다.
실행 순서
- SimpleMeterRegistry를 새로 만든다.
- TransferMetrics를 연결한다.
- 성공 또는 예외 action을 호출한다.
- outcome과 timer count를 각각 단언한다.
W23 조각별 정밀 해설
F03-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `tests=2`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `tests=2`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F03-C02 · 원문 11–20줄
- 문법 해부
- 11~20줄의 `원문 11–20줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `completed=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `completed=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: PDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다.
- 착각 방지
- `원문 11–20줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: PDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F03-C03 · 원문 21–28줄
- 문법 해부
- 21~28줄의 `원문 21–28줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `business_rejected=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `business_rejected=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: tag cardinality 전체를 검증하지 않는다.
- 착각 방지
- `원문 21–28줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: tag cardinality 전체를 검증하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
두 @Test가 보이면 실제 Gate가 Green이라고 한다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F03-T01 success | returns ok | record | completed=1, duration=1 | 실제 packaged test 실행 아님 |
| F03-T02 rejection | IllegalArgumentException | record | rejected=1, duration=1 | 다른 RuntimeException 분류 미검증 |
| F03-T03 isolation | fresh registry | 각 test 독립 | 누적 오염 없음 | concurrency 미검증 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `성공·거절 계측 불변식 학습 test 코드`이야.
-
대표 경계는 `운영 exporter·dashboard를 검증하지 않는다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
각 @Test method를 독립 실행한다.
이 페이지는 test runner 결과 XML을 제공하지 않는다.값·예외·meter count를 읽기 쉽게 단언한다.
정확한 metric tag cardinality 전체를 검사하지 않는다.메모리 안에서 meter를 즉시 조회한다.
Prometheus scrape·dashboard를 대신하지 않는다.이 파일의 @Test가 실제로 고정하는 범위
success_records_fixed_outcome_and_duration
- 새 SimpleMeterRegistry와 TransferMetrics를 준비한다.
- `ok`를 반환하는 Supplier를 준비한다.
- metrics.record를 한 번 호출한다.
- 반환값은 `ok`다.
- outcome=completed counter와 duration timer count가 각각 1이다.
- 제공된 learner test source가 의도하는 성공 분기 oracle
- 성공 경로에서 outcome과 timer를 함께 조회하는 test shape
- 이 HTML pipeline에서 JUnit이 실제 실행됐다는 사실
- Prometheus scrape·dashboard p95
- meter API failure가 원래 반환값을 mask하지 않는다는 보장
첫 실패 경계 learner 구현이 성공 counter를 올리지 않으면 completed counter lookup/count assertion에서, finally stop이 없으면 duration count assertion에서 깨진다.
rejection_records_fixed_outcome_and_still_stops_timer
- 새 SimpleMeterRegistry와 TransferMetrics를 준비한다.
- IllegalArgumentException을 던지는 Supplier를 준비한다.
- metrics.record를 호출해 예외 경로를 통과시킨다.
- IllegalArgumentException이 호출자에게 다시 전달된다.
- outcome=business_rejected counter와 duration timer count가 각각 1이다.
- 제공된 learner test source가 의도하는 RuntimeException 거절 분기 oracle
- 예외 경로에서도 finally timer를 조회하는 test shape
- 이 HTML pipeline에서 두 @Test가 실제 실행됐다는 사실
- PDF 설명의 BusinessException과 source RuntimeException이 같은 taxonomy라는 주장
- registry.counter 또는 timer.stop 실패가 원래 예외를 mask하지 않는다는 보장
첫 실패 경계 catch가 예외를 삼키면 exception assertion에서, 거절 counter나 finally stop이 없으면 뒤 meter count assertion에서 깨진다.
STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ 두 @Test가 보이면 실제 Gate가 Green이라고 한다
왜 틀리나 성공·거절 계측 불변식 학습 test 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 SimpleMeterRegistry를 새로 만든다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다` 상태인데도 Green을 주장하게 된다.
❌ 예외 type만 보고 timer count를 빼먹는다
왜 틀리나 성공·거절 계측 불변식 학습 test 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 TransferMetrics를 연결한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `PDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다` 상태인데도 Green을 주장하게 된다.
❌ 두 test가 같은 registry를 공유한다고 본다
왜 틀리나 성공·거절 계측 불변식 학습 test 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 성공 또는 예외 action을 호출한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `tag cardinality 전체를 검증하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ counter 1을 실제 고객 요청 1건으로 부른다
왜 틀리나 성공·거절 계측 불변식 학습 test 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 outcome과 timer count를 각각 단언한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `운영 exporter·dashboard를 검증하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ SimpleMeterRegistry 결과를 외부 dashboard 증거로 부른다
왜 틀리나 성공·거절 계측 불변식 학습 test 코드의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 outcome과 timer count를 각각 단언한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
이 HTML 제작 과정에서 Gradle로 실행한 결과가 아니다
이 책임을 맡는 곳: metric/query grainPDF 학습 경로의 예시 test이지 packaged W23 selector가 아니다
이 책임을 맡는 곳: 별도 local/external 실행tag cardinality 전체를 검증하지 않는다
이 책임을 맡는 곳: secret·개인정보·운영 정책운영 exporter·dashboard를 검증하지 않는다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: 성공과 거절 두 경로 모두 counter 1회와 timer stop 1회를 확인하는 learner-typed 단위 테스트다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–20줄
- 원문 21–28줄
3단계 · 파일 전체 다시 쓰기
28개 물리 줄을 원본 순서로 복원하고 SHA-256 e7199c15cc451d30de589de1f5bda26cd40c4142fcf1ae5b9d645e99c7d28227와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
package com.example.financialcore.observability;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;
class TransferMetricsTest {
@Test
void success_records_fixed_outcome_and_duration() {
var registry = new SimpleMeterRegistry();
var metrics = new TransferMetrics(registry);
assertThat(metrics.record(() -> "ok")).isEqualTo("ok");
assertThat(registry.get("fcl.transfer").tag("outcome", "completed").counter().count()).isEqualTo(1.0);
assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1);
}
@Test
void rejection_records_fixed_outcome_and_still_stops_timer() {
var registry = new SimpleMeterRegistry();
var metrics = new TransferMetrics(registry);
assertThatThrownBy(() -> metrics.record(() -> {
throw new IllegalArgumentException("rejected");
})).isInstanceOf(IllegalArgumentException.class);
assertThat(registry.get("fcl.transfer").tag("outcome", "business_rejected").counter().count()).isEqualTo(1.0);
assertThat(registry.get("fcl.transfer.duration").timer().count()).isEqualTo(1);
}
}
04incident-runbook.md — requestId에서 격리 DB wait evidence까지 이어지는 정본 절차
evidence/w23/incident-runbook.md
원문 정본 · 제공 incident runbook bytes · 정본 · W23-F046줄 연결6줄 번역1 chunks
incident-runbook.md — requestId에서 격리 DB wait evidence까지 이어지는 정본 절차
evidence/w23/incident-runbook.md
원문 정본 · 제공 incident runbook bytes · 정본 · W23-F04STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다.
- `X-Request-Id`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `runbook review만으로 Green이 아니다` 경계와 어떻게 연결되는가?
X-Request-Idsanitized accountrun-w23-db-wait.ps1gate.jsoncleanup nonzero escalationSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
incident-runbook.md — requestId에서 격리 DB wait evidence까지 이어지는 정본 절차 ― 관측 증거 흐름으로 바꾸기
requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다.
핵심값 X-Request-Id, sanitized account, run-w23-db-wait.ps1, gate.json, cleanup nonzero escalation을 원본 줄로 따라가되, `runbook review만으로 Green이 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–7줄
1~7줄을 한 덩어리로 읽어 1번째 움직임을 본다. requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다.
- 코드 연결
1~7줄- 비유
- 사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트
- 비유의 끝
- runbook review만으로 Green이 아니다
문제의 첫 장면
-
첫 고정값은 `X-Request-Id` 맞아?
-
requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다.
-
원문에서 `X-Request-Id`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `runbook review만으로 Green이 아니다`도 같이 적어.
입력에서 결과까지
-
`sanitized account`은 언제 생겨?
-
X-Request-Id와 응답을 보존한다. → 시각·상태·식별자를 sanitize해 기록한다.
-
disposable DB wait drill을 실행한다. → 두 evidence hash와 escalation 조건을 확인한다.
-
관찰값과 `공유 DB를 가정해 조사하지 않는다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 6줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F04-L01 | # W23 incident runbook ( |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 3줄F04-L03 | 1. |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | 한 실패 요청을 response와 application log 사이에서 연결할 correlation key다.
|
| 4줄F04-L04 | 2. |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | 민감정보 원문 대신 최소화한 계좌 식별자를 incident evidence에 남긴다.
|
| 5줄F04-L05 | 3. |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | 공유 DB가 아닌 disposable 환경에서 row-lock wait를 관찰하는 정본 runner를 호출한다.
|
| 6줄F04-L06 | 4. |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | requestId regression 결과와 runbook hash를 담는 local evidence 파일을 가리킨다.
|
| 7줄F04-L07 | 5. |
사고 신고 번호를 받아 발생 시각·증상·격리 실험 영수증을 한 봉투에 넣고, 위험 신호가 남으면 담당자에게 넘기는 체크리스트 | 정본 incident 초동 대응 runbook의 7번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `cleanup nonzero escalation`이야.
-
`민감 account 식별자를 원문으로 남기지 않는다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 1개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 hash로 고정한 native 정본 source에서 그대로 잘랐습니다.
# W23 incident runbook (provided fixture)
1. Preserve the inbound `X-Request-Id` from the application log and response.
2. Capture the exact failing request time, HTTP status, and sanitized account identifier.
3. Run the disposable `run-w23-db-wait.ps1` drill; never inspect an assumed shared database.
4. Attach `gate.json` and `db-wait.json` hashes. A runbook review alone never owns Green.
5. Escalate when the request ID is absent, the wait remains after rollback, or cleanup is nonzero.
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 6줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | # W23 incident runbook (provided fixture) | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 3 | 1. Preserve the inbound `X-Request-Id` from the application log and response. | 한 실패 요청을 response와 application log 사이에서 연결할 correlation key다. |
| 4 | 2. Capture the exact failing request time, HTTP status, and sanitized account identifier. | 민감정보 원문 대신 최소화한 계좌 식별자를 incident evidence에 남긴다. |
| 5 | 3. Run the disposable `run-w23-db-wait.ps1` drill; never inspect an assumed shared database. | 공유 DB가 아닌 disposable 환경에서 row-lock wait를 관찰하는 정본 runner를 호출한다. |
| 6 | 4. Attach `gate.json` and `db-wait.json` hashes. A runbook review alone never owns Green. | requestId regression 결과와 runbook hash를 담는 local evidence 파일을 가리킨다. |
| 7 | 5. Escalate when the request ID is absent, the wait remains after rollback, or cleanup is nonzero. | 정본 incident 초동 대응 runbook의 7번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다. 다만 runbook review만으로 Green이 아니다
문법 해부
- 번호 목록은 수행 순서를 고정하고 backtick 값은 exact header·runner·evidence 이름을 가리킨다.
- `never`와 `alone never owns Green` 문장은 공유 DB 접근과 self-declared Green을 막는 책임 경계다.
실행 순서
- X-Request-Id와 응답을 보존한다.
- 시각·상태·식별자를 sanitize해 기록한다.
- disposable DB wait drill을 실행한다.
- 두 evidence hash와 escalation 조건을 확인한다.
W23 조각별 정밀 해설
F04-C01 · 원문 1–7줄
- 문법 해부
- 1~7줄의 `원문 1–7줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `X-Request-Id`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `X-Request-Id`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: runbook review만으로 Green이 아니다.
- 착각 방지
- `원문 1–7줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: runbook review만으로 Green이 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
requestId 대신 전체 고객 정보를 log에 남긴다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F04-T01 identify | X-Request-Id | log/response 대조 | 한 요청 연결 | header 부재 시 escalation |
| F04-T02 isolate | disposable DB | row wait drill | 격리 evidence | 공유 DB 추정 접근 금지 |
| F04-T03 attach | gate.json+db-wait.json | hash bind | review packet | runbook review만으로 Green 아님 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `정본 incident 초동 대응 runbook`이야.
-
대표 경계는 `실제 incident timeline은 별도다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
HTTP 응답과 구조화 log를 한 사건으로 연결한다.
전 구간 trace completeness를 자동 보장하지 않는다.가정한 운영 DB 대신 격리 fixture에서 wait를 만든다.
운영 장애 재현과 동일하다는 뜻은 아니다.이 root incident-runbook.md가 escalation·evidence 누락 때 멈출 기준을 준다.
D6의 illustrative runbooks index와 서로 다른 파일이며 실행 evidence 생산자는 별도 runner다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ requestId 대신 전체 고객 정보를 log에 남긴다
왜 틀리나 정본 incident 초동 대응 runbook의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 X-Request-Id와 응답을 보존한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `runbook review만으로 Green이 아니다` 상태인데도 Green을 주장하게 된다.
❌ 공유 DB를 운영 DB라고 추정하고 조회한다
왜 틀리나 정본 incident 초동 대응 runbook의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 시각·상태·식별자를 sanitize해 기록한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `공유 DB를 가정해 조사하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ runbook을 읽은 것만으로 incident 해결이라 한다
왜 틀리나 정본 incident 초동 대응 runbook의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 disposable DB wait drill을 실행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `민감 account 식별자를 원문으로 남기지 않는다` 상태인데도 Green을 주장하게 된다.
❌ gate.json hash 없이 screenshot만 붙인다
왜 틀리나 정본 incident 초동 대응 runbook의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 두 evidence hash와 escalation 조건을 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `실제 incident timeline은 별도다` 상태인데도 Green을 주장하게 된다.
❌ cleanup nonzero인데 escalation을 생략한다
왜 틀리나 정본 incident 초동 대응 runbook의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 두 evidence hash와 escalation 조건을 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `runbook review만으로 Green이 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
runbook review만으로 Green이 아니다
이 책임을 맡는 곳: metric/query grain공유 DB를 가정해 조사하지 않는다
이 책임을 맡는 곳: 별도 local/external 실행민감 account 식별자를 원문으로 남기지 않는다
이 책임을 맡는 곳: secret·개인정보·운영 정책실제 incident timeline은 별도다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: requestId 보존, 정확한 시각·상태, disposable DB wait, gate/db-wait hash와 escalation을 묶는 제공 정본 runbook이다.
2단계 · 코드 조각 재조립
- 원문 1–7줄
3단계 · 파일 전체 다시 쓰기
7개 물리 줄을 원본 순서로 복원하고 SHA-256 a09ab2252b379c1ab1fb88ccb861f39ebf6a955a90f30d92646f941e6dd04446와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
원문 정본 전체 source 확인하기
# W23 incident runbook (provided fixture)
1. Preserve the inbound `X-Request-Id` from the application log and response.
2. Capture the exact failing request time, HTTP status, and sanitized account identifier.
3. Run the disposable `run-w23-db-wait.ps1` drill; never inspect an assumed shared database.
4. Attach `gate.json` and `db-wait.json` hashes. A runbook review alone never owns Green.
5. Escalate when the request ID is absent, the wait remains after rollback, or cleanup is nonzero.
05run-w23-gate.ps1 — RequestIdFilterTest 정확히 2개와 runbook hash를 묶는 정본 Gate
scripts/run-w23-gate.ps1
원문 정본 · 완전한 packaged PowerShell source · 정본 · W23-F053줄 연결3줄 번역1 chunks
run-w23-gate.ps1 — RequestIdFilterTest 정확히 2개와 runbook hash를 묶는 정본 Gate
scripts/run-w23-gate.ps1
원문 정본 · 완전한 packaged PowerShell source · 정본 · W23-F05STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다.
- `classes=1`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `dashboard·p95·pool·외부 알림 Green을 주장하지 않는다` 경계와 어떻게 연결되는가?
classes=1tests=2failures=0runbook_sha256W23_OBSERVABILITY_GREENSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
run-w23-gate.ps1 — RequestIdFilterTest 정확히 2개와 runbook hash를 묶는 정본 Gate ― 관측 증거 흐름으로 바꾸기
RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다.
핵심값 classes=1, tests=2, failures=0, runbook_sha256, W23_OBSERVABILITY_GREEN을 원본 줄로 따라가되, `dashboard·p95·pool·외부 알림 Green을 주장하지 않는다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–3줄
1~3줄을 한 덩어리로 읽어 1번째 움직임을 본다. RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다.
- 코드 연결
1~3줄- 비유
- 감독이 지정 선수 한 명의 시험지 두 장과 규정집 봉인 hash를 확인한 뒤에만 공식 도장을 찍는 자동 검문소
- 비유의 끝
- dashboard·p95·pool·외부 알림 Green을 주장하지 않는다
문제의 첫 장면
-
첫 고정값은 `classes=1` 맞아?
-
RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다.
-
원문에서 `classes=1`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `dashboard·p95·pool·외부 알림 Green을 주장하지 않는다`도 같이 적어.
입력에서 결과까지
-
`tests=2`은 언제 생겨?
-
project와 runbook 존재를 고정한다. → RequestIdFilterTest selector만 재실행한다.
-
XML tests=2와 실패 0을 확인한다. → runbook hash가 든 gate.json을 원자적으로 발행한다.
-
관찰값과 `Gradle·JDK 실행 환경이 필요하다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 3줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F05-L01 | param( |
감독이 지정 선수 한 명의 시험지 두 장과 규정집 봉인 hash를 확인한 뒤에만 공식 도장을 찍는 자동 검문소 | 정본 requestId regression·runbook hash evidence producer에서 `param([Parameter(Mandatory=$true)][string]$Proje` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 2줄F05-L02 | $ErrorActionPreference= |
감독이 지정 선수 한 명의 시험지 두 장과 규정집 봉인 hash를 확인한 뒤에만 공식 도장을 찍는 자동 검문소 | W23 Gate가 정확히 선택하는 requestId regression class 이름을 고정한다.
|
| 3줄F05-L03 | try{ |
감독이 지정 선수 한 명의 시험지 두 장과 규정집 봉인 hash를 확인한 뒤에만 공식 도장을 찍는 자동 검문소 | requestId regression 결과와 runbook hash를 담는 local evidence 파일을 가리킨다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `W23_OBSERVABILITY_GREEN`이야.
-
`exact XML selector만 검증한다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 1개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 hash로 고정한 native 정본 source에서 그대로 잘랐습니다.
param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir)
$ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);$class='com.example.financialcore.api.RequestIdFilterTest';$runbook=Join-Path $root 'evidence/w23/incident-runbook.md';if(!(Test-Path -LiteralPath $runbook -PathType Leaf)){throw 'W23 hashed runbook missing'};Push-Location $root
try{& .\gradlew.bat test --rerun-tasks --no-daemon --tests $class;if($LASTEXITCODE-ne 0){throw "W23 Gradle exit=$LASTEXITCODE"};$xml=Get-Item -LiteralPath 'build/test-results/test/TEST-com.example.financialcore.api.RequestIdFilterTest.xml';[xml]$x=Get-Content -Raw $xml.FullName;$s=$x.testsuite;if([string]$s.name-ne$class-or[int]$s.tests-ne 2-or[int]$s.failures-ne 0-or[int]$s.errors-ne 0-or[int]$s.skipped-ne 0){throw 'W23 exact RequestIdFilter XML mismatch'};New-Item -ItemType Directory -Force $e|Out-Null;$body=[ordered]@{classes=@($class);tests=2;failures=0;errors=0;skipped=0;claim='request-id-regression-and-runbook';runbook_sha256=(Get-FileHash -Algorithm SHA256 -LiteralPath $runbook).Hash.ToLowerInvariant();native_exit=0};$tmp=Join-Path $e 'gate.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -Force -LiteralPath $tmp -Destination (Join-Path $e 'gate.json');'W23_OBSERVABILITY_GREEN classes=1 tests=2 claim=request-id-regression-and-runbook native_exit=0'}finally{Pop-Location}
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 3줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir) | 정본 requestId regression·runbook hash evidence producer에서 `param([Parameter(Mandatory=$true)][string]$Proje` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 2 | $ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);$class='com.example.financialcore.api.RequestIdFilterTest';$runbook=Join-Path $root 'evidence/w23/incident-runbook.md';if(!(Test-Path -LiteralPath $runbook -PathType Leaf)){throw 'W23 hashed runbook missing'};Push-Location $root | W23 Gate가 정확히 선택하는 requestId regression class 이름을 고정한다. |
| 3 | try{& .\gradlew.bat test --rerun-tasks --no-daemon --tests $class;if($LASTEXITCODE-ne 0){throw "W23 Gradle exit=$LASTEXITCODE"};$xml=Get-Item -LiteralPath 'build/test-results/test/TEST-com.example.financialcore.api.RequestIdFilterTest.xml';[xml]$x=Get-Content -Raw $xml.FullName;$s=$x.testsuite;if([string]$s.name-ne$class-or[int]$s.tests-ne 2-or[int]$s.failures-ne 0-or[int]$s.errors-ne 0-or[int]$s.skipped-ne 0){throw 'W23 exact RequestIdFilter XML mismatch'};New-Item -ItemType Directory -Force $e|Out-Null;$body=[ordered]@{classes=@($class);tests=2;failures=0;errors=0;skipped=0;claim='request-id-regression-and-runbook';runbook_sha256=(Get-FileHash -Algorithm SHA256 -LiteralPath $runbook).Hash.ToLowerInvariant();native_exit=0};$tmp=Join-Path $e 'gate.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -Force -LiteralPath $tmp -Destination (Join-Path $e 'gate.json');'W23_OBSERVABILITY_GREEN classes=1 tests=2 claim=request-id-regression-and-runbook native_exit=0'}finally{Pop-Location} | requestId regression 결과와 runbook hash를 담는 local evidence 파일을 가리킨다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다. 다만 dashboard·p95·pool·외부 알림 Green을 주장하지 않는다
문법 해부
- 두 mandatory parameter는 project source와 evidence 목적지를 caller가 명시하게 한다.
- minified 본문도 Gradle exit·exact XML counts·runbook hash·atomic move·exact marker 순서가 모두 살아 있다.
실행 순서
- project와 runbook 존재를 고정한다.
- RequestIdFilterTest selector만 재실행한다.
- XML tests=2와 실패 0을 확인한다.
- runbook hash가 든 gate.json을 원자적으로 발행한다.
W23 조각별 정밀 해설
F05-C01 · 원문 1–3줄
- 문법 해부
- 1~3줄의 `원문 1–3줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `classes=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `classes=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: dashboard·p95·pool·외부 알림 Green을 주장하지 않는다.
- 착각 방지
- `원문 1–3줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: dashboard·p95·pool·외부 알림 Green을 주장하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
전체 test suite가 Green이라고 확대한다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F05-T01 selector | exact class | Gradle --tests | 한 class 실행 | TransferMetricsTest selector 아님 |
| F05-T02 XML | testsuite | 2/0/0/0 검사 | regression pass | 외부 dashboard 미검증 |
| F05-T03 evidence | runbook bytes | SHA-256+move | W23_OBSERVABILITY_GREEN | requestId+runbook 범위만 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `정본 requestId regression·runbook hash evidence producer`이야.
-
대표 경계는 `marker 한 줄만으로 evidence byte를 대신하지 않는다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
지정 test class를 clean 재실행하고 native exit를 돌려준다.
전체 test suite 성공을 뜻하지 않는다.tests·failures·errors·skipped exact 값을 제공한다.
파일 존재만으로 최신 실행이라고 볼 수 없다.temporary JSON을 final gate.json으로 교체한다.
marker만 복사하면 runbook hash binding이 없다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ 전체 test suite가 Green이라고 확대한다
왜 틀리나 정본 requestId regression·runbook hash evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 project와 runbook 존재를 고정한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `dashboard·p95·pool·외부 알림 Green을 주장하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ tests가 2 이상이면 통과시킨다
왜 틀리나 정본 requestId regression·runbook hash evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 RequestIdFilterTest selector만 재실행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `Gradle·JDK 실행 환경이 필요하다` 상태인데도 Green을 주장하게 된다.
❌ runbook 파일만 있고 hash를 쓰지 않는다
왜 틀리나 정본 requestId regression·runbook hash evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 XML tests=2와 실패 0을 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `exact XML selector만 검증한다` 상태인데도 Green을 주장하게 된다.
❌ Gradle exit를 확인하지 않는다
왜 틀리나 정본 requestId regression·runbook hash evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 runbook hash가 든 gate.json을 원자적으로 발행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `marker 한 줄만으로 evidence byte를 대신하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ W23 marker를 dashboard·alert Green으로 해석한다
왜 틀리나 정본 requestId regression·runbook hash evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 runbook hash가 든 gate.json을 원자적으로 발행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `dashboard·p95·pool·외부 알림 Green을 주장하지 않는다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
dashboard·p95·pool·외부 알림 Green을 주장하지 않는다
이 책임을 맡는 곳: metric/query grainGradle·JDK 실행 환경이 필요하다
이 책임을 맡는 곳: 별도 local/external 실행exact XML selector만 검증한다
이 책임을 맡는 곳: secret·개인정보·운영 정책marker 한 줄만으로 evidence byte를 대신하지 않는다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: RequestIdFilterTest 정확히 2개와 runbook hash를 검증해 request-id-regression-and-runbook 범위 gate.json을 원자 발행하는 제공 정본 Gate다.
2단계 · 코드 조각 재조립
- 원문 1–3줄
3단계 · 파일 전체 다시 쓰기
3개 물리 줄을 원본 순서로 복원하고 SHA-256 48de77ede64ab17dfaeda7f36bf34211c9007d1df8606ab4689559846093632a와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
원문 정본 전체 source 확인하기
param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir)
$ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);$class='com.example.financialcore.api.RequestIdFilterTest';$runbook=Join-Path $root 'evidence/w23/incident-runbook.md';if(!(Test-Path -LiteralPath $runbook -PathType Leaf)){throw 'W23 hashed runbook missing'};Push-Location $root
try{& .\gradlew.bat test --rerun-tasks --no-daemon --tests $class;if($LASTEXITCODE-ne 0){throw "W23 Gradle exit=$LASTEXITCODE"};$xml=Get-Item -LiteralPath 'build/test-results/test/TEST-com.example.financialcore.api.RequestIdFilterTest.xml';[xml]$x=Get-Content -Raw $xml.FullName;$s=$x.testsuite;if([string]$s.name-ne$class-or[int]$s.tests-ne 2-or[int]$s.failures-ne 0-or[int]$s.errors-ne 0-or[int]$s.skipped-ne 0){throw 'W23 exact RequestIdFilter XML mismatch'};New-Item -ItemType Directory -Force $e|Out-Null;$body=[ordered]@{classes=@($class);tests=2;failures=0;errors=0;skipped=0;claim='request-id-regression-and-runbook';runbook_sha256=(Get-FileHash -Algorithm SHA256 -LiteralPath $runbook).Hash.ToLowerInvariant();native_exit=0};$tmp=Join-Path $e 'gate.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -Force -LiteralPath $tmp -Destination (Join-Path $e 'gate.json');'W23_OBSERVABILITY_GREEN classes=1 tests=2 claim=request-id-regression-and-runbook native_exit=0'}finally{Pop-Location}
06W23-SQL-Q37 — 두 1:N JOIN의 N×M 증폭과 선집계 해법
illustrative/sql/W23-SQL-Q37.sql
학습용 예시 · Q37/Q38 prompt 기반 SQL · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F0637줄 연결37줄 번역4 chunks
W23-SQL-Q37 — 두 1:N JOIN의 N×M 증폭과 선집계 해법
illustrative/sql/W23-SQL-Q37.sql
학습용 예시 · Q37/Q38 prompt 기반 SQL · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F06STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
- `1:N:N`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `workbook 제공 정답이 아니다` 경계와 어떻게 연결되는가?
1:N:NCOUNT DISTINCTpre-aggregatetransaction grain2x3=6STEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
W23-SQL-Q37 — 두 1:N JOIN의 N×M 증폭과 선집계 해법 ― 관측 증거 흐름으로 바꾸기
두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
핵심값 1:N:N, COUNT DISTINCT, pre-aggregate, transaction grain, 2x3=6을 원본 줄로 따라가되, `workbook 제공 정답이 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. 두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
- 코드 연결
1~10줄- 비유
- 부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표
- 비유의 끝
- workbook 제공 정답이 아니다
원문 11–20줄
11~20줄을 한 덩어리로 읽어 2번째 움직임을 본다. 두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
- 코드 연결
11~20줄- 비유
- 부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표
- 비유의 끝
- COUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다
원문 21–30줄
21~30줄을 한 덩어리로 읽어 3번째 움직임을 본다. 두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
- 코드 연결
21~30줄- 비유
- 부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표
- 비유의 끝
- audit_event의 nullable tx_id를 설명해야 한다
문제의 첫 장면
-
첫 고정값은 `1:N:N` 맞아?
-
두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
-
원문에서 `1:N:N`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `workbook 제공 정답이 아니다`도 같이 적어.
입력에서 결과까지
-
`COUNT DISTINCT`은 언제 생겨?
-
business_tx를 parent grain으로 선언한다. → raw JOIN의 joined_rows와 distinct child 수를 비교한다.
-
각 child를 tx_id별로 선집계한다. → 격리 2×3 반례에서 6행을 확인한다.
-
관찰값과 `COUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 37줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F06-L01 | -- 학습용 예시 · 정본 답안 아님 |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 2줄F06-L02 | -- 입력 grain: |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 3줄F06-L03 | -- 출력 grain: |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 5줄F06-L05 | SELECT tx. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 6줄F06-L06 | COUNT( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 비정본 Q37 JOIN grain 진단 SQL의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 7줄F06-L07 | COUNT( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | JOIN 증폭과 무관하게 unique ledger child 수를 따로 센다.
|
| 8줄F06-L08 | COUNT( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | JOIN 증폭과 무관하게 unique audit child 수를 따로 센다.
|
| 9줄F06-L09 | FROM business_tx AS tx |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 10줄F06-L10 | JOIN ledger_entry AS le ON le. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | parent transaction에 첫 1:N ledger child를 연결한다.
|
| 11줄F06-L11 | JOIN audit_event AS ae ON ae. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 같은 parent에 둘째 1:N audit child를 raw 연결해 N×M 가능성을 만든다.
|
| 12줄F06-L12 | GROUP BY tx. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 13줄F06-L13 | ORDER BY tx. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 15줄F06-L15 | -- 안전한 집계 예시: |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 16줄F06-L16 | WITH ledger_counts AS ( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | ledger child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다.
|
| 17줄F06-L17 | SELECT tx_id, |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 18줄F06-L18 | FROM ledger_entry |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 19줄F06-L19 | GROUP BY tx_id |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 20줄F06-L20 | ), |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 비정본 Q37 JOIN grain 진단 SQL의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 21줄F06-L21 | audit_counts AS ( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | audit child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다.
|
| 22줄F06-L22 | SELECT tx_id, |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 23줄F06-L23 | FROM audit_event |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 24줄F06-L24 | WHERE tx_id IS NOT NULL |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 25줄F06-L25 | GROUP BY tx_id |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 26줄F06-L26 | ) |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 27줄F06-L27 | SELECT tx. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 28줄F06-L28 | COALESCE( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 비정본 Q37 JOIN grain 진단 SQL의 28번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 29줄F06-L29 | COALESCE( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 비정본 Q37 JOIN grain 진단 SQL의 29번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 30줄F06-L30 | FROM business_tx AS tx |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 31줄F06-L31 | LEFT JOIN ledger_counts AS le ON le. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | ledger child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다.
|
| 32줄F06-L32 | LEFT JOIN audit_counts AS ae ON ae. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | audit child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다.
|
| 33줄F06-L33 | ORDER BY tx. |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 35줄F06-L35 | -- 격리 반례: |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 36줄F06-L36 | WITH child_a( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 37줄F06-L37 | child_b( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 비정본 Q37 JOIN grain 진단 SQL의 37번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 38줄F06-L38 | SELECT COUNT( |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 39줄F06-L39 | FROM child_a |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 40줄F06-L40 | CROSS JOIN child_b; |
부모 한 명에게 장부 쪽지 2장과 감사 쪽지 3장을 동시에 붙이면 5장이 아니라 모든 조합 6장이 되는 좌석표 | 2행과 3행의 모든 조합 6행을 만드는 격리 반례다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `2x3=6`이야.
-
`audit_event의 nullable tx_id를 설명해야 한다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 4개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
-- 학습용 예시 · 정본 답안 아님
-- 입력 grain: business_tx 1행/거래, ledger_entry 1행/원장 항목, audit_event 1행/감사 사건
-- 출력 grain: 거래 1행. 두 1:N 자식을 동시에 생으로 JOIN하면 N×M으로 행이 늘어날 수 있다.
SELECT tx.tx_id,
COUNT(*) AS joined_rows,
COUNT(DISTINCT le.entry_id) AS ledger_rows,
COUNT(DISTINCT ae.audit_id) AS audit_rows
FROM business_tx AS tx
JOIN ledger_entry AS le ON le.tx_id = tx.tx_id
JOIN audit_event AS ae ON ae.tx_id = tx.tx_id
GROUP BY tx.tx_id
ORDER BY tx.tx_id;
-- 안전한 집계 예시: 각 1:N 자식을 거래 grain으로 먼저 줄인 뒤 JOIN한다.
WITH ledger_counts AS (
SELECT tx_id, COUNT(*) AS ledger_rows
FROM ledger_entry
GROUP BY tx_id
),
audit_counts AS (
SELECT tx_id, COUNT(*) AS audit_rows
FROM audit_event
WHERE tx_id IS NOT NULL
GROUP BY tx_id
)
SELECT tx.tx_id,
COALESCE(le.ledger_rows, 0) AS ledger_rows,
COALESCE(ae.audit_rows, 0) AS audit_rows
FROM business_tx AS tx
LEFT JOIN ledger_counts AS le ON le.tx_id = tx.tx_id
LEFT JOIN audit_counts AS ae ON ae.tx_id = tx.tx_id
ORDER BY tx.tx_id;
-- 격리 반례: parent 1행 × child_a 2행 × child_b 3행 = JOIN 결과 6행.
WITH child_a(id) AS (VALUES (1), (2)),
child_b(id) AS (VALUES (10), (20), (30))
SELECT COUNT(*) AS multiplied_rows
FROM child_a
CROSS JOIN child_b;
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 37줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | -- 학습용 예시 · 정본 답안 아님 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 2 | -- 입력 grain: business_tx 1행/거래, ledger_entry 1행/원장 항목, audit_event 1행/감사 사건 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 3 | -- 출력 grain: 거래 1행. 두 1:N 자식을 동시에 생으로 JOIN하면 N×M으로 행이 늘어날 수 있다. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 5 | SELECT tx.tx_id, | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 6 | COUNT(*) AS joined_rows, | 비정본 Q37 JOIN grain 진단 SQL의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 7 | COUNT(DISTINCT le.entry_id) AS ledger_rows, | JOIN 증폭과 무관하게 unique ledger child 수를 따로 센다. |
| 8 | COUNT(DISTINCT ae.audit_id) AS audit_rows | JOIN 증폭과 무관하게 unique audit child 수를 따로 센다. |
| 9 | FROM business_tx AS tx | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 10 | JOIN ledger_entry AS le ON le.tx_id = tx.tx_id | parent transaction에 첫 1:N ledger child를 연결한다. |
| 11 | JOIN audit_event AS ae ON ae.tx_id = tx.tx_id | 같은 parent에 둘째 1:N audit child를 raw 연결해 N×M 가능성을 만든다. |
| 12 | GROUP BY tx.tx_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 13 | ORDER BY tx.tx_id; | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 15 | -- 안전한 집계 예시: 각 1:N 자식을 거래 grain으로 먼저 줄인 뒤 JOIN한다. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 16 | WITH ledger_counts AS ( | ledger child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다. |
| 17 | SELECT tx_id, COUNT(*) AS ledger_rows | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 18 | FROM ledger_entry | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 19 | GROUP BY tx_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 20 | ), | 비정본 Q37 JOIN grain 진단 SQL의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 21 | audit_counts AS ( | audit child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다. |
| 22 | SELECT tx_id, COUNT(*) AS audit_rows | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 23 | FROM audit_event | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 24 | WHERE tx_id IS NOT NULL | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 25 | GROUP BY tx_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 26 | ) | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 27 | SELECT tx.tx_id, | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 28 | COALESCE(le.ledger_rows, 0) AS ledger_rows, | 비정본 Q37 JOIN grain 진단 SQL의 28번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 29 | COALESCE(ae.audit_rows, 0) AS audit_rows | 비정본 Q37 JOIN grain 진단 SQL의 29번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 30 | FROM business_tx AS tx | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 31 | LEFT JOIN ledger_counts AS le ON le.tx_id = tx.tx_id | ledger child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다. |
| 32 | LEFT JOIN audit_counts AS ae ON ae.tx_id = tx.tx_id | audit child를 tx_id 한 행으로 먼저 줄이는 선집계 CTE다. |
| 33 | ORDER BY tx.tx_id; | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 35 | -- 격리 반례: parent 1행 × child_a 2행 × child_b 3행 = JOIN 결과 6행. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 36 | WITH child_a(id) AS (VALUES (1), (2)), | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 37 | child_b(id) AS (VALUES (10), (20), (30)) | 비정본 Q37 JOIN grain 진단 SQL의 37번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 38 | SELECT COUNT(*) AS multiplied_rows | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 39 | FROM child_a | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 40 | CROSS JOIN child_b; | 2행과 3행의 모든 조합 6행을 만드는 격리 반례다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다. 다만 workbook 제공 정답이 아니다
문법 해부
- 두 1:N 자식을 raw JOIN하면 같은 parent 안에서 ledger N × audit M 행이 만들어진다.
- 안전한 CTE는 각 child를 `tx_id` 한 행으로 먼저 집계한 뒤 parent에 LEFT JOIN한다.
실행 순서
- business_tx를 parent grain으로 선언한다.
- raw JOIN의 joined_rows와 distinct child 수를 비교한다.
- 각 child를 tx_id별로 선집계한다.
- 격리 2×3 반례에서 6행을 확인한다.
W23 조각별 정밀 해설
F06-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `1:N:N`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `1:N:N`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: workbook 제공 정답이 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: workbook 제공 정답이 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F06-C02 · 원문 11–20줄
- 문법 해부
- 11~20줄의 `원문 11–20줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `COUNT DISTINCT`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `COUNT DISTINCT`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: COUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다.
- 착각 방지
- `원문 11–20줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: COUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F06-C03 · 원문 21–30줄
- 문법 해부
- 21~30줄의 `원문 21–30줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `pre-aggregate`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `pre-aggregate`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: audit_event의 nullable tx_id를 설명해야 한다.
- 착각 방지
- `원문 21–30줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: audit_event의 nullable tx_id를 설명해야 한다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F06-C04 · 원문 31–40줄
- 문법 해부
- 31~40줄의 `원문 31–40줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `transaction grain`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `transaction grain`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 격리 반례는 production fixture가 아니다.
- 착각 방지
- `원문 31–40줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 격리 반례는 production fixture가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
COUNT(*)를 ledger 건수라고 부른다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F06-T01 raw | 1 tx, N ledger, M audit | two joins | N×M joined rows | COUNT(*)는 child 수 아님 |
| F06-T02 preaggregate | child tables | GROUP BY tx_id | one row per child summary | 원본 상세 행은 사라짐 |
| F06-T03 counterexample | 2 and 3 rows | CROSS JOIN | 6 | workbook 정본 답안 아님 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `비정본 Q37 JOIN grain 진단 SQL`이야.
-
대표 경계는 `격리 반례는 production fixture가 아니다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
조건을 만족하는 좌우 행 조합을 모두 만든다.
서로 다른 child cardinality를 자동 보호하지 않는다.증폭된 결과에서 unique child id를 센다.
SUM amount 중복을 자동 고치지는 않는다.JOIN 전 grain을 parent key로 맞춘다.
원하는 output grain을 먼저 합의해야 한다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ COUNT(*)를 ledger 건수라고 부른다
왜 틀리나 비정본 Q37 JOIN grain 진단 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 business_tx를 parent grain으로 선언한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `workbook 제공 정답이 아니다` 상태인데도 Green을 주장하게 된다.
❌ DISTINCT를 SELECT 전체에 붙이면 모든 집계가 해결된다고 본다
왜 틀리나 비정본 Q37 JOIN grain 진단 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 raw JOIN의 joined_rows와 distinct child 수를 비교한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `COUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다` 상태인데도 Green을 주장하게 된다.
❌ 두 child를 raw JOIN한 뒤 amount를 SUM한다
왜 틀리나 비정본 Q37 JOIN grain 진단 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 각 child를 tx_id별로 선집계한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `audit_event의 nullable tx_id를 설명해야 한다` 상태인데도 Green을 주장하게 된다.
❌ LEFT JOIN을 쓰면 N×M이 사라진다고 본다
왜 틀리나 비정본 Q37 JOIN grain 진단 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 격리 2×3 반례에서 6행을 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `격리 반례는 production fixture가 아니다` 상태인데도 Green을 주장하게 된다.
❌ Q37 예시를 workbook 정본 답안이라 부른다
왜 틀리나 비정본 Q37 JOIN grain 진단 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 격리 2×3 반례에서 6행을 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `workbook 제공 정답이 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
workbook 제공 정답이 아니다
이 책임을 맡는 곳: metric/query grainCOUNT DISTINCT만으로 금액 SUM 중복이 자동 해결되지는 않는다
이 책임을 맡는 곳: 별도 local/external 실행audit_event의 nullable tx_id를 설명해야 한다
이 책임을 맡는 곳: secret·개인정보·운영 정책격리 반례는 production fixture가 아니다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: 두 1:N 자식을 생 JOIN할 때 N×M 행 폭증이 생기는 이유와 자식별 선집계 해법을 보여 주는 비정본 Q37 예시다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–20줄
- 원문 21–30줄
- 원문 31–40줄
3단계 · 파일 전체 다시 쓰기
40개 물리 줄을 원본 순서로 복원하고 SHA-256 cc87fecae930e197d9982e720550e86ca999d6fec30bde7362eed9d607a8de53와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
-- 학습용 예시 · 정본 답안 아님
-- 입력 grain: business_tx 1행/거래, ledger_entry 1행/원장 항목, audit_event 1행/감사 사건
-- 출력 grain: 거래 1행. 두 1:N 자식을 동시에 생으로 JOIN하면 N×M으로 행이 늘어날 수 있다.
SELECT tx.tx_id,
COUNT(*) AS joined_rows,
COUNT(DISTINCT le.entry_id) AS ledger_rows,
COUNT(DISTINCT ae.audit_id) AS audit_rows
FROM business_tx AS tx
JOIN ledger_entry AS le ON le.tx_id = tx.tx_id
JOIN audit_event AS ae ON ae.tx_id = tx.tx_id
GROUP BY tx.tx_id
ORDER BY tx.tx_id;
-- 안전한 집계 예시: 각 1:N 자식을 거래 grain으로 먼저 줄인 뒤 JOIN한다.
WITH ledger_counts AS (
SELECT tx_id, COUNT(*) AS ledger_rows
FROM ledger_entry
GROUP BY tx_id
),
audit_counts AS (
SELECT tx_id, COUNT(*) AS audit_rows
FROM audit_event
WHERE tx_id IS NOT NULL
GROUP BY tx_id
)
SELECT tx.tx_id,
COALESCE(le.ledger_rows, 0) AS ledger_rows,
COALESCE(ae.audit_rows, 0) AS audit_rows
FROM business_tx AS tx
LEFT JOIN ledger_counts AS le ON le.tx_id = tx.tx_id
LEFT JOIN audit_counts AS ae ON ae.tx_id = tx.tx_id
ORDER BY tx.tx_id;
-- 격리 반례: parent 1행 × child_a 2행 × child_b 3행 = JOIN 결과 6행.
WITH child_a(id) AS (VALUES (1), (2)),
child_b(id) AS (VALUES (10), (20), (30))
SELECT COUNT(*) AS multiplied_rows
FROM child_a
CROSS JOIN child_b;
07run-w23-db-wait.ps1 — 실제 row-lock wait 1→0과 final value 2를 증명하는 정본 drill
scripts/run-w23-db-wait.ps1
원문 정본 · 완전한 packaged PowerShell source · 정본 · W23-F07175줄 연결175줄 번역16 chunks
run-w23-db-wait.ps1 — 실제 row-lock wait 1→0과 final value 2를 증명하는 정본 drill
scripts/run-w23-db-wait.ps1
원문 정본 · 완전한 packaged PowerShell source · 정본 · W23-F07STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
- `scope=ROW_UPDATE`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `실제 운영 DB를 관찰하지 않는다` 경계와 어떻게 연결되는가?
scope=ROW_UPDATEobserved=1cleared=0final_value=2native_exits=0 cleanup=1STEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
run-w23-db-wait.ps1 — 실제 row-lock wait 1→0과 final value 2를 증명하는 정본 drill ― 관측 증거 흐름으로 바꾸기
disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
핵심값 scope=ROW_UPDATE, observed=1, cleared=0, final_value=2, native_exits=0 cleanup=1을 원본 줄로 따라가되, `실제 운영 DB를 관찰하지 않는다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–12줄
1~12줄을 한 덩어리로 읽어 1번째 움직임을 본다. disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
- 코드 연결
1~12줄- 비유
- 한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험
- 비유의 끝
- 실제 운영 DB를 관찰하지 않는다
원문 13–24줄
13~24줄을 한 덩어리로 읽어 2번째 움직임을 본다. disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
- 코드 연결
13~24줄- 비유
- 한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험
- 비유의 끝
- Compose/Container와 Docker·PostgreSQL이 필요하다
원문 25–36줄
25~36줄을 한 덩어리로 읽어 3번째 움직임을 본다. disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
- 코드 연결
25~36줄- 비유
- 한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험
- 비유의 끝
- row-update lock 한 종류만 다룬다
문제의 첫 장면
-
첫 고정값은 `scope=ROW_UPDATE` 맞아?
-
disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
-
원문에서 `scope=ROW_UPDATE`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `실제 운영 DB를 관찰하지 않는다`도 같이 적어.
입력에서 결과까지
-
`observed=1`은 언제 생겨?
-
disposable schema와 value=0 fixture를 만든다. → holder와 waiter UPDATE를 별도 process로 시작한다.
-
Lock wait와 blocking pid를 관찰한다. → wait=0·value=2·native exit=0·cleanup=1 뒤 evidence를 발행한다.
-
관찰값과 `Compose/Container와 Docker·PostgreSQL이 필요하다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 175줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F07-L01 | param( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 2줄F07-L02 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | DB wait runner의 lifecycle mode를 Compose와 Container 두 값으로 제한한다.
|
| 3줄F07-L03 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `[string]$ComposeProject = 'w23-db-wait-lab',` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 4줄F07-L04 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `[string]$Container = '',` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 5줄F07-L05 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `[Parameter(Mandatory = $true)][string]$EvidenceP` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 6줄F07-L06 | ) |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 8줄F07-L08 | Set-StrictMode -Version Latest |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 미정의 변수 같은 PowerShell 오류를 조기에 드러낸다.
|
| 9줄F07-L09 | $ErrorActionPreference = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = 'Stop'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 11줄F07-L11 | $root = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$root = Split-Path -Parent $PSScriptRoot` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 12줄F07-L12 | $evidence = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$evidence = if ([IO.Path]::IsPathRooted($Evidenc` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 13줄F07-L13 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `[IO.Path]::GetFullPath($EvidencePath)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 14줄F07-L14 | } else { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 14번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 15줄F07-L15 | [ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `[IO.Path]::GetFullPath((Join-Path $root $Evidenc` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 16줄F07-L16 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 17줄F07-L17 | $compose = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$compose = Join-Path $root 'compose.yaml'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 18줄F07-L18 | $runId = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$runId = [guid]::NewGuid().ToString('N').Substri` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 19줄F07-L19 | $holderApp = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderApp = "w23_holder_$runId"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 20줄F07-L20 | $waiterApp = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterApp = "w23_waiter_$runId"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 21줄F07-L21 | $holderSqlInContainer = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderSqlInContainer = "/tmp/w23-holder-$runId.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 22줄F07-L22 | $waiterSqlInContainer = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterSqlInContainer = "/tmp/w23-waiter-$runId.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 23줄F07-L23 | $holderSqlLocal = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderSqlLocal = Join-Path $env:TEMP "w23-holde` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 24줄F07-L24 | $waiterSqlLocal = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterSqlLocal = Join-Path $env:TEMP "w23-waite` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 25줄F07-L25 | $holderOut = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderOut = Join-Path $env:TEMP "w23-holder-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 26줄F07-L26 | $holderErr = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderErr = Join-Path $env:TEMP "w23-holder-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 27줄F07-L27 | $waiterOut = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterOut = Join-Path $env:TEMP "w23-waiter-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 28줄F07-L28 | $waiterErr = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterErr = Join-Path $env:TEMP "w23-waiter-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 29줄F07-L29 | $owned = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$owned = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 30줄F07-L30 | $fixtureCreated = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$fixtureCreated = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 31줄F07-L31 | $cleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$cleanup = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 32줄F07-L32 | $holderProcess = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderProcess = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 33줄F07-L33 | $waiterProcess = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterProcess = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 34줄F07-L34 | $failure = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$failure = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 35줄F07-L35 | $body = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$body = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 37줄F07-L37 | function Invoke-PsqlScalar( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다.
|
| 38줄F07-L38 | $previousPreference = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$previousPreference = $ErrorActionPreference` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 39줄F07-L39 | $ErrorActionPreference = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = 'Continue'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 40줄F07-L40 | try { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다.
|
| 41줄F07-L41 | $rows = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$rows = @(& docker exec $Container psql -X -q -A` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 42줄F07-L42 | $nativeExit = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$nativeExit = $LASTEXITCODE` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 43줄F07-L43 | } finally { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 43번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 44줄F07-L44 | $ErrorActionPreference = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = $previousPreference` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 45줄F07-L45 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 46줄F07-L46 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 47줄F07-L47 | $values = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$values = @($rows | ForEach-Object { ([string]$_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 48줄F07-L48 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 49줄F07-L49 | return $values[ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 49번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 50줄F07-L50 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 52줄F07-L52 | try { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다.
|
| 53줄F07-L53 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 54줄F07-L54 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 55줄F07-L55 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 56줄F07-L56 | $env: |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$env:FCL_DB_PASSWORD = 'w23-disposable-password'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 57줄F07-L57 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 58줄F07-L58 | # Mark ownership before startup so partial Compose creation is always |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 59줄F07-L59 | # torn down in finally, |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 60줄F07-L60 | $owned = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$owned = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 61줄F07-L61 | & docker compose -f $compose -p $ComposeProject up -d --wait db |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 61번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 62줄F07-L62 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 63줄F07-L63 | $Container = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$Container = (& docker compose -f $compose -p $C` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 64줄F07-L64 | } elseif ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `} elseif ([string]::IsNullOrWhiteSpace($Containe` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 65줄F07-L65 | throw 'W23 Container mode requires Container' |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 66줄F07-L66 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 67줄F07-L67 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 69줄F07-L69 | $setup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$setup = "DROP SCHEMA IF EXISTS w23_wait CASCADE` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 70줄F07-L70 | Invoke-PsqlScalar( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다.
|
| 71줄F07-L71 | $fixtureCreated = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$fixtureCreated = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 73줄F07-L73 | @" |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 73번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 74줄F07-L74 | \set ON_ERROR_STOP on |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 74번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 75줄F07-L75 | SET application_name= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다.
|
| 76줄F07-L76 | BEGIN; |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 76번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 77줄F07-L77 | UPDATE w23_wait. |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `UPDATE w23_wait.probe SET value=value+1 WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 78줄F07-L78 | SELECT pg_sleep( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | holder가 row lock을 잠시 유지해 waiter를 관찰할 시간을 만든다.
|
| 79줄F07-L79 | COMMIT; |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 79번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 80줄F07-L80 | "@ | Set-Content -Encoding utf8 -LiteralPath $holderSqlLocal |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 80번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 81줄F07-L81 | @" |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 81번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 82줄F07-L82 | \set ON_ERROR_STOP on |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 82번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 83줄F07-L83 | SET application_name= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다.
|
| 84줄F07-L84 | SET lock_timeout= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `SET lock_timeout='10s';` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 85줄F07-L85 | UPDATE w23_wait. |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `UPDATE w23_wait.probe SET value=value+1 WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 86줄F07-L86 | "@ | Set-Content -Encoding utf8 -LiteralPath $waiterSqlLocal |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 86번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 88줄F07-L88 | & docker cp $holderSqlLocal "${ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker cp $holderSqlLocal "${Container}:$holde` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 89줄F07-L89 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 90줄F07-L90 | & docker cp $waiterSqlLocal "${ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker cp $waiterSqlLocal "${Container}:$waite` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 91줄F07-L91 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 93줄F07-L93 | $holderProcess = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderProcess = Start-Process docker -ArgumentL` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 94줄F07-L94 | 'exec', |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `'exec',$Container,'psql','-X','-v','ON_ERROR_STO` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 95줄F07-L95 | ) -RedirectStandardOutput $holderOut -RedirectStandardError $holderErr -PassThru -WindowStyle Hidden |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 95번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 96줄F07-L96 | Start-Sleep -Seconds 1 |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 96번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 97줄F07-L97 | $waiterProcess = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterProcess = Start-Process docker -ArgumentL` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 98줄F07-L98 | 'exec', |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `'exec',$Container,'psql','-X','-v','ON_ERROR_STO` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 99줄F07-L99 | ) -RedirectStandardOutput $waiterOut -RedirectStandardError $waiterErr -PassThru -WindowStyle Hidden |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 99번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 100줄F07-L100 | Start-Sleep -Seconds 1 |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 100번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 102줄F07-L102 | $snapshotRaw = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다.
|
| 103줄F07-L103 | "SELECT wait_event_type||'|'||coalesce( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다.
|
| 104줄F07-L104 | ) |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 105줄F07-L105 | $snapshot = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$snapshot = $snapshotRaw.Split('|')` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 106줄F07-L106 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 107줄F07-L107 | throw "W23 exact row-lock snapshot invalid: |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 108줄F07-L108 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 109줄F07-L109 | $observed = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | row-lock wait가 실제 snapshot에서 관찰됐다는 local evidence 값을 고정한다.
|
| 111줄F07-L111 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 112줄F07-L112 | throw 'W23 native process timeout' |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 113줄F07-L113 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 114줄F07-L114 | $holderProcess. |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 114번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 115줄F07-L115 | $holderProcess. |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 115번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 116줄F07-L116 | $holderExit = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderExit = [int]$holderProcess.ExitCode` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 117줄F07-L117 | $waiterExit = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterExit = [int]$waiterProcess.ExitCode` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 118줄F07-L118 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 119줄F07-L119 | $errors = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$errors = @(` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 120줄F07-L120 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 121줄F07-L121 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 122줄F07-L122 | ) -join "`n" |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 122번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 123줄F07-L123 | throw "W23 native process failure holder= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 124줄F07-L124 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 125줄F07-L125 | $cleared = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다.
|
| 126줄F07-L126 | $finalValue = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다.
|
| 127줄F07-L127 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다.
|
| 128줄F07-L128 | throw "W23 wait/ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다.
|
| 129줄F07-L129 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 131줄F07-L131 | $body = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$body = [ordered]@{` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 132줄F07-L132 | run_id = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `run_id = $runId` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 133줄F07-L133 | holder_session = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `holder_session = $holderApp` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 134줄F07-L134 | waiter_session = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `waiter_session = $waiterApp` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 135줄F07-L135 | fixture = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `fixture = 'w23_wait.probe(id=1,value=0)'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 136줄F07-L136 | lock_scope = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `lock_scope = 'ROW_UPDATE'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 137줄F07-L137 | observed = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `observed = $observed` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 138줄F07-L138 | cleared = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다.
|
| 139줄F07-L139 | holder_exit = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `holder_exit = $holderExit` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 140줄F07-L140 | waiter_exit = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `waiter_exit = $waiterExit` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 141줄F07-L141 | native_exits = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `native_exits = 0` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 142줄F07-L142 | final_value = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 두 UPDATE가 모두 commit되어 fixture value가 0에서 2가 됐는지 읽는다.
|
| 143줄F07-L143 | snapshot = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `snapshot = [ordered]@{` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 144줄F07-L144 | wait_event_type = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | snapshot이 추정이 아니라 PostgreSQL `Lock` wait인지 확인한다.
|
| 145줄F07-L145 | wait_event = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `wait_event = $snapshot[1]` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 146줄F07-L146 | blocking_session_count = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `blocking_session_count = [int]$snapshot[2]` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 147줄F07-L147 | query_redacted = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `query_redacted = 'UPDATE w23_wait.probe WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 148줄F07-L148 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 149줄F07-L149 | source_sha256 = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `source_sha256 = (Get-FileHash -LiteralPath $MyIn` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 150줄F07-L150 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 151줄F07-L151 | } catch { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 151번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 152줄F07-L152 | $failure = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$failure = $_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 153줄F07-L153 | } finally { |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 153번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 154줄F07-L154 | foreach ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 154번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 155줄F07-L155 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 156줄F07-L156 | Stop-Process -Id $process. |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 156번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 157줄F07-L157 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 158줄F07-L158 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 159줄F07-L159 | $schemaCleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$schemaCleanup = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 160줄F07-L160 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 161줄F07-L161 | & docker exec $Container psql -X -q -v ON_ERROR_STOP= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker exec $Container psql -X -q -v ON_ERROR_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 162줄F07-L162 | $schemaCleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$schemaCleanup = ($LASTEXITCODE -eq 0)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 163줄F07-L163 | & docker exec $Container rm -f $holderSqlInContainer $waiterSqlInContainer | Out-Null |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 163번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 164줄F07-L164 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 165줄F07-L165 | $composeCleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$composeCleanup = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 166줄F07-L166 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 167줄F07-L167 | & docker compose -f $compose -p $ComposeProject down -v --remove-orphans | Out-Null |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 167번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 168줄F07-L168 | $composeCleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$composeCleanup = ($LASTEXITCODE -eq 0)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 169줄F07-L169 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 170줄F07-L170 | $cleanup = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$cleanup = $schemaCleanup -and $composeCleanup` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 171줄F07-L171 | Remove-Item $holderSqlLocal, |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 171번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 172줄F07-L172 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 174줄F07-L174 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 175줄F07-L175 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 176줄F07-L176 | throw "W23 DB wait failed and cleanup was not confirmed: |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 177줄F07-L177 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 178줄F07-L178 | throw 'W23 DB wait cleanup was not confirmed' |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 179줄F07-L179 | } |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 180줄F07-L180 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 181줄F07-L181 | if ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다.
|
| 183줄F07-L183 | $body[ |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | schema·Compose cleanup이 확인된 뒤 evidence cleanup 값을 1로 넣는다.
|
| 184줄F07-L184 | New-Item -ItemType Directory -Force ( |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 184번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 185줄F07-L185 | $temporary = |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer에서 `$temporary = "$evidence.tmp"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 186줄F07-L186 | $body | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 -LiteralPath $temporary |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 186번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 187줄F07-L187 | Move-Item -LiteralPath $temporary -Destination $evidence -Force |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | 정본 PostgreSQL row-lock wait evidence producer의 187번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 189줄F07-L189 | "W23_DB_WAIT_GREEN scope= |
한 작업자가 서랍의 같은 행을 잡은 채 6초 기다리고, 둘째 작업자의 대기 표지판을 촬영한 뒤 두 작업이 끝나면 서랍 값 2와 빈 대기열을 확인하는 격리 실험 | observed=1·cleared=0·value=2·native exits=0·cleanup=1을 닫는 exact marker다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `native_exits=0 cleanup=1`이야.
-
`row-update lock 한 종류만 다룬다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 16개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 hash로 고정한 native 정본 source에서 그대로 잘랐습니다.
param(
[ValidateSet('Compose','Container')][string]$Mode = 'Compose',
[string]$ComposeProject = 'w23-db-wait-lab',
[string]$Container = '',
[Parameter(Mandatory = $true)][string]$EvidencePath
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$evidence = if ([IO.Path]::IsPathRooted($EvidencePath)) {
[IO.Path]::GetFullPath($EvidencePath)
} else {
[IO.Path]::GetFullPath((Join-Path $root $EvidencePath))
}
$compose = Join-Path $root 'compose.yaml'
$runId = [guid]::NewGuid().ToString('N').Substring(0, 12)
$holderApp = "w23_holder_$runId"
$waiterApp = "w23_waiter_$runId"
$holderSqlInContainer = "/tmp/w23-holder-$runId.sql"
$waiterSqlInContainer = "/tmp/w23-waiter-$runId.sql"
$holderSqlLocal = Join-Path $env:TEMP "w23-holder-$runId.sql"
$waiterSqlLocal = Join-Path $env:TEMP "w23-waiter-$runId.sql"
$holderOut = Join-Path $env:TEMP "w23-holder-$runId.out"
$holderErr = Join-Path $env:TEMP "w23-holder-$runId.err"
$waiterOut = Join-Path $env:TEMP "w23-waiter-$runId.out"
$waiterErr = Join-Path $env:TEMP "w23-waiter-$runId.err"
$owned = $false
$fixtureCreated = $false
$cleanup = $false
$holderProcess = $null
$waiterProcess = $null
$failure = $null
$body = $null
function Invoke-PsqlScalar([string]$Sql) {
$previousPreference = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$rows = @(& docker exec $Container psql -X -q -A -t -v ON_ERROR_STOP=1 -U app -d financial_core -c $Sql 2>&1)
$nativeExit = $LASTEXITCODE
} finally {
$ErrorActionPreference = $previousPreference
}
if ($nativeExit -ne 0) { throw "W23 psql failed: $($rows -join "`n")" }
$values = @($rows | ForEach-Object { ([string]$_).Trim() } | Where-Object { $_ })
if ($values.Count -lt 1) { throw 'W23 psql returned no scalar value' }
return $values[-1]
}
try {
if ($Mode -ceq 'Compose') {
if ($Container) { throw 'W23 Compose mode rejects Container' }
if ([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)) {
$env:FCL_DB_PASSWORD = 'w23-disposable-password'
}
# Mark ownership before startup so partial Compose creation is always
# torn down in finally, even when `up --wait` itself fails.
$owned = $true
& docker compose -f $compose -p $ComposeProject up -d --wait db
if ($LASTEXITCODE -ne 0) { throw "W23 compose up exit=$LASTEXITCODE" }
$Container = (& docker compose -f $compose -p $ComposeProject ps -q db).Trim()
} elseif ([string]::IsNullOrWhiteSpace($Container)) {
throw 'W23 Container mode requires Container'
}
if ([string]::IsNullOrWhiteSpace($Container)) { throw 'W23 container resolution failed' }
$setup = "DROP SCHEMA IF EXISTS w23_wait CASCADE; CREATE SCHEMA w23_wait; CREATE TABLE w23_wait.probe(id int PRIMARY KEY,value int NOT NULL); INSERT INTO w23_wait.probe VALUES(1,0);"
Invoke-PsqlScalar("$setup SELECT count(*) FROM w23_wait.probe;") | Out-Null
$fixtureCreated = $true
@"
\set ON_ERROR_STOP on
SET application_name='$holderApp';
BEGIN;
UPDATE w23_wait.probe SET value=value+1 WHERE id=1;
SELECT pg_sleep(6);
COMMIT;
"@ | Set-Content -Encoding utf8 -LiteralPath $holderSqlLocal
@"
\set ON_ERROR_STOP on
SET application_name='$waiterApp';
SET lock_timeout='10s';
UPDATE w23_wait.probe SET value=value+1 WHERE id=1;
"@ | Set-Content -Encoding utf8 -LiteralPath $waiterSqlLocal
& docker cp $holderSqlLocal "${Container}:$holderSqlInContainer" | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'W23 holder SQL copy failed' }
& docker cp $waiterSqlLocal "${Container}:$waiterSqlInContainer" | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'W23 waiter SQL copy failed' }
$holderProcess = Start-Process docker -ArgumentList @(
'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$holderSqlInContainer
) -RedirectStandardOutput $holderOut -RedirectStandardError $holderErr -PassThru -WindowStyle Hidden
Start-Sleep -Seconds 1
$waiterProcess = Start-Process docker -ArgumentList @(
'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$waiterSqlInContainer
) -RedirectStandardOutput $waiterOut -RedirectStandardError $waiterErr -PassThru -WindowStyle Hidden
Start-Sleep -Seconds 1
$snapshotRaw = Invoke-PsqlScalar(
"SELECT wait_event_type||'|'||coalesce(wait_event,'')||'|'||cardinality(pg_blocking_pids(pid)) FROM pg_stat_activity WHERE application_name='$waiterApp' AND wait_event_type='Lock';"
)
$snapshot = $snapshotRaw.Split('|')
if ($snapshot.Count -ne 3 -or $snapshot[0] -cne 'Lock' -or [int]$snapshot[2] -lt 1) {
throw "W23 exact row-lock snapshot invalid: $snapshotRaw"
}
$observed = 1
if (!$holderProcess.WaitForExit(15000) -or !$waiterProcess.WaitForExit(15000)) {
throw 'W23 native process timeout'
}
$holderProcess.WaitForExit(); $waiterProcess.WaitForExit()
$holderProcess.Refresh(); $waiterProcess.Refresh()
$holderExit = [int]$holderProcess.ExitCode
$waiterExit = [int]$waiterProcess.ExitCode
if ($holderExit -ne 0 -or $waiterExit -ne 0) {
$errors = @(
if (Test-Path $holderErr) { Get-Content -Raw $holderErr }
if (Test-Path $waiterErr) { Get-Content -Raw $waiterErr }
) -join "`n"
throw "W23 native process failure holder=$holderExit waiter=$waiterExit`n$errors"
}
$cleared = [int](Invoke-PsqlScalar("SELECT count(*) FROM pg_stat_activity WHERE application_name IN ('$holderApp','$waiterApp') AND wait_event_type='Lock';"))
$finalValue = [int](Invoke-PsqlScalar('SELECT value FROM w23_wait.probe WHERE id=1;'))
if ($cleared -ne 0 -or $finalValue -ne 2) {
throw "W23 wait/final value mismatch cleared=$cleared value=$finalValue"
}
$body = [ordered]@{
run_id = $runId
holder_session = $holderApp
waiter_session = $waiterApp
fixture = 'w23_wait.probe(id=1,value=0)'
lock_scope = 'ROW_UPDATE'
observed = $observed
cleared = $cleared
holder_exit = $holderExit
waiter_exit = $waiterExit
native_exits = 0
final_value = $finalValue
snapshot = [ordered]@{
wait_event_type = $snapshot[0]
wait_event = $snapshot[1]
blocking_session_count = [int]$snapshot[2]
query_redacted = 'UPDATE w23_wait.probe WHERE id=?'
}
source_sha256 = (Get-FileHash -LiteralPath $MyInvocation.MyCommand.Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
} catch {
$failure = $_
} finally {
foreach ($process in @($holderProcess,$waiterProcess)) {
if ($null -ne $process -and !$process.HasExited) {
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
}
}
$schemaCleanup = $true
if ($fixtureCreated -and ![string]::IsNullOrWhiteSpace($Container)) {
& docker exec $Container psql -X -q -v ON_ERROR_STOP=1 -U app -d financial_core -c 'DROP SCHEMA IF EXISTS w23_wait CASCADE;' | Out-Null
$schemaCleanup = ($LASTEXITCODE -eq 0)
& docker exec $Container rm -f $holderSqlInContainer $waiterSqlInContainer | Out-Null
}
$composeCleanup = $true
if ($owned) {
& docker compose -f $compose -p $ComposeProject down -v --remove-orphans | Out-Null
$composeCleanup = ($LASTEXITCODE -eq 0)
}
$cleanup = $schemaCleanup -and $composeCleanup
Remove-Item $holderSqlLocal,$waiterSqlLocal,$holderOut,$holderErr,$waiterOut,$waiterErr -Force -ErrorAction SilentlyContinue
}
if (!$cleanup) {
if ($null -ne $failure) {
throw "W23 DB wait failed and cleanup was not confirmed: $($failure.Exception.Message)"
}
throw 'W23 DB wait cleanup was not confirmed'
}
if ($null -ne $failure) { throw $failure }
if ($null -eq $body) { throw 'W23 DB wait produced no result' }
$body['cleanup'] = 1
New-Item -ItemType Directory -Force (Split-Path -Parent $evidence) | Out-Null
$temporary = "$evidence.tmp"
$body | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 -LiteralPath $temporary
Move-Item -LiteralPath $temporary -Destination $evidence -Force
"W23_DB_WAIT_GREEN scope=ROW_UPDATE observed=1 cleared=0 final_value=2 native_exits=0 cleanup=1"
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 175줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | param( | 정본 PostgreSQL row-lock wait evidence producer의 1번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 2 | [ValidateSet('Compose','Container')][string]$Mode = 'Compose', | DB wait runner의 lifecycle mode를 Compose와 Container 두 값으로 제한한다. |
| 3 | [string]$ComposeProject = 'w23-db-wait-lab', | 정본 PostgreSQL row-lock wait evidence producer에서 `[string]$ComposeProject = 'w23-db-wait-lab',` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 4 | [string]$Container = '', | 정본 PostgreSQL row-lock wait evidence producer에서 `[string]$Container = '',` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 5 | [Parameter(Mandatory = $true)][string]$EvidencePath | 정본 PostgreSQL row-lock wait evidence producer에서 `[Parameter(Mandatory = $true)][string]$EvidenceP` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 6 | ) | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 8 | Set-StrictMode -Version Latest | 미정의 변수 같은 PowerShell 오류를 조기에 드러낸다. |
| 9 | $ErrorActionPreference = 'Stop' | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = 'Stop'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 11 | $root = Split-Path -Parent $PSScriptRoot | 정본 PostgreSQL row-lock wait evidence producer에서 `$root = Split-Path -Parent $PSScriptRoot` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 12 | $evidence = if ([IO.Path]::IsPathRooted($EvidencePath)) { | 정본 PostgreSQL row-lock wait evidence producer에서 `$evidence = if ([IO.Path]::IsPathRooted($Evidenc` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 13 | [IO.Path]::GetFullPath($EvidencePath) | 정본 PostgreSQL row-lock wait evidence producer에서 `[IO.Path]::GetFullPath($EvidencePath)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 14 | } else { | 정본 PostgreSQL row-lock wait evidence producer의 14번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 15 | [IO.Path]::GetFullPath((Join-Path $root $EvidencePath)) | 정본 PostgreSQL row-lock wait evidence producer에서 `[IO.Path]::GetFullPath((Join-Path $root $Evidenc` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 16 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 17 | $compose = Join-Path $root 'compose.yaml' | 정본 PostgreSQL row-lock wait evidence producer에서 `$compose = Join-Path $root 'compose.yaml'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 18 | $runId = [guid]::NewGuid().ToString('N').Substring(0, 12) | 정본 PostgreSQL row-lock wait evidence producer에서 `$runId = [guid]::NewGuid().ToString('N').Substri` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 19 | $holderApp = "w23_holder_$runId" | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderApp = "w23_holder_$runId"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 20 | $waiterApp = "w23_waiter_$runId" | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterApp = "w23_waiter_$runId"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 21 | $holderSqlInContainer = "/tmp/w23-holder-$runId.sql" | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderSqlInContainer = "/tmp/w23-holder-$runId.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 22 | $waiterSqlInContainer = "/tmp/w23-waiter-$runId.sql" | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterSqlInContainer = "/tmp/w23-waiter-$runId.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 23 | $holderSqlLocal = Join-Path $env:TEMP "w23-holder-$runId.sql" | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderSqlLocal = Join-Path $env:TEMP "w23-holde` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 24 | $waiterSqlLocal = Join-Path $env:TEMP "w23-waiter-$runId.sql" | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterSqlLocal = Join-Path $env:TEMP "w23-waite` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 25 | $holderOut = Join-Path $env:TEMP "w23-holder-$runId.out" | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderOut = Join-Path $env:TEMP "w23-holder-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 26 | $holderErr = Join-Path $env:TEMP "w23-holder-$runId.err" | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderErr = Join-Path $env:TEMP "w23-holder-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 27 | $waiterOut = Join-Path $env:TEMP "w23-waiter-$runId.out" | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterOut = Join-Path $env:TEMP "w23-waiter-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 28 | $waiterErr = Join-Path $env:TEMP "w23-waiter-$runId.err" | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterErr = Join-Path $env:TEMP "w23-waiter-$ru` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 29 | $owned = $false | 정본 PostgreSQL row-lock wait evidence producer에서 `$owned = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 30 | $fixtureCreated = $false | 정본 PostgreSQL row-lock wait evidence producer에서 `$fixtureCreated = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 31 | $cleanup = $false | 정본 PostgreSQL row-lock wait evidence producer에서 `$cleanup = $false` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 32 | $holderProcess = $null | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderProcess = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 33 | $waiterProcess = $null | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterProcess = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 34 | $failure = $null | 정본 PostgreSQL row-lock wait evidence producer에서 `$failure = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 35 | $body = $null | 정본 PostgreSQL row-lock wait evidence producer에서 `$body = $null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 37 | function Invoke-PsqlScalar([string]$Sql) { | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다. |
| 38 | $previousPreference = $ErrorActionPreference | 정본 PostgreSQL row-lock wait evidence producer에서 `$previousPreference = $ErrorActionPreference` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 39 | $ErrorActionPreference = 'Continue' | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = 'Continue'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 40 | try { | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다. |
| 41 | $rows = @(& docker exec $Container psql -X -q -A -t -v ON_ERROR_STOP=1 -U app -d financial_core -c $Sql 2>&1) | 정본 PostgreSQL row-lock wait evidence producer에서 `$rows = @(& docker exec $Container psql -X -q -A` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 42 | $nativeExit = $LASTEXITCODE | 정본 PostgreSQL row-lock wait evidence producer에서 `$nativeExit = $LASTEXITCODE` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 43 | } finally { | 정본 PostgreSQL row-lock wait evidence producer의 43번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 44 | $ErrorActionPreference = $previousPreference | 정본 PostgreSQL row-lock wait evidence producer에서 `$ErrorActionPreference = $previousPreference` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 45 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 46 | if ($nativeExit -ne 0) { throw "W23 psql failed: $($rows -join "`n")" } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 47 | $values = @($rows | ForEach-Object { ([string]$_).Trim() } | Where-Object { $_ }) | 정본 PostgreSQL row-lock wait evidence producer에서 `$values = @($rows | ForEach-Object { ([string]$_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 48 | if ($values.Count -lt 1) { throw 'W23 psql returned no scalar value' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 49 | return $values[-1] | 정본 PostgreSQL row-lock wait evidence producer의 49번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 50 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 52 | try { | 정상·실패 경로와 공통 cleanup/계측 경계를 분리하는 control-flow 블록이다. |
| 53 | if ($Mode -ceq 'Compose') { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 54 | if ($Container) { throw 'W23 Compose mode rejects Container' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 55 | if ([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 56 | $env:FCL_DB_PASSWORD = 'w23-disposable-password' | 정본 PostgreSQL row-lock wait evidence producer에서 `$env:FCL_DB_PASSWORD = 'w23-disposable-password'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 57 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 58 | # Mark ownership before startup so partial Compose creation is always | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 59 | # torn down in finally, even when `up --wait` itself fails. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 60 | $owned = $true | 정본 PostgreSQL row-lock wait evidence producer에서 `$owned = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 61 | & docker compose -f $compose -p $ComposeProject up -d --wait db | 정본 PostgreSQL row-lock wait evidence producer의 61번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 62 | if ($LASTEXITCODE -ne 0) { throw "W23 compose up exit=$LASTEXITCODE" } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 63 | $Container = (& docker compose -f $compose -p $ComposeProject ps -q db).Trim() | 정본 PostgreSQL row-lock wait evidence producer에서 `$Container = (& docker compose -f $compose -p $C` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 64 | } elseif ([string]::IsNullOrWhiteSpace($Container)) { | 정본 PostgreSQL row-lock wait evidence producer에서 `} elseif ([string]::IsNullOrWhiteSpace($Containe` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 65 | throw 'W23 Container mode requires Container' | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 66 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 67 | if ([string]::IsNullOrWhiteSpace($Container)) { throw 'W23 container resolution failed' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 69 | $setup = "DROP SCHEMA IF EXISTS w23_wait CASCADE; CREATE SCHEMA w23_wait; CREATE TABLE w23_wait.probe(id int PRIMARY KEY,value int NOT NULL); INSERT INTO w23_wait.probe VALUES(1,0);" | 정본 PostgreSQL row-lock wait evidence producer에서 `$setup = "DROP SCHEMA IF EXISTS w23_wait CASCADE` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 70 | Invoke-PsqlScalar("$setup SELECT count(*) FROM w23_wait.probe;") | Out-Null | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다. |
| 71 | $fixtureCreated = $true | 정본 PostgreSQL row-lock wait evidence producer에서 `$fixtureCreated = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 73 | @" | 정본 PostgreSQL row-lock wait evidence producer의 73번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 74 | \set ON_ERROR_STOP on | 정본 PostgreSQL row-lock wait evidence producer의 74번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 75 | SET application_name='$holderApp'; | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다. |
| 76 | BEGIN; | 정본 PostgreSQL row-lock wait evidence producer의 76번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 77 | UPDATE w23_wait.probe SET value=value+1 WHERE id=1; | 정본 PostgreSQL row-lock wait evidence producer에서 `UPDATE w23_wait.probe SET value=value+1 WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 78 | SELECT pg_sleep(6); | holder가 row lock을 잠시 유지해 waiter를 관찰할 시간을 만든다. |
| 79 | COMMIT; | 정본 PostgreSQL row-lock wait evidence producer의 79번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 80 | "@ | Set-Content -Encoding utf8 -LiteralPath $holderSqlLocal | 정본 PostgreSQL row-lock wait evidence producer의 80번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 81 | @" | 정본 PostgreSQL row-lock wait evidence producer의 81번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 82 | \set ON_ERROR_STOP on | 정본 PostgreSQL row-lock wait evidence producer의 82번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 83 | SET application_name='$waiterApp'; | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다. |
| 84 | SET lock_timeout='10s'; | 정본 PostgreSQL row-lock wait evidence producer에서 `SET lock_timeout='10s';` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 85 | UPDATE w23_wait.probe SET value=value+1 WHERE id=1; | 정본 PostgreSQL row-lock wait evidence producer에서 `UPDATE w23_wait.probe SET value=value+1 WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 86 | "@ | Set-Content -Encoding utf8 -LiteralPath $waiterSqlLocal | 정본 PostgreSQL row-lock wait evidence producer의 86번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 88 | & docker cp $holderSqlLocal "${Container}:$holderSqlInContainer" | Out-Null | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker cp $holderSqlLocal "${Container}:$holde` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 89 | if ($LASTEXITCODE -ne 0) { throw 'W23 holder SQL copy failed' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 90 | & docker cp $waiterSqlLocal "${Container}:$waiterSqlInContainer" | Out-Null | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker cp $waiterSqlLocal "${Container}:$waite` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 91 | if ($LASTEXITCODE -ne 0) { throw 'W23 waiter SQL copy failed' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 93 | $holderProcess = Start-Process docker -ArgumentList @( | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderProcess = Start-Process docker -ArgumentL` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 94 | 'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$holderSqlInContainer | 정본 PostgreSQL row-lock wait evidence producer에서 `'exec',$Container,'psql','-X','-v','ON_ERROR_STO` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 95 | ) -RedirectStandardOutput $holderOut -RedirectStandardError $holderErr -PassThru -WindowStyle Hidden | 정본 PostgreSQL row-lock wait evidence producer의 95번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 96 | Start-Sleep -Seconds 1 | 정본 PostgreSQL row-lock wait evidence producer의 96번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 97 | $waiterProcess = Start-Process docker -ArgumentList @( | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterProcess = Start-Process docker -ArgumentL` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 98 | 'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$waiterSqlInContainer | 정본 PostgreSQL row-lock wait evidence producer에서 `'exec',$Container,'psql','-X','-v','ON_ERROR_STO` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 99 | ) -RedirectStandardOutput $waiterOut -RedirectStandardError $waiterErr -PassThru -WindowStyle Hidden | 정본 PostgreSQL row-lock wait evidence producer의 99번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 100 | Start-Sleep -Seconds 1 | 정본 PostgreSQL row-lock wait evidence producer의 100번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 102 | $snapshotRaw = Invoke-PsqlScalar( | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다. |
| 103 | "SELECT wait_event_type||'|'||coalesce(wait_event,'')||'|'||cardinality(pg_blocking_pids(pid)) FROM pg_stat_activity WHERE application_name='$waiterApp' AND wait_event_type='Lock';" | holder와 waiter session을 pg_stat_activity에서 정확히 찾을 고유 이름을 준다. |
| 104 | ) | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 105 | $snapshot = $snapshotRaw.Split('|') | 정본 PostgreSQL row-lock wait evidence producer에서 `$snapshot = $snapshotRaw.Split('|')` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 106 | if ($snapshot.Count -ne 3 -or $snapshot[0] -cne 'Lock' -or [int]$snapshot[2] -lt 1) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 107 | throw "W23 exact row-lock snapshot invalid: $snapshotRaw" | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 108 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 109 | $observed = 1 | row-lock wait가 실제 snapshot에서 관찰됐다는 local evidence 값을 고정한다. |
| 111 | if (!$holderProcess.WaitForExit(15000) -or !$waiterProcess.WaitForExit(15000)) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 112 | throw 'W23 native process timeout' | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 113 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 114 | $holderProcess.WaitForExit(); $waiterProcess.WaitForExit() | 정본 PostgreSQL row-lock wait evidence producer의 114번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 115 | $holderProcess.Refresh(); $waiterProcess.Refresh() | 정본 PostgreSQL row-lock wait evidence producer의 115번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 116 | $holderExit = [int]$holderProcess.ExitCode | 정본 PostgreSQL row-lock wait evidence producer에서 `$holderExit = [int]$holderProcess.ExitCode` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 117 | $waiterExit = [int]$waiterProcess.ExitCode | 정본 PostgreSQL row-lock wait evidence producer에서 `$waiterExit = [int]$waiterProcess.ExitCode` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 118 | if ($holderExit -ne 0 -or $waiterExit -ne 0) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 119 | $errors = @( | 정본 PostgreSQL row-lock wait evidence producer에서 `$errors = @(` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 120 | if (Test-Path $holderErr) { Get-Content -Raw $holderErr } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 121 | if (Test-Path $waiterErr) { Get-Content -Raw $waiterErr } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 122 | ) -join "`n" | 정본 PostgreSQL row-lock wait evidence producer의 122번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 123 | throw "W23 native process failure holder=$holderExit waiter=$waiterExit`n$errors" | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 124 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 125 | $cleared = [int](Invoke-PsqlScalar("SELECT count(*) FROM pg_stat_activity WHERE application_name IN ('$holderApp','$waiterApp') AND wait_event_type='Lock';")) | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다. |
| 126 | $finalValue = [int](Invoke-PsqlScalar('SELECT value FROM w23_wait.probe WHERE id=1;')) | native psql exit와 scalar output을 함께 검사하는 helper 경계를 연다. |
| 127 | if ($cleared -ne 0 -or $finalValue -ne 2) { | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다. |
| 128 | throw "W23 wait/final value mismatch cleared=$cleared value=$finalValue" | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다. |
| 129 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 131 | $body = [ordered]@{ | 정본 PostgreSQL row-lock wait evidence producer에서 `$body = [ordered]@{` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 132 | run_id = $runId | 정본 PostgreSQL row-lock wait evidence producer에서 `run_id = $runId` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 133 | holder_session = $holderApp | 정본 PostgreSQL row-lock wait evidence producer에서 `holder_session = $holderApp` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 134 | waiter_session = $waiterApp | 정본 PostgreSQL row-lock wait evidence producer에서 `waiter_session = $waiterApp` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 135 | fixture = 'w23_wait.probe(id=1,value=0)' | 정본 PostgreSQL row-lock wait evidence producer에서 `fixture = 'w23_wait.probe(id=1,value=0)'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 136 | lock_scope = 'ROW_UPDATE' | 정본 PostgreSQL row-lock wait evidence producer에서 `lock_scope = 'ROW_UPDATE'` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 137 | observed = $observed | 정본 PostgreSQL row-lock wait evidence producer에서 `observed = $observed` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 138 | cleared = $cleared | 두 process 종료 뒤 남은 Lock wait 수를 다시 측정한다. |
| 139 | holder_exit = $holderExit | 정본 PostgreSQL row-lock wait evidence producer에서 `holder_exit = $holderExit` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 140 | waiter_exit = $waiterExit | 정본 PostgreSQL row-lock wait evidence producer에서 `waiter_exit = $waiterExit` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 141 | native_exits = 0 | 정본 PostgreSQL row-lock wait evidence producer에서 `native_exits = 0` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 142 | final_value = $finalValue | 두 UPDATE가 모두 commit되어 fixture value가 0에서 2가 됐는지 읽는다. |
| 143 | snapshot = [ordered]@{ | 정본 PostgreSQL row-lock wait evidence producer에서 `snapshot = [ordered]@{` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 144 | wait_event_type = $snapshot[0] | snapshot이 추정이 아니라 PostgreSQL `Lock` wait인지 확인한다. |
| 145 | wait_event = $snapshot[1] | 정본 PostgreSQL row-lock wait evidence producer에서 `wait_event = $snapshot[1]` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 146 | blocking_session_count = [int]$snapshot[2] | 정본 PostgreSQL row-lock wait evidence producer에서 `blocking_session_count = [int]$snapshot[2]` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 147 | query_redacted = 'UPDATE w23_wait.probe WHERE id=?' | 정본 PostgreSQL row-lock wait evidence producer에서 `query_redacted = 'UPDATE w23_wait.probe WHERE id` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 148 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 149 | source_sha256 = (Get-FileHash -LiteralPath $MyInvocation.MyCommand.Path -Algorithm SHA256).Hash.ToLowerInvariant() | 정본 PostgreSQL row-lock wait evidence producer에서 `source_sha256 = (Get-FileHash -LiteralPath $MyIn` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 150 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 151 | } catch { | 정본 PostgreSQL row-lock wait evidence producer의 151번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 152 | $failure = $_ | 정본 PostgreSQL row-lock wait evidence producer에서 `$failure = $_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 153 | } finally { | 정본 PostgreSQL row-lock wait evidence producer의 153번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 154 | foreach ($process in @($holderProcess,$waiterProcess)) { | 정본 PostgreSQL row-lock wait evidence producer의 154번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 155 | if ($null -ne $process -and !$process.HasExited) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 156 | Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue | 정본 PostgreSQL row-lock wait evidence producer의 156번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 157 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 158 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 159 | $schemaCleanup = $true | 정본 PostgreSQL row-lock wait evidence producer에서 `$schemaCleanup = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 160 | if ($fixtureCreated -and ![string]::IsNullOrWhiteSpace($Container)) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 161 | & docker exec $Container psql -X -q -v ON_ERROR_STOP=1 -U app -d financial_core -c 'DROP SCHEMA IF EXISTS w23_wait CASCADE;' | Out-Null | 정본 PostgreSQL row-lock wait evidence producer에서 `& docker exec $Container psql -X -q -v ON_ERROR_` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 162 | $schemaCleanup = ($LASTEXITCODE -eq 0) | 정본 PostgreSQL row-lock wait evidence producer에서 `$schemaCleanup = ($LASTEXITCODE -eq 0)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 163 | & docker exec $Container rm -f $holderSqlInContainer $waiterSqlInContainer | Out-Null | 정본 PostgreSQL row-lock wait evidence producer의 163번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 164 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 165 | $composeCleanup = $true | 정본 PostgreSQL row-lock wait evidence producer에서 `$composeCleanup = $true` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 166 | if ($owned) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 167 | & docker compose -f $compose -p $ComposeProject down -v --remove-orphans | Out-Null | 정본 PostgreSQL row-lock wait evidence producer의 167번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 168 | $composeCleanup = ($LASTEXITCODE -eq 0) | 정본 PostgreSQL row-lock wait evidence producer에서 `$composeCleanup = ($LASTEXITCODE -eq 0)` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 169 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 170 | $cleanup = $schemaCleanup -and $composeCleanup | 정본 PostgreSQL row-lock wait evidence producer에서 `$cleanup = $schemaCleanup -and $composeCleanup` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 171 | Remove-Item $holderSqlLocal,$waiterSqlLocal,$holderOut,$holderErr,$waiterOut,$waiterErr -Force -ErrorAction SilentlyContinue | 정본 PostgreSQL row-lock wait evidence producer의 171번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 172 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 174 | if (!$cleanup) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 175 | if ($null -ne $failure) { | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 176 | throw "W23 DB wait failed and cleanup was not confirmed: $($failure.Exception.Message)" | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 177 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 178 | throw 'W23 DB wait cleanup was not confirmed' | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 179 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 180 | if ($null -ne $failure) { throw $failure } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 181 | if ($null -eq $body) { throw 'W23 DB wait produced no result' } | 선행 invariant가 깨졌을 때 evidence 발행 전에 멈추는 fail-closed guard다. |
| 183 | $body['cleanup'] = 1 | schema·Compose cleanup이 확인된 뒤 evidence cleanup 값을 1로 넣는다. |
| 184 | New-Item -ItemType Directory -Force (Split-Path -Parent $evidence) | Out-Null | 정본 PostgreSQL row-lock wait evidence producer의 184번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 185 | $temporary = "$evidence.tmp" | 정본 PostgreSQL row-lock wait evidence producer에서 `$temporary = "$evidence.tmp"` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 186 | $body | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 -LiteralPath $temporary | 정본 PostgreSQL row-lock wait evidence producer의 186번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 187 | Move-Item -LiteralPath $temporary -Destination $evidence -Force | 정본 PostgreSQL row-lock wait evidence producer의 187번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 189 | "W23_DB_WAIT_GREEN scope=ROW_UPDATE observed=1 cleared=0 final_value=2 native_exits=0 cleanup=1" | observed=1·cleared=0·value=2·native exits=0·cleanup=1을 닫는 exact marker다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다. 다만 실제 운영 DB를 관찰하지 않는다
문법 해부
- Compose mode는 runner가 container lifecycle을 소유하고 Container mode는 caller 소유 container를 빌린다. canonical source의 실험은 UPDATE 대 UPDATE `ROW_UPDATE`다.
- PDF p766의 `ACCESS EXCLUSIVE/SELECT` descriptor는 stale 설명이므로 canonical runner의 pg_stat_activity snapshot과 exact marker를 우선한다.
실행 순서
- disposable schema와 value=0 fixture를 만든다.
- holder와 waiter UPDATE를 별도 process로 시작한다.
- Lock wait와 blocking pid를 관찰한다.
- wait=0·value=2·native exit=0·cleanup=1 뒤 evidence를 발행한다.
W23 조각별 정밀 해설
F07-C01 · 원문 1–12줄
- 문법 해부
- 1~12줄의 `원문 1–12줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `scope=ROW_UPDATE`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `scope=ROW_UPDATE`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 실제 운영 DB를 관찰하지 않는다.
- 착각 방지
- `원문 1–12줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 실제 운영 DB를 관찰하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C02 · 원문 13–24줄
- 문법 해부
- 13~24줄의 `원문 13–24줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `observed=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `observed=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 착각 방지
- `원문 13–24줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C03 · 원문 25–36줄
- 문법 해부
- 25~36줄의 `원문 25–36줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `cleared=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `cleared=0`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: row-update lock 한 종류만 다룬다.
- 착각 방지
- `원문 25–36줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: row-update lock 한 종류만 다룬다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C04 · 원문 37–48줄
- 문법 해부
- 37~48줄의 `원문 37–48줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `final_value=2`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `final_value=2`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 외부 dashboard나 pool metric을 증명하지 않는다.
- 착각 방지
- `원문 37–48줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 외부 dashboard나 pool metric을 증명하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C05 · 원문 49–60줄
- 문법 해부
- 49~60줄의 `원문 49–60줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `native_exits=0 cleanup=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `native_exits=0 cleanup=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 실제 운영 DB를 관찰하지 않는다.
- 착각 방지
- `원문 49–60줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 실제 운영 DB를 관찰하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C06 · 원문 61–72줄
- 문법 해부
- 61~72줄의 `원문 61–72줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `scope=ROW_UPDATE`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `scope=ROW_UPDATE`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 착각 방지
- `원문 61–72줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C07 · 원문 73–84줄
- 문법 해부
- 73~84줄의 `원문 73–84줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `observed=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `observed=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: row-update lock 한 종류만 다룬다.
- 착각 방지
- `원문 73–84줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: row-update lock 한 종류만 다룬다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C08 · 원문 85–96줄
- 문법 해부
- 85~96줄의 `원문 85–96줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `cleared=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `cleared=0`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 외부 dashboard나 pool metric을 증명하지 않는다.
- 착각 방지
- `원문 85–96줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 외부 dashboard나 pool metric을 증명하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C09 · 원문 97–108줄
- 문법 해부
- 97~108줄의 `원문 97–108줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `final_value=2`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `final_value=2`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 실제 운영 DB를 관찰하지 않는다.
- 착각 방지
- `원문 97–108줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 실제 운영 DB를 관찰하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C10 · 원문 109–120줄
- 문법 해부
- 109~120줄의 `원문 109–120줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `native_exits=0 cleanup=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `native_exits=0 cleanup=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 착각 방지
- `원문 109–120줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C11 · 원문 121–132줄
- 문법 해부
- 121~132줄의 `원문 121–132줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `scope=ROW_UPDATE`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `scope=ROW_UPDATE`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: row-update lock 한 종류만 다룬다.
- 착각 방지
- `원문 121–132줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: row-update lock 한 종류만 다룬다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C12 · 원문 133–144줄
- 문법 해부
- 133~144줄의 `원문 133–144줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `observed=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `observed=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 외부 dashboard나 pool metric을 증명하지 않는다.
- 착각 방지
- `원문 133–144줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 외부 dashboard나 pool metric을 증명하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C13 · 원문 145–156줄
- 문법 해부
- 145~156줄의 `원문 145–156줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `cleared=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `cleared=0`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 실제 운영 DB를 관찰하지 않는다.
- 착각 방지
- `원문 145–156줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 실제 운영 DB를 관찰하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C14 · 원문 157–168줄
- 문법 해부
- 157~168줄의 `원문 157–168줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `final_value=2`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `final_value=2`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 착각 방지
- `원문 157–168줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: Compose/Container와 Docker·PostgreSQL이 필요하다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C15 · 원문 169–180줄
- 문법 해부
- 169~180줄의 `원문 169–180줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `native_exits=0 cleanup=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `native_exits=0 cleanup=1`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: row-update lock 한 종류만 다룬다.
- 착각 방지
- `원문 169–180줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: row-update lock 한 종류만 다룬다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F07-C16 · 원문 181–189줄
- 문법 해부
- 181~189줄의 `원문 181–189줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `scope=ROW_UPDATE`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `scope=ROW_UPDATE`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 외부 dashboard나 pool metric을 증명하지 않는다.
- 착각 방지
- `원문 181–189줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 외부 dashboard나 pool metric을 증명하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
p766의 ACCESS EXCLUSIVE/SELECT 설명을 canonical UPDATE/UPDATE ROW_UPDATE보다 우선한다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F07-T01 observe | same row two UPDATE | pg_stat_activity | observed=1 | 실제 lock snapshot 필요 |
| F07-T02 release | holder commit | both processes exit | cleared=0, final=2 | timeout이면 실패 |
| F07-T03 close | schema/Compose/temp | finally cleanup | cleanup=1 | 운영 DB p95 미보장 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `정본 PostgreSQL row-lock wait evidence producer`이야.
-
대표 경계는 `외부 dashboard나 pool metric을 증명하지 않는다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
첫 UPDATE가 commit 전 같은 row의 둘째 UPDATE를 기다리게 한다.
p766의 stale ACCESS EXCLUSIVE/SELECT 설명이나 table lock·pool wait 전체를 대표하지 않는다.두 psql process의 exit code와 stderr를 직접 회수한다.
PowerShell cmdlet 성공만으로 native 성공이 아니다.Compose ownership에 따라 down 여부를 결정한다.
Container mode의 caller resource를 지우지 않는다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ p766의 ACCESS EXCLUSIVE/SELECT 설명을 canonical UPDATE/UPDATE ROW_UPDATE보다 우선한다
왜 틀리나 정본 PostgreSQL row-lock wait evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 disposable schema와 value=0 fixture를 만든다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `실제 운영 DB를 관찰하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ sleep만 보고 lock wait를 관찰했다고 한다
왜 틀리나 정본 PostgreSQL row-lock wait evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 holder와 waiter UPDATE를 별도 process로 시작한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `Compose/Container와 Docker·PostgreSQL이 필요하다` 상태인데도 Green을 주장하게 된다.
❌ final_value=2만 보고 대기 해제를 생략한다
왜 틀리나 정본 PostgreSQL row-lock wait evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 Lock wait와 blocking pid를 관찰한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `row-update lock 한 종류만 다룬다` 상태인데도 Green을 주장하게 된다.
❌ 실패 때 cleanup 검사를 건너뛴다
왜 틀리나 정본 PostgreSQL row-lock wait evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 wait=0·value=2·native exit=0·cleanup=1 뒤 evidence를 발행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `외부 dashboard나 pool metric을 증명하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ 격리 drill을 운영 DB 성능 증거로 부른다
왜 틀리나 정본 PostgreSQL row-lock wait evidence producer의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 wait=0·value=2·native exit=0·cleanup=1 뒤 evidence를 발행한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `실제 운영 DB를 관찰하지 않는다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
실제 운영 DB를 관찰하지 않는다
이 책임을 맡는 곳: metric/query grainCompose/Container와 Docker·PostgreSQL이 필요하다
이 책임을 맡는 곳: 별도 local/external 실행row-update lock 한 종류만 다룬다
이 책임을 맡는 곳: secret·개인정보·운영 정책외부 dashboard나 pool metric을 증명하지 않는다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: disposable PostgreSQL row lock wait를 만들고 관찰·해소·최종값·native exit·cleanup을 검증해 db-wait.json을 원자 발행하는 제공 정본 runner다.
2단계 · 코드 조각 재조립
- 원문 1–12줄
- 원문 13–24줄
- 원문 25–36줄
- 원문 37–48줄
- 원문 49–60줄
- 원문 61–72줄
- 원문 73–84줄
- 원문 85–96줄
- 원문 97–108줄
- 원문 109–120줄
- 원문 121–132줄
- 원문 133–144줄
- 원문 145–156줄
- 원문 157–168줄
- 원문 169–180줄
- 원문 181–189줄
3단계 · 파일 전체 다시 쓰기
189개 물리 줄을 원본 순서로 복원하고 SHA-256 94f195680c8a23b549bd8a4b650b63df7811ef58965ba2edce8f29691620cb81와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
원문 정본 전체 source 확인하기
param(
[ValidateSet('Compose','Container')][string]$Mode = 'Compose',
[string]$ComposeProject = 'w23-db-wait-lab',
[string]$Container = '',
[Parameter(Mandatory = $true)][string]$EvidencePath
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$evidence = if ([IO.Path]::IsPathRooted($EvidencePath)) {
[IO.Path]::GetFullPath($EvidencePath)
} else {
[IO.Path]::GetFullPath((Join-Path $root $EvidencePath))
}
$compose = Join-Path $root 'compose.yaml'
$runId = [guid]::NewGuid().ToString('N').Substring(0, 12)
$holderApp = "w23_holder_$runId"
$waiterApp = "w23_waiter_$runId"
$holderSqlInContainer = "/tmp/w23-holder-$runId.sql"
$waiterSqlInContainer = "/tmp/w23-waiter-$runId.sql"
$holderSqlLocal = Join-Path $env:TEMP "w23-holder-$runId.sql"
$waiterSqlLocal = Join-Path $env:TEMP "w23-waiter-$runId.sql"
$holderOut = Join-Path $env:TEMP "w23-holder-$runId.out"
$holderErr = Join-Path $env:TEMP "w23-holder-$runId.err"
$waiterOut = Join-Path $env:TEMP "w23-waiter-$runId.out"
$waiterErr = Join-Path $env:TEMP "w23-waiter-$runId.err"
$owned = $false
$fixtureCreated = $false
$cleanup = $false
$holderProcess = $null
$waiterProcess = $null
$failure = $null
$body = $null
function Invoke-PsqlScalar([string]$Sql) {
$previousPreference = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
$rows = @(& docker exec $Container psql -X -q -A -t -v ON_ERROR_STOP=1 -U app -d financial_core -c $Sql 2>&1)
$nativeExit = $LASTEXITCODE
} finally {
$ErrorActionPreference = $previousPreference
}
if ($nativeExit -ne 0) { throw "W23 psql failed: $($rows -join "`n")" }
$values = @($rows | ForEach-Object { ([string]$_).Trim() } | Where-Object { $_ })
if ($values.Count -lt 1) { throw 'W23 psql returned no scalar value' }
return $values[-1]
}
try {
if ($Mode -ceq 'Compose') {
if ($Container) { throw 'W23 Compose mode rejects Container' }
if ([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)) {
$env:FCL_DB_PASSWORD = 'w23-disposable-password'
}
# Mark ownership before startup so partial Compose creation is always
# torn down in finally, even when `up --wait` itself fails.
$owned = $true
& docker compose -f $compose -p $ComposeProject up -d --wait db
if ($LASTEXITCODE -ne 0) { throw "W23 compose up exit=$LASTEXITCODE" }
$Container = (& docker compose -f $compose -p $ComposeProject ps -q db).Trim()
} elseif ([string]::IsNullOrWhiteSpace($Container)) {
throw 'W23 Container mode requires Container'
}
if ([string]::IsNullOrWhiteSpace($Container)) { throw 'W23 container resolution failed' }
$setup = "DROP SCHEMA IF EXISTS w23_wait CASCADE; CREATE SCHEMA w23_wait; CREATE TABLE w23_wait.probe(id int PRIMARY KEY,value int NOT NULL); INSERT INTO w23_wait.probe VALUES(1,0);"
Invoke-PsqlScalar("$setup SELECT count(*) FROM w23_wait.probe;") | Out-Null
$fixtureCreated = $true
@"
\set ON_ERROR_STOP on
SET application_name='$holderApp';
BEGIN;
UPDATE w23_wait.probe SET value=value+1 WHERE id=1;
SELECT pg_sleep(6);
COMMIT;
"@ | Set-Content -Encoding utf8 -LiteralPath $holderSqlLocal
@"
\set ON_ERROR_STOP on
SET application_name='$waiterApp';
SET lock_timeout='10s';
UPDATE w23_wait.probe SET value=value+1 WHERE id=1;
"@ | Set-Content -Encoding utf8 -LiteralPath $waiterSqlLocal
& docker cp $holderSqlLocal "${Container}:$holderSqlInContainer" | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'W23 holder SQL copy failed' }
& docker cp $waiterSqlLocal "${Container}:$waiterSqlInContainer" | Out-Null
if ($LASTEXITCODE -ne 0) { throw 'W23 waiter SQL copy failed' }
$holderProcess = Start-Process docker -ArgumentList @(
'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$holderSqlInContainer
) -RedirectStandardOutput $holderOut -RedirectStandardError $holderErr -PassThru -WindowStyle Hidden
Start-Sleep -Seconds 1
$waiterProcess = Start-Process docker -ArgumentList @(
'exec',$Container,'psql','-X','-v','ON_ERROR_STOP=1','-U','app','-d','financial_core','-f',$waiterSqlInContainer
) -RedirectStandardOutput $waiterOut -RedirectStandardError $waiterErr -PassThru -WindowStyle Hidden
Start-Sleep -Seconds 1
$snapshotRaw = Invoke-PsqlScalar(
"SELECT wait_event_type||'|'||coalesce(wait_event,'')||'|'||cardinality(pg_blocking_pids(pid)) FROM pg_stat_activity WHERE application_name='$waiterApp' AND wait_event_type='Lock';"
)
$snapshot = $snapshotRaw.Split('|')
if ($snapshot.Count -ne 3 -or $snapshot[0] -cne 'Lock' -or [int]$snapshot[2] -lt 1) {
throw "W23 exact row-lock snapshot invalid: $snapshotRaw"
}
$observed = 1
if (!$holderProcess.WaitForExit(15000) -or !$waiterProcess.WaitForExit(15000)) {
throw 'W23 native process timeout'
}
$holderProcess.WaitForExit(); $waiterProcess.WaitForExit()
$holderProcess.Refresh(); $waiterProcess.Refresh()
$holderExit = [int]$holderProcess.ExitCode
$waiterExit = [int]$waiterProcess.ExitCode
if ($holderExit -ne 0 -or $waiterExit -ne 0) {
$errors = @(
if (Test-Path $holderErr) { Get-Content -Raw $holderErr }
if (Test-Path $waiterErr) { Get-Content -Raw $waiterErr }
) -join "`n"
throw "W23 native process failure holder=$holderExit waiter=$waiterExit`n$errors"
}
$cleared = [int](Invoke-PsqlScalar("SELECT count(*) FROM pg_stat_activity WHERE application_name IN ('$holderApp','$waiterApp') AND wait_event_type='Lock';"))
$finalValue = [int](Invoke-PsqlScalar('SELECT value FROM w23_wait.probe WHERE id=1;'))
if ($cleared -ne 0 -or $finalValue -ne 2) {
throw "W23 wait/final value mismatch cleared=$cleared value=$finalValue"
}
$body = [ordered]@{
run_id = $runId
holder_session = $holderApp
waiter_session = $waiterApp
fixture = 'w23_wait.probe(id=1,value=0)'
lock_scope = 'ROW_UPDATE'
observed = $observed
cleared = $cleared
holder_exit = $holderExit
waiter_exit = $waiterExit
native_exits = 0
final_value = $finalValue
snapshot = [ordered]@{
wait_event_type = $snapshot[0]
wait_event = $snapshot[1]
blocking_session_count = [int]$snapshot[2]
query_redacted = 'UPDATE w23_wait.probe WHERE id=?'
}
source_sha256 = (Get-FileHash -LiteralPath $MyInvocation.MyCommand.Path -Algorithm SHA256).Hash.ToLowerInvariant()
}
} catch {
$failure = $_
} finally {
foreach ($process in @($holderProcess,$waiterProcess)) {
if ($null -ne $process -and !$process.HasExited) {
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
}
}
$schemaCleanup = $true
if ($fixtureCreated -and ![string]::IsNullOrWhiteSpace($Container)) {
& docker exec $Container psql -X -q -v ON_ERROR_STOP=1 -U app -d financial_core -c 'DROP SCHEMA IF EXISTS w23_wait CASCADE;' | Out-Null
$schemaCleanup = ($LASTEXITCODE -eq 0)
& docker exec $Container rm -f $holderSqlInContainer $waiterSqlInContainer | Out-Null
}
$composeCleanup = $true
if ($owned) {
& docker compose -f $compose -p $ComposeProject down -v --remove-orphans | Out-Null
$composeCleanup = ($LASTEXITCODE -eq 0)
}
$cleanup = $schemaCleanup -and $composeCleanup
Remove-Item $holderSqlLocal,$waiterSqlLocal,$holderOut,$holderErr,$waiterOut,$waiterErr -Force -ErrorAction SilentlyContinue
}
if (!$cleanup) {
if ($null -ne $failure) {
throw "W23 DB wait failed and cleanup was not confirmed: $($failure.Exception.Message)"
}
throw 'W23 DB wait cleanup was not confirmed'
}
if ($null -ne $failure) { throw $failure }
if ($null -eq $body) { throw 'W23 DB wait produced no result' }
$body['cleanup'] = 1
New-Item -ItemType Directory -Force (Split-Path -Parent $evidence) | Out-Null
$temporary = "$evidence.tmp"
$body | ConvertTo-Json -Depth 6 | Set-Content -Encoding utf8 -LiteralPath $temporary
Move-Item -LiteralPath $temporary -Destination $evidence -Force
"W23_DB_WAIT_GREEN scope=ROW_UPDATE observed=1 cleared=0 final_value=2 native_exits=0 cleanup=1"
08D5 alerts — 5xx·p95·ledger mismatch 세 경보의 조건과 사람 대응
illustrative/markdown/W23-D5-alerts.md
학습용 예시 · alert/runbook template · 정본 답안 아님 · 외부 미실행 · 학습용 예시 · 정본 답안 아님 · W23-F089줄 연결9줄 번역2 chunks
D5 alerts — 5xx·p95·ledger mismatch 세 경보의 조건과 사람 대응
illustrative/markdown/W23-D5-alerts.md
학습용 예시 · alert/runbook template · 정본 답안 아님 · 외부 미실행 · 학습용 예시 · 정본 답안 아님 · W23-F08STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
- `alerts=3`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `notification URL·시각·screenshot hash 없이는 외부 실행이 아니다` 경계와 어떻게 연결되는가?
alerts=35xx_rate>2%p95>1000msmismatch_count>0NOT_RUN_EXTERNALSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
D5 alerts — 5xx·p95·ledger mismatch 세 경보의 조건과 사람 대응 ― 관측 증거 흐름으로 바꾸기
High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
핵심값 alerts=3, 5xx_rate>2%, p95>1000ms, mismatch_count>0, NOT_RUN_EXTERNAL을 원본 줄로 따라가되, `notification URL·시각·screenshot hash 없이는 외부 실행이 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
- 코드 연결
1~10줄- 비유
- 화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표
- 비유의 끝
- notification URL·시각·screenshot hash 없이는 외부 실행이 아니다
원문 11–11줄
11~11줄을 한 덩어리로 읽어 2번째 움직임을 본다. High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
- 코드 연결
11~11줄- 비유
- 화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표
- 비유의 끝
- 학습 threshold는 운영 SLA가 아니다
문제의 첫 장면
-
첫 고정값은 `alerts=3` 맞아?
-
High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
-
원문에서 `alerts=3`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `notification URL·시각·screenshot hash 없이는 외부 실행이 아니다`도 같이 적어.
입력에서 결과까지
-
`5xx_rate>2%`은 언제 생겨?
-
5xx rate와 업무 409를 분리한다. → p95와 pool/DB wait를 함께 진단한다.
-
mismatch>0이면 재처리를 멈춘다. → clear 조건과 외부 evidence를 사람이 확인한다.
-
관찰값과 `학습 threshold는 운영 SLA가 아니다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 9줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F08-L01 | # W23 alert 3종 학습용 예시 - 사람 검토 전 Green 아님 |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 3줄F08-L03 | | 이름 | 조건 | window | severity | owner | first query/ |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 비정본 alert 조건·owner·clear 학습 template의 3번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 4줄F08-L04 | |---|---|---|---|---|---|---|---| |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 비정본 alert 조건·owner·clear 학습 template의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 5줄F08-L05 | | High5xx | 5xx_rate > 2% | 5분 지속 | SEV2 | API on-call | requestId sample과 route/ |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 업무 거절을 제외한 5xx 비율의 지속 조건·owner·clear 계약을 한 행에 묶는다.
|
| 6줄F08-L06 | | SlowTransfer | p95 > 1000ms | 10분 지속 | SEV2 | transfer on-call | pool pending과 DB lock wait를 분리 확인 | runbooks/ |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | p95 지연과 pool pending·DB wait 분리 진단을 연결하는 학습 alert다.
|
| 7줄F08-L07 | | LedgerMismatch | mismatch_count > 0 | 대사 1회 | SEV1 | finance ops | 재처리 중지, |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 불일치가 하나라도 있으면 재처리를 멈추고 finance 승인을 요구하는 alert다.
|
| 9줄F08-L09 | - 업무 409 거절은 5xx 장애율에 합산하지 않는다. |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 비정본 alert 조건·owner·clear 학습 template의 9번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 10줄F08-L10 | - notification URL, |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | URL·관찰시각·screenshot hash가 없어 외부 실행을 아직 주장하지 않는 정확한 상태다.
|
| 11줄F08-L11 | - threshold는 학습 baseline이며 운영 SLA라고 주장하지 않는다. |
화재·느림·원장 불일치 세 경보마다 울리는 조건, 기다릴 시간, 담당자, 첫 행동, 해제 조건을 한 줄씩 붙인 비상벨 표 | 비정본 alert 조건·owner·clear 학습 template의 11번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `NOT_RUN_EXTERNAL`이야.
-
`업무 409를 5xx에 합산하지 않는다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 2개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
# W23 alert 3종 학습용 예시 - 사람 검토 전 Green 아님
| 이름 | 조건 | window | severity | owner | first query/action | runbook | clear |
|---|---|---|---|---|---|---|---|
| High5xx | 5xx_rate > 2% | 5분 지속 | SEV2 | API on-call | requestId sample과 route/status 확인 | runbooks/db-timeout.md | 10분간 1% 미만 |
| SlowTransfer | p95 > 1000ms | 10분 지속 | SEV2 | transfer on-call | pool pending과 DB lock wait를 분리 확인 | runbooks/db-timeout.md | 10분간 p95 < 500ms |
| LedgerMismatch | mismatch_count > 0 | 대사 1회 | SEV1 | finance ops | 재처리 중지, 영향 계좌 격리, source 대조 | runbooks/ledger-mismatch.md | 승인된 correction 뒤 mismatch 0 |
- 업무 409 거절은 5xx 장애율에 합산하지 않는다.
- notification URL, 실행시각, screenshot SHA-256이 없으면 `NOT_RUN_EXTERNAL`이다.
- threshold는 학습 baseline이며 운영 SLA라고 주장하지 않는다.
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 9줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | # W23 alert 3종 학습용 예시 - 사람 검토 전 Green 아님 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 3 | | 이름 | 조건 | window | severity | owner | first query/action | runbook | clear | | 비정본 alert 조건·owner·clear 학습 template의 3번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 4 | |---|---|---|---|---|---|---|---| | 비정본 alert 조건·owner·clear 학습 template의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 5 | | High5xx | 5xx_rate > 2% | 5분 지속 | SEV2 | API on-call | requestId sample과 route/status 확인 | runbooks/db-timeout.md | 10분간 1% 미만 | | 업무 거절을 제외한 5xx 비율의 지속 조건·owner·clear 계약을 한 행에 묶는다. |
| 6 | | SlowTransfer | p95 > 1000ms | 10분 지속 | SEV2 | transfer on-call | pool pending과 DB lock wait를 분리 확인 | runbooks/db-timeout.md | 10분간 p95 < 500ms | | p95 지연과 pool pending·DB wait 분리 진단을 연결하는 학습 alert다. |
| 7 | | LedgerMismatch | mismatch_count > 0 | 대사 1회 | SEV1 | finance ops | 재처리 중지, 영향 계좌 격리, source 대조 | runbooks/ledger-mismatch.md | 승인된 correction 뒤 mismatch 0 | | 불일치가 하나라도 있으면 재처리를 멈추고 finance 승인을 요구하는 alert다. |
| 9 | - 업무 409 거절은 5xx 장애율에 합산하지 않는다. | 비정본 alert 조건·owner·clear 학습 template의 9번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 10 | - notification URL, 실행시각, screenshot SHA-256이 없으면 `NOT_RUN_EXTERNAL`이다. | URL·관찰시각·screenshot hash가 없어 외부 실행을 아직 주장하지 않는 정확한 상태다. |
| 11 | - threshold는 학습 baseline이며 운영 SLA라고 주장하지 않는다. | 비정본 alert 조건·owner·clear 학습 template의 11번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다. 다만 notification URL·시각·screenshot hash 없이는 외부 실행이 아니다
문법 해부
- Markdown 표의 한 행이 condition·window·severity·owner·action·runbook·clear를 한 alert 계약으로 묶는다.
- notification URL·observed_at·screenshot hash가 없으면 표를 작성해도 `NOT_RUN_EXTERNAL`이다.
실행 순서
- 5xx rate와 업무 409를 분리한다.
- p95와 pool/DB wait를 함께 진단한다.
- mismatch>0이면 재처리를 멈춘다.
- clear 조건과 외부 evidence를 사람이 확인한다.
W23 조각별 정밀 해설
F08-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `alerts=3`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `alerts=3`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: notification URL·시각·screenshot hash 없이는 외부 실행이 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: notification URL·시각·screenshot hash 없이는 외부 실행이 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F08-C02 · 원문 11–11줄
- 문법 해부
- 11~11줄의 `원문 11–11줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `5xx_rate>2%`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `5xx_rate>2%`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 학습 threshold는 운영 SLA가 아니다.
- 착각 방지
- `원문 11–11줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 학습 threshold는 운영 SLA가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
업무 409를 5xx 분자에 넣는다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F08-T01 High5xx | 5xx_rate >2% | 5분 | SEV2 | 409 제외 |
| F08-T02 SlowTransfer | p95 >1000ms | 10분 | SEV2 | pool과 DB wait 분리 |
| F08-T03 LedgerMismatch | count >0 | one reconciliation | SEV1 | 승인 correction 필요 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `비정본 alert 조건·owner·clear 학습 template`이야.
-
대표 경계는 `문서 작성만으로 alert delivery가 증명되지 않는다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
순간 spike가 아니라 지속 조건을 평가한다.
학습 threshold가 실제 SLA는 아니다.조건 충족 event를 on-call에게 전달한다.
URL·시각·screenshot이 없으면 미실행이다.필수 열·행 수·파일 hash 같은 shape를 검사할 수 있다.
threshold·owner·첫 행동의 운영 의미까지 자동 검증하지 않는다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ 업무 409를 5xx 분자에 넣는다
왜 틀리나 비정본 alert 조건·owner·clear 학습 template의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 5xx rate와 업무 409를 분리한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `notification URL·시각·screenshot hash 없이는 외부 실행이 아니다` 상태인데도 Green을 주장하게 된다.
❌ p95 alert만 보고 DB lock이라고 단정한다
왜 틀리나 비정본 alert 조건·owner·clear 학습 template의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 p95와 pool/DB wait를 함께 진단한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `학습 threshold는 운영 SLA가 아니다` 상태인데도 Green을 주장하게 된다.
❌ mismatch 경보 뒤 자동 balance UPDATE를 한다
왜 틀리나 비정본 alert 조건·owner·clear 학습 template의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 mismatch>0이면 재처리를 멈춘다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `업무 409를 5xx에 합산하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ clear 조건 없이 alert를 닫는다
왜 틀리나 비정본 alert 조건·owner·clear 학습 template의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 clear 조건과 외부 evidence를 사람이 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `문서 작성만으로 alert delivery가 증명되지 않는다` 상태인데도 Green을 주장하게 된다.
❌ template을 실제 notification Green이라 부른다
왜 틀리나 비정본 alert 조건·owner·clear 학습 template의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 clear 조건과 외부 evidence를 사람이 확인한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `notification URL·시각·screenshot hash 없이는 외부 실행이 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
notification URL·시각·screenshot hash 없이는 외부 실행이 아니다
이 책임을 맡는 곳: metric/query grain학습 threshold는 운영 SLA가 아니다
이 책임을 맡는 곳: 별도 local/external 실행업무 409를 5xx에 합산하지 않는다
이 책임을 맡는 곳: secret·개인정보·운영 정책문서 작성만으로 alert delivery가 증명되지 않는다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: High5xx·SlowTransfer·LedgerMismatch 세 알림의 조건·window·severity·owner·action·runbook·clear를 적는 학습 template이다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–11줄
3단계 · 파일 전체 다시 쓰기
11개 물리 줄을 원본 순서로 복원하고 SHA-256 b06540f4b92c2e3e36a910ac2759fadfd6f1e8c202c9d134894dee2af872141f와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
# W23 alert 3종 학습용 예시 - 사람 검토 전 Green 아님
| 이름 | 조건 | window | severity | owner | first query/action | runbook | clear |
|---|---|---|---|---|---|---|---|
| High5xx | 5xx_rate > 2% | 5분 지속 | SEV2 | API on-call | requestId sample과 route/status 확인 | runbooks/db-timeout.md | 10분간 1% 미만 |
| SlowTransfer | p95 > 1000ms | 10분 지속 | SEV2 | transfer on-call | pool pending과 DB lock wait를 분리 확인 | runbooks/db-timeout.md | 10분간 p95 < 500ms |
| LedgerMismatch | mismatch_count > 0 | 대사 1회 | SEV1 | finance ops | 재처리 중지, 영향 계좌 격리, source 대조 | runbooks/ledger-mismatch.md | 승인된 correction 뒤 mismatch 0 |
- 업무 409 거절은 5xx 장애율에 합산하지 않는다.
- notification URL, 실행시각, screenshot SHA-256이 없으면 `NOT_RUN_EXTERNAL`이다.
- threshold는 학습 baseline이며 운영 SLA라고 주장하지 않는다.
09W23-SQL-Q38 — 두 반개구간 모두 거래한 고객을 INTERSECT로 찾기
illustrative/sql/W23-SQL-Q38.sql
학습용 예시 · Q37/Q38 prompt 기반 SQL · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F0930줄 연결30줄 번역4 chunks
W23-SQL-Q38 — 두 반개구간 모두 거래한 고객을 INTERSECT로 찾기
illustrative/sql/W23-SQL-Q38.sql
학습용 예시 · Q37/Q38 prompt 기반 SQL · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W23-F09STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
- `status=SUCCESS`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `workbook 제공 정답이 아니다` 경계와 어떻게 연결되는가?
status=SUCCESShalf-open periodsDISTINCTINTERSECTcustomer_id=1 김민수STEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
W23-SQL-Q38 — 두 반개구간 모두 거래한 고객을 INTERSECT로 찾기 ― 관측 증거 흐름으로 바꾸기
두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
핵심값 status=SUCCESS, half-open periods, DISTINCT, INTERSECT, customer_id=1 김민수을 원본 줄로 따라가되, `workbook 제공 정답이 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. 두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
- 코드 연결
1~10줄- 비유
- 여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사
- 비유의 끝
- workbook 제공 정답이 아니다
원문 11–20줄
11~20줄을 한 덩어리로 읽어 2번째 움직임을 본다. 두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
- 코드 연결
11~20줄- 비유
- 여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사
- 비유의 끝
- 기간·timezone·status는 명시한 학습 가정이다
원문 21–30줄
21~30줄을 한 덩어리로 읽어 3번째 움직임을 본다. 두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
- 코드 연결
21~30줄- 비유
- 여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사
- 비유의 끝
- 여러 거래를 고객 중복 행으로 내지 않는다
문제의 첫 장면
-
첫 고정값은 `status=SUCCESS` 맞아?
-
두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
-
원문에서 `status=SUCCESS`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `workbook 제공 정답이 아니다`도 같이 적어.
입력에서 결과까지
-
`half-open periods`은 언제 생겨?
-
period A의 성공 고객 집합을 만든다. → period B의 성공 고객 집합을 만든다.
-
INTERSECT로 공통 customer_id를 구한다. → customer를 JOIN하고 stable order로 출력한다.
-
관찰값과 `기간·timezone·status는 명시한 학습 가정이다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 30줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F09-L01 | -- 학습용 예시 · 정본 답안 아님 |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 2줄F09-L02 | -- 가정: |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 3줄F09-L03 | -- 입력 grain: |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 5줄F09-L05 | WITH period_a AS ( |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 첫 반개구간의 SUCCESS 거래 고객 집합을 만드는 CTE를 연다.
|
| 6줄F09-L06 | SELECT DISTINCT a. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 7줄F09-L07 | FROM business_tx AS tx |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 8줄F09-L08 | JOIN account AS a ON a. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 9줄F09-L09 | WHERE tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 10줄F09-L10 | AND tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다.
|
| 11줄F09-L11 | AND tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다.
|
| 12줄F09-L12 | ), |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 비정본 Q38 두 기간 교집합 SQL의 12번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 13줄F09-L13 | period_b AS ( |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 둘째 반개구간의 SUCCESS 거래 고객 집합을 만든다.
|
| 14줄F09-L14 | SELECT DISTINCT a. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 15줄F09-L15 | FROM business_tx AS tx |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 16줄F09-L16 | JOIN account AS a ON a. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 17줄F09-L17 | WHERE tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 18줄F09-L18 | AND tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다.
|
| 19줄F09-L19 | AND tx. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다.
|
| 20줄F09-L20 | ), |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 비정본 Q38 두 기간 교집합 SQL의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 21줄F09-L21 | both_periods AS ( |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 비정본 Q38 두 기간 교집합 SQL의 21번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 22줄F09-L22 | SELECT customer_id FROM period_a |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 23줄F09-L23 | INTERSECT |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 두 기간에 모두 존재하는 customer_id만 중복 없이 남긴다.
|
| 24줄F09-L24 | SELECT customer_id FROM period_b |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 둘째 반개구간의 SUCCESS 거래 고객 집합을 만든다.
|
| 25줄F09-L25 | ) |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 26줄F09-L26 | SELECT c. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 27줄F09-L27 | FROM both_periods AS b |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 28줄F09-L28 | JOIN customer AS c ON c. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 29줄F09-L29 | ORDER BY c. |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다.
|
| 31줄F09-L31 | -- workbook fixture 예상: |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 32줄F09-L32 | -- DISTINCT/ |
여름 출석 명단과 겨울 출석 명단을 각각 중복 없이 만든 뒤, 두 명단에 모두 있는 사람만 남기는 교집합 검사 | 두 기간에 모두 존재하는 customer_id만 중복 없이 남긴다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `customer_id=1 김민수`이야.
-
`여러 거래를 고객 중복 행으로 내지 않는다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 4개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
-- 학습용 예시 · 정본 답안 아님
-- 가정: 거래는 status='SUCCESS'이며 두 기간은 [start, end) 반개구간이다.
-- 입력 grain: business_tx 1행/거래. 출력 grain: customer 1행.
WITH period_a AS (
SELECT DISTINCT a.customer_id
FROM business_tx AS tx
JOIN account AS a ON a.account_id = tx.account_id
WHERE tx.status = 'SUCCESS'
AND tx.occurred_at >= TIMESTAMPTZ '2026-06-30 00:00:00+09'
AND tx.occurred_at < TIMESTAMPTZ '2026-07-03 00:00:00+09'
),
period_b AS (
SELECT DISTINCT a.customer_id
FROM business_tx AS tx
JOIN account AS a ON a.account_id = tx.account_id
WHERE tx.status = 'SUCCESS'
AND tx.occurred_at >= TIMESTAMPTZ '2026-12-28 00:00:00+09'
AND tx.occurred_at < TIMESTAMPTZ '2027-01-03 00:00:00+09'
),
both_periods AS (
SELECT customer_id FROM period_a
INTERSECT
SELECT customer_id FROM period_b
)
SELECT c.customer_id, c.customer_name
FROM both_periods AS b
JOIN customer AS c ON c.customer_id = b.customer_id
ORDER BY c.customer_id;
-- workbook fixture 예상: customer_id=1, 김민수 한 행.
-- DISTINCT/INTERSECT가 없으면 같은 고객의 여러 거래 때문에 중복될 수 있다.
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 30줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | -- 학습용 예시 · 정본 답안 아님 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 2 | -- 가정: 거래는 status='SUCCESS'이며 두 기간은 [start, end) 반개구간이다. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 3 | -- 입력 grain: business_tx 1행/거래. 출력 grain: customer 1행. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 5 | WITH period_a AS ( | 첫 반개구간의 SUCCESS 거래 고객 집합을 만드는 CTE를 연다. |
| 6 | SELECT DISTINCT a.customer_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 7 | FROM business_tx AS tx | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 8 | JOIN account AS a ON a.account_id = tx.account_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 9 | WHERE tx.status = 'SUCCESS' | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 10 | AND tx.occurred_at >= TIMESTAMPTZ '2026-06-30 00:00:00+09' | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다. |
| 11 | AND tx.occurred_at < TIMESTAMPTZ '2026-07-03 00:00:00+09' | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다. |
| 12 | ), | 비정본 Q38 두 기간 교집합 SQL의 12번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 13 | period_b AS ( | 둘째 반개구간의 SUCCESS 거래 고객 집합을 만든다. |
| 14 | SELECT DISTINCT a.customer_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 15 | FROM business_tx AS tx | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 16 | JOIN account AS a ON a.account_id = tx.account_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 17 | WHERE tx.status = 'SUCCESS' | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 18 | AND tx.occurred_at >= TIMESTAMPTZ '2026-12-28 00:00:00+09' | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다. |
| 19 | AND tx.occurred_at < TIMESTAMPTZ '2027-01-03 00:00:00+09' | 기간 경계를 명시적 +09 timezone의 timestamp로 고정한다. |
| 20 | ), | 비정본 Q38 두 기간 교집합 SQL의 20번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 21 | both_periods AS ( | 비정본 Q38 두 기간 교집합 SQL의 21번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 22 | SELECT customer_id FROM period_a | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 23 | INTERSECT | 두 기간에 모두 존재하는 customer_id만 중복 없이 남긴다. |
| 24 | SELECT customer_id FROM period_b | 둘째 반개구간의 SUCCESS 거래 고객 집합을 만든다. |
| 25 | ) | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 26 | SELECT c.customer_id, c.customer_name | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 27 | FROM both_periods AS b | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 28 | JOIN customer AS c ON c.customer_id = b.customer_id | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 29 | ORDER BY c.customer_id; | SQL의 입력 grain·연결·필터·집계·stable 출력 순서를 구성하는 절이다. |
| 31 | -- workbook fixture 예상: customer_id=1, 김민수 한 행. | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 32 | -- DISTINCT/INTERSECT가 없으면 같은 고객의 여러 거래 때문에 중복될 수 있다. | 두 기간에 모두 존재하는 customer_id만 중복 없이 남긴다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다. 다만 workbook 제공 정답이 아니다
문법 해부
- 각 period CTE는 SUCCESS 거래를 반개구간 `[start,end)`로 거른 뒤 customer_id를 DISTINCT한다.
- `INTERSECT`가 두 customer 집합의 공통 원소만 남기고 마지막 JOIN이 이름을 붙인다.
실행 순서
- period A의 성공 고객 집합을 만든다.
- period B의 성공 고객 집합을 만든다.
- INTERSECT로 공통 customer_id를 구한다.
- customer를 JOIN하고 stable order로 출력한다.
W23 조각별 정밀 해설
F09-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `status=SUCCESS`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `status=SUCCESS`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: workbook 제공 정답이 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: workbook 제공 정답이 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F09-C02 · 원문 11–20줄
- 문법 해부
- 11~20줄의 `원문 11–20줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `half-open periods`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `half-open periods`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 기간·timezone·status는 명시한 학습 가정이다.
- 착각 방지
- `원문 11–20줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 기간·timezone·status는 명시한 학습 가정이다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F09-C03 · 원문 21–30줄
- 문법 해부
- 21~30줄의 `원문 21–30줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `DISTINCT`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `DISTINCT`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 여러 거래를 고객 중복 행으로 내지 않는다.
- 착각 방지
- `원문 21–30줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 여러 거래를 고객 중복 행으로 내지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F09-C04 · 원문 31–32줄
- 문법 해부
- 31~32줄의 `원문 31–32줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `INTERSECT`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `INTERSECT`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: fixture 예상은 실제 query 실행 결과가 아니다.
- 착각 방지
- `원문 31–32줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: fixture 예상은 실제 query 실행 결과가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
OR로 두 기간 중 하나만 거래한 고객도 포함한다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F09-T01 A | 2026-06-30≤t<2026-07-03 | DISTINCT | customer set A | KST 가정 |
| F09-T02 B | 2026-12-28≤t<2027-01-03 | DISTINCT | customer set B | SUCCESS만 |
| F09-T03 both | A and B | INTERSECT | fixture customer 1 김민수 | prompt-only oracle |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `비정본 Q38 두 기간 교집합 SQL`이야.
-
대표 경계는 `fixture 예상은 실제 query 실행 결과가 아니다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
끝 시각을 다음 구간에 한 번만 포함하게 한다.
timezone 정책은 명시한 +09 가정에 의존한다.한 기간의 여러 거래를 고객 한 값으로 줄인다.
거래 금액·횟수는 보존하지 않는다.두 집합 모두에 존재하는 값을 중복 없이 반환한다.
기간 중 하나만 거래한 고객은 제외한다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ OR로 두 기간 중 하나만 거래한 고객도 포함한다
왜 틀리나 비정본 Q38 두 기간 교집합 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 period A의 성공 고객 집합을 만든다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `workbook 제공 정답이 아니다` 상태인데도 Green을 주장하게 된다.
❌ BETWEEN으로 끝 경계를 중복 포함한다
왜 틀리나 비정본 Q38 두 기간 교집합 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 period B의 성공 고객 집합을 만든다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `기간·timezone·status는 명시한 학습 가정이다` 상태인데도 Green을 주장하게 된다.
❌ DISTINCT 없이 고객 중복을 그대로 둔다
왜 틀리나 비정본 Q38 두 기간 교집합 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 INTERSECT로 공통 customer_id를 구한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `여러 거래를 고객 중복 행으로 내지 않는다` 상태인데도 Green을 주장하게 된다.
❌ account_id를 customer_id처럼 비교한다
왜 틀리나 비정본 Q38 두 기간 교집합 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 customer를 JOIN하고 stable order로 출력한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `fixture 예상은 실제 query 실행 결과가 아니다` 상태인데도 Green을 주장하게 된다.
❌ Q38 fixture 예상을 운영 DB 결과로 부른다
왜 틀리나 비정본 Q38 두 기간 교집합 SQL의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 customer를 JOIN하고 stable order로 출력한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `workbook 제공 정답이 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
workbook 제공 정답이 아니다
이 책임을 맡는 곳: metric/query grain기간·timezone·status는 명시한 학습 가정이다
이 책임을 맡는 곳: 별도 local/external 실행여러 거래를 고객 중복 행으로 내지 않는다
이 책임을 맡는 곳: secret·개인정보·운영 정책fixture 예상은 실제 query 실행 결과가 아니다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: 두 반개구간 모두에서 성공 거래가 있는 고객을 DISTINCT와 INTERSECT로 한 번만 반환하는 비정본 Q38 예시다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–20줄
- 원문 21–30줄
- 원문 31–32줄
3단계 · 파일 전체 다시 쓰기
32개 물리 줄을 원본 순서로 복원하고 SHA-256 272af1c9a1014ea94ed5071c2015154c1f25ded763bd4b9d6f2c0a8816c2f8fe와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
-- 학습용 예시 · 정본 답안 아님
-- 가정: 거래는 status='SUCCESS'이며 두 기간은 [start, end) 반개구간이다.
-- 입력 grain: business_tx 1행/거래. 출력 grain: customer 1행.
WITH period_a AS (
SELECT DISTINCT a.customer_id
FROM business_tx AS tx
JOIN account AS a ON a.account_id = tx.account_id
WHERE tx.status = 'SUCCESS'
AND tx.occurred_at >= TIMESTAMPTZ '2026-06-30 00:00:00+09'
AND tx.occurred_at < TIMESTAMPTZ '2026-07-03 00:00:00+09'
),
period_b AS (
SELECT DISTINCT a.customer_id
FROM business_tx AS tx
JOIN account AS a ON a.account_id = tx.account_id
WHERE tx.status = 'SUCCESS'
AND tx.occurred_at >= TIMESTAMPTZ '2026-12-28 00:00:00+09'
AND tx.occurred_at < TIMESTAMPTZ '2027-01-03 00:00:00+09'
),
both_periods AS (
SELECT customer_id FROM period_a
INTERSECT
SELECT customer_id FROM period_b
)
SELECT c.customer_id, c.customer_name
FROM both_periods AS b
JOIN customer AS c ON c.customer_id = b.customer_id
ORDER BY c.customer_id;
-- workbook fixture 예상: customer_id=1, 김민수 한 행.
-- DISTINCT/INTERSECT가 없으면 같은 고객의 여러 거래 때문에 중복될 수 있다.
10D6 runbooks index — DB timeout과 ledger mismatch의 멈춤·확인·escalation 순서
illustrative/markdown/W23-D6-runbooks-index.md
학습용 예시 · alert/runbook template · 정본 답안 아님 · 외부 미실행 · 학습용 예시 · 정본 답안 아님 · W23-F1016줄 연결16줄 번역2 chunks
D6 runbooks index — DB timeout과 ledger mismatch의 멈춤·확인·escalation 순서
illustrative/markdown/W23-D6-runbooks-index.md
학습용 예시 · alert/runbook template · 정본 답안 아님 · 외부 미실행 · 학습용 예시 · 정본 답안 아님 · W23-F10STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
- `DB timeout`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `외부 fault injection을 수행한 증거가 아니다` 경계와 어떻게 연결되는가?
DB timeoutLedger mismatchrequestIdmismatch=0HUMAN_SEMANTIC_REVIEW_REQUIREDSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
D6 runbooks index — DB timeout과 ledger mismatch의 멈춤·확인·escalation 순서 ― 관측 증거 흐름으로 바꾸기
DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
핵심값 DB timeout, Ledger mismatch, requestId, mismatch=0, HUMAN_SEMANTIC_REVIEW_REQUIRED을 원본 줄로 따라가되, `외부 fault injection을 수행한 증거가 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
- 코드 연결
1~10줄- 비유
- DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판
- 비유의 끝
- 외부 fault injection을 수행한 증거가 아니다
원문 11–19줄
11~19줄을 한 덩어리로 읽어 2번째 움직임을 본다. DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
- 코드 연결
11~19줄- 비유
- DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판
- 비유의 끝
- 무제한 retry나 즉시 balance UPDATE를 금지한다
문제의 첫 장면
-
첫 고정값은 `DB timeout` 맞아?
-
DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
-
원문에서 `DB timeout`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `외부 fault injection을 수행한 증거가 아니다`도 같이 적어.
입력에서 결과까지
-
`Ledger mismatch`은 언제 생겨?
-
증상·영향·requestId 또는 mismatch row를 보존한다. → 무제한 retry·자동 재처리를 멈춘다.
-
정해진 순서로 진단하고 승인된 조치만 한다. → wait=0 또는 mismatch=0과 timeline을 남긴다.
-
관찰값과 `무제한 retry나 즉시 balance UPDATE를 금지한다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 16줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F10-L01 | # W23 incident runbook index - 학습용 예시 |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 3줄F10-L03 | ## DB timeout |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 4줄F10-L04 | 1. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 5줄F10-L05 | 2. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 5번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 6줄F10-L06 | 3. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 7줄F10-L07 | 4. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 7번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 8줄F10-L08 | 5. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index에서 `5. API sample과 DB wait=0을 함께 확인한다.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 9줄F10-L09 | 6. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 9번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 11줄F10-L11 | ## Ledger mismatch |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다.
|
| 12줄F10-L12 | 1. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 12번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 13줄F10-L13 | 2. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 13번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 14줄F10-L14 | 3. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 14번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 15줄F10-L15 | 4. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 16줄F10-L16 | 5. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index에서 `5. 대사 mismatch=0과 고객 응답을 다시 확인한다.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 17줄F10-L17 | 6. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | 비정본 incident runbook index의 17번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다.
|
| 19줄F10-L19 | 외부 fault injection과 incident timeline이 없으면 `HUMAN_SEMANTIC_REVIEW_REQUIRED`다. |
DB가 느릴 때와 장부가 어긋날 때 서로 다른 비상 카드를 꺼내, 먼저 피해를 멈추고 좁은 범위에서 확인한 뒤 담당 승인으로 복구하는 안내판 | fault injection과 incident timeline을 사람이 검토해야 closure되는 경계다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `HUMAN_SEMANTIC_REVIEW_REQUIRED`이야.
-
`correction은 finance 승인이 필요하다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 2개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
# W23 incident runbook index - 학습용 예시
## DB timeout
1. 증상과 고객 영향, 최초 requestId와 발생시각을 보존한다.
2. 무제한 retry와 자동 재처리를 중지한다.
3. readiness -> pool pending -> network -> PostgreSQL wait 순서로 확인한다.
4. 승인된 범위에서만 timeout 완화 또는 traffic 축소를 수행한다.
5. API sample과 DB wait=0을 함께 확인한다.
6. rollback 기준, escalation owner, 명령 결과와 timeline을 남긴다.
## Ledger mismatch
1. mismatch row와 영향 계좌를 격리하고 원본 evidence를 보존한다.
2. balance를 즉시 UPDATE하거나 자동 재처리하지 않는다.
3. ledger pair, business_tx, idempotency state, source event를 대조한다.
4. finance 승인 뒤 correction entry만 추가한다.
5. 대사 mismatch=0과 고객 응답을 다시 확인한다.
6. 승인자, correction 근거, rollback 및 후속조치를 기록한다.
외부 fault injection과 incident timeline이 없으면 `HUMAN_SEMANTIC_REVIEW_REQUIRED`다.
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 16줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | # W23 incident runbook index - 학습용 예시 | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 3 | ## DB timeout | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 4 | 1. 증상과 고객 영향, 최초 requestId와 발생시각을 보존한다. | 비정본 incident runbook index의 4번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 5 | 2. 무제한 retry와 자동 재처리를 중지한다. | 비정본 incident runbook index의 5번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 6 | 3. readiness -> pool pending -> network -> PostgreSQL wait 순서로 확인한다. | 비정본 incident runbook index의 6번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 7 | 4. 승인된 범위에서만 timeout 완화 또는 traffic 축소를 수행한다. | 비정본 incident runbook index의 7번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 8 | 5. API sample과 DB wait=0을 함께 확인한다. | 비정본 incident runbook index에서 `5. API sample과 DB wait=0을 함께 확인한다.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 9 | 6. rollback 기준, escalation owner, 명령 결과와 timeline을 남긴다. | 비정본 incident runbook index의 9번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 11 | ## Ledger mismatch | 이 source의 provenance·grain·가정·반례·실행 경계를 사람이 확인할 설명 줄이다. |
| 12 | 1. mismatch row와 영향 계좌를 격리하고 원본 evidence를 보존한다. | 비정본 incident runbook index의 12번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 13 | 2. balance를 즉시 UPDATE하거나 자동 재처리하지 않는다. | 비정본 incident runbook index의 13번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 14 | 3. ledger pair, business_tx, idempotency state, source event를 대조한다. | 비정본 incident runbook index의 14번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 15 | 4. finance 승인 뒤 correction entry만 추가한다. | 비정본 incident runbook index의 15번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 16 | 5. 대사 mismatch=0과 고객 응답을 다시 확인한다. | 비정본 incident runbook index에서 `5. 대사 mismatch=0과 고객 응답을 다시 확인한다.` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 17 | 6. 승인자, correction 근거, rollback 및 후속조치를 기록한다. | 비정본 incident runbook index의 17번째 원문을 앞 입력과 뒤 관찰·책임 경계 사이에 연결한다. |
| 19 | 외부 fault injection과 incident timeline이 없으면 `HUMAN_SEMANTIC_REVIEW_REQUIRED`다. | fault injection과 incident timeline을 사람이 검토해야 closure되는 경계다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다. 다만 외부 fault injection을 수행한 증거가 아니다
문법 해부
- 두 `##` heading이 DB timeout과 Ledger mismatch의 서로 다른 대응 책임을 분리한다. 이 D6 index는 F04의 root incident-runbook.md와 별도 학습 파일이다.
- 각 번호 목록은 preserve/stop→diagnose→approved action→verify→record 순서로 읽는다. validator의 shape/hash PASS는 절차 의미 PASS가 아니다.
실행 순서
- 증상·영향·requestId 또는 mismatch row를 보존한다.
- 무제한 retry·자동 재처리를 멈춘다.
- 정해진 순서로 진단하고 승인된 조치만 한다.
- wait=0 또는 mismatch=0과 timeline을 남긴다.
W23 조각별 정밀 해설
F10-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `DB timeout`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `DB timeout`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 외부 fault injection을 수행한 증거가 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 외부 fault injection을 수행한 증거가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F10-C02 · 원문 11–19줄
- 문법 해부
- 11~19줄의 `원문 11–19줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `Ledger mismatch`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `Ledger mismatch`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 무제한 retry나 즉시 balance UPDATE를 금지한다.
- 착각 방지
- `원문 11–19줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 무제한 retry나 즉시 balance UPDATE를 금지한다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
timeout이면 즉시 retry 횟수를 무제한으로 늘린다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F10-T01 DB timeout | requestId+time | readiness→pool→network→wait | narrow cause | 무제한 retry 금지 |
| F10-T02 ledger | mismatch rows | ledger/tx/idempotency/source 대조 | approved correction | 직접 UPDATE 금지 |
| F10-T03 closure | API+DB or reconciliation | double check | timeline | fault injection 미실행 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `비정본 incident runbook index`이야.
-
대표 경계는 `실제 incident timeline은 사람이 검토해야 한다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
고객 영향과 증거를 먼저 보존한다.
원인 확정 전 임의 수정은 금지한다.승인된 범위의 traffic 축소·correction entry를 사용한다.
운영 권한과 승인자는 별도다.heading·단계 수·hash 같은 shape를 자동 대조한다.
root runbook과 D6 index는 distinct이며 절차의 안전성·timeline은 사람 검토가 남는다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ timeout이면 즉시 retry 횟수를 무제한으로 늘린다
왜 틀리나 비정본 incident runbook index의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 증상·영향·requestId 또는 mismatch row를 보존한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `외부 fault injection을 수행한 증거가 아니다` 상태인데도 Green을 주장하게 된다.
❌ pool pending을 곧바로 DB row lock이라고 부른다
왜 틀리나 비정본 incident runbook index의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 무제한 retry·자동 재처리를 멈춘다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `무제한 retry나 즉시 balance UPDATE를 금지한다` 상태인데도 Green을 주장하게 된다.
❌ mismatch balance를 직접 UPDATE한다
왜 틀리나 비정본 incident runbook index의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 정해진 순서로 진단하고 승인된 조치만 한다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `correction은 finance 승인이 필요하다` 상태인데도 Green을 주장하게 된다.
❌ 승인 전 자동 재처리한다
왜 틀리나 비정본 incident runbook index의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 wait=0 또는 mismatch=0과 timeline을 남긴다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `실제 incident timeline은 사람이 검토해야 한다` 상태인데도 Green을 주장하게 된다.
❌ index 문서를 실제 incident timeline이라 부른다
왜 틀리나 비정본 incident runbook index의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 wait=0 또는 mismatch=0과 timeline을 남긴다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `외부 fault injection을 수행한 증거가 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
외부 fault injection을 수행한 증거가 아니다
이 책임을 맡는 곳: metric/query grain무제한 retry나 즉시 balance UPDATE를 금지한다
이 책임을 맡는 곳: 별도 local/external 실행correction은 finance 승인이 필요하다
이 책임을 맡는 곳: secret·개인정보·운영 정책실제 incident timeline은 사람이 검토해야 한다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: DB timeout과 ledger mismatch의 증상 보존·금지 행동·진단 순서·완화·검증·timeline을 분리한 D6 학습 runbook index다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–19줄
3단계 · 파일 전체 다시 쓰기
19개 물리 줄을 원본 순서로 복원하고 SHA-256 a48ebc06c4d61f6904fc29e7a837645fda0d08a2aac00b97157ee0ac5c83b076와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
# W23 incident runbook index - 학습용 예시
## DB timeout
1. 증상과 고객 영향, 최초 requestId와 발생시각을 보존한다.
2. 무제한 retry와 자동 재처리를 중지한다.
3. readiness -> pool pending -> network -> PostgreSQL wait 순서로 확인한다.
4. 승인된 범위에서만 timeout 완화 또는 traffic 축소를 수행한다.
5. API sample과 DB wait=0을 함께 확인한다.
6. rollback 기준, escalation owner, 명령 결과와 timeline을 남긴다.
## Ledger mismatch
1. mismatch row와 영향 계좌를 격리하고 원본 evidence를 보존한다.
2. balance를 즉시 UPDATE하거나 자동 재처리하지 않는다.
3. ledger pair, business_tx, idempotency state, source event를 대조한다.
4. finance 승인 뒤 correction entry만 추가한다.
5. 대사 mismatch=0과 고객 응답을 다시 확인한다.
6. 승인자, correction 근거, rollback 및 후속조치를 기록한다.
외부 fault injection과 incident timeline이 없으면 `HUMAN_SEMANTIC_REVIEW_REQUIRED`다.
11D7 claim registry — local 자동 증거와 external 수동 증거를 합산하지 않는 장부
illustrative/json/W23-D7-claim-registry.json
학습용 예시 · claim registry · 정본 답안 아님 · 비실행 · 학습용 예시 · 정본 답안 아님 · W23-F1131줄 연결31줄 번역4 chunks
D7 claim registry — local 자동 증거와 external 수동 증거를 합산하지 않는 장부
illustrative/json/W23-D7-claim-registry.json
학습용 예시 · claim registry · 정본 답안 아님 · 비실행 · 학습용 예시 · 정본 답안 아님 · W23-F11STEP 01 / 13
오늘 이 코드에서 해결할 문제
무엇을 이해해야 하는지 질문부터 잡습니다.
local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
- `VERIFIABLE_LOCAL`은 어느 줄에서 생기거나 검증될까?
- 입력→guard→관찰→cleanup 또는 stable output 순서는 무엇인가?
- local 자동 owner와 external human owner는 각각 누구인가?
- 이 source가 직접 관찰하지 않은 Green은 무엇인가?
- 반례는 `illustrative registry 자체가 실행 evidence가 아니다` 경계와 어떻게 연결되는가?
VERIFIABLE_LOCALNOT_RUN_EXTERNALMANUAL_REVIEW_REQUIREDobserved=1 cleared=0local!=externalSTEP 02 / 13
아주 짧게: 이 코드는 왜 필요할까?
웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.
STARRY가 metric catalog·requestId 봉투·DB wait 영수증·alert/runbook·SQL grain 표를 서로 다른 칸에 놓는다.
D7 claim registry — local 자동 증거와 external 수동 증거를 합산하지 않는 장부 ― 관측 증거 흐름으로 바꾸기
local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
핵심값 VERIFIABLE_LOCAL, NOT_RUN_EXTERNAL, MANUAL_REVIEW_REQUIRED, observed=1 cleared=0, local!=external을 원본 줄로 따라가되, `illustrative registry 자체가 실행 evidence가 아니다`까지 함께 표시해 local 관찰을 외부 운영 Green으로 부풀리지 않는다.
딱 여기까지만 비유는 grain·owner·순서를 기억하게 할 뿐 실제 Gradle/Docker/PostgreSQL 실행, dashboard·notification, 운영 SLA를 자동 증명하지 않는다.
STEP 03 / 13
초등학생도 이해하는 설명
생활 비유와 실제 코드의 경계를 함께 확인합니다.
원문 1–10줄
1~10줄을 한 덩어리로 읽어 1번째 움직임을 본다. local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
- 코드 연결
1~10줄- 비유
- 자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부
- 비유의 끝
- illustrative registry 자체가 실행 evidence가 아니다
원문 11–20줄
11~20줄을 한 덩어리로 읽어 2번째 움직임을 본다. local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
- 코드 연결
11~20줄- 비유
- 자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부
- 비유의 끝
- local Gate와 외부 수동 evidence를 합산하지 않는다
원문 21–30줄
21~30줄을 한 덩어리로 읽어 3번째 움직임을 본다. local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
- 코드 연결
21~30줄- 비유
- 자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부
- 비유의 끝
- URL·시각·screenshot hash가 필요하다
문제의 첫 장면
-
첫 고정값은 `VERIFIABLE_LOCAL` 맞아?
-
local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
-
원문에서 `VERIFIABLE_LOCAL`이 만들어지거나 확인되는 줄을 찾자.
-
그리고 `illustrative registry 자체가 실행 evidence가 아니다`도 같이 적어.
입력에서 결과까지
-
`NOT_RUN_EXTERNAL`은 언제 생겨?
-
requestId Gate의 selector·test 수·runbook hash를 묶는다. → DB wait의 1→0·value 2·cleanup 1을 묶는다.
-
dashboard 미실행을 null field로 드러낸다. → alert 사람 검토가 남았음을 별도 claim으로 둔다.
-
관찰값과 `local Gate와 외부 수동 evidence를 합산하지 않는다`을 분리하면 돼.
STEP 04 / 13
비유 ↔ 코드 전체 연결표
감사 규칙상 연결 대상인 원본 31줄을 빠짐없이 연결합니다.
| 줄 | 정확한 원본 줄 | STARRY 비유 | 실제 뜻·입력·결과·한계 |
|---|---|---|---|
| 1줄F11-L01 | { |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 2줄F11-L02 | "classification": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"classification": "ILLUSTRATIVE_NOT_CANONICAL",` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 3줄F11-L03 | "week": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"week": 23,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 4줄F11-L04 | "local_request_id_gate": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"local_request_id_gate": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 5줄F11-L05 | "status": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 정본 runner로 로컬에서 검증 가능한 claim임을 나타내며 실행 완료 PASS와는 다르다.
|
| 6줄F11-L06 | "selector": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | W23 Gate가 정확히 선택하는 requestId regression class 이름을 고정한다.
|
| 7줄F11-L07 | "tests": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"tests": 2,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 8줄F11-L08 | "runbook_sha256": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 runbook bytes의 SHA-256을 evidence에 결박한다.
|
| 9줄F11-L09 | }, |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 10줄F11-L10 | "db_wait_drill": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"db_wait_drill": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 11줄F11-L11 | "status": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 정본 runner로 로컬에서 검증 가능한 claim임을 나타내며 실행 완료 PASS와는 다르다.
|
| 12줄F11-L12 | "scope": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"scope": "ROW_UPDATE",` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 13줄F11-L13 | "observed": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | DB wait snapshot에서 기대하는 관찰값 1을 claim에 연결한다.
|
| 14줄F11-L14 | "cleared": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | holder·waiter 종료 뒤 남은 Lock wait가 0이어야 함을 claim에 연결한다.
|
| 15줄F11-L15 | "final_value": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 두 row UPDATE의 최종 fixture 값이 2여야 함을 claim에 연결한다.
|
| 16줄F11-L16 | "cleanup": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 소유한 disposable resource cleanup 확인값 1을 claim에 연결한다.
|
| 17줄F11-L17 | }, |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 18줄F11-L18 | "dashboard": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"dashboard": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 19줄F11-L19 | "status": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | URL·관찰시각·screenshot hash가 없어 외부 실행을 아직 주장하지 않는 정확한 상태다.
|
| 20줄F11-L20 | "url": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"url": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 21줄F11-L21 | "observed_at": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"observed_at": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 22줄F11-L22 | "screenshot_sha256": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"screenshot_sha256": null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 23줄F11-L23 | }, |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 24줄F11-L24 | "alert_notification": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"alert_notification": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 25줄F11-L25 | "status": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 자동 local 검증과 별도로 사람의 dashboard·notification 의미 검토가 남았음을 표시한다.
|
| 26줄F11-L26 | "url": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"url": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 27줄F11-L27 | "observed_at": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"observed_at": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 28줄F11-L28 | "screenshot_sha256": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"screenshot_sha256": null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 29줄F11-L29 | }, |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
| 30줄F11-L30 | "boundary": |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 비정본 local/external claim 분리 registry에서 `"boundary": "local Gate와 외부 수동 evidence를 합산하지 않는` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다.
|
| 31줄F11-L31 | } |
자동 검표기가 확인한 두 칸과 아직 현장 담당자가 채워야 할 두 칸을 색깔별로 나눠, 빈 칸을 합계로 숨기지 않는 증거 장부 | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다.
|
Green 범위
-
marker나 문서가 보이면 W23 전체 성공이야?
-
아니. 이 source가 직접 검증한 claim만 말해야 해.
-
직접 연결되는 대표 값은 `local!=external`이야.
-
`URL·시각·screenshot hash가 필요하다`는 별도 실행·사람 증거가 필요해.
STEP 05 / 13
원본 코드 조각
원본을 4개 의미 조각으로 나누어 그대로 확인합니다.
파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.
{
"classification": "ILLUSTRATIVE_NOT_CANONICAL",
"week": 23,
"local_request_id_gate": {
"status": "VERIFIABLE_LOCAL",
"selector": "com.example.financialcore.api.RequestIdFilterTest",
"tests": 2,
"runbook_sha256": "FROM_GATE_JSON"
},
"db_wait_drill": {
"status": "VERIFIABLE_LOCAL",
"scope": "ROW_UPDATE",
"observed": 1,
"cleared": 0,
"final_value": 2,
"cleanup": 1
},
"dashboard": {
"status": "NOT_RUN_EXTERNAL",
"url": null,
"observed_at": null,
"screenshot_sha256": null
},
"alert_notification": {
"status": "MANUAL_REVIEW_REQUIRED",
"url": null,
"observed_at": null,
"screenshot_sha256": null
},
"boundary": "local Gate와 외부 수동 evidence를 합산하지 않는다"
}
STEP 06 / 13
코드 한 줄씩 한국어로 번역
비어 있지 않은 31줄을 모두 한국어로 옮깁니다.
비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.
| 줄 | 원본 | 한국어 번역 |
|---|---|---|
| 1 | { | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 2 | "classification": "ILLUSTRATIVE_NOT_CANONICAL", | 비정본 local/external claim 분리 registry에서 `"classification": "ILLUSTRATIVE_NOT_CANONICAL",` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 3 | "week": 23, | 비정본 local/external claim 분리 registry에서 `"week": 23,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 4 | "local_request_id_gate": { | 비정본 local/external claim 분리 registry에서 `"local_request_id_gate": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 5 | "status": "VERIFIABLE_LOCAL", | 정본 runner로 로컬에서 검증 가능한 claim임을 나타내며 실행 완료 PASS와는 다르다. |
| 6 | "selector": "com.example.financialcore.api.RequestIdFilterTest", | W23 Gate가 정확히 선택하는 requestId regression class 이름을 고정한다. |
| 7 | "tests": 2, | 비정본 local/external claim 분리 registry에서 `"tests": 2,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 8 | "runbook_sha256": "FROM_GATE_JSON" | 현재 runbook bytes의 SHA-256을 evidence에 결박한다. |
| 9 | }, | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 10 | "db_wait_drill": { | 비정본 local/external claim 분리 registry에서 `"db_wait_drill": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 11 | "status": "VERIFIABLE_LOCAL", | 정본 runner로 로컬에서 검증 가능한 claim임을 나타내며 실행 완료 PASS와는 다르다. |
| 12 | "scope": "ROW_UPDATE", | 비정본 local/external claim 분리 registry에서 `"scope": "ROW_UPDATE",` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 13 | "observed": 1, | DB wait snapshot에서 기대하는 관찰값 1을 claim에 연결한다. |
| 14 | "cleared": 0, | holder·waiter 종료 뒤 남은 Lock wait가 0이어야 함을 claim에 연결한다. |
| 15 | "final_value": 2, | 두 row UPDATE의 최종 fixture 값이 2여야 함을 claim에 연결한다. |
| 16 | "cleanup": 1 | 소유한 disposable resource cleanup 확인값 1을 claim에 연결한다. |
| 17 | }, | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 18 | "dashboard": { | 비정본 local/external claim 분리 registry에서 `"dashboard": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 19 | "status": "NOT_RUN_EXTERNAL", | URL·관찰시각·screenshot hash가 없어 외부 실행을 아직 주장하지 않는 정확한 상태다. |
| 20 | "url": null, | 비정본 local/external claim 분리 registry에서 `"url": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 21 | "observed_at": null, | 비정본 local/external claim 분리 registry에서 `"observed_at": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 22 | "screenshot_sha256": null | 비정본 local/external claim 분리 registry에서 `"screenshot_sha256": null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 23 | }, | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 24 | "alert_notification": { | 비정본 local/external claim 분리 registry에서 `"alert_notification": {` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 25 | "status": "MANUAL_REVIEW_REQUIRED", | 자동 local 검증과 별도로 사람의 dashboard·notification 의미 검토가 남았음을 표시한다. |
| 26 | "url": null, | 비정본 local/external claim 분리 registry에서 `"url": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 27 | "observed_at": null, | 비정본 local/external claim 분리 registry에서 `"observed_at": null,` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 28 | "screenshot_sha256": null | 비정본 local/external claim 분리 registry에서 `"screenshot_sha256": null` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 29 | }, | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
| 30 | "boundary": "local Gate와 외부 수동 evidence를 합산하지 않는다" | 비정본 local/external claim 분리 registry에서 `"boundary": "local Gate와 외부 수동 evidence를 합산하지 않는` field·값 또는 assignment를 다음 guard·관찰 단계로 전달한다. |
| 31 | } | 현재 object·array·block 또는 query 범위를 열거나 닫아 구조와 실행 순서를 유지한다. |
STEP 07 / 13
기존 수준의 한 줄 읽기·문법 해부
쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.
한 줄로 읽기local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다. 다만 illustrative registry 자체가 실행 evidence가 아니다
문법 해부
- JSON top-level 항목마다 claim owner와 status를 따로 둔다.
- `VERIFIABLE_LOCAL`, `NOT_RUN_EXTERNAL`, `MANUAL_REVIEW_REQUIRED`를 서로 다른 상태로 유지한다.
실행 순서
- requestId Gate의 selector·test 수·runbook hash를 묶는다.
- DB wait의 1→0·value 2·cleanup 1을 묶는다.
- dashboard 미실행을 null field로 드러낸다.
- alert 사람 검토가 남았음을 별도 claim으로 둔다.
W23 조각별 정밀 해설
F11-C01 · 원문 1–10줄
- 문법 해부
- 1~10줄의 `원문 1–10줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `VERIFIABLE_LOCAL`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `VERIFIABLE_LOCAL`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: illustrative registry 자체가 실행 evidence가 아니다.
- 착각 방지
- `원문 1–10줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: illustrative registry 자체가 실행 evidence가 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F11-C02 · 원문 11–20줄
- 문법 해부
- 11~20줄의 `원문 11–20줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `NOT_RUN_EXTERNAL`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `NOT_RUN_EXTERNAL`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: local Gate와 외부 수동 evidence를 합산하지 않는다.
- 착각 방지
- `원문 11–20줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: local Gate와 외부 수동 evidence를 합산하지 않는다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F11-C03 · 원문 21–30줄
- 문법 해부
- 21~30줄의 `원문 21–30줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `MANUAL_REVIEW_REQUIRED`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `MANUAL_REVIEW_REQUIRED`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: URL·시각·screenshot hash가 필요하다.
- 착각 방지
- `원문 21–30줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: URL·시각·screenshot hash가 필요하다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
F11-C04 · 원문 31–31줄
- 문법 해부
- 31~31줄의 `원문 31–31줄`을 입력·grain/guard·관찰·cleanup/stable output 단계로 나눈다.
- 실제 값 추적
- 항목 전체 흐름에 연결해 `observed=1 cleared=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
- 정상 예
- 정상 예에서는 source 순서와 owner 계약이 일치해 `observed=1 cleared=0`을 관찰한다.
- 틀린 예·반례
- 반례에서는 선행 guard·grain·stable order·cleanup을 생략한다. 동결 경계: 사람 검토 전 외부 Green이 아니다.
- 착각 방지
- `원문 31–31줄`이 항목 전체 또는 외부 운영 Green을 단독 증명한다고 읽지 않는다.
- 하지 않는 일
- 이 chunk가 책임지지 않는 범위: 사람 검토 전 외부 Green이 아니다.
- 다음 연결
- 다음 source chunk의 입력 또는 exact local marker·SQL oracle·사람 검토 경계로 결과를 넘긴다.
반례
-
겉보기에는 맞지만 깨지는 예는?
-
VERIFIABLE_LOCAL을 실행 완료 PASS로 읽는다
-
그 상태는 입력 grain·owner·관찰 중 한 칸이 비어 있어.
-
수정한 뒤 같은 source bytes와 evidence를 다시 대조해.
STEP 08 / 13
실제 값 따라가기
같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.
| 순서 | 들어온 값 | 코드가 하는 일 | 나온 값·상태 | 경계 |
|---|---|---|---|---|
| F11-T01 local gate | selector+hash | automatic | VERIFIABLE_LOCAL | 실행 전에는 실제 PASS 아님 |
| F11-T02 local DB | row wait values | automatic | VERIFIABLE_LOCAL | 운영 DB 아님 |
| F11-T03 external | URL/time/screenshot | manual | NOT_RUN/MANUAL | local 값과 합산 금지 |
책임 경계
-
이 항목의 마지막 책임선은?
-
직접 역할은 `비정본 local/external claim 분리 registry`이야.
-
대표 경계는 `사람 검토 전 외부 Green이 아니다`이야.
-
정본/학습용 label과 local/external owner까지 확인하면 끝이야.
STEP 09 / 13
Micrometer·RequestId·PowerShell·PostgreSQL 내부에서 벌어지는 일
Micrometer·JUnit·PowerShell·PostgreSQL·alert/runbook에서 실제로 일어나는 일과 증명 범위를 구분합니다.
어떤 증거가 어떤 결론을 소유하는지 구조화한다.
registry 자체가 source runner를 실행하지 않는다.현재 predecessor가 gate.json만 index한다.
db-wait.json·dashboard·alert evidence까지 index됐다고 확대하면 안 된다.threshold·screenshot·timeline 의미를 사람이 확인한다.
형식 검사가 운영 의미를 대신하지 않는다.STEP 10 / 13
흔한 착각과 틀린 예
그럴듯하지만 틀린 해석을 반례로 고칩니다.
❌ VERIFIABLE_LOCAL을 실행 완료 PASS로 읽는다
왜 틀리나 비정본 local/external claim 분리 registry의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 requestId Gate의 selector·test 수·runbook hash를 묶는다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `illustrative registry 자체가 실행 evidence가 아니다` 상태인데도 Green을 주장하게 된다.
❌ predecessor gate.json index가 db-wait/external까지 포함한다고 본다
왜 틀리나 비정본 local/external claim 분리 registry의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 DB wait의 1→0·value 2·cleanup 1을 묶는다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `local Gate와 외부 수동 evidence를 합산하지 않는다` 상태인데도 Green을 주장하게 된다.
❌ null URL을 예시 URL로 채워 통과시킨다
왜 틀리나 비정본 local/external claim 분리 registry의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 dashboard 미실행을 null field로 드러낸다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `URL·시각·screenshot hash가 필요하다` 상태인데도 Green을 주장하게 된다.
❌ MANUAL_REVIEW_REQUIRED를 실패와 같은 뜻으로 본다
왜 틀리나 비정본 local/external claim 분리 registry의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 alert 사람 검토가 남았음을 별도 claim으로 둔다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `사람 검토 전 외부 Green이 아니다` 상태인데도 Green을 주장하게 된다.
❌ claim registry 자체를 primary evidence producer라 부른다
왜 틀리나 비정본 local/external claim 분리 registry의 grain·owner·관찰·경계 중 적어도 하나를 건너뛴다.
바르게 읽기 alert 사람 검토가 남았음을 별도 claim으로 둔다. 그리고 source의 실제 값과 provenance를 다시 대조한다.
반례 반례에서는 `illustrative registry 자체가 실행 evidence가 아니다` 상태인데도 Green을 주장하게 된다.
STEP 11 / 13
이 코드가 보장하지 않는 것
이 코드가 책임지지 않는 일을 분리합니다.
illustrative registry 자체가 실행 evidence가 아니다
이 책임을 맡는 곳: metric/query grainlocal Gate와 외부 수동 evidence를 합산하지 않는다
이 책임을 맡는 곳: 별도 local/external 실행URL·시각·screenshot hash가 필요하다
이 책임을 맡는 곳: secret·개인정보·운영 정책사람 검토 전 외부 Green이 아니다
이 책임을 맡는 곳: infrastructure·사람 검토STEP 12 / 13
직접 다시 써보기
뜻 → 조각 → 전체 코드 순서로 다시 씁니다.
1단계 · 뜻부터 복원
다음 역할을 고정값과 미보장 경계까지 말한다: local requestId·DB wait와 외부 dashboard·alert evidence를 서로 다른 status로 분리하는 D7 claim registry 예시다.
2단계 · 코드 조각 재조립
- 원문 1–10줄
- 원문 11–20줄
- 원문 21–30줄
- 원문 31–31줄
3단계 · 파일 전체 다시 쓰기
31개 물리 줄을 원본 순서로 복원하고 SHA-256 38e00eda0957286b254161c8cc830631f2111be6068400dcde8e83447f1f0aec와 대조한다.
자가 점검
- 원본 source/template bytes와 줄 번호를 바꾸지 않았는가?
- 정본 3개와 학습용 예시 8개를 구분했는가?
- RequestId local Gate와 DB wait local drill의 Green 범위를 분리했는가?
- dashboard·alert·incident timeline을 NOT_RUN_EXTERNAL 또는 사람 검토로 남겼는가?
- Q37의 JOIN grain과 Q38의 SUCCESS·반개구간 가정을 드러냈는가?
STEP 13 / 13
전체 원본 source
감사로 고정한 전체 source를 가감 없이 확인합니다.
학습용 예시 전체 확인하기 · 정본 답안 아님
{
"classification": "ILLUSTRATIVE_NOT_CANONICAL",
"week": 23,
"local_request_id_gate": {
"status": "VERIFIABLE_LOCAL",
"selector": "com.example.financialcore.api.RequestIdFilterTest",
"tests": 2,
"runbook_sha256": "FROM_GATE_JSON"
},
"db_wait_drill": {
"status": "VERIFIABLE_LOCAL",
"scope": "ROW_UPDATE",
"observed": 1,
"cleared": 0,
"final_value": 2,
"cleanup": 1
},
"dashboard": {
"status": "NOT_RUN_EXTERNAL",
"url": null,
"observed_at": null,
"screenshot_sha256": null
},
"alert_notification": {
"status": "MANUAL_REVIEW_REQUIRED",
"url": null,
"observed_at": null,
"screenshot_sha256": null
},
"boundary": "local Gate와 외부 수동 evidence를 합산하지 않는다"
}