W21 · Linux·Docker 배포 준비

21주차 코드 뒤풀이: image 검증부터 DB-only Compose·host JVM teardown까지

완전한 packaged PowerShell source 2개와 learner-owned 참조 입력·PDF template을 출처 등급별로 분리해 읽습니다. D3가 DB-only Compose와 host JVM의 lifecycle owner라는 점, D6·D7은 그 manifest를 읽는 검토 단계라는 점을 연결하고, PDF 안의 ProjectRoot·포트·종료 방식 차이도 그대로 비교합니다. Q33·Q34는 workbook에 정답 query가 배포되지 않아 가정을 표시한 비정본 예시로만 제공합니다.

원문 정본 · 완전한 packaged PowerShell source 2개학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님 3개학습용 예시 · PDF 학습 골격 · 정본 답안 아님 · 그대로 실행 금지 1개학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님 2개학습용 예시 · 정본 답안 아님 2개항목마다 13단계연결 217줄번역 217줄
01

W21 Linux 진단 골격 — process·port·health·SIGTERM

illustrative/shell/W21-linux-diagnostics.sh

학습용 예시 · PDF 학습 골격 · 정본 답안 아님 · 그대로 실행 금지 · 학습용 예시 · 정본 답안 아님 · W21-F01
4줄 연결4줄 번역2 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

  1. `java PID`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `완전한 실행 script가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값java PIDLISTEN 8080health UPbounded SIGTERM exit
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

W21 Linux 진단 골격 — process·port·health·SIGTERM를 무대 운영표로 바꾸기

ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

핵심값 java PID, LISTEN 8080, health UP, bounded SIGTERM exit을 원본 줄로 따라가되, `완전한 실행 script가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

process and listening port

1~2줄을 한 덩어리로 읽어 1번째 움직임을 본다. ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

코드 연결
1~2줄
비유
가게의 직원(process), 열린 문(port), 영업 상태(health), 폐점 요청(signal)을 같은 순서로 확인하는 점검표
비유의 끝
완전한 실행 script가 아니다

health and SIGTERM

3~4줄을 한 덩어리로 읽어 2번째 움직임을 본다. ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

코드 연결
3~4줄
비유
가게의 직원(process), 열린 문(port), 영업 상태(health), 폐점 요청(signal)을 같은 순서로 확인하는 점검표
비유의 끝
실제 PID를 채워야 한다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `java PID` 맞아?

  2. 니지카

    ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

  3. 첫 source 값은 `java PID`로 잡자.

  4. 키타

    단, `완전한 실행 script가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `LISTEN 8080`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. java process를 찾는다. → 8080 LISTEN socket을 찾는다.

  3. 그 다음은 aggregate health가 응답하는지 읽는다. → 실제 PID에 SIGTERM을 보내고 종료 시간을 관찰한다.

  4. 키타

    관찰값과 `실제 PID를 채워야 한다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 4줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · PDF 학습 골격 · 정본 답안 아님 · 그대로 실행 금지에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.4 / 4 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F01-L01 ps -ef | grep '[j]ava' 직원·문·영업등·폐점 요청을 순서대로 보는 운영 점검표 전체 process에서 현재 java instance만 찾아 PID 확인의 출발점을 만든다.
입력
실행 중 java process, 8080 socket, health URL, 실제 PID
결과·효과
이 줄 뒤에는 `java PID` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
완전한 실행 script가 아니다
2줄F01-L02 ss -lntp | grep 8080 직원·문·영업등·폐점 요청을 순서대로 보는 운영 점검표 LISTEN TCP socket 중 8080을 골라 실제 port open 상태를 확인한다.
입력
실행 중 java process, 8080 socket, health URL, 실제 PID
결과·효과
이 줄 뒤에는 `LISTEN 8080` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
실제 PID를 채워야 한다
3줄F01-L03 curl -fsS http://localhost:8080/actuator/health 직원·문·영업등·폐점 요청을 순서대로 보는 운영 점검표 localhost aggregate health를 호출하고 HTTP 실패면 nonzero로 종료한다.
입력
실행 중 java process, 8080 socket, health URL, 실제 PID
결과·효과
이 줄 뒤에는 `health UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
kill -9은 graceful proof가 아니다
4줄F01-L04 kill -TERM <PID> 직원·문·영업등·폐점 요청을 순서대로 보는 운영 점검표 실제 PID에 SIGTERM 정상 종료 요청을 보낸다.
입력
실행 중 java process, 8080 socket, health URL, 실제 PID
결과·효과
이 줄 뒤에는 `bounded SIGTERM exit` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D3의 Stop-Process -Force와 같은 증거가 아니다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `bounded SIGTERM exit`이야.

  4. 키타

    `kill -9은 graceful proof가 아니다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 2개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F01-C01 · process and listening port1–2줄
1–2줄 원본
ps -ef | grep '[j]ava'
ss -lntp | grep 8080
F01-C02 · health and SIGTERM3–4줄
3–4줄 원본
curl -fsS http://localhost:8080/actuator/health
kill -TERM <PID>
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 4줄을 모두 한국어로 옮깁니다.

전체 번역 4 / 4

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1ps -ef | grep '[j]ava'전체 process에서 현재 java instance만 찾아 PID 확인의 출발점을 만든다.
2ss -lntp | grep 8080LISTEN TCP socket 중 8080을 골라 실제 port open 상태를 확인한다.
3curl -fsS http://localhost:8080/actuator/healthlocalhost aggregate health를 호출하고 HTTP 실패면 nonzero로 종료한다.
4kill -TERM <PID>실제 PID에 SIGTERM 정상 종료 요청을 보낸다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다. 다만 완전한 실행 script가 아니다

문법 해부

  • pipe는 앞 명령의 출력을 grep에 넘겨 원하는 process·port만 남긴다.
  • `kill -TERM`은 PID에 정상 종료 요청 signal을 보내며 강제 종료와 의미가 다르다.

실행 순서

  1. java process를 찾는다.
  2. 8080 LISTEN socket을 찾는다.
  3. aggregate health가 응답하는지 읽는다.
  4. 실제 PID에 SIGTERM을 보내고 종료 시간을 관찰한다.

W21 조각별 정밀 해설

F01-C01 · process and listening port
문법 해부
1~2줄의 `process and listening port`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `java PID`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `java PID`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 완전한 실행 script가 아니다.
착각 방지
`process and listening port`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 완전한 실행 script가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F01-C02 · health and SIGTERM
문법 해부
3~4줄의 `health and SIGTERM`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `LISTEN 8080`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `LISTEN 8080`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 실제 PID를 채워야 한다.
착각 방지
`health and SIGTERM`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 실제 PID를 채워야 한다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    PID는 있지만 8080이 닫혀 있을 수 있다.

  3. 왜 틀렸는지: port와 health는 별도 상태다.

  4. 키타

    수정: ps·ss·curl을 모두 관찰한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F01-T01 실행 중java app + 8080ps·ss·curlPID/LISTEN/UP세 관찰은 서로 대체하지 않는다
F01-T02 정상 종료actual PIDkill -TERM유한 시간 안에 종료강제 kill과 다르다
F01-T03 골격 그대로<PID> placeholder복사 실행실패 또는 오작동실제 값과 환경을 먼저 채워야 한다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `Linux 진단 순서 학습 골격`이야.

  3. 대표 경계는 `D3의 Stop-Process -Force와 같은 증거가 아니다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

Linux process

kernel이 실행 instance에 PID를 붙인다.

PID 존재만으로 요청 가능 상태는 모른다.
TCP socket

ss가 LISTEN 상태와 port를 읽는다.

socket open만으로 dependency 정상은 모른다.
signal

SIGTERM handler와 JVM shutdown hook이 정상 종료 기회를 얻는다.

D3의 Stop-Process -Force와 같은 proof가 아니다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ process가 보이면 서비스는 정상이다

왜 틀리나 port와 health는 별도 상태다.

바르게 읽기 ps·ss·curl을 모두 관찰한다.

반례 PID는 있지만 8080이 닫혀 있을 수 있다.

❌ 8080이 열리면 내 app이다

왜 틀리나 다른 process가 선점할 수 있다.

바르게 읽기 PID와 owning process를 함께 묶는다.

반례 기존 process가 8080을 쓰면 잘못된 Green이다.

❌ kill -9도 graceful shutdown 증거다

왜 틀리나 SIGKILL은 정리 기회를 주지 않는다.

바르게 읽기 SIGTERM과 bounded exit를 기록한다.

반례 DB pool·요청 drain이 생략된다.

❌ <PID>를 그대로 실행해도 된다

왜 틀리나 placeholder는 실제 process ID가 아니다.

바르게 읽기 현재 실행에서 얻은 양의 PID로 바꾼다.

반례 shell이 syntax error를 내거나 잘못된 target을 겨냥한다.

❌ D1과 D3 종료는 같은 방식이다

왜 틀리나 D3 source는 Stop-Process -Force를 사용한다.

바르게 읽기 D1 graceful proof와 D3 cleanup proof를 분리한다.

반례 강제 cleanup으로 SIGTERM 처리 시간을 주장할 수 없다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

완전한 실행 script가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

실제 PID를 채워야 한다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

kill -9은 graceful proof가 아니다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

D3의 Stop-Process -Force와 같은 증거가 아니다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: ps·ss·curl·SIGTERM을 같은 진단 순서로 묶은 PDF 학습 골격이다.

2단계 · 코드 조각 재조립

  1. process and listening port
  2. health and SIGTERM

3단계 · 파일 전체 다시 쓰기

4개 물리 줄을 원본 순서로 복원하고 SHA-256 1f906b734946f4de1c7374ae4bf5a3a4fdfb5c0e1ee92262d55a2f1f0020b839와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · PDF 학습 골격 · 정본 답안 아님 · 그대로 실행 금지illustrative/shell/W21-linux-diagnostics.shSHA-256 1f906b734946f4de1c7374ae4bf5a3a4fdfb5c0e1ee92262d55a2f1f0020b839
W21 Linux 진단 골격 — process·port·health·SIGTERM 전체
ps -ef | grep '[j]ava'
ss -lntp | grep 8080
curl -fsS http://localhost:8080/actuator/health
kill -TERM <PID>
02

Dockerfile — build 도구는 builder에, 실행은 UID 10001로

project/Dockerfile

학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W21-F02
13줄 연결13줄 번역3 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

  1. `stage_count=2`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `multi-stage만으로 완전 격리가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값stage_count=2COPY --from=builderUSER 10001/app/app.jar
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

Dockerfile — build 도구는 builder에, 실행은 UID 10001로를 무대 운영표로 바꾸기

Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

핵심값 stage_count=2, COPY --from=builder, USER 10001, /app/app.jar을 원본 줄로 따라가되, `multi-stage만으로 완전 격리가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

builder base and workdir

1~2줄을 한 덩어리로 읽어 1번째 움직임을 본다. Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

코드 연결
1~2줄
비유
공사 도구는 작업장(builder)에 남기고 완성된 jar만 매장(runtime)으로 옮긴 뒤 직원에게 제한 출입증 10001을 주는 과정
비유의 끝
multi-stage만으로 완전 격리가 아니다

Gradle bootJar build

3~6줄을 한 덩어리로 읽어 2번째 움직임을 본다. Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

코드 연결
3~6줄
비유
공사 도구는 작업장(builder)에 남기고 완성된 jar만 매장(runtime)으로 옮긴 뒤 직원에게 제한 출입증 10001을 주는 과정
비유의 끝
health는 이 파일이 증명하지 않는다

non-root runtime image

7~14줄을 한 덩어리로 읽어 3번째 움직임을 본다. Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

코드 연결
7~14줄
비유
공사 도구는 작업장(builder)에 남기고 완성된 jar만 매장(runtime)으로 옮긴 뒤 직원에게 제한 출입증 10001을 주는 과정
비유의 끝
runner가 대신 생성하지 않는다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `stage_count=2` 맞아?

  2. 니지카

    Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

  3. 첫 source 값은 `stage_count=2`로 잡자.

  4. 키타

    단, `multi-stage만으로 완전 격리가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `COPY --from=builder`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. JDK builder에서 project를 복사한다. → Wrapper로 clean bootJar를 만든다.

  3. 그 다음은 JRE runtime에 uid 10001 app user를 만든다. → jar를 복사하고 USER·ENTRYPOINT를 고정한다.

  4. 키타

    관찰값과 `health는 이 파일이 증명하지 않는다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 13줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.13 / 13 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F02-L01 FROM eclipse-temurin:21-jdk AS builder 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 새 Docker stage의 base image를 선택한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `stage_count=2` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
multi-stage만으로 완전 격리가 아니다
2줄F02-L02 WORKDIR /src 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 뒤 directive와 runtime command의 작업 directory를 고정한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `COPY --from=builder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health는 이 파일이 증명하지 않는다
3줄F02-L03 COPY . . 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner project를 builder filesystem에 복사한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `USER 10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
runner가 대신 생성하지 않는다
4줄F02-L04 RUN sed -i 's/\r$//' gradlew \ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Gradle Wrapper의 CRLF를 정리하고 executable로 만든 뒤 bootJar build를 시작한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `/app/app.jar` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
read-only filesystem proof가 아니다
5줄F02-L05 && chmod +x gradlew \ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Gradle Wrapper에 실행 권한을 부여한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `stage_count=2` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
multi-stage만으로 완전 격리가 아니다
6줄F02-L06 && ./gradlew clean bootJar --no-daemon 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 daemon 없이 clean bootJar를 실행해 새 jar를 만든다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `COPY --from=builder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health는 이 파일이 증명하지 않는다
8줄F02-L08 FROM eclipse-temurin:21-jre 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 새 Docker stage의 base image를 선택한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `/app/app.jar` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
read-only filesystem proof가 아니다
9줄F02-L09 RUN useradd --system --uid 10001 app 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 runtime image에 system uid 10001 app user를 만든다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `stage_count=2` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
multi-stage만으로 완전 격리가 아니다
10줄F02-L10 WORKDIR /app 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 뒤 directive와 runtime command의 작업 directory를 고정한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `COPY --from=builder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health는 이 파일이 증명하지 않는다
11줄F02-L11 COPY --from=builder /src/build/libs/*.jar /app/app.jar 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 builder stage에서 만든 jar만 runtime stage로 복사한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `USER 10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
runner가 대신 생성하지 않는다
12줄F02-L12 USER 10001 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 이후 runtime process의 기본 uid를 10001로 고정한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `/app/app.jar` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
read-only filesystem proof가 아니다
13줄F02-L13 EXPOSE 8080 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 image가 의도하는 application port를 metadata로 기록한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `stage_count=2` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
multi-stage만으로 완전 격리가 아니다
14줄F02-L14 ENTRYPOINT ["java","-jar","/app/app.jar"] 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 container start 시 java -jar /app/app.jar를 실행한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `COPY --from=builder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health는 이 파일이 증명하지 않는다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `/app/app.jar`이야.

  4. 키타

    `runner가 대신 생성하지 않는다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 3개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F02-C01 · builder base and workdir1–2줄
1–2줄 원본
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /src
F02-C02 · Gradle bootJar build3–6줄
3–6줄 원본
COPY . .
RUN sed -i 's/\r$//' gradlew \
    && chmod +x gradlew \
    && ./gradlew clean bootJar --no-daemon
F02-C03 · non-root runtime image7–14줄
7–14줄 원본

FROM eclipse-temurin:21-jre
RUN useradd --system --uid 10001 app
WORKDIR /app
COPY --from=builder /src/build/libs/*.jar /app/app.jar
USER 10001
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 13줄을 모두 한국어로 옮깁니다.

전체 번역 13 / 13

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1FROM eclipse-temurin:21-jdk AS builder새 Docker stage의 base image를 선택한다.
2WORKDIR /src뒤 directive와 runtime command의 작업 directory를 고정한다.
3COPY . .learner project를 builder filesystem에 복사한다.
4RUN sed -i 's/\r$//' gradlew \Gradle Wrapper의 CRLF를 정리하고 executable로 만든 뒤 bootJar build를 시작한다.
5 && chmod +x gradlew \Gradle Wrapper에 실행 권한을 부여한다.
6 && ./gradlew clean bootJar --no-daemondaemon 없이 clean bootJar를 실행해 새 jar를 만든다.
8FROM eclipse-temurin:21-jre새 Docker stage의 base image를 선택한다.
9RUN useradd --system --uid 10001 appruntime image에 system uid 10001 app user를 만든다.
10WORKDIR /app뒤 directive와 runtime command의 작업 directory를 고정한다.
11COPY --from=builder /src/build/libs/*.jar /app/app.jarbuilder stage에서 만든 jar만 runtime stage로 복사한다.
12USER 10001이후 runtime process의 기본 uid를 10001로 고정한다.
13EXPOSE 8080image가 의도하는 application port를 metadata로 기록한다.
14ENTRYPOINT ["java","-jar","/app/app.jar"]container start 시 java -jar /app/app.jar를 실행한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다. 다만 multi-stage만으로 완전 격리가 아니다

문법 해부

  • 두 `FROM`은 builder와 runtime이라는 서로 다른 filesystem stage를 만든다.
  • `COPY --from=builder`는 첫 stage의 jar만 final stage로 가져온다.

실행 순서

  1. JDK builder에서 project를 복사한다.
  2. Wrapper로 clean bootJar를 만든다.
  3. JRE runtime에 uid 10001 app user를 만든다.
  4. jar를 복사하고 USER·ENTRYPOINT를 고정한다.

W21 조각별 정밀 해설

F02-C01 · builder base and workdir
문법 해부
1~2줄의 `builder base and workdir`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `stage_count=2`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `stage_count=2`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: multi-stage만으로 완전 격리가 아니다.
착각 방지
`builder base and workdir`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: multi-stage만으로 완전 격리가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F02-C02 · Gradle bootJar build
문법 해부
3~6줄의 `Gradle bootJar build`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `COPY --from=builder`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `COPY --from=builder`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: health는 이 파일이 증명하지 않는다.
착각 방지
`Gradle bootJar build`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: health는 이 파일이 증명하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F02-C03 · non-root runtime image
문법 해부
7~14줄의 `non-root runtime image`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `USER 10001`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `USER 10001`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: runner가 대신 생성하지 않는다.
착각 방지
`non-root runtime image`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: runner가 대신 생성하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    실수로 source를 다시 COPY할 수 있다.

  3. 왜 틀렸는지: COPY와 layer 내용에 달려 있다.

  4. 키타

    수정: final image path와 builder tool 부재를 probe한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F02-T01 stageJava 21 projectbuilder bootJarbuild/libs jar빌드 성공은 runtime 검증 전이다
F02-T02 runtimebuilder jarCOPY --from/app/app.jarsource 전체를 옮기지 않는다
F02-T03 processimage startUSER 10001 + java -jarnon-root JVMcapability/read-only까지 자동 보장하지 않는다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `learner-owned multi-stage image recipe`이야.

  3. 대표 경계는 `read-only filesystem proof가 아니다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

Docker build

각 directive가 immutable layer를 만든다.

잘못 복사한 secret은 뒤 layer에서 지워도 남을 수 있다.
Linux user

USER가 container process의 uid를 10001로 고정한다.

non-root만으로 sandbox가 완성되지는 않는다.
JVM

ENTRYPOINT가 /app/app.jar를 시작한다.

health/DB readiness는 별도 lifecycle 책임이다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ multi-stage면 source가 절대 새지 않는다

왜 틀리나 COPY와 layer 내용에 달려 있다.

바르게 읽기 final image path와 builder tool 부재를 probe한다.

반례 실수로 source를 다시 COPY할 수 있다.

❌ non-root면 image가 완전히 안전하다

왜 틀리나 권한·capability·filesystem·network 경계가 더 있다.

바르게 읽기 W21 직접 검사 범위만 말한다.

반례 writable path나 과도한 capability는 남을 수 있다.

❌ runner가 Dockerfile을 만들어 준다

왜 틀리나 runner는 learner input을 소비한다.

바르게 읽기 learner가 먼저 exact file을 작성한다.

반례 파일이 없으면 build 전에 실패한다.

❌ EXPOSE가 host port를 연다

왜 틀리나 EXPOSE는 image metadata다.

바르게 읽기 실제 publish/listen을 별도로 확인한다.

반례 container start만으로 host 8080이 열리지 않는다.

❌ JDK가 final image에도 필요하다

왜 틀리나 runtime은 이미 만든 jar만 실행한다.

바르게 읽기 JRE stage로 builder tool을 떼어낸다.

반례 Gradle·javac가 final image에 있으면 검사 실패다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

multi-stage만으로 완전 격리가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

health는 이 파일이 증명하지 않는다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

runner가 대신 생성하지 않는다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

read-only filesystem proof가 아니다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: Gradle builder와 JRE runtime을 분리하고 final image를 UID 10001로 실행하는 learner-owned Dockerfile이다.

2단계 · 코드 조각 재조립

  1. builder base and workdir
  2. Gradle bootJar build
  3. non-root runtime image

3단계 · 파일 전체 다시 쓰기

14개 물리 줄을 원본 순서로 복원하고 SHA-256 047be48ded4d1ed05471fe106e106723e2a9f28dc78f2fa11e31c93b6d9e207a와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님project/DockerfileSHA-256 047be48ded4d1ed05471fe106e106723e2a9f28dc78f2fa11e31c93b6d9e207a
Dockerfile — build 도구는 builder에, 실행은 UID 10001로 전체
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /src
COPY . .
RUN sed -i 's/\r$//' gradlew \
    && chmod +x gradlew \
    && ./gradlew clean bootJar --no-daemon

FROM eclipse-temurin:21-jre
RUN useradd --system --uid 10001 app
WORKDIR /app
COPY --from=builder /src/build/libs/*.jar /app/app.jar
USER 10001
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app/app.jar"]
03

.dockerignore — build context에서 증거·환경 파일 제외

project/.dockerignore

학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W21-F03
7줄 연결7줄 번역2 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

  1. `.git excluded`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `secret manager가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값.git excludedbuild excludedevidence excluded*.env/.env excluded
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

.dockerignore — build context에서 증거·환경 파일 제외를 무대 운영표로 바꾸기

VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

핵심값 .git excluded, build excluded, evidence excluded, *.env/.env excluded을 원본 줄로 따라가되, `secret manager가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

VCS cache build evidence exclusions

1~4줄을 한 덩어리로 읽어 1번째 움직임을 본다. VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

코드 연결
1~4줄
비유
이삿짐 상자에 넣지 말아야 할 `.git`·증거·환경 파일 목록을 문 앞에 붙이는 규칙
비유의 끝
secret manager가 아니다

environment and log exclusions

5~7줄을 한 덩어리로 읽어 2번째 움직임을 본다. VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

코드 연결
5~7줄
비유
이삿짐 상자에 넣지 말아야 할 `.git`·증거·환경 파일 목록을 문 앞에 붙이는 규칙
비유의 끝
이미 image layer에 들어간 secret을 지우지 않는다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `.git excluded` 맞아?

  2. 니지카

    VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

  3. 첫 source 값은 `.git excluded`로 잡자.

  4. 키타

    단, `secret manager가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `build excluded`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. build context root를 정한다. → ignore pattern과 path를 비교한다.

  3. 그 다음은 일치 파일을 daemon 전송에서 뺀다. → 남은 context로 Dockerfile COPY를 수행한다.

  4. 키타

    관찰값과 `이미 image layer에 들어간 secret을 지우지 않는다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 7줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.7 / 7 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F03-L01 .git 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `.git` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `.git excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
secret manager가 아니다
2줄F03-L02 .gradle 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `.gradle` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `build excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
이미 image layer에 들어간 secret을 지우지 않는다
3줄F03-L03 build 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `build` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `evidence excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
runner는 일부 exact pattern만 검사한다
4줄F03-L04 evidence 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `evidence` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `*.env/.env excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
노출 secret은 revoke/rotate가 먼저다
5줄F03-L05 *.env 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `*.env` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `.git excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
secret manager가 아니다
6줄F03-L06 .env 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `.env` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `build excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
이미 image layer에 들어간 secret을 지우지 않는다
7줄F03-L07 *.log 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Docker build context에서 `*.log` pattern에 맞는 path를 제외한다.
입력
learner project build context와 Java 21 Gradle Wrapper
결과·효과
이 줄 뒤에는 `evidence excluded` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
runner는 일부 exact pattern만 검사한다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `*.env/.env excluded`이야.

  4. 키타

    `runner는 일부 exact pattern만 검사한다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 2개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F03-C01 · VCS cache build evidence exclusions1–4줄
1–4줄 원본
.git
.gradle
build
evidence
F03-C02 · environment and log exclusions5–7줄
5–7줄 원본
*.env
.env
*.log
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 7줄을 모두 한국어로 옮깁니다.

전체 번역 7 / 7

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1.gitDocker build context에서 `.git` pattern에 맞는 path를 제외한다.
2.gradleDocker build context에서 `.gradle` pattern에 맞는 path를 제외한다.
3buildDocker build context에서 `build` pattern에 맞는 path를 제외한다.
4evidenceDocker build context에서 `evidence` pattern에 맞는 path를 제외한다.
5*.envDocker build context에서 `*.env` pattern에 맞는 path를 제외한다.
6.envDocker build context에서 `.env` pattern에 맞는 path를 제외한다.
7*.logDocker build context에서 `*.log` pattern에 맞는 path를 제외한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다. 다만 secret manager가 아니다

문법 해부

  • 각 줄은 Docker build context에서 제외할 path 또는 glob pattern이다.
  • `*.env`와 `.env`는 이름 모양이 다르므로 둘 다 적는다.

실행 순서

  1. build context root를 정한다.
  2. ignore pattern과 path를 비교한다.
  3. 일치 파일을 daemon 전송에서 뺀다.
  4. 남은 context로 Dockerfile COPY를 수행한다.

W21 조각별 정밀 해설

F03-C01 · VCS cache build evidence exclusions
문법 해부
1~4줄의 `VCS cache build evidence exclusions`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `.git excluded`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `.git excluded`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: secret manager가 아니다.
착각 방지
`VCS cache build evidence exclusions`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: secret manager가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F03-C02 · environment and log exclusions
문법 해부
5~7줄의 `environment and log exclusions`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `build excluded`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `build excluded`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 이미 image layer에 들어간 secret을 지우지 않는다.
착각 방지
`environment and log exclusions`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 이미 image layer에 들어간 secret을 지우지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    Git history의 유출값은 남는다.

  3. 왜 틀렸는지: 전송 제외 목록일 뿐이다.

  4. 키타

    수정: secret은 외부 secret store·trusted terminal에서 공급한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F03-T01 Git.gitexact exclusioncontext 밖VCS secret 전체를 대체하지 않는다
F03-T02 증거evidencedirectory exclusionlearner proof 밖이미 복사된 layer는 별도 문제다
F03-T03 환경prod.env/.envglob+exact exclusioncontext 밖노출됐으면 rotate가 먼저다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `Docker build-context 제외 규칙`이야.

  3. 대표 경계는 `노출 secret은 revoke/rotate가 먼저다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

Docker client

ignore를 적용한 context archive를 만든다.

pattern이 빠진 파일은 daemon으로 전송될 수 있다.
build cache

context 변화가 COPY cache key에 영향을 준다.

ignore가 secret lifecycle을 관리하지 않는다.
final image probe

runner가 /app 금지 경로 부재를 다시 확인한다.

모든 filesystem path를 전수 검사하지는 않는다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ .dockerignore는 secret manager다

왜 틀리나 전송 제외 목록일 뿐이다.

바르게 읽기 secret은 외부 secret store·trusted terminal에서 공급한다.

반례 Git history의 유출값은 남는다.

❌ *.env면 .env도 자동 포함된다

왜 틀리나 dotfile/exact name matching을 따로 고정하는 편이 안전하다.

바르게 읽기 두 pattern을 모두 둔다.

반례 `.env`가 context에 들어갈 수 있다.

❌ ignore 뒤에는 source 검사가 필요 없다

왜 틀리나 Dockerfile이 다른 path를 복사할 수 있다.

바르게 읽기 final image forbidden-path probe를 함께 둔다.

반례 /app/evidence가 남으면 실패다.

❌ build directory는 넣어도 빠르다

왜 틀리나 오래된 jar가 섞일 수 있다.

바르게 읽기 build를 제외하고 container 안에서 새로 만든다.

반례 로컬 stale artifact가 final jar가 될 수 있다.

❌ 노출 secret은 ignore 추가로 해결된다

왜 틀리나 이미 노출된 값은 회수되지 않는다.

바르게 읽기 먼저 revoke/rotate하고 history/image를 조사한다.

반례 기존 token은 계속 유효할 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

secret manager가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

이미 image layer에 들어간 secret을 지우지 않는다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

runner는 일부 exact pattern만 검사한다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

노출 secret은 revoke/rotate가 먼저다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: VCS·build 산출물·evidence·환경 파일·log를 Docker build context에서 제외한다.

2단계 · 코드 조각 재조립

  1. VCS cache build evidence exclusions
  2. environment and log exclusions

3단계 · 파일 전체 다시 쓰기

7개 물리 줄을 원본 순서로 복원하고 SHA-256 75468239aae8dcb6a4344c8322494e83a3550c89689254491588bc9352c95c2a와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님project/.dockerignoreSHA-256 75468239aae8dcb6a4344c8322494e83a3550c89689254491588bc9352c95c2a
.dockerignore — build context에서 증거·환경 파일 제외 전체
.git
.gradle
build
evidence
*.env
.env
*.log
04

run-w21-image.ps1 — image build·검사·hash evidence owner

scripts/run-w21-image.ps1

원문 정본 · 완전한 packaged PowerShell source · 정본 · W21-F04
85줄 연결85줄 번역5 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

  1. `W21_IMAGE_GREEN`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `Dockerfile을 생성하지 않는다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값W21_IMAGE_GREENruntime_uid=10001builder_tools_absent=truenative_exit=0
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

run-w21-image.ps1 — image build·검사·hash evidence owner를 무대 운영표로 바꾸기

learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

핵심값 W21_IMAGE_GREEN, runtime_uid=10001, builder_tools_absent=true, native_exit=0을 원본 줄로 따라가되, `Dockerfile을 생성하지 않는다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

parameters and learner inputs

1~16줄을 한 덩어리로 읽어 1번째 움직임을 본다. learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

코드 연결
1~16줄
비유
세관 검사원이 learner 상자를 완성한 뒤 신분증·금지 물품·내용물 hash를 확인하고 learner-owned 영수증에 통과 도장을 찍는 과정
비유의 끝
Dockerfile을 생성하지 않는다

static Dockerfile and ignore guards

17~28줄을 한 덩어리로 읽어 2번째 움직임을 본다. learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

코드 연결
17~28줄
비유
세관 검사원이 learner 상자를 완성한 뒤 신분증·금지 물품·내용물 hash를 확인하고 learner-owned 영수증에 통과 도장을 찍는 과정
비유의 끝
Compose/DB lifecycle을 실행하지 않는다

build inspect and runtime probes

29~48줄을 한 덩어리로 읽어 3번째 움직임을 본다. learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

코드 연결
29~48줄
비유
세관 검사원이 learner 상자를 완성한 뒤 신분증·금지 물품·내용물 hash를 확인하고 learner-owned 영수증에 통과 도장을 찍는 과정
비유의 끝
application health를 증명하지 않는다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `W21_IMAGE_GREEN` 맞아?

  2. 니지카

    learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

  3. 첫 source 값은 `W21_IMAGE_GREEN`로 잡자.

  4. 키타

    단, `Dockerfile을 생성하지 않는다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `runtime_uid=10001`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. absolute path·필수 파일·placeholder를 검증한다. → Dockerfile stage/USER와 ignore pattern을 정적으로 검사한다.

  3. 그 다음은 image build 뒤 user·image id·runtime uid·금지 경로를 probe한다. → JAR/source hash evidence를 temporary file에 쓴 뒤 atomic replace하고 cleanup한다.

  4. 키타

    관찰값과 `Compose/DB lifecycle을 실행하지 않는다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 85줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.원문 정본 · 완전한 packaged PowerShell source에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.85 / 85 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F04-L01 param( 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 runner의 mandatory/optional PowerShell parameter 계약을 연다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
2줄F04-L02 [Parameter(Mandatory=$true)][string]$ProjectRoot, 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 caller가 반드시 넘겨야 할 absolute learner/evidence path parameter를 선언한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
3줄F04-L03 [Parameter(Mandatory=$true)][string]$EvidencePath, 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 caller가 반드시 넘겨야 할 absolute learner/evidence path parameter를 선언한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
4줄F04-L04 [string]$Tag='financial-core:local' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 검사할 local image tag 기본값을 선언한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
5줄F04-L05 ) 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
6줄F04-L06 $ErrorActionPreference='Stop' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 PowerShell cmdlet error를 즉시 terminating error로 바꾼다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
7줄F04-L07 if(-not[IO.Path]::IsPathRooted($ProjectRoot)){throw 'ProjectRoot must be an absolute learner project path'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 상대 path를 거절해 learner root/evidence owner를 명확히 한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
8줄F04-L08 if(-not[IO.Path]::IsPathRooted($EvidencePath)){throw 'EvidencePath must be an absolute learner-owned path'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 상대 path를 거절해 learner root/evidence owner를 명확히 한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
9줄F04-L09 $project=(Resolve-Path -LiteralPath $ProjectRoot).Path 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner project path를 resolve해 canonical absolute path로 만든다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
10줄F04-L10 $target=[IO.Path]::GetFullPath($EvidencePath) 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 evidence target을 normalized absolute path로 만든다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
11줄F04-L11 $wrapper=Join-Path $project 'gradlew.bat' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner Gradle Wrapper path를 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
12줄F04-L12 $buildFile=Join-Path $project 'build.gradle' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner build.gradle path를 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
13줄F04-L13 $dockerfile=Join-Path $project 'Dockerfile' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner Dockerfile path를 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
14줄F04-L14 $dockerignore=Join-Path $project '.dockerignore' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner .dockerignore path를 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
15줄F04-L15 foreach($path in @($wrapper,$buildFile,$dockerfile,$dockerignore)){if(!(Test-Path -LiteralPath $path -PathType Leaf)){throw "W21 learner build input missing: $path"}} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 모든 필수 learner input file이 실제 leaf인지 검사한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
16줄F04-L16 $dockerfileBody=Get-Content -Raw -LiteralPath $dockerfile 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Dockerfile raw text를 static guard 입력으로 읽는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
17줄F04-L17 $dockerignoreBody=Get-Content -Raw -LiteralPath $dockerignore 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 .dockerignore raw text를 static guard 입력으로 읽는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
18줄F04-L18 if($dockerfileBody-cmatch'(?i)TODO|TBD|FILL_ME|PLACEHOLDER|REPLACE_WITH_'){throw 'W21 Dockerfile contains an unresolved placeholder'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 미해결 TODO/placeholder가 있으면 build 전에 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
19줄F04-L19 $fromCount=([regex]::Matches($dockerfileBody,'(?im)^\s*FROM\s+')).Count 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 Dockerfile의 FROM directive 수를 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
20줄F04-L20 if($fromCount-lt2){throw "W21 Dockerfile must contain builder and runtime stages actual=$fromCount"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 builder/runtime 최소 두 stage가 아니면 거절한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
21줄F04-L21 if($dockerfileBody-cnotmatch'(?im)^\s*COPY\s+--from=[^\s]+\s+' ){throw 'W21 Dockerfile must copy the jar from the builder stage'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 COPY --from 또는 exact USER 10001 계약이 없으면 거절한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
22줄F04-L22 if($dockerfileBody-cnotmatch'(?im)^\s*USER\s+10001\s*$'){throw 'W21 runtime stage must declare USER 10001'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 COPY --from 또는 exact USER 10001 계약이 없으면 거절한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
23줄F04-L23 foreach($token in @('.git','evidence','build','*.env')){ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 필수 ignore token 네 개를 하나씩 검사한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
24줄F04-L24 $pattern='(?im)^\s*'+[regex]::Escape($token)+'(?:\s*|/?)$' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 token의 exact-line regex pattern을 만든다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
25줄F04-L25 if($dockerignoreBody-cnotmatch$pattern){throw "W21 .dockerignore missing exact exclusion: $token"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 ignore token이 없으면 fail-closed로 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
26줄F04-L26 } 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
27줄F04-L27 Push-Location $project 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 native docker/Gradle 상대 path 기준을 learner root로 바꾼다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
28줄F04-L28 $createdContainer='' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 finally cleanup에 사용할 inspection container ID를 초기화한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
29줄F04-L29 $copiedJar='' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 finally cleanup에 사용할 임시 jar path를 초기화한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
30줄F04-L30 try{ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 build·inspect·evidence 단계와 반드시 실행할 cleanup 범위를 연다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
31줄F04-L31 & docker build --file $dockerfile --tag $Tag $project 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 검증된 Dockerfile로 learner image를 build한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
32줄F04-L32 if($LASTEXITCODE-ne 0){throw "W21 docker build exit=$LASTEXITCODE"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
33줄F04-L33 $user=((& docker inspect $Tag --format '{{.Config.User}}')-join'').Trim() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 image Config.User 값을 docker inspect로 읽는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
34줄F04-L34 if($LASTEXITCODE-ne 0){throw "W21 docker inspect exit=$LASTEXITCODE"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
35줄F04-L35 if($user-cne'10001'){throw "W21 image user must be 10001 actual=$user"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 35번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
36줄F04-L36 $imageId=((& docker inspect $Tag --format '{{.Id}}')-join'').Trim() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 immutable image ID를 inspect해 sha256 형태로 읽는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
37줄F04-L37 if($LASTEXITCODE-ne0-or$imageId-cnotmatch'^sha256:[0-9a-f]{64}$'){throw "W21 invalid image id: $imageId"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
38줄F04-L38 $runtimeUid=((& docker run --rm --entrypoint sh $Tag -c 'id -u')-join'').Trim() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 실제 container shell의 `id -u`를 읽는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
39줄F04-L39 if($LASTEXITCODE-ne0-or$runtimeUid-cne'10001'){throw "W21 runtime uid mismatch actual=$runtimeUid"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
40줄F04-L40 & docker run --rm --entrypoint sh $Tag -c 'test ! -e /app/.git -a ! -e /app/evidence -a ! -e /app/.env; ! command -v gradle >/dev/null 2>&1; ! command -v javac >/dev/null 2>&1' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 final image에 금지 path와 builder tool이 없는지 probe한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
41줄F04-L41 if($LASTEXITCODE-ne0){throw 'W21 final image leaks source/evidence/secret or builder tools'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
42줄F04-L42 $createdContainer=((& docker create $Tag)-join'').Trim() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 finally cleanup에 사용할 inspection container ID를 초기화한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
43줄F04-L43 if($LASTEXITCODE-ne0-or[string]::IsNullOrWhiteSpace($createdContainer)){throw 'W21 could not create image inspection container'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
44줄F04-L44 $copiedJar=Join-Path ([IO.Path]::GetTempPath()) ("w21-app-"+[guid]::NewGuid().ToString('N')+'.jar') 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 finally cleanup에 사용할 임시 jar path를 초기화한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
45줄F04-L45 & docker cp "${createdContainer}:/app/app.jar" $copiedJar 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 final image의 /app/app.jar를 host temporary file로 복사한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
46줄F04-L46 if($LASTEXITCODE-ne0-or!(Test-Path -LiteralPath $copiedJar -PathType Leaf)){throw 'W21 runtime app.jar extraction failed'} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 바로 앞 native docker command가 실패했으면 즉시 중단한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
47줄F04-L47 $jarHash=(Get-FileHash -Algorithm SHA256 $copiedJar).Hash.ToLowerInvariant() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 실제 packaged jar SHA-256을 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
48줄F04-L48 $buildHash=(Get-FileHash -Algorithm SHA256 $buildFile).Hash.ToLowerInvariant() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 learner build.gradle SHA-256을 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
49줄F04-L49 $dockerfileHash=(Get-FileHash -Algorithm SHA256 $dockerfile).Hash.ToLowerInvariant() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 learner Dockerfile SHA-256을 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
50줄F04-L50 $dockerignoreHash=(Get-FileHash -Algorithm SHA256 $dockerignore).Hash.ToLowerInvariant() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 learner .dockerignore SHA-256을 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
51줄F04-L51 New-Item -ItemType Directory -Force (Split-Path -Parent $target)|Out-Null 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 learner-owned evidence parent directory가 없으면 만든다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
52줄F04-L52 $tmp="$target.tmp" 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 부분 receipt가 final path에 보이지 않도록 temporary path를 정한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
53줄F04-L53 if([IO.Path]::GetExtension($target)-ieq'.json'){ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 evidence extension에 따라 JSON 또는 text receipt 형식을 선택한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
54줄F04-L54 [ordered]@{status='W21_IMAGE_GREEN';source='LEARNER_PROJECT';image=$Tag;image_id=$imageId;user=$user;stage_count=$fromCount;runtime_uid=$runtimeUid;builder_tools_absent=$true;forbidden_paths_absent=$true;build_gradle_sha256=$buildHash;jar_sha256=$jarHash;dockerfile_sha256=$dockerfileHash;dockerignore_sha256=$dockerignoreHash;native_exit=0}|ConvertTo-Json|Set-Content -Encoding utf8 $tmp 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 검사 결과와 source/JAR/image hash를 ordered JSON receipt에 담는다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
55줄F04-L55 }else{ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 뒤의 선언·검증 문장을 묶을 block을 연다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
56줄F04-L56 @( 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 text evidence에 쓸 안정된 marker line 배열을 연다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
57줄F04-L57 '# W21 Docker image evidence', 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 57번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
58줄F04-L58 'status: W21_IMAGE_GREEN', 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 58번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
59줄F04-L59 "image: $Tag", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 59번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
60줄F04-L60 "image_id: $imageId", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 60번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
61줄F04-L61 "user: $user", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 61번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
62줄F04-L62 'source: LEARNER_PROJECT', 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 62번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
63줄F04-L63 "build_gradle_sha256: $buildHash", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 63번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
64줄F04-L64 "jar_sha256: $jarHash", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 64번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
65줄F04-L65 "dockerfile_sha256: $dockerfileHash", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 65번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
66줄F04-L66 "dockerignore_sha256: $dockerignoreHash", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 66번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
67줄F04-L67 "stage_count: $fromCount", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 67번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
68줄F04-L68 "runtime_uid: $runtimeUid", 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 68번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
69줄F04-L69 'builder_tools_absent: true', 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 69번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
70줄F04-L70 'forbidden_paths_absent: true', 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 70번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
71줄F04-L71 'native_exit: 0' 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 71번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
72줄F04-L72 )|Set-Content -Encoding utf8 $tmp 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 72번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
73줄F04-L73 } 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
74줄F04-L74 Move-Item -Force -LiteralPath $tmp -Destination $target 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 완성 temporary evidence를 final target으로 atomic replace한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
75줄F04-L75 $saved=Get-Content -Raw -LiteralPath $target 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 방금 쓴 evidence를 다시 읽어 marker를 재검증한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
76줄F04-L76 foreach($marker in @('W21_IMAGE_GREEN','LEARNER_PROJECT',$Tag,$imageId,'10001',$fromCount,$buildHash,$jarHash,$dockerfileHash,$dockerignoreHash,'native_exit')){ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 필수 marker/hash/value가 저장본에 모두 있는지 검사한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
77줄F04-L77 if($saved-cnotmatch[regex]::Escape([string]$marker)){throw "W21 evidence marker missing: $marker"} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 정본 image evidence producer에서 77번째 원문 문장을 앞뒤 단계와 연결한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
78줄F04-L78 } 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
79줄F04-L79 $hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 final evidence file 자체 SHA-256을 계산한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
80줄F04-L80 "W21_IMAGE_GREEN user=10001 image_id=$imageId evidence_sha256=$hash native_exit=0" 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 image evidence 성공 marker와 native exit 0을 출력한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
81줄F04-L81 }finally{ 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 성공·실패와 무관하게 임시 resource cleanup을 시작한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
82줄F04-L82 if($createdContainer){& docker rm -f $createdContainer 2>$null|Out-Null} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 만든 inspection container만 강제로 제거한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `runtime_uid=10001` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Compose/DB lifecycle을 실행하지 않는다
83줄F04-L83 if($copiedJar-and(Test-Path -LiteralPath $copiedJar)){Remove-Item -LiteralPath $copiedJar -Force} 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 복사한 temporary jar가 있으면 제거한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `builder_tools_absent=true` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application health를 증명하지 않는다
84줄F04-L84 Pop-Location 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 caller의 원래 working directory를 복원한다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `native_exit=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
cloud 배포 Green이 아니다
85줄F04-L85 } 공사장과 완성 매장을 분리해 포장·검수하는 image 체크리스트 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
absolute ProjectRoot/EvidencePath, Dockerfile, .dockerignore, Docker engine
결과·효과
이 줄 뒤에는 `W21_IMAGE_GREEN` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Dockerfile을 생성하지 않는다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `native_exit=0`이야.

  4. 키타

    `application health를 증명하지 않는다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 5개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 hash로 고정한 native 정본 source에서 그대로 잘랐습니다.

F04-C01 · parameters and learner inputs1–16줄
1–16줄 원본
param(
 [Parameter(Mandatory=$true)][string]$ProjectRoot,
 [Parameter(Mandatory=$true)][string]$EvidencePath,
 [string]$Tag='financial-core:local'
)
$ErrorActionPreference='Stop'
if(-not[IO.Path]::IsPathRooted($ProjectRoot)){throw 'ProjectRoot must be an absolute learner project path'}
if(-not[IO.Path]::IsPathRooted($EvidencePath)){throw 'EvidencePath must be an absolute learner-owned path'}
$project=(Resolve-Path -LiteralPath $ProjectRoot).Path
$target=[IO.Path]::GetFullPath($EvidencePath)
$wrapper=Join-Path $project 'gradlew.bat'
$buildFile=Join-Path $project 'build.gradle'
$dockerfile=Join-Path $project 'Dockerfile'
$dockerignore=Join-Path $project '.dockerignore'
foreach($path in @($wrapper,$buildFile,$dockerfile,$dockerignore)){if(!(Test-Path -LiteralPath $path -PathType Leaf)){throw "W21 learner build input missing: $path"}}
$dockerfileBody=Get-Content -Raw -LiteralPath $dockerfile
F04-C02 · static Dockerfile and ignore guards17–28줄
17–28줄 원본
$dockerignoreBody=Get-Content -Raw -LiteralPath $dockerignore
if($dockerfileBody-cmatch'(?i)TODO|TBD|FILL_ME|PLACEHOLDER|REPLACE_WITH_'){throw 'W21 Dockerfile contains an unresolved placeholder'}
$fromCount=([regex]::Matches($dockerfileBody,'(?im)^\s*FROM\s+')).Count
if($fromCount-lt2){throw "W21 Dockerfile must contain builder and runtime stages actual=$fromCount"}
if($dockerfileBody-cnotmatch'(?im)^\s*COPY\s+--from=[^\s]+\s+' ){throw 'W21 Dockerfile must copy the jar from the builder stage'}
if($dockerfileBody-cnotmatch'(?im)^\s*USER\s+10001\s*$'){throw 'W21 runtime stage must declare USER 10001'}
foreach($token in @('.git','evidence','build','*.env')){
 $pattern='(?im)^\s*'+[regex]::Escape($token)+'(?:\s*|/?)$'
 if($dockerignoreBody-cnotmatch$pattern){throw "W21 .dockerignore missing exact exclusion: $token"}
}
Push-Location $project
$createdContainer=''
F04-C03 · build inspect and runtime probes29–48줄
29–48줄 원본
$copiedJar=''
try{
 & docker build --file $dockerfile --tag $Tag $project
 if($LASTEXITCODE-ne 0){throw "W21 docker build exit=$LASTEXITCODE"}
 $user=((& docker inspect $Tag --format '{{.Config.User}}')-join'').Trim()
 if($LASTEXITCODE-ne 0){throw "W21 docker inspect exit=$LASTEXITCODE"}
 if($user-cne'10001'){throw "W21 image user must be 10001 actual=$user"}
 $imageId=((& docker inspect $Tag --format '{{.Id}}')-join'').Trim()
 if($LASTEXITCODE-ne0-or$imageId-cnotmatch'^sha256:[0-9a-f]{64}$'){throw "W21 invalid image id: $imageId"}
 $runtimeUid=((& docker run --rm --entrypoint sh $Tag -c 'id -u')-join'').Trim()
 if($LASTEXITCODE-ne0-or$runtimeUid-cne'10001'){throw "W21 runtime uid mismatch actual=$runtimeUid"}
 & docker run --rm --entrypoint sh $Tag -c 'test ! -e /app/.git -a ! -e /app/evidence -a ! -e /app/.env; ! command -v gradle >/dev/null 2>&1; ! command -v javac >/dev/null 2>&1'
 if($LASTEXITCODE-ne0){throw 'W21 final image leaks source/evidence/secret or builder tools'}
 $createdContainer=((& docker create $Tag)-join'').Trim()
 if($LASTEXITCODE-ne0-or[string]::IsNullOrWhiteSpace($createdContainer)){throw 'W21 could not create image inspection container'}
 $copiedJar=Join-Path ([IO.Path]::GetTempPath()) ("w21-app-"+[guid]::NewGuid().ToString('N')+'.jar')
 & docker cp "${createdContainer}:/app/app.jar" $copiedJar
 if($LASTEXITCODE-ne0-or!(Test-Path -LiteralPath $copiedJar -PathType Leaf)){throw 'W21 runtime app.jar extraction failed'}
 $jarHash=(Get-FileHash -Algorithm SHA256 $copiedJar).Hash.ToLowerInvariant()
 $buildHash=(Get-FileHash -Algorithm SHA256 $buildFile).Hash.ToLowerInvariant()
F04-C04 · JAR and source hash receipt49–64줄
49–64줄 원본
 $dockerfileHash=(Get-FileHash -Algorithm SHA256 $dockerfile).Hash.ToLowerInvariant()
 $dockerignoreHash=(Get-FileHash -Algorithm SHA256 $dockerignore).Hash.ToLowerInvariant()
 New-Item -ItemType Directory -Force (Split-Path -Parent $target)|Out-Null
 $tmp="$target.tmp"
 if([IO.Path]::GetExtension($target)-ieq'.json'){
  [ordered]@{status='W21_IMAGE_GREEN';source='LEARNER_PROJECT';image=$Tag;image_id=$imageId;user=$user;stage_count=$fromCount;runtime_uid=$runtimeUid;builder_tools_absent=$true;forbidden_paths_absent=$true;build_gradle_sha256=$buildHash;jar_sha256=$jarHash;dockerfile_sha256=$dockerfileHash;dockerignore_sha256=$dockerignoreHash;native_exit=0}|ConvertTo-Json|Set-Content -Encoding utf8 $tmp
 }else{
  @(
   '# W21 Docker image evidence',
   'status: W21_IMAGE_GREEN',
   "image: $Tag",
   "image_id: $imageId",
   "user: $user",
   'source: LEARNER_PROJECT',
   "build_gradle_sha256: $buildHash",
   "jar_sha256: $jarHash",
F04-C05 · atomic publish marker and cleanup65–85줄
65–85줄 원본
   "dockerfile_sha256: $dockerfileHash",
   "dockerignore_sha256: $dockerignoreHash",
   "stage_count: $fromCount",
   "runtime_uid: $runtimeUid",
   'builder_tools_absent: true',
   'forbidden_paths_absent: true',
   'native_exit: 0'
  )|Set-Content -Encoding utf8 $tmp
 }
 Move-Item -Force -LiteralPath $tmp -Destination $target
 $saved=Get-Content -Raw -LiteralPath $target
 foreach($marker in @('W21_IMAGE_GREEN','LEARNER_PROJECT',$Tag,$imageId,'10001',$fromCount,$buildHash,$jarHash,$dockerfileHash,$dockerignoreHash,'native_exit')){
  if($saved-cnotmatch[regex]::Escape([string]$marker)){throw "W21 evidence marker missing: $marker"}
 }
 $hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()
 "W21_IMAGE_GREEN user=10001 image_id=$imageId evidence_sha256=$hash native_exit=0"
}finally{
 if($createdContainer){& docker rm -f $createdContainer 2>$null|Out-Null}
 if($copiedJar-and(Test-Path -LiteralPath $copiedJar)){Remove-Item -LiteralPath $copiedJar -Force}
 Pop-Location
}
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 85줄을 모두 한국어로 옮깁니다.

전체 번역 85 / 85

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1param(runner의 mandatory/optional PowerShell parameter 계약을 연다.
2 [Parameter(Mandatory=$true)][string]$ProjectRoot,caller가 반드시 넘겨야 할 absolute learner/evidence path parameter를 선언한다.
3 [Parameter(Mandatory=$true)][string]$EvidencePath,caller가 반드시 넘겨야 할 absolute learner/evidence path parameter를 선언한다.
4 [string]$Tag='financial-core:local'검사할 local image tag 기본값을 선언한다.
5)현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
6$ErrorActionPreference='Stop'PowerShell cmdlet error를 즉시 terminating error로 바꾼다.
7if(-not[IO.Path]::IsPathRooted($ProjectRoot)){throw 'ProjectRoot must be an absolute learner project path'}상대 path를 거절해 learner root/evidence owner를 명확히 한다.
8if(-not[IO.Path]::IsPathRooted($EvidencePath)){throw 'EvidencePath must be an absolute learner-owned path'}상대 path를 거절해 learner root/evidence owner를 명확히 한다.
9$project=(Resolve-Path -LiteralPath $ProjectRoot).Pathlearner project path를 resolve해 canonical absolute path로 만든다.
10$target=[IO.Path]::GetFullPath($EvidencePath)evidence target을 normalized absolute path로 만든다.
11$wrapper=Join-Path $project 'gradlew.bat'learner Gradle Wrapper path를 계산한다.
12$buildFile=Join-Path $project 'build.gradle'learner build.gradle path를 계산한다.
13$dockerfile=Join-Path $project 'Dockerfile'learner Dockerfile path를 계산한다.
14$dockerignore=Join-Path $project '.dockerignore'learner .dockerignore path를 계산한다.
15foreach($path in @($wrapper,$buildFile,$dockerfile,$dockerignore)){if(!(Test-Path -LiteralPath $path -PathType Leaf)){throw "W21 learner build input missing: $path"}}모든 필수 learner input file이 실제 leaf인지 검사한다.
16$dockerfileBody=Get-Content -Raw -LiteralPath $dockerfileDockerfile raw text를 static guard 입력으로 읽는다.
17$dockerignoreBody=Get-Content -Raw -LiteralPath $dockerignore.dockerignore raw text를 static guard 입력으로 읽는다.
18if($dockerfileBody-cmatch'(?i)TODO|TBD|FILL_ME|PLACEHOLDER|REPLACE_WITH_'){throw 'W21 Dockerfile contains an unresolved placeholder'}미해결 TODO/placeholder가 있으면 build 전에 중단한다.
19$fromCount=([regex]::Matches($dockerfileBody,'(?im)^\s*FROM\s+')).CountDockerfile의 FROM directive 수를 계산한다.
20if($fromCount-lt2){throw "W21 Dockerfile must contain builder and runtime stages actual=$fromCount"}builder/runtime 최소 두 stage가 아니면 거절한다.
21if($dockerfileBody-cnotmatch'(?im)^\s*COPY\s+--from=[^\s]+\s+' ){throw 'W21 Dockerfile must copy the jar from the builder stage'}COPY --from 또는 exact USER 10001 계약이 없으면 거절한다.
22if($dockerfileBody-cnotmatch'(?im)^\s*USER\s+10001\s*$'){throw 'W21 runtime stage must declare USER 10001'}COPY --from 또는 exact USER 10001 계약이 없으면 거절한다.
23foreach($token in @('.git','evidence','build','*.env')){필수 ignore token 네 개를 하나씩 검사한다.
24 $pattern='(?im)^\s*'+[regex]::Escape($token)+'(?:\s*|/?)$'현재 token의 exact-line regex pattern을 만든다.
25 if($dockerignoreBody-cnotmatch$pattern){throw "W21 .dockerignore missing exact exclusion: $token"}현재 ignore token이 없으면 fail-closed로 중단한다.
26}현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
27Push-Location $projectnative docker/Gradle 상대 path 기준을 learner root로 바꾼다.
28$createdContainer=''finally cleanup에 사용할 inspection container ID를 초기화한다.
29$copiedJar=''finally cleanup에 사용할 임시 jar path를 초기화한다.
30try{build·inspect·evidence 단계와 반드시 실행할 cleanup 범위를 연다.
31 & docker build --file $dockerfile --tag $Tag $project검증된 Dockerfile로 learner image를 build한다.
32 if($LASTEXITCODE-ne 0){throw "W21 docker build exit=$LASTEXITCODE"}바로 앞 native docker command가 실패했으면 즉시 중단한다.
33 $user=((& docker inspect $Tag --format '{{.Config.User}}')-join'').Trim()image Config.User 값을 docker inspect로 읽는다.
34 if($LASTEXITCODE-ne 0){throw "W21 docker inspect exit=$LASTEXITCODE"}바로 앞 native docker command가 실패했으면 즉시 중단한다.
35 if($user-cne'10001'){throw "W21 image user must be 10001 actual=$user"}정본 image evidence producer에서 35번째 원문 문장을 앞뒤 단계와 연결한다.
36 $imageId=((& docker inspect $Tag --format '{{.Id}}')-join'').Trim()immutable image ID를 inspect해 sha256 형태로 읽는다.
37 if($LASTEXITCODE-ne0-or$imageId-cnotmatch'^sha256:[0-9a-f]{64}$'){throw "W21 invalid image id: $imageId"}바로 앞 native docker command가 실패했으면 즉시 중단한다.
38 $runtimeUid=((& docker run --rm --entrypoint sh $Tag -c 'id -u')-join'').Trim()실제 container shell의 `id -u`를 읽는다.
39 if($LASTEXITCODE-ne0-or$runtimeUid-cne'10001'){throw "W21 runtime uid mismatch actual=$runtimeUid"}바로 앞 native docker command가 실패했으면 즉시 중단한다.
40 & docker run --rm --entrypoint sh $Tag -c 'test ! -e /app/.git -a ! -e /app/evidence -a ! -e /app/.env; ! command -v gradle >/dev/null 2>&1; ! command -v javac >/dev/null 2>&1'final image에 금지 path와 builder tool이 없는지 probe한다.
41 if($LASTEXITCODE-ne0){throw 'W21 final image leaks source/evidence/secret or builder tools'}바로 앞 native docker command가 실패했으면 즉시 중단한다.
42 $createdContainer=((& docker create $Tag)-join'').Trim()finally cleanup에 사용할 inspection container ID를 초기화한다.
43 if($LASTEXITCODE-ne0-or[string]::IsNullOrWhiteSpace($createdContainer)){throw 'W21 could not create image inspection container'}바로 앞 native docker command가 실패했으면 즉시 중단한다.
44 $copiedJar=Join-Path ([IO.Path]::GetTempPath()) ("w21-app-"+[guid]::NewGuid().ToString('N')+'.jar')finally cleanup에 사용할 임시 jar path를 초기화한다.
45 & docker cp "${createdContainer}:/app/app.jar" $copiedJarfinal image의 /app/app.jar를 host temporary file로 복사한다.
46 if($LASTEXITCODE-ne0-or!(Test-Path -LiteralPath $copiedJar -PathType Leaf)){throw 'W21 runtime app.jar extraction failed'}바로 앞 native docker command가 실패했으면 즉시 중단한다.
47 $jarHash=(Get-FileHash -Algorithm SHA256 $copiedJar).Hash.ToLowerInvariant()실제 packaged jar SHA-256을 계산한다.
48 $buildHash=(Get-FileHash -Algorithm SHA256 $buildFile).Hash.ToLowerInvariant()현재 learner build.gradle SHA-256을 계산한다.
49 $dockerfileHash=(Get-FileHash -Algorithm SHA256 $dockerfile).Hash.ToLowerInvariant()현재 learner Dockerfile SHA-256을 계산한다.
50 $dockerignoreHash=(Get-FileHash -Algorithm SHA256 $dockerignore).Hash.ToLowerInvariant()현재 learner .dockerignore SHA-256을 계산한다.
51 New-Item -ItemType Directory -Force (Split-Path -Parent $target)|Out-Nulllearner-owned evidence parent directory가 없으면 만든다.
52 $tmp="$target.tmp"부분 receipt가 final path에 보이지 않도록 temporary path를 정한다.
53 if([IO.Path]::GetExtension($target)-ieq'.json'){evidence extension에 따라 JSON 또는 text receipt 형식을 선택한다.
54 [ordered]@{status='W21_IMAGE_GREEN';source='LEARNER_PROJECT';image=$Tag;image_id=$imageId;user=$user;stage_count=$fromCount;runtime_uid=$runtimeUid;builder_tools_absent=$true;forbidden_paths_absent=$true;build_gradle_sha256=$buildHash;jar_sha256=$jarHash;dockerfile_sha256=$dockerfileHash;dockerignore_sha256=$dockerignoreHash;native_exit=0}|ConvertTo-Json|Set-Content -Encoding utf8 $tmp검사 결과와 source/JAR/image hash를 ordered JSON receipt에 담는다.
55 }else{뒤의 선언·검증 문장을 묶을 block을 연다.
56 @(text evidence에 쓸 안정된 marker line 배열을 연다.
57 '# W21 Docker image evidence',정본 image evidence producer에서 57번째 원문 문장을 앞뒤 단계와 연결한다.
58 'status: W21_IMAGE_GREEN',정본 image evidence producer에서 58번째 원문 문장을 앞뒤 단계와 연결한다.
59 "image: $Tag",정본 image evidence producer에서 59번째 원문 문장을 앞뒤 단계와 연결한다.
60 "image_id: $imageId",정본 image evidence producer에서 60번째 원문 문장을 앞뒤 단계와 연결한다.
61 "user: $user",정본 image evidence producer에서 61번째 원문 문장을 앞뒤 단계와 연결한다.
62 'source: LEARNER_PROJECT',정본 image evidence producer에서 62번째 원문 문장을 앞뒤 단계와 연결한다.
63 "build_gradle_sha256: $buildHash",정본 image evidence producer에서 63번째 원문 문장을 앞뒤 단계와 연결한다.
64 "jar_sha256: $jarHash",정본 image evidence producer에서 64번째 원문 문장을 앞뒤 단계와 연결한다.
65 "dockerfile_sha256: $dockerfileHash",정본 image evidence producer에서 65번째 원문 문장을 앞뒤 단계와 연결한다.
66 "dockerignore_sha256: $dockerignoreHash",정본 image evidence producer에서 66번째 원문 문장을 앞뒤 단계와 연결한다.
67 "stage_count: $fromCount",정본 image evidence producer에서 67번째 원문 문장을 앞뒤 단계와 연결한다.
68 "runtime_uid: $runtimeUid",정본 image evidence producer에서 68번째 원문 문장을 앞뒤 단계와 연결한다.
69 'builder_tools_absent: true',정본 image evidence producer에서 69번째 원문 문장을 앞뒤 단계와 연결한다.
70 'forbidden_paths_absent: true',정본 image evidence producer에서 70번째 원문 문장을 앞뒤 단계와 연결한다.
71 'native_exit: 0'정본 image evidence producer에서 71번째 원문 문장을 앞뒤 단계와 연결한다.
72 )|Set-Content -Encoding utf8 $tmp정본 image evidence producer에서 72번째 원문 문장을 앞뒤 단계와 연결한다.
73 }현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
74 Move-Item -Force -LiteralPath $tmp -Destination $target완성 temporary evidence를 final target으로 atomic replace한다.
75 $saved=Get-Content -Raw -LiteralPath $target방금 쓴 evidence를 다시 읽어 marker를 재검증한다.
76 foreach($marker in @('W21_IMAGE_GREEN','LEARNER_PROJECT',$Tag,$imageId,'10001',$fromCount,$buildHash,$jarHash,$dockerfileHash,$dockerignoreHash,'native_exit')){필수 marker/hash/value가 저장본에 모두 있는지 검사한다.
77 if($saved-cnotmatch[regex]::Escape([string]$marker)){throw "W21 evidence marker missing: $marker"}정본 image evidence producer에서 77번째 원문 문장을 앞뒤 단계와 연결한다.
78 }현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
79 $hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()final evidence file 자체 SHA-256을 계산한다.
80 "W21_IMAGE_GREEN user=10001 image_id=$imageId evidence_sha256=$hash native_exit=0"image evidence 성공 marker와 native exit 0을 출력한다.
81}finally{성공·실패와 무관하게 임시 resource cleanup을 시작한다.
82 if($createdContainer){& docker rm -f $createdContainer 2>$null|Out-Null}만든 inspection container만 강제로 제거한다.
83 if($copiedJar-and(Test-Path -LiteralPath $copiedJar)){Remove-Item -LiteralPath $copiedJar -Force}복사한 temporary jar가 있으면 제거한다.
84 Pop-Locationcaller의 원래 working directory를 복원한다.
85}현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다. 다만 Dockerfile을 생성하지 않는다

문법 해부

  • PowerShell native command 뒤 `$LASTEXITCODE`를 즉시 읽어 실패를 fail-closed로 바꾼다.
  • `try/finally`는 검사 중 실패해도 임시 container와 JAR를 정리한다.

실행 순서

  1. absolute path·필수 파일·placeholder를 검증한다.
  2. Dockerfile stage/USER와 ignore pattern을 정적으로 검사한다.
  3. image build 뒤 user·image id·runtime uid·금지 경로를 probe한다.
  4. JAR/source hash evidence를 temporary file에 쓴 뒤 atomic replace하고 cleanup한다.

W21 조각별 정밀 해설

F04-C01 · parameters and learner inputs
문법 해부
1~16줄의 `parameters and learner inputs`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `W21_IMAGE_GREEN`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `W21_IMAGE_GREEN`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: Dockerfile을 생성하지 않는다.
착각 방지
`parameters and learner inputs`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: Dockerfile을 생성하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F04-C02 · static Dockerfile and ignore guards
문법 해부
17~28줄의 `static Dockerfile and ignore guards`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `runtime_uid=10001`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `runtime_uid=10001`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: Compose/DB lifecycle을 실행하지 않는다.
착각 방지
`static Dockerfile and ignore guards`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: Compose/DB lifecycle을 실행하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F04-C03 · build inspect and runtime probes
문법 해부
29~48줄의 `build inspect and runtime probes`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `builder_tools_absent=true`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `builder_tools_absent=true`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: application health를 증명하지 않는다.
착각 방지
`build inspect and runtime probes`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: application health를 증명하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F04-C04 · JAR and source hash receipt
문법 해부
49~64줄의 `JAR and source hash receipt`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `native_exit=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `native_exit=0`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: cloud 배포 Green이 아니다.
착각 방지
`JAR and source hash receipt`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: cloud 배포 Green이 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F04-C05 · atomic publish marker and cleanup
문법 해부
65~85줄의 `atomic publish marker and cleanup`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `W21_IMAGE_GREEN`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `W21_IMAGE_GREEN`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: Dockerfile을 생성하지 않는다.
착각 방지
`atomic publish marker and cleanup`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: Dockerfile을 생성하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    root image도 build는 성공한다.

  3. 왜 틀렸는지: user·tool·path·jar hash는 아직 모른다.

  4. 키타

    수정: 성공 뒤 inspect/run/cp를 순서대로 한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F04-T01 input guardProjectRoot/EvidencePathabsolute+file checks검사 가능 pathlearner input을 생성하지 않는다
F04-T02 imageDockerfile/.dockerignorebuild+inspect+runuid10001·tools/path absenthealth는 보지 않는다
F04-T03 receiptimage/JAR/source hashestmp -> targetW21_IMAGE_GREENmarker는 Docker 범위만 닫는다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `정본 image evidence producer`이야.

  3. 대표 경계는 `cloud 배포 Green이 아니다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

PowerShell

strict errors와 native exit checks가 제어 흐름을 중단한다.

cmdlet 성공과 docker native 성공을 섞으면 안 된다.
Docker engine

build·inspect·run·create·cp가 서로 다른 관찰을 제공한다.

한 단계 성공이 다음 단계 성공을 보장하지 않는다.
evidence

learner source와 extracted jar hash를 같은 receipt에 묶는다.

receipt hash가 learner authorship을 대신하지 않는다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ docker build 0이면 모든 검증이 끝난다

왜 틀리나 user·tool·path·jar hash는 아직 모른다.

바르게 읽기 성공 뒤 inspect/run/cp를 순서대로 한다.

반례 root image도 build는 성공한다.

❌ inspect를 build 실패 뒤에도 해도 된다

왜 틀리나 오래된 같은 tag를 읽을 수 있다.

바르게 읽기 failed build 즉시 throw한다.

반례 이전 image가 거짓 Green을 만든다.

❌ non-root user 문자열만 보면 충분하다

왜 틀리나 runtime 실제 uid도 확인해야 한다.

바르게 읽기 Config.User와 `id -u`를 둘 다 본다.

반례 entrypoint가 user를 바꿀 수 있다.

❌ evidence 파일이 있으면 현재 source와 같다

왜 틀리나 stale receipt일 수 있다.

바르게 읽기 현재 build/Dockerfile/.dockerignore hash를 저장·재대조한다.

반례 파일만 남은 이전 실행이 통과처럼 보인다.

❌ W21_IMAGE_GREEN은 app health Green이다

왜 틀리나 runner는 app을 health probe하지 않는다.

바르게 읽기 D3 host lifecycle과 분리한다.

반례 DB·API가 죽어도 image 검사는 통과할 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

Dockerfile을 생성하지 않는다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

Compose/DB lifecycle을 실행하지 않는다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

application health를 증명하지 않는다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

cloud 배포 Green이 아니다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: learner Docker input을 검증해 image를 build·inspect하고 JAR/source hash가 묶인 evidence를 atomic하게 만든다.

2단계 · 코드 조각 재조립

  1. parameters and learner inputs
  2. static Dockerfile and ignore guards
  3. build inspect and runtime probes
  4. JAR and source hash receipt
  5. atomic publish marker and cleanup

3단계 · 파일 전체 다시 쓰기

85개 물리 줄을 원본 순서로 복원하고 SHA-256 023c9ed2cabb937e6804c26344f650efbb7b8a64635157375fa16c29ee1bbdef와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

원문 정본 전체 source 확인하기
원문 정본 · 완전한 packaged PowerShell sourcescripts/run-w21-image.ps1SHA-256 023c9ed2cabb937e6804c26344f650efbb7b8a64635157375fa16c29ee1bbdef
run-w21-image.ps1 — image build·검사·hash evidence owner 전체
param(
 [Parameter(Mandatory=$true)][string]$ProjectRoot,
 [Parameter(Mandatory=$true)][string]$EvidencePath,
 [string]$Tag='financial-core:local'
)
$ErrorActionPreference='Stop'
if(-not[IO.Path]::IsPathRooted($ProjectRoot)){throw 'ProjectRoot must be an absolute learner project path'}
if(-not[IO.Path]::IsPathRooted($EvidencePath)){throw 'EvidencePath must be an absolute learner-owned path'}
$project=(Resolve-Path -LiteralPath $ProjectRoot).Path
$target=[IO.Path]::GetFullPath($EvidencePath)
$wrapper=Join-Path $project 'gradlew.bat'
$buildFile=Join-Path $project 'build.gradle'
$dockerfile=Join-Path $project 'Dockerfile'
$dockerignore=Join-Path $project '.dockerignore'
foreach($path in @($wrapper,$buildFile,$dockerfile,$dockerignore)){if(!(Test-Path -LiteralPath $path -PathType Leaf)){throw "W21 learner build input missing: $path"}}
$dockerfileBody=Get-Content -Raw -LiteralPath $dockerfile
$dockerignoreBody=Get-Content -Raw -LiteralPath $dockerignore
if($dockerfileBody-cmatch'(?i)TODO|TBD|FILL_ME|PLACEHOLDER|REPLACE_WITH_'){throw 'W21 Dockerfile contains an unresolved placeholder'}
$fromCount=([regex]::Matches($dockerfileBody,'(?im)^\s*FROM\s+')).Count
if($fromCount-lt2){throw "W21 Dockerfile must contain builder and runtime stages actual=$fromCount"}
if($dockerfileBody-cnotmatch'(?im)^\s*COPY\s+--from=[^\s]+\s+' ){throw 'W21 Dockerfile must copy the jar from the builder stage'}
if($dockerfileBody-cnotmatch'(?im)^\s*USER\s+10001\s*$'){throw 'W21 runtime stage must declare USER 10001'}
foreach($token in @('.git','evidence','build','*.env')){
 $pattern='(?im)^\s*'+[regex]::Escape($token)+'(?:\s*|/?)$'
 if($dockerignoreBody-cnotmatch$pattern){throw "W21 .dockerignore missing exact exclusion: $token"}
}
Push-Location $project
$createdContainer=''
$copiedJar=''
try{
 & docker build --file $dockerfile --tag $Tag $project
 if($LASTEXITCODE-ne 0){throw "W21 docker build exit=$LASTEXITCODE"}
 $user=((& docker inspect $Tag --format '{{.Config.User}}')-join'').Trim()
 if($LASTEXITCODE-ne 0){throw "W21 docker inspect exit=$LASTEXITCODE"}
 if($user-cne'10001'){throw "W21 image user must be 10001 actual=$user"}
 $imageId=((& docker inspect $Tag --format '{{.Id}}')-join'').Trim()
 if($LASTEXITCODE-ne0-or$imageId-cnotmatch'^sha256:[0-9a-f]{64}$'){throw "W21 invalid image id: $imageId"}
 $runtimeUid=((& docker run --rm --entrypoint sh $Tag -c 'id -u')-join'').Trim()
 if($LASTEXITCODE-ne0-or$runtimeUid-cne'10001'){throw "W21 runtime uid mismatch actual=$runtimeUid"}
 & docker run --rm --entrypoint sh $Tag -c 'test ! -e /app/.git -a ! -e /app/evidence -a ! -e /app/.env; ! command -v gradle >/dev/null 2>&1; ! command -v javac >/dev/null 2>&1'
 if($LASTEXITCODE-ne0){throw 'W21 final image leaks source/evidence/secret or builder tools'}
 $createdContainer=((& docker create $Tag)-join'').Trim()
 if($LASTEXITCODE-ne0-or[string]::IsNullOrWhiteSpace($createdContainer)){throw 'W21 could not create image inspection container'}
 $copiedJar=Join-Path ([IO.Path]::GetTempPath()) ("w21-app-"+[guid]::NewGuid().ToString('N')+'.jar')
 & docker cp "${createdContainer}:/app/app.jar" $copiedJar
 if($LASTEXITCODE-ne0-or!(Test-Path -LiteralPath $copiedJar -PathType Leaf)){throw 'W21 runtime app.jar extraction failed'}
 $jarHash=(Get-FileHash -Algorithm SHA256 $copiedJar).Hash.ToLowerInvariant()
 $buildHash=(Get-FileHash -Algorithm SHA256 $buildFile).Hash.ToLowerInvariant()
 $dockerfileHash=(Get-FileHash -Algorithm SHA256 $dockerfile).Hash.ToLowerInvariant()
 $dockerignoreHash=(Get-FileHash -Algorithm SHA256 $dockerignore).Hash.ToLowerInvariant()
 New-Item -ItemType Directory -Force (Split-Path -Parent $target)|Out-Null
 $tmp="$target.tmp"
 if([IO.Path]::GetExtension($target)-ieq'.json'){
  [ordered]@{status='W21_IMAGE_GREEN';source='LEARNER_PROJECT';image=$Tag;image_id=$imageId;user=$user;stage_count=$fromCount;runtime_uid=$runtimeUid;builder_tools_absent=$true;forbidden_paths_absent=$true;build_gradle_sha256=$buildHash;jar_sha256=$jarHash;dockerfile_sha256=$dockerfileHash;dockerignore_sha256=$dockerignoreHash;native_exit=0}|ConvertTo-Json|Set-Content -Encoding utf8 $tmp
 }else{
  @(
   '# W21 Docker image evidence',
   'status: W21_IMAGE_GREEN',
   "image: $Tag",
   "image_id: $imageId",
   "user: $user",
   'source: LEARNER_PROJECT',
   "build_gradle_sha256: $buildHash",
   "jar_sha256: $jarHash",
   "dockerfile_sha256: $dockerfileHash",
   "dockerignore_sha256: $dockerignoreHash",
   "stage_count: $fromCount",
   "runtime_uid: $runtimeUid",
   'builder_tools_absent: true',
   'forbidden_paths_absent: true',
   'native_exit: 0'
  )|Set-Content -Encoding utf8 $tmp
 }
 Move-Item -Force -LiteralPath $tmp -Destination $target
 $saved=Get-Content -Raw -LiteralPath $target
 foreach($marker in @('W21_IMAGE_GREEN','LEARNER_PROJECT',$Tag,$imageId,'10001',$fromCount,$buildHash,$jarHash,$dockerfileHash,$dockerignoreHash,'native_exit')){
  if($saved-cnotmatch[regex]::Escape([string]$marker)){throw "W21 evidence marker missing: $marker"}
 }
 $hash=(Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()
 "W21_IMAGE_GREEN user=10001 image_id=$imageId evidence_sha256=$hash native_exit=0"
}finally{
 if($createdContainer){& docker rm -f $createdContainer 2>$null|Out-Null}
 if($copiedJar-and(Test-Path -LiteralPath $copiedJar)){Remove-Item -LiteralPath $copiedJar -Force}
 Pop-Location
}
05

compose.yaml — PostgreSQL db 하나만 소유하는 Compose

project/compose.yaml

학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W21-F05
18줄 연결18줄 번역4 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

  1. `service=db only`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `application container는 없다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값service=db onlypostgres:17.10-alpinepg_isreadyfinancial-core-db
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

compose.yaml — PostgreSQL db 하나만 소유하는 Compose를 무대 운영표로 바꾸기

W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

핵심값 service=db only, postgres:17.10-alpine, pg_isready, financial-core-db을 원본 줄로 따라가되, `application container는 없다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

db service image and port

1~4줄을 한 덩어리로 읽어 1번째 움직임을 본다. W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

코드 연결
1~4줄
비유
무대 뒤 창고는 DB 하나만 열고 배우인 application은 host 무대에서 따로 서게 하는 운영 배치표
비유의 끝
application container는 없다

database environment

5~9줄을 한 덩어리로 읽어 2번째 움직임을 본다. W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

코드 연결
5~9줄
비유
무대 뒤 창고는 DB 하나만 열고 배우인 application은 host 무대에서 따로 서게 하는 운영 배치표
비유의 끝
FCL_DB_PASSWORD가 필요하다

readiness healthcheck

10~14줄을 한 덩어리로 읽어 3번째 움직임을 본다. W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

코드 연결
10~14줄
비유
무대 뒤 창고는 DB 하나만 열고 배우인 application은 host 무대에서 따로 서게 하는 운영 배치표
비유의 끝
host port는 환경 변수로 정한다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `service=db only` 맞아?

  2. 니지카

    W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

  3. 첫 source 값은 `service=db only`로 잡자.

  4. 키타

    단, `application container는 없다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `postgres:17.10-alpine`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. exact service 이름 db를 읽는다. → PostgreSQL image와 host-port mapping을 정한다.

  3. 그 다음은 DB/user/password를 환경에서 주입한다. → pg_isready healthcheck와 named volume을 연결한다.

  4. 키타

    관찰값과 `FCL_DB_PASSWORD가 필요하다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 18줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.18 / 18 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F05-L01 services: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 Compose service map을 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `service=db only` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application container는 없다
2줄F05-L02 db: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 W21이 소유할 유일한 db service를 선언한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `postgres:17.10-alpine` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
FCL_DB_PASSWORD가 필요하다
3줄F05-L03 image: postgres:17.10-alpine DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 PostgreSQL 17.10 Alpine image를 고정한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `pg_isready` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
host port는 환경 변수로 정한다
4줄F05-L04 ports: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 host-container port mapping 목록을 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `financial-core-db` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
production HA 구성이 아니다
5줄F05-L05 - "${FCL_DB_PORT:-5432}:5432" DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 환경 변수 host port를 container 5432에 publish하고 없으면 5432를 쓴다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `service=db only` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application container는 없다
6줄F05-L06 environment: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 PostgreSQL process에 전달할 DB/user/password 환경 값을 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `postgres:17.10-alpine` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
FCL_DB_PASSWORD가 필요하다
7줄F05-L07 POSTGRES_DB: financial_core DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 database 이름을 financial_core로 고정한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `pg_isready` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
host port는 환경 변수로 정한다
8줄F05-L08 POSTGRES_USER: app DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 application DB user를 app으로 고정한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `financial-core-db` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
production HA 구성이 아니다
9줄F05-L09 POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal} DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 현재 terminal의 필수 FCL_DB_PASSWORD를 전달한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `service=db only` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application container는 없다
10줄F05-L10 healthcheck: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 DB readiness를 판단할 container healthcheck를 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `postgres:17.10-alpine` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
FCL_DB_PASSWORD가 필요하다
11줄F05-L11 test: ["CMD-SHELL", "pg_isready -U app -d financial_core"] DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 pg_isready로 app@financial_core 접속 수락 상태를 검사한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `pg_isready` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
host port는 환경 변수로 정한다
12줄F05-L12 interval: 2s DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 healthcheck 반복 간격을 2초로 정한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `financial-core-db` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
production HA 구성이 아니다
13줄F05-L13 timeout: 2s DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 한 번의 healthcheck timeout을 2초로 정한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `service=db only` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
application container는 없다
14줄F05-L14 retries: 30 DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 최대 30번 재시도해 startup 시간을 허용한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `postgres:17.10-alpine` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
FCL_DB_PASSWORD가 필요하다
15줄F05-L15 volumes: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 service 또는 project volume mapping을 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `pg_isready` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
host port는 환경 변수로 정한다
16줄F05-L16 - financial-core-db:/var/lib/postgresql/data DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 PostgreSQL data용 named volume을 선언하거나 연결한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `financial-core-db` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
production HA 구성이 아니다
18줄F05-L18 volumes: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 service 또는 project volume mapping을 연다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `postgres:17.10-alpine` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
FCL_DB_PASSWORD가 필요하다
19줄F05-L19 financial-core-db: DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 PostgreSQL data용 named volume을 선언하거나 연결한다.
입력
FCL_DB_PASSWORD, FCL_DB_PORT와 PostgreSQL service
결과·효과
이 줄 뒤에는 `pg_isready` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
host port는 환경 변수로 정한다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `financial-core-db`이야.

  4. 키타

    `host port는 환경 변수로 정한다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 4개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F05-C01 · db service image and port1–4줄
1–4줄 원본
services:
  db:
    image: postgres:17.10-alpine
    ports:
F05-C02 · database environment5–9줄
5–9줄 원본
      - "${FCL_DB_PORT:-5432}:5432"
    environment:
      POSTGRES_DB: financial_core
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal}
F05-C03 · readiness healthcheck10–14줄
10–14줄 원본
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d financial_core"]
      interval: 2s
      timeout: 2s
      retries: 30
F05-C04 · named database volume15–19줄
15–19줄 원본
    volumes:
      - financial-core-db:/var/lib/postgresql/data

volumes:
  financial-core-db:
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 18줄을 모두 한국어로 옮깁니다.

전체 번역 18 / 18

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1services:Compose service map을 연다.
2 db:W21이 소유할 유일한 db service를 선언한다.
3 image: postgres:17.10-alpinePostgreSQL 17.10 Alpine image를 고정한다.
4 ports:host-container port mapping 목록을 연다.
5 - "${FCL_DB_PORT:-5432}:5432"환경 변수 host port를 container 5432에 publish하고 없으면 5432를 쓴다.
6 environment:PostgreSQL process에 전달할 DB/user/password 환경 값을 연다.
7 POSTGRES_DB: financial_coredatabase 이름을 financial_core로 고정한다.
8 POSTGRES_USER: appapplication DB user를 app으로 고정한다.
9 POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal}현재 terminal의 필수 FCL_DB_PASSWORD를 전달한다.
10 healthcheck:DB readiness를 판단할 container healthcheck를 연다.
11 test: ["CMD-SHELL", "pg_isready -U app -d financial_core"]pg_isready로 app@financial_core 접속 수락 상태를 검사한다.
12 interval: 2shealthcheck 반복 간격을 2초로 정한다.
13 timeout: 2s한 번의 healthcheck timeout을 2초로 정한다.
14 retries: 30최대 30번 재시도해 startup 시간을 허용한다.
15 volumes:service 또는 project volume mapping을 연다.
16 - financial-core-db:/var/lib/postgresql/dataPostgreSQL data용 named volume을 선언하거나 연결한다.
18volumes:service 또는 project volume mapping을 연다.
19 financial-core-db:PostgreSQL data용 named volume을 선언하거나 연결한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다. 다만 application container는 없다

문법 해부

  • Compose YAML indentation이 service·environment·healthcheck·volume 소속을 정한다.
  • `${NAME:?message}`는 필수 환경 변수가 없으면 config 단계에서 중단시킨다.

실행 순서

  1. exact service 이름 db를 읽는다.
  2. PostgreSQL image와 host-port mapping을 정한다.
  3. DB/user/password를 환경에서 주입한다.
  4. pg_isready healthcheck와 named volume을 연결한다.

W21 조각별 정밀 해설

F05-C01 · db service image and port
문법 해부
1~4줄의 `db service image and port`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `service=db only`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `service=db only`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: application container는 없다.
착각 방지
`db service image and port`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: application container는 없다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F05-C02 · database environment
문법 해부
5~9줄의 `database environment`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `postgres:17.10-alpine`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `postgres:17.10-alpine`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: FCL_DB_PASSWORD가 필요하다.
착각 방지
`database environment`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: FCL_DB_PASSWORD가 필요하다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F05-C03 · readiness healthcheck
문법 해부
10~14줄의 `readiness healthcheck`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `pg_isready`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `pg_isready`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: host port는 환경 변수로 정한다.
착각 방지
`readiness healthcheck`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: host port는 환경 변수로 정한다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F05-C04 · named database volume
문법 해부
15~19줄의 `named database volume`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `financial-core-db`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `financial-core-db`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: production HA 구성이 아니다.
착각 방지
`named database volume`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: production HA 구성이 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    application container claim은 실패다.

  3. 왜 틀렸는지: exact service는 db 하나다.

  4. 키타

    수정: app은 host Gradle bootRun으로 실행한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F05-T01 configFCL_DB_PASSWORD/FCL_DB_PORTCompose interpolationdb service specpassword를 파일에 쓰지 않는다
F05-T02 startuppostgres imageup --wait + pg_isreadyready dbapp은 container가 아니다
F05-T03 teardownCompose projectdown -vowned container/volume absent다른 사용자 volume까지 지우지 않는다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `reference DB-only Compose dependency`이야.

  3. 대표 경계는 `production HA 구성이 아니다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

Compose model

service가 db 하나인지 config --services로 확인할 수 있다.

파일 존재만으로 실행 상태는 모른다.
PostgreSQL

pg_isready가 접속 수락 가능성을 health로 보고한다.

schema migration·business query는 별도다.
volume

named volume이 DB data directory를 보존한다.

D3 disposable lifecycle은 own project volume을 제거한다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ Compose가 app도 띄운다

왜 틀리나 exact service는 db 하나다.

바르게 읽기 app은 host Gradle bootRun으로 실행한다.

반례 application container claim은 실패다.

❌ 5432를 고정해도 된다

왜 틀리나 host port 충돌 위험이 있다.

바르게 읽기 FCL_DB_PORT로 disposable port를 주입한다.

반례 기존 PostgreSQL이 선점할 수 있다.

❌ healthcheck면 schema도 준비됐다

왜 틀리나 pg_isready는 연결 수락만 본다.

바르게 읽기 migration/health/API를 뒤에서 확인한다.

반례 테이블이 없는데 DB health는 healthy일 수 있다.

❌ down만 하면 volume도 사라진다

왜 틀리나 `-v`가 없으면 named volume이 남는다.

바르게 읽기 owned lifecycle에서 down -v와 absent를 확인한다.

반례 stale data가 다음 실행을 오염시킨다.

❌ 비밀번호 기본값을 YAML에 넣으면 편하다

왜 틀리나 source/history에 secret이 남는다.

바르게 읽기 필수 환경 변수로 fail-fast한다.

반례 공유 repo에 credential이 노출된다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

application container는 없다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

FCL_DB_PASSWORD가 필요하다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

host port는 환경 변수로 정한다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

production HA 구성이 아니다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: W21 lifecycle이 소유하는 PostgreSQL db service 하나와 healthcheck·전용 volume을 정의한다.

2단계 · 코드 조각 재조립

  1. db service image and port
  2. database environment
  3. readiness healthcheck
  4. named database volume

3단계 · 파일 전체 다시 쓰기

19개 물리 줄을 원본 순서로 복원하고 SHA-256 3ef6ca229dfa8d54120fb66f2215b906ba8155784d32791d7f93f3be879ade8d와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 참조 프로젝트 입력 · learner-owned · 정본 답안 아님project/compose.yamlSHA-256 3ef6ca229dfa8d54120fb66f2215b906ba8155784d32791d7f93f3be879ade8d
compose.yaml — PostgreSQL db 하나만 소유하는 Compose 전체
services:
  db:
    image: postgres:17.10-alpine
    ports:
      - "${FCL_DB_PORT:-5432}:5432"
    environment:
      POSTGRES_DB: financial_core
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d financial_core"]
      interval: 2s
      timeout: 2s
      retries: 30
    volumes:
      - financial-core-db:/var/lib/postgresql/data

volumes:
  financial-core-db:
06

run-w21-host-app.ps1 — D3 DB+host JVM lifecycle owner

scripts/run-w21-host-app.ps1

원문 정본 · 완전한 packaged PowerShell source · 정본 · W21-F06
6줄 연결6줄 번역4 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

  1. `compose_services=[db]`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `app container proof가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값compose_services=[db]runtime=host JVMhealth=UPteardown=1container/volume/process absent
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

run-w21-host-app.ps1 — D3 DB+host JVM lifecycle owner를 무대 운영표로 바꾸기

Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

핵심값 compose_services=[db], runtime=host JVM, health=UP, teardown=1, container/volume/process absent을 원본 줄로 따라가되, `app container proof가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

parameters and packaged dependency

1~2줄을 한 덩어리로 읽어 1번째 움직임을 본다. Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

코드 연결
1~2줄
비유
공연 감독이 창고(DB)만 Compose로 열고 배우(app)는 host에서 출연시킨 뒤, 공연 종료 후 배우·창고·짐을 모두 철수하고 manifest를 남기는 한 번의 lifecycle
비유의 끝
app container proof가 아니다

host JVM environment and project scope

3~4줄을 한 덩어리로 읽어 2번째 움직임을 본다. Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

코드 연결
3~4줄
비유
공연 감독이 창고(DB)만 Compose로 열고 배우(app)는 host에서 출연시킨 뒤, 공연 종료 후 배우·창고·짐을 모두 철수하고 manifest를 남기는 한 번의 lifecycle
비유의 끝
API 200/401은 authorization proof가 아니다

D3 lifecycle try finally

5~5줄을 한 덩어리로 읽어 3번째 움직임을 본다. Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

코드 연결
5~5줄
비유
공연 감독이 창고(DB)만 Compose로 열고 배우(app)는 host에서 출연시킨 뒤, 공연 종료 후 배우·창고·짐을 모두 철수하고 manifest를 남기는 한 번의 lifecycle
비유의 끝
Stop-Process -Force는 graceful SIGTERM proof가 아니다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `compose_services=[db]` 맞아?

  2. 니지카

    Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

  3. 첫 source 값은 `compose_services=[db]`로 잡자.

  4. 키타

    단, `app container proof가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `runtime=host JVM`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. db service exactness와 readiness를 확인한다. → host JVM을 bootRun하고 aggregate health·listen PID·API 200/401을 관찰한다.

  3. 그 다음은 finally에서 process와 Compose project/volume을 정리한다. → 세 absent invariant 뒤에만 host-app-manifest.json과 W21_HOST_APP_GREEN을 만든다.

  4. 키타

    관찰값과 `API 200/401은 authorization proof가 아니다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 6줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.원문 정본 · 완전한 packaged PowerShell source에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.6 / 6 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F06-L01 param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir,[string]$ComposeProject='w21-learner-host',[int]$Port=28021,[int]$DbPort=62121,[switch]$ForceFailureAfterCompose) DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 runner의 mandatory/optional PowerShell parameter 계약을 연다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `compose_services=[db]` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
app container proof가 아니다
2줄F06-L02 $ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$referenceRoot=Split-Path -Parent $PSScriptRoot;$e=[IO.Path]::GetFullPath($EvidenceDir);$compose=Join-Path $referenceRoot 'compose.yaml';if(!(Test-Path -LiteralPath $compose)){throw 'W21 packaged DB-only compose.yaml missing'};New-Item -ItemType Directory -Force $e|Out-Null;$proc=$null;$appPid=$null;$body=$null;$caught=$null DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 PowerShell cmdlet error를 즉시 terminating error로 바꾼다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `runtime=host JVM` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
API 200/401은 authorization proof가 아니다
3줄F06-L03 $env:FCL_DB_PASSWORD='w21-disposable-password';$env:FCL_DB_PORT="$DbPort";$env:SPRING_DATASOURCE_URL="jdbc:postgresql://localhost:$DbPort/financial_core";$env:SPRING_DATASOURCE_USERNAME='app';$env:SPRING_DATASOURCE_PASSWORD=$env:FCL_DB_PASSWORD;$env:SERVER_PORT="$Port" DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 disposable DB secret/port와 Spring datasource, host server port를 현재 process 환경에 주입한다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `health=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Stop-Process -Force는 graceful SIGTERM proof가 아니다
4줄F06-L04 Push-Location $root DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 native docker/Gradle 상대 path 기준을 learner root로 바꾼다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `teardown=1` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D6/D7은 새 lifecycle 없이 이 manifest만 읽는다
5줄F06-L05 try{$services=@(& docker compose -f $compose -p $ComposeProject config --services);if($LASTEXITCODE-ne 0-or$services.Count-ne 1-or$services[0]-cne'db'){throw "W21 compose must be exact db: $services"};& docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw 'W21 db readiness failed'};if($ForceFailureAfterCompose){throw 'W21 forced partial-startup failure'};$stdout=Join-Path $e 'host-app.stdout.log';$stderr=Join-Path $e 'host-app.stderr.log';$proc=Start-Process -FilePath (Join-Path $root 'gradlew.bat') -ArgumentList @('bootRun','--no-daemon') -WorkingDirectory $root -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr;$health=$null;for($i=0;$i-lt 90;$i++){if($proc.HasExited){throw "host app exited=$($proc.ExitCode)"};try{$health=Invoke-RestMethod "http://localhost:$Port/actuator/health" -TimeoutSec 2;if($health.status-eq'UP'){break}}catch{};Start-Sleep 1};if(!$health-or$health.status-ne'UP'){throw 'host health never UP'};$listen=Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue;if(!$listen){throw "host port $Port not listening"};$appPid=[int]$listen[0].OwningProcess;$apiStatus=0;try{Invoke-WebRequest "http://localhost:$Port/api/accounts/1" -TimeoutSec 5|Out-Null;$apiStatus=200}catch{$apiStatus=[int]$_.Exception.Response.StatusCode};if($apiStatus-notin@(200,401)){throw "unexpected API status=$apiStatus"};$body=[ordered]@{compose_services=@($services);compose_project=$ComposeProject;runtime='host JVM';pid=$appPid;port=$Port;health='UP';api_status=$apiStatus;native_exit=0}}catch{$caught=$_}finally{if($appPid){Stop-Process -Id $appPid -Force -ErrorAction SilentlyContinue};if($proc-and!$proc.HasExited){Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue};& docker compose -f $compose -p $ComposeProject down -v|Out-Null;$down=$LASTEXITCODE;$containers=@(& docker compose -f $compose -p $ComposeProject ps -q);$containerAbsent=($LASTEXITCODE-eq 0-and$containers.Count-eq 0);$volumes=@(& docker volume ls -q --filter "name=^${ComposeProject}_financial-core-db$");$volumeAbsent=($LASTEXITCODE-eq 0-and$volumes.Count-eq 0);$processAbsent=if($appPid){-not[bool](Get-Process -Id $appPid -ErrorAction SilentlyContinue)}else{$true};Pop-Location;Remove-Item Env:FCL_DB_PASSWORD,Env:FCL_DB_PORT,Env:SPRING_DATASOURCE_URL,Env:SPRING_DATASOURCE_USERNAME,Env:SPRING_DATASOURCE_PASSWORD,Env:SERVER_PORT -ErrorAction SilentlyContinue} DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 D3의 핵심 try/catch/finally 한 줄이다. exact db 확인, DB ready, host bootRun, health/listen/API 관찰, process·container·volume cleanup을 한 owner lifecycle로 묶는다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `container/volume/process absent` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
app container proof가 아니다
6줄F06-L06 if($down-ne 0-or!$containerAbsent-or!$volumeAbsent-or!$processAbsent){throw "W21 cleanup failed down=$down container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"};if($caught){throw $caught};$body.teardown=1;$body.down_native_exit=$down;$body.container_absent=$containerAbsent;$body.volume_absent=$volumeAbsent;$body.process_absent=$processAbsent;$tmp=Join-Path $e 'host-app-manifest.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -LiteralPath $tmp -Destination (Join-Path $e 'host-app-manifest.json') -Force;"W21_HOST_APP_GREEN compose=db runtime=host-JVM health=UP teardown=1 container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent" DB 창고와 host JVM 배우를 한 감독이 열고 닫는 lifecycle 장부 cleanup exit와 세 absent 값을 fail-closed로 확인한 뒤 manifest를 atomic replace하고 W21_HOST_APP_GREEN marker를 출력한다.
입력
learner root, evidence dir, db-only Compose, host/DB port
결과·효과
이 줄 뒤에는 `compose_services=[db]` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
API 200/401은 authorization proof가 아니다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `container/volume/process absent`이야.

  4. 키타

    `Stop-Process -Force는 graceful SIGTERM proof가 아니다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 4개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 hash로 고정한 native 정본 source에서 그대로 잘랐습니다.

F06-C01 · parameters and packaged dependency1–2줄
1–2줄 원본
param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir,[string]$ComposeProject='w21-learner-host',[int]$Port=28021,[int]$DbPort=62121,[switch]$ForceFailureAfterCompose)
$ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$referenceRoot=Split-Path -Parent $PSScriptRoot;$e=[IO.Path]::GetFullPath($EvidenceDir);$compose=Join-Path $referenceRoot 'compose.yaml';if(!(Test-Path -LiteralPath $compose)){throw 'W21 packaged DB-only compose.yaml missing'};New-Item -ItemType Directory -Force $e|Out-Null;$proc=$null;$appPid=$null;$body=$null;$caught=$null
F06-C02 · host JVM environment and project scope3–4줄
3–4줄 원본
$env:FCL_DB_PASSWORD='w21-disposable-password';$env:FCL_DB_PORT="$DbPort";$env:SPRING_DATASOURCE_URL="jdbc:postgresql://localhost:$DbPort/financial_core";$env:SPRING_DATASOURCE_USERNAME='app';$env:SPRING_DATASOURCE_PASSWORD=$env:FCL_DB_PASSWORD;$env:SERVER_PORT="$Port"
Push-Location $root
F06-C03 · D3 lifecycle try finally5–5줄
5–5줄 원본
try{$services=@(& docker compose -f $compose -p $ComposeProject config --services);if($LASTEXITCODE-ne 0-or$services.Count-ne 1-or$services[0]-cne'db'){throw "W21 compose must be exact db: $services"};& docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw 'W21 db readiness failed'};if($ForceFailureAfterCompose){throw 'W21 forced partial-startup failure'};$stdout=Join-Path $e 'host-app.stdout.log';$stderr=Join-Path $e 'host-app.stderr.log';$proc=Start-Process -FilePath (Join-Path $root 'gradlew.bat') -ArgumentList @('bootRun','--no-daemon') -WorkingDirectory $root -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr;$health=$null;for($i=0;$i-lt 90;$i++){if($proc.HasExited){throw "host app exited=$($proc.ExitCode)"};try{$health=Invoke-RestMethod "http://localhost:$Port/actuator/health" -TimeoutSec 2;if($health.status-eq'UP'){break}}catch{};Start-Sleep 1};if(!$health-or$health.status-ne'UP'){throw 'host health never UP'};$listen=Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue;if(!$listen){throw "host port $Port not listening"};$appPid=[int]$listen[0].OwningProcess;$apiStatus=0;try{Invoke-WebRequest "http://localhost:$Port/api/accounts/1" -TimeoutSec 5|Out-Null;$apiStatus=200}catch{$apiStatus=[int]$_.Exception.Response.StatusCode};if($apiStatus-notin@(200,401)){throw "unexpected API status=$apiStatus"};$body=[ordered]@{compose_services=@($services);compose_project=$ComposeProject;runtime='host JVM';pid=$appPid;port=$Port;health='UP';api_status=$apiStatus;native_exit=0}}catch{$caught=$_}finally{if($appPid){Stop-Process -Id $appPid -Force -ErrorAction SilentlyContinue};if($proc-and!$proc.HasExited){Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue};& docker compose -f $compose -p $ComposeProject down -v|Out-Null;$down=$LASTEXITCODE;$containers=@(& docker compose -f $compose -p $ComposeProject ps -q);$containerAbsent=($LASTEXITCODE-eq 0-and$containers.Count-eq 0);$volumes=@(& docker volume ls -q --filter "name=^${ComposeProject}_financial-core-db$");$volumeAbsent=($LASTEXITCODE-eq 0-and$volumes.Count-eq 0);$processAbsent=if($appPid){-not[bool](Get-Process -Id $appPid -ErrorAction SilentlyContinue)}else{$true};Pop-Location;Remove-Item Env:FCL_DB_PASSWORD,Env:FCL_DB_PORT,Env:SPRING_DATASOURCE_URL,Env:SPRING_DATASOURCE_USERNAME,Env:SPRING_DATASOURCE_PASSWORD,Env:SERVER_PORT -ErrorAction SilentlyContinue}
F06-C04 · teardown invariant and atomic manifest6–6줄
6–6줄 원본
if($down-ne 0-or!$containerAbsent-or!$volumeAbsent-or!$processAbsent){throw "W21 cleanup failed down=$down container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"};if($caught){throw $caught};$body.teardown=1;$body.down_native_exit=$down;$body.container_absent=$containerAbsent;$body.volume_absent=$volumeAbsent;$body.process_absent=$processAbsent;$tmp=Join-Path $e 'host-app-manifest.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -LiteralPath $tmp -Destination (Join-Path $e 'host-app-manifest.json') -Force;"W21_HOST_APP_GREEN compose=db runtime=host-JVM health=UP teardown=1 container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 6줄을 모두 한국어로 옮깁니다.

전체 번역 6 / 6

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir,[string]$ComposeProject='w21-learner-host',[int]$Port=28021,[int]$DbPort=62121,[switch]$ForceFailureAfterCompose)runner의 mandatory/optional PowerShell parameter 계약을 연다.
2$ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$referenceRoot=Split-Path -Parent $PSScriptRoot;$e=[IO.Path]::GetFullPath($EvidenceDir);$compose=Join-Path $referenceRoot 'compose.yaml';if(!(Test-Path -LiteralPath $compose)){throw 'W21 packaged DB-only compose.yaml missing'};New-Item -ItemType Directory -Force $e|Out-Null;$proc=$null;$appPid=$null;$body=$null;$caught=$nullPowerShell cmdlet error를 즉시 terminating error로 바꾼다.
3$env:FCL_DB_PASSWORD='w21-disposable-password';$env:FCL_DB_PORT="$DbPort";$env:SPRING_DATASOURCE_URL="jdbc:postgresql://localhost:$DbPort/financial_core";$env:SPRING_DATASOURCE_USERNAME='app';$env:SPRING_DATASOURCE_PASSWORD=$env:FCL_DB_PASSWORD;$env:SERVER_PORT="$Port"disposable DB secret/port와 Spring datasource, host server port를 현재 process 환경에 주입한다.
4Push-Location $rootnative docker/Gradle 상대 path 기준을 learner root로 바꾼다.
5try{$services=@(& docker compose -f $compose -p $ComposeProject config --services);if($LASTEXITCODE-ne 0-or$services.Count-ne 1-or$services[0]-cne'db'){throw "W21 compose must be exact db: $services"};& docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw 'W21 db readiness failed'};if($ForceFailureAfterCompose){throw 'W21 forced partial-startup failure'};$stdout=Join-Path $e 'host-app.stdout.log';$stderr=Join-Path $e 'host-app.stderr.log';$proc=Start-Process -FilePath (Join-Path $root 'gradlew.bat') -ArgumentList @('bootRun','--no-daemon') -WorkingDirectory $root -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr;$health=$null;for($i=0;$i-lt 90;$i++){if($proc.HasExited){throw "host app exited=$($proc.ExitCode)"};try{$health=Invoke-RestMethod "http://localhost:$Port/actuator/health" -TimeoutSec 2;if($health.status-eq'UP'){break}}catch{};Start-Sleep 1};if(!$health-or$health.status-ne'UP'){throw 'host health never UP'};$listen=Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue;if(!$listen){throw "host port $Port not listening"};$appPid=[int]$listen[0].OwningProcess;$apiStatus=0;try{Invoke-WebRequest "http://localhost:$Port/api/accounts/1" -TimeoutSec 5|Out-Null;$apiStatus=200}catch{$apiStatus=[int]$_.Exception.Response.StatusCode};if($apiStatus-notin@(200,401)){throw "unexpected API status=$apiStatus"};$body=[ordered]@{compose_services=@($services);compose_project=$ComposeProject;runtime='host JVM';pid=$appPid;port=$Port;health='UP';api_status=$apiStatus;native_exit=0}}catch{$caught=$_}finally{if($appPid){Stop-Process -Id $appPid -Force -ErrorAction SilentlyContinue};if($proc-and!$proc.HasExited){Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue};& docker compose -f $compose -p $ComposeProject down -v|Out-Null;$down=$LASTEXITCODE;$containers=@(& docker compose -f $compose -p $ComposeProject ps -q);$containerAbsent=($LASTEXITCODE-eq 0-and$containers.Count-eq 0);$volumes=@(& docker volume ls -q --filter "name=^${ComposeProject}_financial-core-db$");$volumeAbsent=($LASTEXITCODE-eq 0-and$volumes.Count-eq 0);$processAbsent=if($appPid){-not[bool](Get-Process -Id $appPid -ErrorAction SilentlyContinue)}else{$true};Pop-Location;Remove-Item Env:FCL_DB_PASSWORD,Env:FCL_DB_PORT,Env:SPRING_DATASOURCE_URL,Env:SPRING_DATASOURCE_USERNAME,Env:SPRING_DATASOURCE_PASSWORD,Env:SERVER_PORT -ErrorAction SilentlyContinue}D3의 핵심 try/catch/finally 한 줄이다. exact db 확인, DB ready, host bootRun, health/listen/API 관찰, process·container·volume cleanup을 한 owner lifecycle로 묶는다.
6if($down-ne 0-or!$containerAbsent-or!$volumeAbsent-or!$processAbsent){throw "W21 cleanup failed down=$down container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"};if($caught){throw $caught};$body.teardown=1;$body.down_native_exit=$down;$body.container_absent=$containerAbsent;$body.volume_absent=$volumeAbsent;$body.process_absent=$processAbsent;$tmp=Join-Path $e 'host-app-manifest.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -LiteralPath $tmp -Destination (Join-Path $e 'host-app-manifest.json') -Force;"W21_HOST_APP_GREEN compose=db runtime=host-JVM health=UP teardown=1 container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"cleanup exit와 세 absent 값을 fail-closed로 확인한 뒤 manifest를 atomic replace하고 W21_HOST_APP_GREEN marker를 출력한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다. 다만 app container proof가 아니다

문법 해부

  • 한 줄로 압축된 PowerShell에서도 `try/catch/finally`의 owner 순서는 그대로다.
  • `Start-Process -PassThru -WindowStyle Hidden`이 host Gradle JVM process handle을 돌려준다.

실행 순서

  1. db service exactness와 readiness를 확인한다.
  2. host JVM을 bootRun하고 aggregate health·listen PID·API 200/401을 관찰한다.
  3. finally에서 process와 Compose project/volume을 정리한다.
  4. 세 absent invariant 뒤에만 host-app-manifest.json과 W21_HOST_APP_GREEN을 만든다.

W21 조각별 정밀 해설

F06-C01 · parameters and packaged dependency
문법 해부
1~2줄의 `parameters and packaged dependency`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `compose_services=[db]`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `compose_services=[db]`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: app container proof가 아니다.
착각 방지
`parameters and packaged dependency`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: app container proof가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F06-C02 · host JVM environment and project scope
문법 해부
3~4줄의 `host JVM environment and project scope`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `runtime=host JVM`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `runtime=host JVM`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: API 200/401은 authorization proof가 아니다.
착각 방지
`host JVM environment and project scope`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: API 200/401은 authorization proof가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F06-C03 · D3 lifecycle try finally
문법 해부
5~5줄의 `D3 lifecycle try finally`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `health=UP`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `health=UP`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: Stop-Process -Force는 graceful SIGTERM proof가 아니다.
착각 방지
`D3 lifecycle try finally`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: Stop-Process -Force는 graceful SIGTERM proof가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F06-C04 · teardown invariant and atomic manifest
문법 해부
6~6줄의 `teardown invariant and atomic manifest`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `teardown=1`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `teardown=1`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: D6/D7은 새 lifecycle 없이 이 manifest만 읽는다.
착각 방지
`teardown invariant and atomic manifest`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: D6/D7은 새 lifecycle 없이 이 manifest만 읽는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    docker ps에 app container가 없어도 정상이다.

  3. 왜 틀렸는지: source가 gradlew.bat bootRun을 host에서 시작한다.

  4. 키타

    수정: db-only Compose + host JVM으로 설명한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F06-T01 startupdb-only compose + learner rootup --wait + bootRunhost health UPdefault port 28021과 PDF 8080을 섞지 않는다
F06-T02 probelisten PID + APIGet-NetTCPConnection + HTTPpid/port/api_status200/401만으로 BOLA를 증명하지 않는다
F06-T03 cleanupprocess/project/volumefinally stop + down -vteardown1 + absent flagsforced cleanup은 graceful proof가 아니다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `정본 D3 lifecycle producer`이야.

  3. 대표 경계는 `D6/D7은 새 lifecycle 없이 이 manifest만 읽는다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

process ownership

Compose는 db, Start-Process는 host JVM을 소유한다.

app container로 표현하면 topology가 바뀐다.
health loop

최대 90회 aggregate health를 읽고 process 조기 종료도 감지한다.

liveness/readiness outage transition은 직접 보지 않는다.
receipt

성공 body에 teardown fields를 더한 뒤 tmp manifest를 atomic replace한다.

D6/D7은 같은 manifest의 read-only consumer다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ D3는 app container lifecycle이다

왜 틀리나 source가 gradlew.bat bootRun을 host에서 시작한다.

바르게 읽기 db-only Compose + host JVM으로 설명한다.

반례 docker ps에 app container가 없어도 정상이다.

❌ API 200/401이면 authorization이 맞다

왜 틀리나 허용 status 범위만 본다.

바르게 읽기 object owner·write no-effect는 별도 test로 검증한다.

반례 잘못된 body도 200일 수 있다.

❌ health UP이면 liveness/readiness 전이도 증명된다

왜 틀리나 aggregate endpoint healthy 한 시점만 본다.

바르게 읽기 D5 matrix에서 DB 중단·복구를 별도 관찰한다.

반례 dependency DOWN 때 liveness 유지 여부는 미검증이다.

❌ D6/D7이 새 lifecycle을 실행한다

왜 틀리나 둘은 D3 manifest를 읽는 review다.

바르게 읽기 no new lifecycle claim을 유지한다.

반례 같은 hash 재검토를 재실행으로 부르면 과장이다.

❌ 8080과 default 28021은 자동으로 같다

왜 틀리나 source default와 PDF overview가 충돌한다.

바르게 읽기 실제 invocation과 manifest port에 바인딩한다.

반례 다른 port를 probe하고 8080 Green이라 쓸 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

app container proof가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

API 200/401은 authorization proof가 아니다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

Stop-Process -Force는 graceful SIGTERM proof가 아니다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

D6/D7은 새 lifecycle 없이 이 manifest만 읽는다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: Compose db와 host JVM을 한 번 실행·관찰·정리해 D3 manifest를 만드는 W21 lifecycle owner다.

2단계 · 코드 조각 재조립

  1. parameters and packaged dependency
  2. host JVM environment and project scope
  3. D3 lifecycle try finally
  4. teardown invariant and atomic manifest

3단계 · 파일 전체 다시 쓰기

6개 물리 줄을 원본 순서로 복원하고 SHA-256 875a1a471ffbbd74b6c2471eb66b8d3d0a538238f49d89af3b4ccf1faed74dc0와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

원문 정본 전체 source 확인하기
원문 정본 · 완전한 packaged PowerShell sourcescripts/run-w21-host-app.ps1SHA-256 875a1a471ffbbd74b6c2471eb66b8d3d0a538238f49d89af3b4ccf1faed74dc0
run-w21-host-app.ps1 — D3 DB+host JVM lifecycle owner 전체
param([Parameter(Mandatory=$true)][string]$ProjectRoot,[Parameter(Mandatory=$true)][string]$EvidenceDir,[string]$ComposeProject='w21-learner-host',[int]$Port=28021,[int]$DbPort=62121,[switch]$ForceFailureAfterCompose)
$ErrorActionPreference='Stop';$root=(Resolve-Path -LiteralPath $ProjectRoot).Path;$referenceRoot=Split-Path -Parent $PSScriptRoot;$e=[IO.Path]::GetFullPath($EvidenceDir);$compose=Join-Path $referenceRoot 'compose.yaml';if(!(Test-Path -LiteralPath $compose)){throw 'W21 packaged DB-only compose.yaml missing'};New-Item -ItemType Directory -Force $e|Out-Null;$proc=$null;$appPid=$null;$body=$null;$caught=$null
$env:FCL_DB_PASSWORD='w21-disposable-password';$env:FCL_DB_PORT="$DbPort";$env:SPRING_DATASOURCE_URL="jdbc:postgresql://localhost:$DbPort/financial_core";$env:SPRING_DATASOURCE_USERNAME='app';$env:SPRING_DATASOURCE_PASSWORD=$env:FCL_DB_PASSWORD;$env:SERVER_PORT="$Port"
Push-Location $root
try{$services=@(& docker compose -f $compose -p $ComposeProject config --services);if($LASTEXITCODE-ne 0-or$services.Count-ne 1-or$services[0]-cne'db'){throw "W21 compose must be exact db: $services"};& docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw 'W21 db readiness failed'};if($ForceFailureAfterCompose){throw 'W21 forced partial-startup failure'};$stdout=Join-Path $e 'host-app.stdout.log';$stderr=Join-Path $e 'host-app.stderr.log';$proc=Start-Process -FilePath (Join-Path $root 'gradlew.bat') -ArgumentList @('bootRun','--no-daemon') -WorkingDirectory $root -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr;$health=$null;for($i=0;$i-lt 90;$i++){if($proc.HasExited){throw "host app exited=$($proc.ExitCode)"};try{$health=Invoke-RestMethod "http://localhost:$Port/actuator/health" -TimeoutSec 2;if($health.status-eq'UP'){break}}catch{};Start-Sleep 1};if(!$health-or$health.status-ne'UP'){throw 'host health never UP'};$listen=Get-NetTCPConnection -State Listen -LocalPort $Port -ErrorAction SilentlyContinue;if(!$listen){throw "host port $Port not listening"};$appPid=[int]$listen[0].OwningProcess;$apiStatus=0;try{Invoke-WebRequest "http://localhost:$Port/api/accounts/1" -TimeoutSec 5|Out-Null;$apiStatus=200}catch{$apiStatus=[int]$_.Exception.Response.StatusCode};if($apiStatus-notin@(200,401)){throw "unexpected API status=$apiStatus"};$body=[ordered]@{compose_services=@($services);compose_project=$ComposeProject;runtime='host JVM';pid=$appPid;port=$Port;health='UP';api_status=$apiStatus;native_exit=0}}catch{$caught=$_}finally{if($appPid){Stop-Process -Id $appPid -Force -ErrorAction SilentlyContinue};if($proc-and!$proc.HasExited){Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue};& docker compose -f $compose -p $ComposeProject down -v|Out-Null;$down=$LASTEXITCODE;$containers=@(& docker compose -f $compose -p $ComposeProject ps -q);$containerAbsent=($LASTEXITCODE-eq 0-and$containers.Count-eq 0);$volumes=@(& docker volume ls -q --filter "name=^${ComposeProject}_financial-core-db$");$volumeAbsent=($LASTEXITCODE-eq 0-and$volumes.Count-eq 0);$processAbsent=if($appPid){-not[bool](Get-Process -Id $appPid -ErrorAction SilentlyContinue)}else{$true};Pop-Location;Remove-Item Env:FCL_DB_PASSWORD,Env:FCL_DB_PORT,Env:SPRING_DATASOURCE_URL,Env:SPRING_DATASOURCE_USERNAME,Env:SPRING_DATASOURCE_PASSWORD,Env:SERVER_PORT -ErrorAction SilentlyContinue}
if($down-ne 0-or!$containerAbsent-or!$volumeAbsent-or!$processAbsent){throw "W21 cleanup failed down=$down container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"};if($caught){throw $caught};$body.teardown=1;$body.down_native_exit=$down;$body.container_absent=$containerAbsent;$body.volume_absent=$volumeAbsent;$body.process_absent=$processAbsent;$tmp=Join-Path $e 'host-app-manifest.json.tmp';$body|ConvertTo-Json|Set-Content -Encoding utf8 $tmp;Move-Item -LiteralPath $tmp -Destination (Join-Path $e 'host-app-manifest.json') -Force;"W21_HOST_APP_GREEN compose=db runtime=host-JVM health=UP teardown=1 container_absent=$containerAbsent volume_absent=$volumeAbsent process_absent=$processAbsent"
07

PowerShell 환경 변수 template — secret을 파일에 쓰지 않기

illustrative/powershell/W21-host-env-template.txt

학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님 · 학습용 예시 · 정본 답안 아님 · W21-F07
7줄 연결7줄 번역3 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

  1. `status=TEMPLATE`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `실제 Green owner가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값status=TEMPLATEURL uses db-port placeholderusername=apppassword not written
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

PowerShell 환경 변수 template — secret을 파일에 쓰지 않기를 무대 운영표로 바꾸기

PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

핵심값 status=TEMPLATE, URL uses db-port placeholder, username=app, password not written을 원본 줄로 따라가되, `실제 Green owner가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

template provenance

1~4줄을 한 덩어리로 읽어 1번째 움직임을 본다. PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

코드 연결
1~4줄
비유
주소·사용자명은 배포 표에 적되 비밀번호는 잠긴 봉투처럼 현재 trusted terminal에서만 전달하는 수동 점검표
비유의 끝
실제 Green owner가 아니다

non-secret environment shape

5~6줄을 한 덩어리로 읽어 2번째 움직임을 본다. PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

코드 연결
5~6줄
비유
주소·사용자명은 배포 표에 적되 비밀번호는 잠긴 봉투처럼 현재 trusted terminal에서만 전달하는 수동 점검표
비유의 끝
완성 PowerShell script가 아니다

trusted-terminal password rule

7~7줄을 한 덩어리로 읽어 3번째 움직임을 본다. PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

코드 연결
7~7줄
비유
주소·사용자명은 배포 표에 적되 비밀번호는 잠긴 봉투처럼 현재 trusted terminal에서만 전달하는 수동 점검표
비유의 끝
secret non-exposure를 자동 증명하지 않는다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `status=TEMPLATE` 맞아?

  2. 니지카

    PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

  3. 첫 source 값은 `status=TEMPLATE`로 잡자.

  4. 키타

    단, `실제 Green owner가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `URL uses db-port placeholder`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. template provenance를 읽는다. → URL placeholder와 username 모양을 검토한다.

  3. 그 다음은 password가 source에 없는지 확인한다. → actual Green은 D3 owner runner에서 수행한다.

  4. 키타

    관찰값과 `완성 PowerShell script가 아니다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 7줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.7 / 7 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F07-L01 status: TEMPLATE 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 이 block이 실행 정답이 아니라 TEMPLATE임을 선언한다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `status=TEMPLATE` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
실제 Green owner가 아니다
2줄F07-L02 target: learner reviews PowerShell environment-variable shape only 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 검토 대상이 PowerShell 환경 변수 모양임을 제한한다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `URL uses db-port placeholder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
완성 PowerShell script가 아니다
3줄F07-L03 evidence_owner: scripts/run-w21-host-app.ps1 (Sunday) 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 실제 evidence owner가 Sunday host-app runner임을 가리킨다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `username=app` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
secret non-exposure를 자동 증명하지 않는다
4줄F07-L04 actual_green_owner: run-w21-host-app.ps1 only 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 이 template가 아니라 run-w21-host-app.ps1만 Green owner임을 못박는다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `password not written` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
Linux NAME=value 문법과 다르다
5줄F07-L05 $env:SPRING_DATASOURCE_URL='jdbc:postgresql://localhost:<db-port>/financial_core' 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 host의 disposable DB port를 쓰는 Spring datasource JDBC URL 모양을 보여준다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `status=TEMPLATE` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
실제 Green owner가 아니다
6줄F07-L06 $env:SPRING_DATASOURCE_USERNAME='app' 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 Spring datasource username을 app으로 지정하는 PowerShell 환경 변수 모양이다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `URL uses db-port placeholder` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
완성 PowerShell script가 아니다
7줄F07-L07 # password is supplied from the current trusted terminal, never written here 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 실제 password는 source가 아니라 trusted terminal에서 공급한다고 경고한다.
입력
trusted terminal과 Spring datasource URL/username shape
결과·효과
이 줄 뒤에는 `username=app` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
secret non-exposure를 자동 증명하지 않는다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `password not written`이야.

  4. 키타

    `secret non-exposure를 자동 증명하지 않는다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 3개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F07-C01 · template provenance1–4줄
1–4줄 원본
status: TEMPLATE
target: learner reviews PowerShell environment-variable shape only
evidence_owner: scripts/run-w21-host-app.ps1 (Sunday)
actual_green_owner: run-w21-host-app.ps1 only
F07-C02 · non-secret environment shape5–6줄
5–6줄 원본
$env:SPRING_DATASOURCE_URL='jdbc:postgresql://localhost:<db-port>/financial_core'
$env:SPRING_DATASOURCE_USERNAME='app'
F07-C03 · trusted-terminal password rule7–7줄
7–7줄 원본
# password is supplied from the current trusted terminal, never written here
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 7줄을 모두 한국어로 옮깁니다.

전체 번역 7 / 7

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1status: TEMPLATE이 block이 실행 정답이 아니라 TEMPLATE임을 선언한다.
2target: learner reviews PowerShell environment-variable shape only검토 대상이 PowerShell 환경 변수 모양임을 제한한다.
3evidence_owner: scripts/run-w21-host-app.ps1 (Sunday)실제 evidence owner가 Sunday host-app runner임을 가리킨다.
4actual_green_owner: run-w21-host-app.ps1 only이 template가 아니라 run-w21-host-app.ps1만 Green owner임을 못박는다.
5$env:SPRING_DATASOURCE_URL='jdbc:postgresql://localhost:<db-port>/financial_core'host의 disposable DB port를 쓰는 Spring datasource JDBC URL 모양을 보여준다.
6$env:SPRING_DATASOURCE_USERNAME='app'Spring datasource username을 app으로 지정하는 PowerShell 환경 변수 모양이다.
7# password is supplied from the current trusted terminal, never written here실제 password는 source가 아니라 trusted terminal에서 공급한다고 경고한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다. 다만 실제 Green owner가 아니다

문법 해부

  • PowerShell 환경 변수는 `$env:NAME='value'` 형식이며 Linux의 `NAME=value command`와 다르다.
  • 앞의 `status:` metadata는 설명용 text이므로 파일 전체가 실행 가능한 ps1은 아니다.

실행 순서

  1. template provenance를 읽는다.
  2. URL placeholder와 username 모양을 검토한다.
  3. password가 source에 없는지 확인한다.
  4. actual Green은 D3 owner runner에서 수행한다.

W21 조각별 정밀 해설

F07-C01 · template provenance
문법 해부
1~4줄의 `template provenance`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `status=TEMPLATE`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `status=TEMPLATE`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 실제 Green owner가 아니다.
착각 방지
`template provenance`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 실제 Green owner가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F07-C02 · non-secret environment shape
문법 해부
5~6줄의 `non-secret environment shape`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `URL uses db-port placeholder`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `URL uses db-port placeholder`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 완성 PowerShell script가 아니다.
착각 방지
`non-secret environment shape`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 완성 PowerShell script가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F07-C03 · trusted-terminal password rule
문법 해부
7~7줄의 `trusted-terminal password rule`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `username=app`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `username=app`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: secret non-exposure를 자동 증명하지 않는다.
착각 방지
`trusted-terminal password rule`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: secret non-exposure를 자동 증명하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    그대로 실행하면 status: 줄에서 실패한다.

  3. 왜 틀렸는지: metadata와 placeholder가 남아 있다.

  4. 키타

    수정: D3 packaged runner를 actual owner로 둔다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F07-T01 URLdb-port$env:SPRING_DATASOURCE_URLhost DB targetplaceholder를 실제 값으로 바꿔야 한다
F07-T02 userapp$env:...USERNAMESpring datasource username권한 최소화는 별도다
F07-T03 passwordtrusted terminalsource에 쓰지 않음file secret absentruntime secret store 검증은 별도다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `수동 환경 변수 모양 template`이야.

  3. 대표 경계는 `Linux NAME=value 문법과 다르다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

PowerShell

$env:` assignment가 child process 환경에 상속된다.

현재 terminal scope 밖 보존 정책은 별도다.
Spring Boot

SPRING_DATASOURCE_*가 externalized configuration으로 binding된다.

profile precedence 전체는 matrix로 검토한다.
secret boundary

password value는 source block에 존재하지 않는다.

log/actuator 노출이 자동으로 막히지는 않는다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ 이 template 자체가 Green 실행기다

왜 틀리나 metadata와 placeholder가 남아 있다.

바르게 읽기 D3 packaged runner를 actual owner로 둔다.

반례 그대로 실행하면 status: 줄에서 실패한다.

❌ Linux 문법을 PowerShell에 붙이면 된다

왜 틀리나 shell assignment syntax가 다르다.

바르게 읽기 각 `$env:` 값을 먼저 설정한다.

반례 `NAME=value java`는 PowerShell에서 다른 해석이다.

❌ password 주석이면 값도 안전하다

왜 틀리나 실제 terminal·log·history 취급이 중요하다.

바르게 읽기 trusted source에서 주입하고 출력하지 않는다.

반례 command history에 평문이 남을 수 있다.

❌ 환경 변수면 Git 노출은 불가능하다

왜 틀리나 스크립트에 값을 하드코딩하면 Git에 남는다.

바르게 읽기 이 파일에는 이름과 placeholder만 둔다.

반례 prod password literal commit은 실패다.

❌ URL과 username이 맞으면 profile도 맞다

왜 틀리나 active profile과 property precedence는 별도다.

바르게 읽기 config matrix와 startup source를 검토한다.

반례 local 값이 prod를 덮을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

실제 Green owner가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

완성 PowerShell script가 아니다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

secret non-exposure를 자동 증명하지 않는다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

Linux NAME=value 문법과 다르다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: PowerShell `$env:` 형식과 trusted terminal secret 공급 원칙을 보여주는 PDF 수동 검토 template다.

2단계 · 코드 조각 재조립

  1. template provenance
  2. non-secret environment shape
  3. trusted-terminal password rule

3단계 · 파일 전체 다시 쓰기

7개 물리 줄을 원본 순서로 복원하고 SHA-256 8eb0b7325514b73c96b5034920866dc5154790a9fd4ef55890868be5a9c5dcca와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님illustrative/powershell/W21-host-env-template.txtSHA-256 8eb0b7325514b73c96b5034920866dc5154790a9fd4ef55890868be5a9c5dcca
PowerShell 환경 변수 template — secret을 파일에 쓰지 않기 전체
status: TEMPLATE
target: learner reviews PowerShell environment-variable shape only
evidence_owner: scripts/run-w21-host-app.ps1 (Sunday)
actual_green_owner: run-w21-host-app.ps1 only
$env:SPRING_DATASOURCE_URL='jdbc:postgresql://localhost:<db-port>/financial_core'
$env:SPRING_DATASOURCE_USERNAME='app'
# password is supplied from the current trusted terminal, never written here
08

health worksheet — liveness·readiness·aggregate 분리

illustrative/powershell/W21-health-probe.ps1

학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님 · 학습용 예시 · 정본 답안 아님 · W21-F08
13줄 연결13줄 번역3 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

  1. `liveness=UP`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `D3 lifecycle owner가 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값liveness=UPreadiness=UPaggregate=UPchecked_at UTC
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

health worksheet — liveness·readiness·aggregate 분리를 무대 운영표로 바꾸기

liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

핵심값 liveness=UP, readiness=UP, aggregate=UP, checked_at UTC을 원본 줄로 따라가되, `D3 lifecycle owner가 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

three health requests

1~4줄을 한 덩어리로 읽어 1번째 움직임을 본다. liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

코드 연결
1~4줄
비유
직원이 살아 있는지(liveness), 재료와 결제기가 준비됐는지(readiness), 매장 전체 상태가 어떤지(aggregate)를 서로 다른 칸에 적는 건강 점검표
비유의 끝
D3 lifecycle owner가 아니다

meaning-specific UP guards

5~7줄을 한 덩어리로 읽어 2번째 움직임을 본다. liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

코드 연결
5~7줄
비유
직원이 살아 있는지(liveness), 재료와 결제기가 준비됐는지(readiness), 매장 전체 상태가 어떤지(aggregate)를 서로 다른 칸에 적는 건강 점검표
비유의 끝
DB 중단/복구 전이를 자동 실행하지 않는다

JSON worksheet output

8~13줄을 한 덩어리로 읽어 3번째 움직임을 본다. liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

코드 연결
8~13줄
비유
직원이 살아 있는지(liveness), 재료와 결제기가 준비됐는지(readiness), 매장 전체 상태가 어떤지(aggregate)를 서로 다른 칸에 적는 건강 점검표
비유의 끝
health-matrix.csv를 대신 만들지 않는다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `liveness=UP` 맞아?

  2. 니지카

    liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

  3. 첫 source 값은 `liveness=UP`로 잡자.

  4. 키타

    단, `D3 lifecycle owner가 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `readiness=UP`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. 세 health endpoint를 각각 호출한다. → 각 의미에 맞는 status가 UP인지 guard한다.

  3. 그 다음은 liveness/readiness/aggregate 값을 모은다. → UTC checked_at과 함께 JSON을 출력한다.

  4. 키타

    관찰값과 `DB 중단/복구 전이를 자동 실행하지 않는다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 13줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.13 / 13 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F08-L01 $BaseUrl = 'http://localhost:8080' 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 세 health endpoint가 공유할 localhost base URL을 정한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `liveness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D3 lifecycle owner가 아니다
2줄F08-L02 $Live = Invoke-RestMethod "$BaseUrl/actuator/health/liveness" 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 세 health endpoint가 공유할 localhost base URL을 정한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `readiness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
DB 중단/복구 전이를 자동 실행하지 않는다
3줄F08-L03 $Ready = Invoke-RestMethod "$BaseUrl/actuator/health/readiness" 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 세 health endpoint가 공유할 localhost base URL을 정한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `aggregate=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health-matrix.csv를 대신 만들지 않는다
4줄F08-L04 $Health = Invoke-RestMethod "$BaseUrl/actuator/health" 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 세 health endpoint가 공유할 localhost base URL을 정한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `checked_at UTC` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
detail secret 비노출은 별도 검토다
5줄F08-L05 if($Live.status -ne 'UP'){throw 'liveness must describe this JVM process'} 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 liveness가 UP이 아니면 process 의미가 깨졌다고 중단한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `liveness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D3 lifecycle owner가 아니다
6줄F08-L06 if($Ready.status -ne 'UP'){throw 'readiness must include required dependencies'} 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 readiness가 UP이 아니면 dependency 준비 의미가 깨졌다고 중단한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `readiness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
DB 중단/복구 전이를 자동 실행하지 않는다
7줄F08-L07 if($Health.status -ne 'UP'){throw 'aggregate health is not UP'} 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 aggregate health가 UP이 아니면 worksheet를 중단한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `aggregate=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health-matrix.csv를 대신 만들지 않는다
8줄F08-L08 [ordered]@{ 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 안정된 순서의 health result map을 연다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `checked_at UTC` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
detail secret 비노출은 별도 검토다
9줄F08-L09 liveness=$Live.status 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 liveness status를 result에 기록한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `liveness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D3 lifecycle owner가 아니다
10줄F08-L10 readiness=$Ready.status 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 readiness status를 result에 기록한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `readiness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
DB 중단/복구 전이를 자동 실행하지 않는다
11줄F08-L11 aggregate=$Health.status 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 aggregate health status를 result에 기록한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `aggregate=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
health-matrix.csv를 대신 만들지 않는다
12줄F08-L12 checked_at=[DateTimeOffset]::UtcNow.ToString('o') 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 관찰 시각을 UTC ISO-8601 문자열로 기록한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `checked_at UTC` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
detail secret 비노출은 별도 검토다
13줄F08-L13 }|ConvertTo-Json 설정 봉투와 세 종류의 건강등을 구분하는 수동 점검표 ordered health result를 JSON text로 출력한다.
입력
liveness, readiness, aggregate health JSON
결과·효과
이 줄 뒤에는 `liveness=UP` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
D3 lifecycle owner가 아니다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `checked_at UTC`이야.

  4. 키타

    `health-matrix.csv를 대신 만들지 않는다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 3개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F08-C01 · three health requests1–4줄
1–4줄 원본
$BaseUrl = 'http://localhost:8080'
$Live = Invoke-RestMethod "$BaseUrl/actuator/health/liveness"
$Ready = Invoke-RestMethod "$BaseUrl/actuator/health/readiness"
$Health = Invoke-RestMethod "$BaseUrl/actuator/health"
F08-C02 · meaning-specific UP guards5–7줄
5–7줄 원본
if($Live.status -ne 'UP'){throw 'liveness must describe this JVM process'}
if($Ready.status -ne 'UP'){throw 'readiness must include required dependencies'}
if($Health.status -ne 'UP'){throw 'aggregate health is not UP'}
F08-C03 · JSON worksheet output8–13줄
8–13줄 원본
[ordered]@{
  liveness=$Live.status
  readiness=$Ready.status
  aggregate=$Health.status
  checked_at=[DateTimeOffset]::UtcNow.ToString('o')
}|ConvertTo-Json
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 13줄을 모두 한국어로 옮깁니다.

전체 번역 13 / 13

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1$BaseUrl = 'http://localhost:8080'세 health endpoint가 공유할 localhost base URL을 정한다.
2$Live = Invoke-RestMethod "$BaseUrl/actuator/health/liveness"세 health endpoint가 공유할 localhost base URL을 정한다.
3$Ready = Invoke-RestMethod "$BaseUrl/actuator/health/readiness"세 health endpoint가 공유할 localhost base URL을 정한다.
4$Health = Invoke-RestMethod "$BaseUrl/actuator/health"세 health endpoint가 공유할 localhost base URL을 정한다.
5if($Live.status -ne 'UP'){throw 'liveness must describe this JVM process'}liveness가 UP이 아니면 process 의미가 깨졌다고 중단한다.
6if($Ready.status -ne 'UP'){throw 'readiness must include required dependencies'}readiness가 UP이 아니면 dependency 준비 의미가 깨졌다고 중단한다.
7if($Health.status -ne 'UP'){throw 'aggregate health is not UP'}aggregate health가 UP이 아니면 worksheet를 중단한다.
8[ordered]@{안정된 순서의 health result map을 연다.
9 liveness=$Live.statusliveness status를 result에 기록한다.
10 readiness=$Ready.statusreadiness status를 result에 기록한다.
11 aggregate=$Health.statusaggregate health status를 result에 기록한다.
12 checked_at=[DateTimeOffset]::UtcNow.ToString('o')관찰 시각을 UTC ISO-8601 문자열로 기록한다.
13}|ConvertTo-Jsonordered health result를 JSON text로 출력한다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다. 다만 D3 lifecycle owner가 아니다

문법 해부

  • `Invoke-RestMethod`는 JSON body를 PowerShell object로 변환해 `.status`로 읽게 한다.
  • `[ordered]@{...}|ConvertTo-Json`은 관찰 순서가 안정된 JSON worksheet를 만든다.

실행 순서

  1. 세 health endpoint를 각각 호출한다.
  2. 각 의미에 맞는 status가 UP인지 guard한다.
  3. liveness/readiness/aggregate 값을 모은다.
  4. UTC checked_at과 함께 JSON을 출력한다.

W21 조각별 정밀 해설

F08-C01 · three health requests
문법 해부
1~4줄의 `three health requests`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `liveness=UP`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `liveness=UP`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: D3 lifecycle owner가 아니다.
착각 방지
`three health requests`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: D3 lifecycle owner가 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F08-C02 · meaning-specific UP guards
문법 해부
5~7줄의 `meaning-specific UP guards`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `readiness=UP`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `readiness=UP`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: DB 중단/복구 전이를 자동 실행하지 않는다.
착각 방지
`meaning-specific UP guards`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: DB 중단/복구 전이를 자동 실행하지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F08-C03 · JSON worksheet output
문법 해부
8~13줄의 `JSON worksheet output`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `aggregate=UP`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `aggregate=UP`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: health-matrix.csv를 대신 만들지 않는다.
착각 방지
`JSON worksheet output`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: health-matrix.csv를 대신 만들지 않는다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    둘 다 DOWN이면 불필요한 restart loop가 생긴다.

  3. 왜 틀렸는지: process 생존과 dependency 준비는 다르다.

  4. 키타

    수정: DB outage에도 liveness는 유지하도록 분리한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F08-T01 healthythree endpointsGET + status guardsUP/UP/UPhealthy 한 시점뿐이다
F08-T02 DB outagedependency DOWN별도 장애 주입readiness DOWN 예상worksheet는 이 전이를 실행하지 않는다
F08-T03 recoveryDB restored별도 재조회readiness UP 예상CSV evidence 작성은 learner 책임이다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `healthy 시점 health 의미 worksheet`이야.

  3. 대표 경계는 `detail secret 비노출은 별도 검토다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

Actuator group

liveness와 readiness가 서로 다른 indicator 집합을 노출할 수 있다.

group 설정이 실제로 올바른지는 별도 검증이다.
traffic control

readiness DOWN은 traffic 제외 판단에 쓰인다.

process restart 판단과 섞으면 loop가 날 수 있다.
worksheet

status와 timestamp를 stdout JSON으로 만든다.

canonical health-matrix.csv를 자동 생성하지 않는다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ liveness와 readiness는 같은 값이어야 한다

왜 틀리나 process 생존과 dependency 준비는 다르다.

바르게 읽기 DB outage에도 liveness는 유지하도록 분리한다.

반례 둘 다 DOWN이면 불필요한 restart loop가 생긴다.

❌ aggregate health만 보면 충분하다

왜 틀리나 어떤 layer가 DOWN인지 의미가 섞인다.

바르게 읽기 세 endpoint를 따로 읽는다.

반례 traffic 판단과 process 판단을 나눌 수 없다.

❌ 이 worksheet가 DB를 중단시킨다

왜 틀리나 source에는 장애 주입 명령이 없다.

바르게 읽기 D3 owner와 별도 matrix 절차에서 수행한다.

반례 healthy JSON만으로 recovery를 주장할 수 없다.

❌ JSON 출력이 곧 CSV evidence다

왜 틀리나 형식과 owner가 다르다.

바르게 읽기 health-matrix.csv를 별도로 작성·검토한다.

반례 stdout만 저장하면 required path가 없다.

❌ health detail은 무조건 안전하다

왜 틀리나 URL·username 등이 노출될 수 있다.

바르게 읽기 운영 detail과 log를 점검한다.

반례 UP이어도 secret leak이면 실패다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

D3 lifecycle owner가 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

DB 중단/복구 전이를 자동 실행하지 않는다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

health-matrix.csv를 대신 만들지 않는다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

detail secret 비노출은 별도 검토다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: liveness·readiness·aggregate health를 구분해 healthy 시점의 세 상태를 JSON으로 읽는 worksheet다.

2단계 · 코드 조각 재조립

  1. three health requests
  2. meaning-specific UP guards
  3. JSON worksheet output

3단계 · 파일 전체 다시 쓰기

13개 물리 줄을 원본 순서로 복원하고 SHA-256 927a28375b497cf2ddd15120120293c928fd39b8bb43d7d1bbbd85642ed96dd2와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · PDF template/worksheet · 정본 답안 아님 · lifecycle owner 아님illustrative/powershell/W21-health-probe.ps1SHA-256 927a28375b497cf2ddd15120120293c928fd39b8bb43d7d1bbbd85642ed96dd2
health worksheet — liveness·readiness·aggregate 분리 전체
$BaseUrl = 'http://localhost:8080'
$Live = Invoke-RestMethod "$BaseUrl/actuator/health/liveness"
$Ready = Invoke-RestMethod "$BaseUrl/actuator/health/readiness"
$Health = Invoke-RestMethod "$BaseUrl/actuator/health"
if($Live.status -ne 'UP'){throw 'liveness must describe this JVM process'}
if($Ready.status -ne 'UP'){throw 'readiness must include required dependencies'}
if($Health.status -ne 'UP'){throw 'aggregate health is not UP'}
[ordered]@{
  liveness=$Live.status
  readiness=$Ready.status
  aggregate=$Health.status
  checked_at=[DateTimeOffset]::UtcNow.ToString('o')
}|ConvertTo-Json
09

W21-SQL-Q33 — LAG로 직전 거래와 금액 차이 읽기

illustrative/sql/W21-SQL-Q33.sql

학습용 예시 · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W21-F09
28줄 연결28줄 번역4 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

  1. `fixture rows=21`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `workbook 제공 정답이 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값fixture rows=21first previous=NULLaccount101 last difference=0stable occurred_at+tx_id
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

W21-SQL-Q33 — LAG로 직전 거래와 금액 차이 읽기를 무대 운영표로 바꾸기

account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

핵심값 fixture rows=21, first previous=NULL, account101 last difference=0, stable occurred_at+tx_id을 원본 줄로 따라가되, `workbook 제공 정답이 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

provenance assumptions and schema

1~5줄을 한 덩어리로 읽어 1번째 움직임을 본다. account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

코드 연결
1~5줄
비유
계좌별 카드 줄에서 각 카드가 바로 앞 카드의 금액을 빌려 적고 현재 금액과 차이를 계산하는 표
비유의 끝
workbook 제공 정답이 아니다

account window and LAG

6~17줄을 한 덩어리로 읽어 2번째 움직임을 본다. account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

코드 연결
6~17줄
비유
계좌별 카드 줄에서 각 카드가 바로 앞 카드의 금액을 빌려 적고 현재 금액과 차이를 계산하는 표
비유의 끝
모든 status 포함은 명시한 가정이다

difference projection and stable output

18~27줄을 한 덩어리로 읽어 3번째 움직임을 본다. account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

코드 연결
18~27줄
비유
계좌별 카드 줄에서 각 카드가 바로 앞 카드의 금액을 빌려 적고 현재 금액과 차이를 계산하는 표
비유의 끝
첫 difference는 NULL이다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `fixture rows=21` 맞아?

  2. 니지카

    account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

  3. 첫 source 값은 `fixture rows=21`로 잡자.

  4. 키타

    단, `workbook 제공 정답이 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `first previous=NULL`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. business_tx 21행을 account별로 나눈다. → 시간·tx_id 순서로 세운다.

  3. 그 다음은 직전 amount를 previous_amount로 붙인다. → 현재-직전 difference를 계산하고 같은 순서로 출력한다.

  4. 키타

    관찰값과 `모든 status 포함은 명시한 가정이다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 28줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.28 / 28 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F09-L01 -- W21-SQL-Q33: 직전 거래와 금액 차이 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
2줄F09-L02 -- 학습용 비정본 예시: workbook에는 제공 정답이 없다. 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 workbook이 배포한 정답이 아닌 학습용 예시임을 선언한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
3줄F09-L03 -- 가정: SUCCESS/FAILED/UNKNOWN/PROCESSING를 포함한 business_tx 전체를 거래 이력으로 읽는다. 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 문제에서 비운 inclusion/tie 정책을 명시적 가정으로 고정한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
4줄F09-L04 -- grain/cardinality: 거래 1건당 1행, fixture 기준 21행. 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 입력·출력 행 단위와 fixture 예상 행 수를 주석으로 고정한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
5줄F09-L05 SET search_path TO :"workbook_schema", public; 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 workbook_schema를 우선 조회하도록 PostgreSQL search_path를 고정한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
7줄F09-L07 WITH ordered_transactions AS ( 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 LAG 계산을 담을 ordered_transactions CTE를 연다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
8줄F09-L08 SELECT account_id, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
9줄F09-L09 tx_id, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
10줄F09-L10 status, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
11줄F09-L11 amount, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
12줄F09-L12 occurred_at, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
13줄F09-L13 LAG(amount) OVER ( 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 같은 account partition의 직전 amount를 previous_amount로 붙인다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
14줄F09-L14 PARTITION BY account_id 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 직전 거래 state를 account마다 독립시킨다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
15줄F09-L15 ORDER BY occurred_at, tx_id 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 시간과 tx_id 오름차순으로 LAG predecessor를 안정화한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
16줄F09-L16 ) AS previous_amount 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 비정본 LAG SQL 예시에서 16번째 원문 문장을 앞뒤 단계와 연결한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
17줄F09-L17 FROM business_tx 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 모든 status를 포함한 business_tx row를 입력으로 읽는다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
18줄F09-L18 ) 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
19줄F09-L19 SELECT account_id, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
20줄F09-L20 tx_id, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
21줄F09-L21 status, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
22줄F09-L22 amount, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
23줄F09-L23 previous_amount, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
24줄F09-L24 amount - previous_amount AS amount_difference, 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 amount에서 직전 amount를 빼 difference를 계산한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `stable occurred_at+tx_id` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
tx_id tie-breaker는 학습 선택이다
25줄F09-L25 occurred_at 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
26줄F09-L26 FROM ordered_transactions 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 LAG가 붙은 거래 relation을 최종 projection 입력으로 읽는다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
27줄F09-L27 ORDER BY account_id, occurred_at, tx_id; 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 account·time·tx_id 순서로 최종 LAG 결과를 안정화한다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `account101 last difference=0` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
첫 difference는 NULL이다
29줄F09-L29 -- 첫 거래는 previous_amount와 amount_difference가 NULL이다. 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=21` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
30줄F09-L30 -- account 101의 마지막 두 reversal은 600 -> 600이므로 마지막 차이는 0이다. 현재 카드 옆에 바로 앞 카드의 금액을 빌려 적는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
business_tx 21행의 account_id·amount·occurred_at·tx_id
결과·효과
이 줄 뒤에는 `first previous=NULL` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 status 포함은 명시한 가정이다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `stable occurred_at+tx_id`이야.

  4. 키타

    `첫 difference는 NULL이다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 4개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F09-C01 · provenance assumptions and schema1–5줄
1–5줄 원본
-- W21-SQL-Q33: 직전 거래와 금액 차이
-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.
-- 가정: SUCCESS/FAILED/UNKNOWN/PROCESSING를 포함한 business_tx 전체를 거래 이력으로 읽는다.
-- grain/cardinality: 거래 1건당 1행, fixture 기준 21행.
SET search_path TO :"workbook_schema", public;
F09-C02 · account window and LAG6–17줄
6–17줄 원본

WITH ordered_transactions AS (
  SELECT account_id,
         tx_id,
         status,
         amount,
         occurred_at,
         LAG(amount) OVER (
           PARTITION BY account_id
           ORDER BY occurred_at, tx_id
         ) AS previous_amount
  FROM business_tx
F09-C03 · difference projection and stable output18–27줄
18–27줄 원본
)
SELECT account_id,
       tx_id,
       status,
       amount,
       previous_amount,
       amount - previous_amount AS amount_difference,
       occurred_at
FROM ordered_transactions
ORDER BY account_id, occurred_at, tx_id;
F09-C04 · fixture oracles28–30줄
28–30줄 원본

-- 첫 거래는 previous_amount와 amount_difference가 NULL이다.
-- account 101의 마지막 두 reversal은 600 -> 600이므로 마지막 차이는 0이다.
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 28줄을 모두 한국어로 옮깁니다.

전체 번역 28 / 28

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1-- W21-SQL-Q33: 직전 거래와 금액 차이fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
2-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.workbook이 배포한 정답이 아닌 학습용 예시임을 선언한다.
3-- 가정: SUCCESS/FAILED/UNKNOWN/PROCESSING를 포함한 business_tx 전체를 거래 이력으로 읽는다.문제에서 비운 inclusion/tie 정책을 명시적 가정으로 고정한다.
4-- grain/cardinality: 거래 1건당 1행, fixture 기준 21행.입력·출력 행 단위와 fixture 예상 행 수를 주석으로 고정한다.
5SET search_path TO :"workbook_schema", public;workbook_schema를 우선 조회하도록 PostgreSQL search_path를 고정한다.
7WITH ordered_transactions AS (LAG 계산을 담을 ordered_transactions CTE를 연다.
8 SELECT account_id,다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
9 tx_id,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
10 status,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
11 amount,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
12 occurred_at,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
13 LAG(amount) OVER (같은 account partition의 직전 amount를 previous_amount로 붙인다.
14 PARTITION BY account_id직전 거래 state를 account마다 독립시킨다.
15 ORDER BY occurred_at, tx_id시간과 tx_id 오름차순으로 LAG predecessor를 안정화한다.
16 ) AS previous_amount비정본 LAG SQL 예시에서 16번째 원문 문장을 앞뒤 단계와 연결한다.
17 FROM business_tx모든 status를 포함한 business_tx row를 입력으로 읽는다.
18)현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
19SELECT account_id,다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
20 tx_id,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
21 status,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
22 amount,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
23 previous_amount,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
24 amount - previous_amount AS amount_difference,현재 amount에서 직전 amount를 빼 difference를 계산한다.
25 occurred_at현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
26FROM ordered_transactionsLAG가 붙은 거래 relation을 최종 projection 입력으로 읽는다.
27ORDER BY account_id, occurred_at, tx_id;account·time·tx_id 순서로 최종 LAG 결과를 안정화한다.
29-- 첫 거래는 previous_amount와 amount_difference가 NULL이다.fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
30-- account 101의 마지막 두 reversal은 600 -> 600이므로 마지막 차이는 0이다.fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다. 다만 workbook 제공 정답이 아니다

문법 해부

  • `LAG(amount) OVER`는 row를 줄이지 않고 같은 partition의 직전 amount를 새 column으로 붙인다.
  • `occurred_at, tx_id`가 같은 시각의 순서를 결정론적으로 푼다.

실행 순서

  1. business_tx 21행을 account별로 나눈다.
  2. 시간·tx_id 순서로 세운다.
  3. 직전 amount를 previous_amount로 붙인다.
  4. 현재-직전 difference를 계산하고 같은 순서로 출력한다.

W21 조각별 정밀 해설

F09-C01 · provenance assumptions and schema
문법 해부
1~5줄의 `provenance assumptions and schema`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `fixture rows=21`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `fixture rows=21`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: workbook 제공 정답이 아니다.
착각 방지
`provenance assumptions and schema`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: workbook 제공 정답이 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F09-C02 · account window and LAG
문법 해부
6~17줄의 `account window and LAG`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `first previous=NULL`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `first previous=NULL`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 모든 status 포함은 명시한 가정이다.
착각 방지
`account window and LAG`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 모든 status 포함은 명시한 가정이다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F09-C03 · difference projection and stable output
문법 해부
18~27줄의 `difference projection and stable output`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `account101 last difference=0`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `account101 last difference=0`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 첫 difference는 NULL이다.
착각 방지
`difference projection and stable output`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 첫 difference는 NULL이다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F09-C04 · fixture oracles
문법 해부
28~30줄의 `fixture oracles`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `stable occurred_at+tx_id`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `stable occurred_at+tx_id`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: tx_id tie-breaker는 학습 선택이다.
착각 방지
`fixture oracles`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: tx_id tie-breaker는 학습 선택이다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    COALESCE 0은 다른 정책이다.

  3. 왜 틀렸는지: 직전 row가 없으므로 SQL NULL이다.

  4. 키타

    수정: NULL을 그대로 표시하고 의미를 설명한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F09-T01 첫 거래account openingLAGprevious/difference NULL0으로 바꾸지 않는다
F09-T02 account10110000 -> 1000current-previous-9000status를 필터하지 않는 가정
F09-T03 same amountlast reversals 600 -> 600difference0같은 시각은 tx_id로 푼다
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `비정본 LAG SQL 예시`이야.

  3. 대표 경계는 `tx_id tie-breaker는 학습 선택이다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

window partition

account마다 별도 previous-row state를 유지한다.

partition key를 빼면 다른 account가 섞인다.
LAG

직전 row 값을 읽되 현재 row cardinality를 보존한다.

첫 row에는 predecessor가 없어 NULL이다.
outer order

window와 같은 stable key로 결과를 표시한다.

표시 순서가 window 계산 key와 다르면 해석이 어려워진다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ 첫 거래 차이는 0이다

왜 틀리나 직전 row가 없으므로 SQL NULL이다.

바르게 읽기 NULL을 그대로 표시하고 의미를 설명한다.

반례 COALESCE 0은 다른 정책이다.

❌ LAG에는 ORDER BY가 없어도 된다

왜 틀리나 직전의 의미가 정해지지 않는다.

바르게 읽기 time과 tie-breaker를 명시한다.

반례 동일 실행에서도 순서가 달라질 수 있다.

❌ FAILED 거래는 자동 제외된다

왜 틀리나 query에 status filter가 없다.

바르게 읽기 전체 status 포함 가정을 주석으로 고정한다.

반례 FAILED amount도 차이에 들어간다.

❌ GROUP BY와 LAG는 같은 결과다

왜 틀리나 GROUP BY는 거래 row를 축약한다.

바르게 읽기 window로 21행을 유지한다.

반례 account별 한 행만 남으면 직전 거래를 잃는다.

❌ 이 query는 workbook 공식 답이다

왜 틀리나 PDF는 LAG 문제 계약만 준다.

바르게 읽기 비정본 label과 가정을 유지한다.

반례 다른 table/status/tie 정책도 가능한 해석이다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

workbook 제공 정답이 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

모든 status 포함은 명시한 가정이다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

첫 difference는 NULL이다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

tx_id tie-breaker는 학습 선택이다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: account별 직전 거래 금액을 LAG로 붙여 현재 금액과의 차이를 계산하는 비정본 학습 예시다.

2단계 · 코드 조각 재조립

  1. provenance assumptions and schema
  2. account window and LAG
  3. difference projection and stable output
  4. fixture oracles

3단계 · 파일 전체 다시 쓰기

30개 물리 줄을 원본 순서로 복원하고 SHA-256 cd249711b847141c8bd6c3884a4cab07ef4230d3dd2128066f5adb19bb4292ed와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 정본 답안 아님illustrative/sql/W21-SQL-Q33.sqlSHA-256 cd249711b847141c8bd6c3884a4cab07ef4230d3dd2128066f5adb19bb4292ed
W21-SQL-Q33 — LAG로 직전 거래와 금액 차이 읽기 전체
-- W21-SQL-Q33: 직전 거래와 금액 차이
-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.
-- 가정: SUCCESS/FAILED/UNKNOWN/PROCESSING를 포함한 business_tx 전체를 거래 이력으로 읽는다.
-- grain/cardinality: 거래 1건당 1행, fixture 기준 21행.
SET search_path TO :"workbook_schema", public;

WITH ordered_transactions AS (
  SELECT account_id,
         tx_id,
         status,
         amount,
         occurred_at,
         LAG(amount) OVER (
           PARTITION BY account_id
           ORDER BY occurred_at, tx_id
         ) AS previous_amount
  FROM business_tx
)
SELECT account_id,
       tx_id,
       status,
       amount,
       previous_amount,
       amount - previous_amount AS amount_difference,
       occurred_at
FROM ordered_transactions
ORDER BY account_id, occurred_at, tx_id;

-- 첫 거래는 previous_amount와 amount_difference가 NULL이다.
-- account 101의 마지막 두 reversal은 600 -> 600이므로 마지막 차이는 0이다.
10

W21-SQL-Q34 — ROW_NUMBER로 고객별 최근 3건 찾기

illustrative/sql/W21-SQL-Q34.sql

학습용 예시 · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W21-F10
36줄 연결36줄 번역4 chunks
01

STEP 01 / 13

오늘 이 코드에서 해결할 문제

무엇을 이해해야 하는지 질문부터 잡습니다.

오늘의 한 문장

customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

  1. `fixture rows=8`은 어느 줄에서 생길까?
  2. 입력→처리→관찰→cleanup 순서는 무엇인가?
  3. producer와 read-only consumer는 누구인가?
  4. 직접 관찰하지 않은 Green은 무엇인가?
  5. counterexample은 `workbook 제공 정답이 아니다`과 어떻게 연결되는가?
이 파일에서 끝까지 다시 쓰는 값fixture rows=8customer1=3 rowscustomer2=3 rowscustomer3/6=1 row
02

STEP 02 / 13

아주 짧게: 이 코드는 왜 필요할까?

웹소설 대신 이 코드가 필요한 이유만 두 문단으로 쉽게 봅니다.

STARRY가 image 세트·DB 창고·host JVM 배우·evidence 영수증을 서로 다른 칸에 놓는다.

W21-SQL-Q34 — ROW_NUMBER로 고객별 최근 3건 찾기를 무대 운영표로 바꾸기

customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

핵심값 fixture rows=8, customer1=3 rows, customer2=3 rows, customer3/6=1 row을 원본 줄로 따라가되, `workbook 제공 정답이 아니다`까지 함께 표시해 Green 범위를 부풀리지 않는다.

딱 여기까지만 무대 비유는 ownership과 순서를 기억하게 할 뿐 Docker isolation, PowerShell native exit, Spring health group, SQL ordering을 자동 증명하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

생활 비유와 실제 코드의 경계를 함께 확인합니다.

provenance assumptions and schema

1~5줄을 한 덩어리로 읽어 1번째 움직임을 본다. customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

코드 연결
1~5줄
비유
고객마다 별도 줄을 세우고 가장 최근 거래부터 1·2·3 번호표를 붙여 세 장만 꺼내는 과정
비유의 끝
workbook 제공 정답이 아니다

customer join and recent row number

6~24줄을 한 덩어리로 읽어 2번째 움직임을 본다. customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

코드 연결
6~24줄
비유
고객마다 별도 줄을 세우고 가장 최근 거래부터 1·2·3 번호표를 붙여 세 장만 꺼내는 과정
비유의 끝
거래 없는 고객 제외는 명시한 가정이다

top-three projection

25~35줄을 한 덩어리로 읽어 3번째 움직임을 본다. customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

코드 연결
25~35줄
비유
고객마다 별도 줄을 세우고 가장 최근 거래부터 1·2·3 번호표를 붙여 세 장만 꺼내는 과정
비유의 끝
모든 account/status 포함은 학습 선택이다
문제의 첫 장면히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 항목의 첫 관찰값은 `fixture rows=8` 맞아?

  2. 니지카

    customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

  3. 첫 source 값은 `fixture rows=8`로 잡자.

  4. 키타

    단, `workbook 제공 정답이 아니다`도 같이 적어.

입력에서 결과까지히토리 → 니지카 → 료 → 키타
  1. 히토리

    `customer1=3 rows`은 언제 생겨?

  2. 니지카

    순서를 두 칸씩 놓자. customer-account-transaction을 연결한다. → 고객별 partition을 만든다.

  3. 그 다음은 시간·tx_id 내림차순으로 recent_no를 붙인다. → 1~3만 남기고 customer/recent_no 순서로 출력한다.

  4. 키타

    관찰값과 `거래 없는 고객 제외는 명시한 가정이다`을 분리하면 돼.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

감사 규칙상 연결 대상인 원본 36줄을 빠짐없이 연결합니다.

단어 몇 개만 뽑은 표가 아닙니다.학습용 예시 · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.36 / 36 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F10-L01 -- W21-SQL-Q34: 고객별 최근 3건 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
2줄F10-L02 -- 학습용 비정본 예시: workbook에는 제공 정답이 없다. 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 workbook이 배포한 정답이 아닌 학습용 예시임을 선언한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
3줄F10-L03 -- 가정: 모든 account 상태와 모든 business_tx 상태를 포함하고, 거래가 없는 고객은 결과에서 제외한다. 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 문제에서 비운 inclusion/tie 정책을 명시적 가정으로 고정한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
4줄F10-L04 -- grain/cardinality: 고객별 최대 3개 거래, fixture 기준 8행. 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 입력·출력 행 단위와 fixture 예상 행 수를 주석으로 고정한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
5줄F10-L05 SET search_path TO :"workbook_schema", public; 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 workbook_schema를 우선 조회하도록 PostgreSQL search_path를 고정한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
7줄F10-L07 WITH ranked_transactions AS ( 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 고객별 recent_no를 계산할 ranked_transactions CTE를 연다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
8줄F10-L08 SELECT c.customer_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
9줄F10-L09 c.customer_name, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 9번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
10줄F10-L10 a.account_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 10번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
11줄F10-L11 t.tx_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 11번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
12줄F10-L12 t.status, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 12번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
13줄F10-L13 t.amount, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 13번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
14줄F10-L14 t.occurred_at, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 14번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
15줄F10-L15 ROW_NUMBER() OVER ( 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 고객 partition 안에서 최신순 고유 번호 recent_no를 붙인다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
16줄F10-L16 PARTITION BY c.customer_id 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 최근 거래 순번을 customer마다 1부터 다시 시작한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
17줄F10-L17 ORDER BY t.occurred_at DESC, t.tx_id DESC 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 가장 최근 거래가 1번이 되도록 time·tx_id 내림차순을 고정한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
18줄F10-L18 ) AS recent_no 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 비정본 최근 3건 SQL 예시에서 18번째 원문 문장을 앞뒤 단계와 연결한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
19줄F10-L19 FROM customer AS c 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 customer를 거래 identity join의 시작 relation으로 읽는다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
20줄F10-L20 JOIN account AS a 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 각 customer에 속한 account를 inner join한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
21줄F10-L21 ON a.customer_id = c.customer_id 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 바로 앞 join·조건을 key equality로 제한한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
22줄F10-L22 JOIN business_tx AS t 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 각 account의 거래를 inner join해 거래 grain을 만든다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
23줄F10-L23 ON t.account_id = a.account_id 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 바로 앞 join·조건을 key equality로 제한한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
24줄F10-L24 ) 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
25줄F10-L25 SELECT customer_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
26줄F10-L26 customer_name, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
27줄F10-L27 recent_no, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
28줄F10-L28 account_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
29줄F10-L29 tx_id, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
30줄F10-L30 status, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
31줄F10-L31 amount, 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
32줄F10-L32 occurred_at 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer3/6=1 row` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
같은 시각은 tx_id DESC로 푼다
33줄F10-L33 FROM ranked_transactions 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 recent_no가 붙은 거래 relation을 최종 projection 입력으로 읽는다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
34줄F10-L34 WHERE recent_no <= 3 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 각 customer partition의 1·2·3번 거래만 남긴다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
35줄F10-L35 ORDER BY customer_id, recent_no; 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 customer와 recent_no 순서로 최종 top-3 결과를 안정화한다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer2=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
모든 account/status 포함은 학습 선택이다
37줄F10-L37 -- customer 1과 2는 각 3행, customer 3과 6은 각 1행이다. 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `fixture rows=8` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
workbook 제공 정답이 아니다
38줄F10-L38 -- 동일 시각의 tx 212와 211은 tx_id DESC로 순서를 고정한다. 고객별 줄에서 최근 순서표 1·2·3을 붙이는 거래표 fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
입력
customer·account·business_tx와 최신 시각·tx_id
결과·효과
이 줄 뒤에는 `customer1=3 rows` 관찰로 이어지는 상태가 만들어진다. 줄 하나가 항목 전체 Green을 단독 증명하지는 않는다.
비유의 한계
거래 없는 고객 제외는 명시한 가정이다
Green 범위만 말하기히토리 → 니지카 → 료 → 키타
  1. 히토리

    marker가 보이면 전부 성공한 거야?

  2. 니지카

    아니. 직접 읽은 process·image·health·SQL row까지만 말해야 해.

  3. 여기서 직접 묶이는 값은 `customer3/6=1 row`이야.

  4. 키타

    `모든 account/status 포함은 학습 선택이다`는 별도 증거가 필요해.

05

STEP 05 / 13

원본 코드 조각

원본을 4개 의미 조각으로 나누어 그대로 확인합니다.

파일을 한꺼번에 외우지 않고 실행 의미가 이어지는 작은 조각으로 봅니다. 아래 코드는 prompt·fixture·가정을 밝힌 학습용 예시 source이며, 제공 정본 답안이 아닙니다.

F10-C01 · provenance assumptions and schema1–5줄
1–5줄 원본
-- W21-SQL-Q34: 고객별 최근 3건
-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.
-- 가정: 모든 account 상태와 모든 business_tx 상태를 포함하고, 거래가 없는 고객은 결과에서 제외한다.
-- grain/cardinality: 고객별 최대 3개 거래, fixture 기준 8행.
SET search_path TO :"workbook_schema", public;
F10-C02 · customer join and recent row number6–24줄
6–24줄 원본

WITH ranked_transactions AS (
  SELECT c.customer_id,
         c.customer_name,
         a.account_id,
         t.tx_id,
         t.status,
         t.amount,
         t.occurred_at,
         ROW_NUMBER() OVER (
           PARTITION BY c.customer_id
           ORDER BY t.occurred_at DESC, t.tx_id DESC
         ) AS recent_no
  FROM customer AS c
  JOIN account AS a
    ON a.customer_id = c.customer_id
  JOIN business_tx AS t
    ON t.account_id = a.account_id
)
F10-C03 · top-three projection25–35줄
25–35줄 원본
SELECT customer_id,
       customer_name,
       recent_no,
       account_id,
       tx_id,
       status,
       amount,
       occurred_at
FROM ranked_transactions
WHERE recent_no <= 3
ORDER BY customer_id, recent_no;
F10-C04 · fixture oracles36–38줄
36–38줄 원본

-- customer 1과 2는 각 3행, customer 3과 6은 각 1행이다.
-- 동일 시각의 tx 212와 211은 tx_id DESC로 순서를 고정한다.
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

비어 있지 않은 36줄을 모두 한국어로 옮깁니다.

전체 번역 36 / 36

비어 있지 않은 원본 줄은 하나도 생략하지 않습니다.

원본한국어 번역
1-- W21-SQL-Q34: 고객별 최근 3건fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
2-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.workbook이 배포한 정답이 아닌 학습용 예시임을 선언한다.
3-- 가정: 모든 account 상태와 모든 business_tx 상태를 포함하고, 거래가 없는 고객은 결과에서 제외한다.문제에서 비운 inclusion/tie 정책을 명시적 가정으로 고정한다.
4-- grain/cardinality: 고객별 최대 3개 거래, fixture 기준 8행.입력·출력 행 단위와 fixture 예상 행 수를 주석으로 고정한다.
5SET search_path TO :"workbook_schema", public;workbook_schema를 우선 조회하도록 PostgreSQL search_path를 고정한다.
7WITH ranked_transactions AS (고객별 recent_no를 계산할 ranked_transactions CTE를 연다.
8 SELECT c.customer_id,다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
9 c.customer_name,비정본 최근 3건 SQL 예시에서 9번째 원문 문장을 앞뒤 단계와 연결한다.
10 a.account_id,비정본 최근 3건 SQL 예시에서 10번째 원문 문장을 앞뒤 단계와 연결한다.
11 t.tx_id,비정본 최근 3건 SQL 예시에서 11번째 원문 문장을 앞뒤 단계와 연결한다.
12 t.status,비정본 최근 3건 SQL 예시에서 12번째 원문 문장을 앞뒤 단계와 연결한다.
13 t.amount,비정본 최근 3건 SQL 예시에서 13번째 원문 문장을 앞뒤 단계와 연결한다.
14 t.occurred_at,비정본 최근 3건 SQL 예시에서 14번째 원문 문장을 앞뒤 단계와 연결한다.
15 ROW_NUMBER() OVER (고객 partition 안에서 최신순 고유 번호 recent_no를 붙인다.
16 PARTITION BY c.customer_id최근 거래 순번을 customer마다 1부터 다시 시작한다.
17 ORDER BY t.occurred_at DESC, t.tx_id DESC가장 최근 거래가 1번이 되도록 time·tx_id 내림차순을 고정한다.
18 ) AS recent_no비정본 최근 3건 SQL 예시에서 18번째 원문 문장을 앞뒤 단계와 연결한다.
19 FROM customer AS ccustomer를 거래 identity join의 시작 relation으로 읽는다.
20 JOIN account AS a각 customer에 속한 account를 inner join한다.
21 ON a.customer_id = c.customer_id바로 앞 join·조건을 key equality로 제한한다.
22 JOIN business_tx AS t각 account의 거래를 inner join해 거래 grain을 만든다.
23 ON t.account_id = a.account_id바로 앞 join·조건을 key equality로 제한한다.
24)현재 block·CTE·map 범위를 닫고 다음 단계로 제어를 넘긴다.
25SELECT customer_id,다음 줄들과 함께 최종 또는 window 입력 column projection을 연다.
26 customer_name,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
27 recent_no,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
28 account_id,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
29 tx_id,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
30 status,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
31 amount,현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
32 occurred_at현재 relation에서 이 column을 결과 또는 window 입력으로 전달한다.
33FROM ranked_transactionsrecent_no가 붙은 거래 relation을 최종 projection 입력으로 읽는다.
34WHERE recent_no <= 3각 customer partition의 1·2·3번 거래만 남긴다.
35ORDER BY customer_id, recent_no;customer와 recent_no 순서로 최종 top-3 결과를 안정화한다.
37-- customer 1과 2는 각 3행, customer 3과 6은 각 1행이다.fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
38-- 동일 시각의 tx 212와 211은 tx_id DESC로 순서를 고정한다.fixture oracle 또는 SQL 책임 경계를 사람이 대조할 주석이다.
07

STEP 07 / 13

기존 수준의 한 줄 읽기·문법 해부

쉬운 설명 다음에 문법과 실행 순서를 정밀하게 읽습니다.

한 줄로 읽기

customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다. 다만 workbook 제공 정답이 아니다

문법 해부

  • `ROW_NUMBER() OVER(PARTITION BY customer_id ORDER BY ... DESC)`가 고객마다 1부터 다시 번호를 매긴다.
  • window 결과는 같은 SELECT의 WHERE에서 바로 쓸 수 없어 CTE 밖에서 `recent_no <= 3`으로 거른다.

실행 순서

  1. customer-account-transaction을 연결한다.
  2. 고객별 partition을 만든다.
  3. 시간·tx_id 내림차순으로 recent_no를 붙인다.
  4. 1~3만 남기고 customer/recent_no 순서로 출력한다.

W21 조각별 정밀 해설

F10-C01 · provenance assumptions and schema
문법 해부
1~5줄의 `provenance assumptions and schema`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `fixture rows=8`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `fixture rows=8`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: workbook 제공 정답이 아니다.
착각 방지
`provenance assumptions and schema`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: workbook 제공 정답이 아니다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F10-C02 · customer join and recent row number
문법 해부
6~24줄의 `customer join and recent row number`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `customer1=3 rows`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `customer1=3 rows`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 거래 없는 고객 제외는 명시한 가정이다.
착각 방지
`customer join and recent row number`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 거래 없는 고객 제외는 명시한 가정이다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F10-C03 · top-three projection
문법 해부
25~35줄의 `top-three projection`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `customer2=3 rows`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `customer2=3 rows`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 모든 account/status 포함은 학습 선택이다.
착각 방지
`top-three projection`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 모든 account/status 포함은 학습 선택이다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
F10-C04 · fixture oracles
문법 해부
36~38줄의 `fixture oracles`을 입력·guard·관찰·cleanup 단계로 나눈다.
실제 값 추적
항목 전체 흐름에 연결해 `customer3/6=1 row`을 대조한다. 이 값이 chunk 단독 산출이라는 뜻은 아니다.
정상 예
정상 예에서는 source 순서와 owner 계약이 일치해 `customer3/6=1 row`을 관찰한다.
틀린 예·반례
반례에서는 선행 guard·stable order·cleanup을 생략한다. 동결 경계: 같은 시각은 tx_id DESC로 푼다.
착각 방지
`fixture oracles`이 항목 전체 Green을 단독 증명한다고 읽지 않는다.
하지 않는 일
이 chunk가 책임지지 않는 범위: 같은 시각은 tx_id DESC로 푼다.
다음 연결
다음 source chunk의 입력 또는 최종 marker/SQL oracle로 결과를 넘긴다.
틀린 예 찾기히토리 → 니지카 → 료 → 키타
  1. 히토리

    겉보기에는 맞지만 깨지는 예를 하나 골라 줘.

  2. 니지카

    한 고객 거래만 남을 수 있다.

  3. 왜 틀렸는지: 전체 결과에서 3건만 자른다.

  4. 키타

    수정: customer partition의 row_number를 필터한다.

08

STEP 08 / 13

실제 값 따라가기

같은 입력값이 어느 줄을 지나 어떤 결과가 되는지 추적합니다.

순서들어온 값코드가 하는 일나온 값·상태경계
F10-T01 customer1accounts101+105latest DESC row_number3 rowsaccount 상태 전체 포함 가정
F10-T02 customer2accounts102+106latest DESC row_number3 rowsPROCESSING도 포함한다
F10-T03 sparsecustomer3/6available rows1 row eachcustomer4/5는 transaction 없어 제외
책임 경계히토리 → 니지카 → 료 → 키타
  1. 히토리

    이 파일이 책임지지 않는 마지막 칸은?

  2. 니지카

    직접 역할은 `비정본 최근 3건 SQL 예시`이야.

  3. 대표 경계는 `같은 시각은 tx_id DESC로 푼다`이야.

  4. 키타

    source SHA·owner·정본/비정본 label까지 대조하면 끝이야.

09

STEP 09 / 13

Docker·PowerShell·Spring·DB 내부에서 벌어지는 일

Docker·PowerShell·Spring·DB에서 실제로 일어나는 일과 증명 범위를 구분합니다.

join grain

각 business_tx가 customer identity와 연결된 한 행이 된다.

잘못된 join은 중복 거래를 만든다.
ROW_NUMBER

tie여도 모든 row에 유일한 순번을 준다.

tx_id DESC 선택은 학습용 stable policy다.
top-N filter

CTE 계산 뒤 recent_no 1~3을 남긴다.

전체 거래 cardinality와 고객별 결과 cardinality를 구분한다.
10

STEP 10 / 13

흔한 착각과 틀린 예

그럴듯하지만 틀린 해석을 반례로 고칩니다.

❌ LIMIT 3이면 고객별 3건이다

왜 틀리나 전체 결과에서 3건만 자른다.

바르게 읽기 customer partition의 row_number를 필터한다.

반례 한 고객 거래만 남을 수 있다.

❌ ORDER BY time만 충분하다

왜 틀리나 같은 timestamp tie가 있다.

바르게 읽기 tx_id DESC를 tie-breaker로 둔다.

반례 tx211/212 순서가 불안정해진다.

❌ 거래 없는 고객도 자동 한 행이다

왜 틀리나 INNER JOIN이므로 사라진다.

바르게 읽기 제외 가정을 명시하거나 LEFT JOIN 정책을 별도 설계한다.

반례 customer4/5는 결과에 없다.

❌ ACTIVE account만 자동 포함된다

왜 틀리나 status filter가 없다.

바르게 읽기 모든 account 상태 포함을 주석으로 밝힌다.

반례 CLOSED account107 거래도 나온다.

❌ 이 SQL은 정본 답안이다

왜 틀리나 PDF는 ROW_NUMBER prompt만 제공한다.

바르게 읽기 비정본 label·grain·tie 가정을 유지한다.

반례 다른 inclusion policy도 가능하다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

이 코드가 책임지지 않는 일을 분리합니다.

직접 미보장

workbook 제공 정답이 아니다

이 책임을 맡는 곳: 별도 selector/실행
직접 미보장

거래 없는 고객 제외는 명시한 가정이다

이 책임을 맡는 곳: secret·운영 정책
직접 미보장

모든 account/status 포함은 학습 선택이다

이 책임을 맡는 곳: concurrency·infrastructure
직접 미보장

같은 시각은 tx_id DESC로 푼다

이 책임을 맡는 곳: owner lifecycle
12

STEP 12 / 13

직접 다시 써보기

뜻 → 조각 → 전체 코드 순서로 다시 씁니다.

1단계 · 뜻부터 복원

다음 역할을 고정값과 미보장 경계까지 말한다: customer별 거래를 최신순 ROW_NUMBER로 매겨 상위 3건만 고르는 비정본 학습 예시다.

2단계 · 코드 조각 재조립

  1. provenance assumptions and schema
  2. customer join and recent row number
  3. top-three projection
  4. fixture oracles

3단계 · 파일 전체 다시 쓰기

38개 물리 줄을 원본 순서로 복원하고 SHA-256 9572e013c586c1f78e5e3e1236971560e8f8d3cf6b72a16af39e8f1766e24faa와 대조한다.

자가 점검
  • 원본 source/template bytes를 바꾸지 않았는가?
  • 입력·처리·관찰·cleanup을 구분했는가?
  • D3 producer와 D6/D7 consumer를 바꾸지 않았는가?
  • 8080과 runner default 28021을 같은 값으로 섞지 않았는가?
  • Q33/Q34를 workbook 정본 답안이라고 부르지 않았는가?
13

STEP 13 / 13

전체 원본 source

감사로 고정한 전체 source를 가감 없이 확인합니다.

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 정본 답안 아님illustrative/sql/W21-SQL-Q34.sqlSHA-256 9572e013c586c1f78e5e3e1236971560e8f8d3cf6b72a16af39e8f1766e24faa
W21-SQL-Q34 — ROW_NUMBER로 고객별 최근 3건 찾기 전체
-- W21-SQL-Q34: 고객별 최근 3건
-- 학습용 비정본 예시: workbook에는 제공 정답이 없다.
-- 가정: 모든 account 상태와 모든 business_tx 상태를 포함하고, 거래가 없는 고객은 결과에서 제외한다.
-- grain/cardinality: 고객별 최대 3개 거래, fixture 기준 8행.
SET search_path TO :"workbook_schema", public;

WITH ranked_transactions AS (
  SELECT c.customer_id,
         c.customer_name,
         a.account_id,
         t.tx_id,
         t.status,
         t.amount,
         t.occurred_at,
         ROW_NUMBER() OVER (
           PARTITION BY c.customer_id
           ORDER BY t.occurred_at DESC, t.tx_id DESC
         ) AS recent_no
  FROM customer AS c
  JOIN account AS a
    ON a.customer_id = c.customer_id
  JOIN business_tx AS t
    ON t.account_id = a.account_id
)
SELECT customer_id,
       customer_name,
       recent_no,
       account_id,
       tx_id,
       status,
       amount,
       occurred_at
FROM ranked_transactions
WHERE recent_no <= 3
ORDER BY customer_id, recent_no;

-- customer 1과 2는 각 3행, customer 3과 6은 각 1행이다.
-- 동일 시각의 tx 212와 211은 tx_id DESC로 순서를 고정한다.