W18 · SQLD 종료 주간 · project SQL 5

18주차 코드 뒤풀이: 다섯 SQL을 증거 경계까지 읽기

공용 Compose와 실제 W18 7개 source, 정답이 배포되지 않은 Q27·Q28 예시를 분리합니다. 원문의 310–400분 표기는 D3 채점 30분·D6 복기 30분을 완전히 포함하지 않으므로 실제 필수 블록은 별도로 더해 읽습니다.

정본 YAML 런타임 1개정본 PowerShell W18 owner 1개정본 SQL fixture 1개정본 SQL invariant 5개학습용 SQL 예시 · 정본 답안 아님 2개항목마다 13단계연결 97줄번역 97줄
01

compose.yaml — W18 PostgreSQL 실행실의 문·준비 신호·저장소

runtime/compose.yaml

정본 YAML 런타임 · 정본 · W18-F01
18줄 연결18줄 번역5 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.

  1. service=db은 어느 줄에서 만들어지거나 검사될까?
  2. postgres:17.10-alpine은 실제 계산값인가 고정 marker인가?
  3. tag는 digest가 아니다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값service=dbpostgres:17.10-alpinefinancial_core/apphealth=2s/2s/30named volume
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

compose.yaml — W18 PostgreSQL 실행실의 문·준비 신호·저장소를 실행 전 검사표로 바꾸기

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.

핵심 관찰값 service=db, postgres:17.10-alpine, financial_core/app, health=2s/2s/30, named volume을 따라가되, tag는 digest가 아니다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

service and image

1~3줄을 한 덩어리로 읽어 W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.의 1번째 단계를 확인한다.

코드 연결
1~3줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
tag는 digest가 아니다

host port mapping

4~5줄을 한 덩어리로 읽어 W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.의 2번째 단계를 확인한다.

코드 연결
4~5줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
pg_isready는 SQL 정답을 증명하지 않는다

database initialization

6~9줄을 한 덩어리로 읽어 W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.의 3번째 단계를 확인한다.

코드 연결
6~9줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
Container mode는 이 Compose lifecycle을 소유하지 않는다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. W18 PostgreSQL 실행실의 문·준비 신호·저장소의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 service=db 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 postgres:17.10-alpine을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 YAML 런타임에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.18 / 18 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F01-L01 services: 다음 설정이나 계산을 담을 새 서랍을 연다. Compose 문서의 services 최상위 mapping을 연다.
입력
runtime/compose.yaml의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
service model의 최상위 수집기가 준비된다.
비유의 한계
tag는 digest가 아니다
2줄F01-L02 db: 다음 설정이나 계산을 담을 새 서랍을 연다. 이름이 db인 단일 service 정의를 연다.
입력
runtime/compose.yaml의 2줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
db service key가 등록된다.
비유의 한계
pg_isready는 SQL 정답을 증명하지 않는다
3줄F01-L03 image: postgres:17.10-alpine 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. db container image tag를 postgres:17.10-alpine으로 지정한다.
입력
runtime/compose.yaml의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
pull·create 대상 image reference가 정해진다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
4줄F01-L04 ports: 다음 설정이나 계산을 담을 새 서랍을 연다. host와 container 사이 port mapping 목록을 연다.
입력
runtime/compose.yaml의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
tag는 digest가 아니다
5줄F01-L05 - "${FCL_DB_PORT:-5432}:5432" 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. FCL_DB_PORT가 비면 host 5432를 container 5432에 연결한다.
입력
runtime/compose.yaml의 5줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
host 접속점 후보가 5432 또는 지정 port로 계산된다.
비유의 한계
pg_isready는 SQL 정답을 증명하지 않는다
6줄F01-L06 environment: 다음 설정이나 계산을 담을 새 서랍을 연다. PostgreSQL image가 읽을 초기화 환경 mapping을 연다.
입력
runtime/compose.yaml의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
7줄F01-L07 POSTGRES_DB: financial_core 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 초기 database 이름을 financial_core로 지정한다.
입력
runtime/compose.yaml의 7줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
tag는 digest가 아니다
8줄F01-L08 POSTGRES_USER: app 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 초기 database user를 app으로 지정한다.
입력
runtime/compose.yaml의 8줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
pg_isready는 SQL 정답을 증명하지 않는다
9줄F01-L09 POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal} 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. FCL_DB_PASSWORD가 없거나 비면 Compose 보간 단계에서 실패시킨다.
입력
runtime/compose.yaml의 9줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
password 부재가 container 생성 전에 드러난다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
10줄F01-L10 healthcheck: 다음 설정이나 계산을 담을 새 서랍을 연다. db service의 healthcheck 정의를 연다.
입력
runtime/compose.yaml의 10줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
tag는 digest가 아니다
11줄F01-L11 test: ["CMD-SHELL", "pg_isready -U app -d financial_core"] 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. app user와 financial_core database에 pg_isready를 실행한다.
입력
runtime/compose.yaml의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
health monitor가 실행할 probe argv를 얻는다.
비유의 한계
pg_isready는 SQL 정답을 증명하지 않는다
12줄F01-L12 interval: 2s 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. healthcheck 반복 간격을 2초로 지정한다.
입력
runtime/compose.yaml의 12줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
13줄F01-L13 timeout: 2s 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 각 healthcheck 시도의 timeout을 2초로 지정한다.
입력
runtime/compose.yaml의 13줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
tag는 digest가 아니다
14줄F01-L14 retries: 30 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 연속 실패 임계를 30으로 지정한다.
입력
runtime/compose.yaml의 14줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
연속 실패 임계가 health state machine에 들어간다.
비유의 한계
pg_isready는 SQL 정답을 증명하지 않는다
15줄F01-L15 volumes: 다음 설정이나 계산을 담을 새 서랍을 연다. service mount 목록을 연다.
입력
runtime/compose.yaml의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
16줄F01-L16 - financial-core-db:/var/lib/postgresql/data 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. financial-core-db volume을 PostgreSQL data directory에 mount한다.
입력
runtime/compose.yaml의 16줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
DB page write가 named volume 경로로 향한다.
비유의 한계
tag는 digest가 아니다
18줄F01-L18 volumes: 다음 설정이나 계산을 담을 새 서랍을 연다. 문서 최상위 named volume mapping을 연다.
입력
runtime/compose.yaml의 18줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Compose service model에 다음 하위 설정을 받을 구체 상태가 남는다.
비유의 한계
Container mode는 이 Compose lifecycle을 소유하지 않는다
19줄F01-L19 financial-core-db: 다음 설정이나 계산을 담을 새 서랍을 연다. financial-core-db라는 named volume을 선언한다.
입력
runtime/compose.yaml의 19줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
mount가 참조할 top-level volume object가 완성된다.
비유의 한계
tag는 digest가 아니다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 tag는 digest가 아니다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F01-C01 · service and image1–3줄
1–3줄 원본
services:
  db:
    image: postgres:17.10-alpine
F01-C02 · host port mapping4–5줄
4–5줄 원본
    ports:
      - "${FCL_DB_PORT:-5432}:5432"
F01-C03 · database initialization6–9줄
6–9줄 원본
    environment:
      POSTGRES_DB: financial_core
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal}
F01-C04 · readiness probe10–14줄
10–14줄 원본
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d financial_core"]
      interval: 2s
      timeout: 2s
      retries: 30
F01-C05 · named 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 문서의 services 최상위 mapping을 연다.
2 db:이름이 db인 단일 service 정의를 연다.
3 image: postgres:17.10-alpinedb container image tag를 postgres:17.10-alpine으로 지정한다.
4 ports:host와 container 사이 port mapping 목록을 연다.
5 - "${FCL_DB_PORT:-5432}:5432"FCL_DB_PORT가 비면 host 5432를 container 5432에 연결한다.
6 environment:PostgreSQL image가 읽을 초기화 환경 mapping을 연다.
7 POSTGRES_DB: financial_core초기 database 이름을 financial_core로 지정한다.
8 POSTGRES_USER: app초기 database user를 app으로 지정한다.
9 POSTGRES_PASSWORD: ${FCL_DB_PASSWORD:?set FCL_DB_PASSWORD in this terminal}FCL_DB_PASSWORD가 없거나 비면 Compose 보간 단계에서 실패시킨다.
10 healthcheck:db service의 healthcheck 정의를 연다.
11 test: ["CMD-SHELL", "pg_isready -U app -d financial_core"]app user와 financial_core database에 pg_isready를 실행한다.
12 interval: 2shealthcheck 반복 간격을 2초로 지정한다.
13 timeout: 2s각 healthcheck 시도의 timeout을 2초로 지정한다.
14 retries: 30연속 실패 임계를 30으로 지정한다.
15 volumes:service mount 목록을 연다.
16 - financial-core-db:/var/lib/postgresql/datafinancial-core-db volume을 PostgreSQL data directory에 mount한다.
18volumes:문서 최상위 named volume mapping을 연다.
19 financial-core-db:financial-core-db라는 named volume을 선언한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다. 다만 tag는 digest가 아니다

문법 해부

  • YAML 들여쓰기와 list marker가 service model의 소속을 정한다.
  • ${VAR:-default}와 ${VAR:?message}는 blank 처리 의미가 다르다.
  • host:container port와 volume:container-path의 콜론은 서로 다른 mapping이다.

실행 순서

  1. Compose parse
  2. environment interpolation
  3. container create
  4. PostgreSQL init
  5. health monitoring
  6. volume-backed writes

원래 W6 수준의 조각별 정밀 해설

F01-C01 · service and image
문법 해부
`service and image` 범위는 Compose 문서의 services 최상위 mapping을 연다. 이어서 db container image tag를 postgres:17.10-alpine으로 지정한다.
실제 값 추적
범위 시작 입력은 runtime/compose.yaml의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 pull·create 대상 image reference가 정해진다.
정상 예
동결 source 1~3줄을 그대로 적용하면 pull·create 대상 image reference가 정해진다.
틀린 예·반례
tag는 digest가 아니다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 service=db이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: tag는 digest가 아니다
다음 연결
끝 상태를 보존한 뒤 `host port mapping` 범위에서 다음 입력·결과를 확인한다.
F01-C02 · host port mapping
문법 해부
`host port mapping` 범위는 host와 container 사이 port mapping 목록을 연다. 이어서 FCL_DB_PORT가 비면 host 5432를 container 5432에 연결한다.
실제 값 추적
범위 시작 입력은 runtime/compose.yaml의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 host 접속점 후보가 5432 또는 지정 port로 계산된다.
정상 예
동결 source 4~5줄을 그대로 적용하면 host 접속점 후보가 5432 또는 지정 port로 계산된다.
틀린 예·반례
pg_isready는 SQL 정답을 증명하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 postgres:17.10-alpine이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: tag는 digest가 아니다
다음 연결
끝 상태를 보존한 뒤 `database initialization` 범위에서 다음 입력·결과를 확인한다.
F01-C03 · database initialization
문법 해부
`database initialization` 범위는 PostgreSQL image가 읽을 초기화 환경 mapping을 연다. 이어서 FCL_DB_PASSWORD가 없거나 비면 Compose 보간 단계에서 실패시킨다.
실제 값 추적
범위 시작 입력은 runtime/compose.yaml의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 password 부재가 container 생성 전에 드러난다.
정상 예
동결 source 6~9줄을 그대로 적용하면 password 부재가 container 생성 전에 드러난다.
틀린 예·반례
Container mode는 이 Compose lifecycle을 소유하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 financial_core/app이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: Container mode는 이 Compose lifecycle을 소유하지 않는다
다음 연결
끝 상태를 보존한 뒤 `readiness probe` 범위에서 다음 입력·결과를 확인한다.
F01-C04 · readiness probe
문법 해부
`readiness probe` 범위는 db service의 healthcheck 정의를 연다. 이어서 연속 실패 임계를 30으로 지정한다.
실제 값 추적
범위 시작 입력은 runtime/compose.yaml의 10줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 연속 실패 임계가 health state machine에 들어간다.
정상 예
동결 source 10~14줄을 그대로 적용하면 연속 실패 임계가 health state machine에 들어간다.
틀린 예·반례
tag는 digest가 아니다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 health=2s/2s/30이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: tag는 digest가 아니다
다음 연결
끝 상태를 보존한 뒤 `named volume` 범위에서 다음 입력·결과를 확인한다.
F01-C05 · named volume
문법 해부
`named volume` 범위는 service mount 목록을 연다. 이어서 financial-core-db라는 named volume을 선언한다.
실제 값 추적
범위 시작 입력은 runtime/compose.yaml의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 mount가 참조할 top-level volume object가 완성된다.
정상 예
동결 source 15~19줄을 그대로 적용하면 mount가 참조할 top-level volume object가 완성된다.
틀린 예·반례
pg_isready는 SQL 정답을 증명하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 named volume이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: Container mode는 이 Compose lifecycle을 소유하지 않는다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 pg_isready는 SQL 정답을 증명하지 않는다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1service=dbF01의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `service=db`가 기록되거나 그 값으로 비교된다.tag는 digest가 아니다
2postgres:17.10-alpineF01의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `postgres:17.10-alpine`가 기록되거나 그 값으로 비교된다.pg_isready는 SQL 정답을 증명하지 않는다
3financial_core/appF01의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `financial_core/app`가 기록되거나 그 값으로 비교된다.Container mode는 이 Compose lifecycle을 소유하지 않는다
4health=2s/2s/30F01의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `health=2s/2s/30`가 기록되거나 그 값으로 비교된다.tag는 digest가 아니다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 named volume을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

Compose parser

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.에서 1번째 내부 책임을 수행한다.

tag는 digest가 아니다
Docker engine

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.에서 2번째 내부 책임을 수행한다.

pg_isready는 SQL 정답을 증명하지 않는다
PostgreSQL image

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.에서 3번째 내부 책임을 수행한다.

Container mode는 이 Compose lifecycle을 소유하지 않는다
health monitor

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.에서 4번째 내부 책임을 수행한다.

tag는 digest가 아니다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ service=db이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 tag는 digest가 아니다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 tag는 digest가 아니다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ postgres:17.10-alpine이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 pg_isready는 SQL 정답을 증명하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 pg_isready는 SQL 정답을 증명하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ financial_core/app이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 Container mode는 이 Compose lifecycle을 소유하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 Container mode는 이 Compose lifecycle을 소유하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ health=2s/2s/30이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 tag는 digest가 아니다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 tag는 digest가 아니다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ named volume이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 pg_isready는 SQL 정답을 증명하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 pg_isready는 SQL 정답을 증명하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

tag는 digest가 아니다

이 책임을 맡는 곳: caller path policy
result proof

pg_isready는 SQL 정답을 증명하지 않는다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

Container mode는 이 Compose lifecycle을 소유하지 않는다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

tag는 digest가 아니다

이 책임을 맡는 곳: runtime owner
provenance

pg_isready는 SQL 정답을 증명하지 않는다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

W18 owner가 disposable PostgreSQL 17.10 service를 올릴 때 공유하는 Compose runtime이다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. service and image
  2. host port mapping
  3. database initialization
  4. readiness probe
  5. named volume

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

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

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourceruntime/compose.yamlSHA-256 3ef6ca229dfa8d54120fb66f2215b906ba8155784d32791d7f93f3be879ade8d
compose.yaml — W18 PostgreSQL 실행실의 문·준비 신호·저장소 전체
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:
02

run-w18-project-sql5.ps1 — fixture와 5개 SQL의 실행 owner

scripts/run-w18-project-sql5.ps1

정본 PowerShell W18 owner · 정본 · W18-F02
20줄 연결20줄 번역7 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.

  1. modes=Compose/Container은 어느 줄에서 만들어지거나 검사될까?
  2. files=5은 실제 계산값인가 고정 marker인가?
  3. SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값modes=Compose/Containerfiles=5native_exits=0rows=5hashes=6cleanup=1 literal
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

run-w18-project-sql5.ps1 — fixture와 5개 SQL의 실행 owner를 실행 전 검사표로 바꾸기

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.

핵심 관찰값 modes=Compose/Container, files=5, native_exits=0, rows=5, hashes=6, cleanup=1 literal을 따라가되, SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

parameters

1~5줄을 한 덩어리로 읽어 fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.의 1번째 단계를 확인한다.

코드 연결
1~5줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다

path and ownership setup

6~10줄을 한 덩어리로 읽어 fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.의 2번째 단계를 확인한다.

코드 연결
6~10줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
marker substring은 결과값·순서·유일성을 인증하지 않는다

Compose or supplied-container resolution

11~16줄을 한 덩어리로 읽어 fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.의 3번째 단계를 확인한다.

코드 연결
11~16줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. fixture와 5개 SQL의 실행 owner의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 modes=Compose/Container 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 files=5을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 PowerShell W18 owner에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.20 / 20 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F02-L01 param( 다음 설정이나 계산을 담을 새 서랍을 연다. PowerShell parameter block을 시작한다.
입력
scripts/run-w18-project-sql5.ps1의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
PowerShell binder가 네 parameter를 받을 범위에 들어간다.
비유의 한계
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
2줄F02-L02 [ValidateSet('Compose','Container')][string]$Mode='Compose',[string]$Container='', 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 실행 mode를 Compose 또는 Container로 제한하고 container 이름 입력을 선언한다.
입력
scripts/run-w18-project-sql5.ps1의 2줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 2줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
marker substring은 결과값·순서·유일성을 인증하지 않는다
3줄F02-L03 [string]$ComposeProject='w18-project-sql-lab',[Parameter(Mandatory=$true)][string]$SqlRoot, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. Compose project 기본값과 필수 SqlRoot 입력을 선언한다.
입력
scripts/run-w18-project-sql5.ps1의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 3줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
4줄F02-L04 [Parameter(Mandatory=$true)][string]$EvidenceDir 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 필수 EvidenceDir 입력을 선언한다.
입력
scripts/run-w18-project-sql5.ps1의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 4줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
5줄F02-L05 ) 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. parameter block을 닫는다.
입력
scripts/run-w18-project-sql5.ps1의 5줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Mode·Container·ComposeProject·SqlRoot·EvidenceDir binding이 끝난다.
비유의 한계
ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
6줄F02-L06 $ErrorActionPreference='Stop';$root=Split-Path -Parent $PSScriptRoot 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 오류를 terminating error로 바꾸고 script의 parent를 project root로 잡는다.
입력
scripts/run-w18-project-sql5.ps1의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 6줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
7줄F02-L07 if(-not [IO.Path]::IsPathRooted($SqlRoot)){throw 'SqlRoot must be an absolute packaged SQL path'} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. SqlRoot가 absolute path가 아니면 즉시 거절한다.
입력
scripts/run-w18-project-sql5.ps1의 7줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 7줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
marker substring은 결과값·순서·유일성을 인증하지 않는다
8줄F02-L08 if(-not [IO.Path]::IsPathRooted($EvidenceDir)){throw 'EvidenceDir must be an absolute learner-owned path'} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. EvidenceDir가 absolute path가 아니면 즉시 거절한다.
입력
scripts/run-w18-project-sql5.ps1의 8줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 8줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
9줄F02-L09 $sql=(Resolve-Path -LiteralPath $SqlRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);New-Item -ItemType Directory -Force $e|Out-Null 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. SqlRoot를 resolve하고 EvidenceDir를 정규화한 뒤 directory를 만든다.
입력
scripts/run-w18-project-sql5.ps1의 9줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
실행에 사용할 absolute SQL directory와 evidence directory object가 생긴다.
비유의 한계
manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
10줄F02-L10 $compose=Join-Path $root 'compose.yaml';$owned=$false 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. compose.yaml path를 만들고 아직 소유하지 않은 상태로 시작한다.
입력
scripts/run-w18-project-sql5.ps1의 10줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 10줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
11줄F02-L11 try{ 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 실행과 cleanup을 묶는 try block을 시작한다.
입력
scripts/run-w18-project-sql5.ps1의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 11줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
12줄F02-L12 if($Mode-eq'Compose'){ 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. Compose mode 분기로 들어간다.
입력
scripts/run-w18-project-sql5.ps1의 12줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 12줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
marker substring은 결과값·순서·유일성을 인증하지 않는다
13줄F02-L13 if($Container){throw 'W18 Compose mode rejects -Container'};if([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)){$env:FCL_DB_PASSWORD='w18-disposable-password'} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. Compose mode의 Container 인자를 거절하고 blank password에는 disposable 값을 넣는다.
입력
scripts/run-w18-project-sql5.ps1의 13줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 13줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
14줄F02-L14 & docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw "W18 compose up exit=$LASTEXITCODE"};$owned=$true;$Container=(& docker compose -f $compose -p $ComposeProject ps -q db).Trim() 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. db를 up --wait로 시작하고 성공 뒤 소유권을 잡아 container id를 구한다.
입력
scripts/run-w18-project-sql5.ps1의 14줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
성공한 Compose 실행이면 owned=true와 container id가 확보된다.
비유의 한계
manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
15줄F02-L15 }elseif([string]::IsNullOrWhiteSpace($Container)){throw 'W18 Container mode requires -Container'} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. Container mode인데 이름이 비었으면 거절한다.
입력
scripts/run-w18-project-sql5.ps1의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 15줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
16줄F02-L16 if([string]::IsNullOrWhiteSpace($Container)){throw 'W18 PostgreSQL container resolution failed'} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. 두 mode 분기 뒤에도 container가 비면 resolution 실패로 끝낸다.
입력
scripts/run-w18-project-sql5.ps1의 16줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owner state machine이 16줄 이후의 분기·실행 단계로 이동한다.
비유의 한계
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
17줄F02-L17 $fixture='fixture.sql';Get-Content -Raw (Join-Path $sql $fixture)|docker exec -i $Container psql -v ON_ERROR_STOP=1 -U app -d financial_core;if($LASTEXITCODE-ne 0){throw "W18 fixture exit=$LASTEXITCODE"} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. fixture.sql을 raw로 psql에 보내고 native exit가 0인지 확인한다.
입력
scripts/run-w18-project-sql5.ps1의 17줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
세 table row 집합이 아니라 고정 w18_project fixture가 DB에 재구성된다.
비유의 한계
marker substring은 결과값·순서·유일성을 인증하지 않는다
18줄F02-L18 $files=@('01-reconcile.sql','02-anti-join.sql','03-aggregation.sql','04-running-balance.sql','05-keyset.sql');$results=@();for($i=0;$i-lt 5;$i++){$out=@(Get-Content -Raw (Join-Path $sql $files[$i])|docker exec -i $Container psql -At -v ON_ERROR_STOP=1 -U app -d financial_core 2>&1);$exit=$LASTEXITCODE;if($exit-ne 0-or($out-join"`n")-cnotmatch "W18_Q$($i+1) "){throw "W18 file=$($files[$i]) exit=$exit output=$out"};$results+=[pscustomobject]@{ordinal=$i+1;file=$files[$i];native_exit=$exit;marker=($out|Where-Object{$_-match'^W18_Q'}|Select-Object -Last 1);sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $files[$i])).Hash.ToLowerInvariant()}} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. 다섯 파일을 순서대로 raw 실행하고 exit·W18_Qn substring·마지막 marker·사후 hash를 모은다.
입력
scripts/run-w18-project-sql5.ps1의 18줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
각 ordinal마다 exit·marker·관찰 hash record가 results에 한 개씩 추가된다.
비유의 한계
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
19줄F02-L19 $hashes=@($fixture)+$files|ForEach-Object{[pscustomobject]@{file=$_;sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $_)).Hash.ToLowerInvariant()}};$manifest=[ordered]@{scope='PROJECT_SQL_RETRIEVAL_NOT_SQLD_MOCK';files=5;native_exits=0;rows=5;cleanup=1;results=$results;hashes=@($hashes)};$manifest|ConvertTo-Json -Depth 5|Set-Content -Encoding utf8 (Join-Path $e 'project-sql5-manifest.json');"W18_PROJECT_SQL5_GREEN files=5 native_exits=0 rows=5 hashes=6 cleanup=1" 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. fixture 포함 여섯 hash와 결과를 manifest에 쓰고 고정 Green 문자열을 출력한다.
입력
scripts/run-w18-project-sql5.ps1의 19줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
project-sql5-manifest.json과 Green stdout이 cleanup보다 먼저 관찰 가능해진다.
비유의 한계
manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
20줄F02-L20 }finally{if($owned){& docker compose -f $compose -p $ComposeProject down -v|Out-Null;if($LASTEXITCODE-ne 0){throw 'W18 compose cleanup failed'}}} 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. 소유한 Compose project만 down -v하고 cleanup 실패를 예외로 올린다.
입력
scripts/run-w18-project-sql5.ps1의 20줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
owned Compose일 때 volume 제거 요청까지 끝나야 process가 정상 반환한다.
비유의 한계
ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F02-C01 · parameters1–5줄
1–5줄 원본
param(
 [ValidateSet('Compose','Container')][string]$Mode='Compose',[string]$Container='',
 [string]$ComposeProject='w18-project-sql-lab',[Parameter(Mandatory=$true)][string]$SqlRoot,
 [Parameter(Mandatory=$true)][string]$EvidenceDir
)
F02-C02 · path and ownership setup6–10줄
6–10줄 원본
$ErrorActionPreference='Stop';$root=Split-Path -Parent $PSScriptRoot
if(-not [IO.Path]::IsPathRooted($SqlRoot)){throw 'SqlRoot must be an absolute packaged SQL path'}
if(-not [IO.Path]::IsPathRooted($EvidenceDir)){throw 'EvidenceDir must be an absolute learner-owned path'}
$sql=(Resolve-Path -LiteralPath $SqlRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);New-Item -ItemType Directory -Force $e|Out-Null
$compose=Join-Path $root 'compose.yaml';$owned=$false
F02-C03 · Compose or supplied-container resolution11–16줄
11–16줄 원본
try{
 if($Mode-eq'Compose'){
  if($Container){throw 'W18 Compose mode rejects -Container'};if([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)){$env:FCL_DB_PASSWORD='w18-disposable-password'}
  & docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw "W18 compose up exit=$LASTEXITCODE"};$owned=$true;$Container=(& docker compose -f $compose -p $ComposeProject ps -q db).Trim()
 }elseif([string]::IsNullOrWhiteSpace($Container)){throw 'W18 Container mode requires -Container'}
 if([string]::IsNullOrWhiteSpace($Container)){throw 'W18 PostgreSQL container resolution failed'}
F02-C04 · fixture execution17–17줄
17–17줄 원본
 $fixture='fixture.sql';Get-Content -Raw (Join-Path $sql $fixture)|docker exec -i $Container psql -v ON_ERROR_STOP=1 -U app -d financial_core;if($LASTEXITCODE-ne 0){throw "W18 fixture exit=$LASTEXITCODE"}
F02-C05 · five-query loop and marker capture18–18줄
18–18줄 원본
 $files=@('01-reconcile.sql','02-anti-join.sql','03-aggregation.sql','04-running-balance.sql','05-keyset.sql');$results=@();for($i=0;$i-lt 5;$i++){$out=@(Get-Content -Raw (Join-Path $sql $files[$i])|docker exec -i $Container psql -At -v ON_ERROR_STOP=1 -U app -d financial_core 2>&1);$exit=$LASTEXITCODE;if($exit-ne 0-or($out-join"`n")-cnotmatch "W18_Q$($i+1) "){throw "W18 file=$($files[$i]) exit=$exit output=$out"};$results+=[pscustomobject]@{ordinal=$i+1;file=$files[$i];native_exit=$exit;marker=($out|Where-Object{$_-match'^W18_Q'}|Select-Object -Last 1);sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $files[$i])).Hash.ToLowerInvariant()}}
F02-C06 · hash manifest and Green literal19–19줄
19–19줄 원본
 $hashes=@($fixture)+$files|ForEach-Object{[pscustomobject]@{file=$_;sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $_)).Hash.ToLowerInvariant()}};$manifest=[ordered]@{scope='PROJECT_SQL_RETRIEVAL_NOT_SQLD_MOCK';files=5;native_exits=0;rows=5;cleanup=1;results=$results;hashes=@($hashes)};$manifest|ConvertTo-Json -Depth 5|Set-Content -Encoding utf8 (Join-Path $e 'project-sql5-manifest.json');"W18_PROJECT_SQL5_GREEN files=5 native_exits=0 rows=5 hashes=6 cleanup=1"
F02-C07 · owned Compose cleanup20–20줄
20–20줄 원본
}finally{if($owned){& docker compose -f $compose -p $ComposeProject down -v|Out-Null;if($LASTEXITCODE-ne 0){throw 'W18 compose cleanup failed'}}}
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 20 / 20

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

원본한국어 번역
1param(PowerShell parameter block을 시작한다.
2 [ValidateSet('Compose','Container')][string]$Mode='Compose',[string]$Container='',실행 mode를 Compose 또는 Container로 제한하고 container 이름 입력을 선언한다.
3 [string]$ComposeProject='w18-project-sql-lab',[Parameter(Mandatory=$true)][string]$SqlRoot,Compose project 기본값과 필수 SqlRoot 입력을 선언한다.
4 [Parameter(Mandatory=$true)][string]$EvidenceDir필수 EvidenceDir 입력을 선언한다.
5)parameter block을 닫는다.
6$ErrorActionPreference='Stop';$root=Split-Path -Parent $PSScriptRoot오류를 terminating error로 바꾸고 script의 parent를 project root로 잡는다.
7if(-not [IO.Path]::IsPathRooted($SqlRoot)){throw 'SqlRoot must be an absolute packaged SQL path'}SqlRoot가 absolute path가 아니면 즉시 거절한다.
8if(-not [IO.Path]::IsPathRooted($EvidenceDir)){throw 'EvidenceDir must be an absolute learner-owned path'}EvidenceDir가 absolute path가 아니면 즉시 거절한다.
9$sql=(Resolve-Path -LiteralPath $SqlRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);New-Item -ItemType Directory -Force $e|Out-NullSqlRoot를 resolve하고 EvidenceDir를 정규화한 뒤 directory를 만든다.
10$compose=Join-Path $root 'compose.yaml';$owned=$falsecompose.yaml path를 만들고 아직 소유하지 않은 상태로 시작한다.
11try{실행과 cleanup을 묶는 try block을 시작한다.
12 if($Mode-eq'Compose'){Compose mode 분기로 들어간다.
13 if($Container){throw 'W18 Compose mode rejects -Container'};if([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)){$env:FCL_DB_PASSWORD='w18-disposable-password'}Compose mode의 Container 인자를 거절하고 blank password에는 disposable 값을 넣는다.
14 & docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw "W18 compose up exit=$LASTEXITCODE"};$owned=$true;$Container=(& docker compose -f $compose -p $ComposeProject ps -q db).Trim()db를 up --wait로 시작하고 성공 뒤 소유권을 잡아 container id를 구한다.
15 }elseif([string]::IsNullOrWhiteSpace($Container)){throw 'W18 Container mode requires -Container'}Container mode인데 이름이 비었으면 거절한다.
16 if([string]::IsNullOrWhiteSpace($Container)){throw 'W18 PostgreSQL container resolution failed'}두 mode 분기 뒤에도 container가 비면 resolution 실패로 끝낸다.
17 $fixture='fixture.sql';Get-Content -Raw (Join-Path $sql $fixture)|docker exec -i $Container psql -v ON_ERROR_STOP=1 -U app -d financial_core;if($LASTEXITCODE-ne 0){throw "W18 fixture exit=$LASTEXITCODE"}fixture.sql을 raw로 psql에 보내고 native exit가 0인지 확인한다.
18 $files=@('01-reconcile.sql','02-anti-join.sql','03-aggregation.sql','04-running-balance.sql','05-keyset.sql');$results=@();for($i=0;$i-lt 5;$i++){$out=@(Get-Content -Raw (Join-Path $sql $files[$i])|docker exec -i $Container psql -At -v ON_ERROR_STOP=1 -U app -d financial_core 2>&1);$exit=$LASTEXITCODE;if($exit-ne 0-or($out-join"`n")-cnotmatch "W18_Q$($i+1) "){throw "W18 file=$($files[$i]) exit=$exit output=$out"};$results+=[pscustomobject]@{ordinal=$i+1;file=$files[$i];native_exit=$exit;marker=($out|Where-Object{$_-match'^W18_Q'}|Select-Object -Last 1);sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $files[$i])).Hash.ToLowerInvariant()}}다섯 파일을 순서대로 raw 실행하고 exit·W18_Qn substring·마지막 marker·사후 hash를 모은다.
19 $hashes=@($fixture)+$files|ForEach-Object{[pscustomobject]@{file=$_;sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $_)).Hash.ToLowerInvariant()}};$manifest=[ordered]@{scope='PROJECT_SQL_RETRIEVAL_NOT_SQLD_MOCK';files=5;native_exits=0;rows=5;cleanup=1;results=$results;hashes=@($hashes)};$manifest|ConvertTo-Json -Depth 5|Set-Content -Encoding utf8 (Join-Path $e 'project-sql5-manifest.json');"W18_PROJECT_SQL5_GREEN files=5 native_exits=0 rows=5 hashes=6 cleanup=1"fixture 포함 여섯 hash와 결과를 manifest에 쓰고 고정 Green 문자열을 출력한다.
20}finally{if($owned){& docker compose -f $compose -p $ComposeProject down -v|Out-Null;if($LASTEXITCODE-ne 0){throw 'W18 compose cleanup failed'}}}소유한 Compose project만 down -v하고 cleanup 실패를 예외로 올린다.
07

STEP 07 / 13

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

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

한 줄로 읽기

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다. 다만 SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다

문법 해부

  • ValidateSet·Mandatory parameter는 native 실행 전에 입력 모양을 제한한다.
  • $LASTEXITCODE는 native process 결과이고 terminating PowerShell exception과 별도로 읽어야 한다.
  • try/finally의 cleanup 범위는 $owned가 언제 true가 되는지에 달렸다.

실행 순서

  1. parameter binding
  2. absolute-path checks
  3. Compose/container resolution
  4. fixture raw execution
  5. five sequential SQL executions
  6. post-execution hashes
  7. manifest and Green literal
  8. owned cleanup

원래 W6 수준의 조각별 정밀 해설

F02-C01 · parameters
문법 해부
`parameters` 범위는 PowerShell parameter block을 시작한다. 이어서 parameter block을 닫는다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Mode·Container·ComposeProject·SqlRoot·EvidenceDir binding이 끝난다.
정상 예
동결 source 1~5줄을 그대로 적용하면 Mode·Container·ComposeProject·SqlRoot·EvidenceDir binding이 끝난다.
틀린 예·반례
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 modes=Compose/Container이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
다음 연결
끝 상태를 보존한 뒤 `path and ownership setup` 범위에서 다음 입력·결과를 확인한다.
F02-C02 · path and ownership setup
문법 해부
`path and ownership setup` 범위는 오류를 terminating error로 바꾸고 script의 parent를 project root로 잡는다. 이어서 compose.yaml path를 만들고 아직 소유하지 않은 상태로 시작한다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 owner state machine이 10줄 이후의 분기·실행 단계로 이동한다.
정상 예
동결 source 6~10줄을 그대로 적용하면 owner state machine이 10줄 이후의 분기·실행 단계로 이동한다.
틀린 예·반례
marker substring은 결과값·순서·유일성을 인증하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 files=5이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
다음 연결
끝 상태를 보존한 뒤 `Compose or supplied-container resolution` 범위에서 다음 입력·결과를 확인한다.
F02-C03 · Compose or supplied-container resolution
문법 해부
`Compose or supplied-container resolution` 범위는 실행과 cleanup을 묶는 try block을 시작한다. 이어서 두 mode 분기 뒤에도 container가 비면 resolution 실패로 끝낸다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 owner state machine이 16줄 이후의 분기·실행 단계로 이동한다.
정상 예
동결 source 11~16줄을 그대로 적용하면 owner state machine이 16줄 이후의 분기·실행 단계로 이동한다.
틀린 예·반례
substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 native_exits=0이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
다음 연결
끝 상태를 보존한 뒤 `fixture execution` 범위에서 다음 입력·결과를 확인한다.
F02-C04 · fixture execution
문법 해부
`fixture execution` 범위는 fixture.sql을 raw로 psql에 보내고 native exit가 0인지 확인한다. 이어서 fixture.sql을 raw로 psql에 보내고 native exit가 0인지 확인한다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 17줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 세 table row 집합이 아니라 고정 w18_project fixture가 DB에 재구성된다.
정상 예
동결 source 17~17줄을 그대로 적용하면 세 table row 집합이 아니라 고정 w18_project fixture가 DB에 재구성된다.
틀린 예·반례
manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 rows=5이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: marker substring은 결과값·순서·유일성을 인증하지 않는다
다음 연결
끝 상태를 보존한 뒤 `five-query loop and marker capture` 범위에서 다음 입력·결과를 확인한다.
F02-C05 · five-query loop and marker capture
문법 해부
`five-query loop and marker capture` 범위는 다섯 파일을 순서대로 raw 실행하고 exit·W18_Qn substring·마지막 marker·사후 hash를 모은다. 이어서 다섯 파일을 순서대로 raw 실행하고 exit·W18_Qn substring·마지막 marker·사후 hash를 모은다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 18줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 각 ordinal마다 exit·marker·관찰 hash record가 results에 한 개씩 추가된다.
정상 예
동결 source 18~18줄을 그대로 적용하면 각 ordinal마다 exit·marker·관찰 hash record가 results에 한 개씩 추가된다.
틀린 예·반례
ComposeProject collision·동시 실행 lock·native timeout 보호가 없다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 hashes=6이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
다음 연결
끝 상태를 보존한 뒤 `hash manifest and Green literal` 범위에서 다음 입력·결과를 확인한다.
F02-C06 · hash manifest and Green literal
문법 해부
`hash manifest and Green literal` 범위는 fixture 포함 여섯 hash와 결과를 manifest에 쓰고 고정 Green 문자열을 출력한다. 이어서 fixture 포함 여섯 hash와 결과를 manifest에 쓰고 고정 Green 문자열을 출력한다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 19줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 project-sql5-manifest.json과 Green stdout이 cleanup보다 먼저 관찰 가능해진다.
정상 예
동결 source 19~19줄을 그대로 적용하면 project-sql5-manifest.json과 Green stdout이 cleanup보다 먼저 관찰 가능해진다.
틀린 예·반례
SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 cleanup=1 literal이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
다음 연결
끝 상태를 보존한 뒤 `owned Compose cleanup` 범위에서 다음 입력·결과를 확인한다.
F02-C07 · owned Compose cleanup
문법 해부
`owned Compose cleanup` 범위는 소유한 Compose project만 down -v하고 cleanup 실패를 예외로 올린다. 이어서 소유한 Compose project만 down -v하고 cleanup 실패를 예외로 올린다.
실제 값 추적
범위 시작 입력은 scripts/run-w18-project-sql5.ps1의 20줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 owned Compose일 때 volume 제거 요청까지 끝나야 process가 정상 반환한다.
정상 예
동결 source 20~20줄을 그대로 적용하면 owned Compose일 때 volume 제거 요청까지 끝나야 process가 정상 반환한다.
틀린 예·반례
marker substring은 결과값·순서·유일성을 인증하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 modes=Compose/Container이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 marker substring은 결과값·순서·유일성을 인증하지 않는다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1modes=Compose/ContainerF02의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `modes=Compose/Container`가 기록되거나 그 값으로 비교된다.SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
2files=5F02의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `files=5`가 기록되거나 그 값으로 비교된다.marker substring은 결과값·순서·유일성을 인증하지 않는다
3native_exits=0F02의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `native_exits=0`가 기록되거나 그 값으로 비교된다.substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
4rows=5F02의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `rows=5`가 기록되거나 그 값으로 비교된다.manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 cleanup=1 literal을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PowerShell binder

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 1번째 내부 책임을 수행한다.

SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
filesystem resolver

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 2번째 내부 책임을 수행한다.

marker substring은 결과값·순서·유일성을 인증하지 않는다
Docker CLI

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 3번째 내부 책임을 수행한다.

substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다
psql process

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 4번째 내부 책임을 수행한다.

manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다
manifest writer

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 5번째 내부 책임을 수행한다.

ComposeProject collision·동시 실행 lock·native timeout 보호가 없다
finally cleanup

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.에서 6번째 내부 책임을 수행한다.

SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ modes=Compose/Container이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ files=5이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker substring은 결과값·순서·유일성을 인증하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker substring은 결과값·순서·유일성을 인증하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ native_exits=0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ rows=5이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ hashes=6이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 ComposeProject collision·동시 실행 lock·native timeout 보호가 없다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 ComposeProject collision·동시 실행 lock·native timeout 보호가 없다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

SqlRoot와 EvidenceDir은 absolute만 요구하고 containment를 강제하지 않는다

이 책임을 맡는 곳: caller path policy
result proof

marker substring은 결과값·순서·유일성을 인증하지 않는다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

substring 통과 문자열이 줄 시작 W18_Q가 아니면 captured marker는 null일 수 있다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

manifest는 cleanup 전 non-atomic write이며 Container mode도 cleanup=1을 쓴다

이 책임을 맡는 곳: runtime owner
provenance

ComposeProject collision·동시 실행 lock·native timeout 보호가 없다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

fixture와 정확히 다섯 SQL을 순차 실행하고 marker·native exit·관찰 hash를 manifest로 기록하는 W18 owner다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. parameters
  2. path and ownership setup
  3. Compose or supplied-container resolution
  4. fixture execution
  5. five-query loop and marker capture
  6. hash manifest and Green literal
  7. owned Compose cleanup

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

20개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 381bdfe24e2ebe0c390c3040416cafbbca873101718f358b5c773aef4d62525d와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcescripts/run-w18-project-sql5.ps1SHA-256 381bdfe24e2ebe0c390c3040416cafbbca873101718f358b5c773aef4d62525d
run-w18-project-sql5.ps1 — fixture와 5개 SQL의 실행 owner 전체
param(
 [ValidateSet('Compose','Container')][string]$Mode='Compose',[string]$Container='',
 [string]$ComposeProject='w18-project-sql-lab',[Parameter(Mandatory=$true)][string]$SqlRoot,
 [Parameter(Mandatory=$true)][string]$EvidenceDir
)
$ErrorActionPreference='Stop';$root=Split-Path -Parent $PSScriptRoot
if(-not [IO.Path]::IsPathRooted($SqlRoot)){throw 'SqlRoot must be an absolute packaged SQL path'}
if(-not [IO.Path]::IsPathRooted($EvidenceDir)){throw 'EvidenceDir must be an absolute learner-owned path'}
$sql=(Resolve-Path -LiteralPath $SqlRoot).Path;$e=[IO.Path]::GetFullPath($EvidenceDir);New-Item -ItemType Directory -Force $e|Out-Null
$compose=Join-Path $root 'compose.yaml';$owned=$false
try{
 if($Mode-eq'Compose'){
  if($Container){throw 'W18 Compose mode rejects -Container'};if([string]::IsNullOrWhiteSpace($env:FCL_DB_PASSWORD)){$env:FCL_DB_PASSWORD='w18-disposable-password'}
  & docker compose -f $compose -p $ComposeProject up -d --wait db;if($LASTEXITCODE-ne 0){throw "W18 compose up exit=$LASTEXITCODE"};$owned=$true;$Container=(& docker compose -f $compose -p $ComposeProject ps -q db).Trim()
 }elseif([string]::IsNullOrWhiteSpace($Container)){throw 'W18 Container mode requires -Container'}
 if([string]::IsNullOrWhiteSpace($Container)){throw 'W18 PostgreSQL container resolution failed'}
 $fixture='fixture.sql';Get-Content -Raw (Join-Path $sql $fixture)|docker exec -i $Container psql -v ON_ERROR_STOP=1 -U app -d financial_core;if($LASTEXITCODE-ne 0){throw "W18 fixture exit=$LASTEXITCODE"}
 $files=@('01-reconcile.sql','02-anti-join.sql','03-aggregation.sql','04-running-balance.sql','05-keyset.sql');$results=@();for($i=0;$i-lt 5;$i++){$out=@(Get-Content -Raw (Join-Path $sql $files[$i])|docker exec -i $Container psql -At -v ON_ERROR_STOP=1 -U app -d financial_core 2>&1);$exit=$LASTEXITCODE;if($exit-ne 0-or($out-join"`n")-cnotmatch "W18_Q$($i+1) "){throw "W18 file=$($files[$i]) exit=$exit output=$out"};$results+=[pscustomobject]@{ordinal=$i+1;file=$files[$i];native_exit=$exit;marker=($out|Where-Object{$_-match'^W18_Q'}|Select-Object -Last 1);sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $files[$i])).Hash.ToLowerInvariant()}}
 $hashes=@($fixture)+$files|ForEach-Object{[pscustomobject]@{file=$_;sha256=(Get-FileHash -Algorithm SHA256 (Join-Path $sql $_)).Hash.ToLowerInvariant()}};$manifest=[ordered]@{scope='PROJECT_SQL_RETRIEVAL_NOT_SQLD_MOCK';files=5;native_exits=0;rows=5;cleanup=1;results=$results;hashes=@($hashes)};$manifest|ConvertTo-Json -Depth 5|Set-Content -Encoding utf8 (Join-Path $e 'project-sql5-manifest.json');"W18_PROJECT_SQL5_GREEN files=5 native_exits=0 rows=5 hashes=6 cleanup=1"
}finally{if($owned){& docker compose -f $compose -p $ComposeProject down -v|Out-Null;if($LASTEXITCODE-ne 0){throw 'W18 compose cleanup failed'}}}
03

fixture.sql — account·ledger 결정형 출발 상태

sql/w18/fixture.sql

정본 SQL fixture · 정본 · W18-F03
6줄 연결6줄 번역3 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.

  1. account=(1,70),(2,0),(3,5)은 어느 줄에서 만들어지거나 검사될까?
  2. ledger account1=100,-30은 실제 계산값인가 고정 marker인가?
  3. 시작할 때 w18_project를 CASCADE drop한다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값account=(1,70),(2,0),(3,5)ledger account1=100,-30ledger account3=5account2 no ledger
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

fixture.sql — account·ledger 결정형 출발 상태를 실행 전 검사표로 바꾸기

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.

핵심 관찰값 account=(1,70),(2,0),(3,5), ledger account1=100,-30, ledger account3=5, account2 no ledger을 따라가되, 시작할 때 w18_project를 CASCADE drop한다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

fail-fast and schema reset

1~2줄을 한 덩어리로 읽어 세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.의 1번째 단계를 확인한다.

코드 연결
1~2줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
시작할 때 w18_project를 CASCADE drop한다

account and ledger tables

3~4줄을 한 덩어리로 읽어 세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.의 2번째 단계를 확인한다.

코드 연결
3~4줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
INSERT에 column list가 없어 table column order에 결합된다

deterministic seed

5~6줄을 한 덩어리로 읽어 세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.의 3번째 단계를 확인한다.

코드 연결
5~6줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. account·ledger 결정형 출발 상태의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 account=(1,70),(2,0),(3,5) 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 ledger account1=100,-30을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL fixture에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.6 / 6 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F03-L01 \set ON_ERROR_STOP on 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. psql이 첫 오류에서 실행을 멈추도록 ON_ERROR_STOP을 켠다.
입력
sql/w18/fixture.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
이후 SQL의 첫 오류가 psql 성공으로 가려지지 않는다.
비유의 한계
시작할 때 w18_project를 CASCADE drop한다
2줄F03-L02 DROP SCHEMA IF EXISTS w18_project CASCADE; CREATE SCHEMA w18_project; 낡은 연습판을 치우고 표와 고정 숫자 자석을 다시 놓는다. 기존 w18_project schema를 CASCADE drop하고 새 schema를 만든다.
입력
sql/w18/fixture.sql의 2줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
이전 실험 data가 사라지고 빈 w18_project namespace가 생긴다.
비유의 한계
INSERT에 column list가 없어 table column order에 결합된다
3줄F03-L03 CREATE TABLE w18_project.account(id bigint PRIMARY KEY,balance bigint NOT NULL); 낡은 연습판을 치우고 표와 고정 숫자 자석을 다시 놓는다. id와 balance를 가진 account table을 만든다.
입력
sql/w18/fixture.sql의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
세 account seed를 받을 relation이 생긴다.
비유의 한계
Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
4줄F03-L04 CREATE TABLE w18_project.ledger(id bigint PRIMARY KEY,account_id bigint NOT NULL REFERENCES w18_project.account(id),signed_amount bigint NOT NULL,created_at timestamptz NOT NULL); 낡은 연습판을 치우고 표와 고정 숫자 자석을 다시 놓는다. account FK·signed amount·stable-order 시간을 가진 ledger table을 만든다.
입력
sql/w18/fixture.sql의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
account FK를 가진 ledger relation이 생긴다.
비유의 한계
시작할 때 w18_project를 CASCADE drop한다
5줄F03-L05 INSERT INTO w18_project.account VALUES(1,70),(2,0),(3,5); 낡은 연습판을 치우고 표와 고정 숫자 자석을 다시 놓는다. account 1=70, 2=0, 3=5 세 행을 넣는다.
입력
sql/w18/fixture.sql의 5줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
account2만 ledger가 없는 비교 상태가 준비된다.
비유의 한계
INSERT에 column list가 없어 table column order에 결합된다
6줄F03-L06 INSERT INTO w18_project.ledger VALUES(1,1,100,'2026-01-01Z'),(2,1,-30,'2026-01-02Z'),(3,3,5,'2026-01-01Z'); 낡은 연습판을 치우고 표와 고정 숫자 자석을 다시 놓는다. account 1의 +100/-30과 account 3의 +5 ledger 세 행을 넣는다.
입력
sql/w18/fixture.sql의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
account1 합계 70과 account3 합계 5가 재현된다.
비유의 한계
Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 시작할 때 w18_project를 CASCADE drop한다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F03-C01 · fail-fast and schema reset1–2줄
1–2줄 원본
\set ON_ERROR_STOP on
DROP SCHEMA IF EXISTS w18_project CASCADE; CREATE SCHEMA w18_project;
F03-C02 · account and ledger tables3–4줄
3–4줄 원본
CREATE TABLE w18_project.account(id bigint PRIMARY KEY,balance bigint NOT NULL);
CREATE TABLE w18_project.ledger(id bigint PRIMARY KEY,account_id bigint NOT NULL REFERENCES w18_project.account(id),signed_amount bigint NOT NULL,created_at timestamptz NOT NULL);
F03-C03 · deterministic seed5–6줄
5–6줄 원본
INSERT INTO w18_project.account VALUES(1,70),(2,0),(3,5);
INSERT INTO w18_project.ledger VALUES(1,1,100,'2026-01-01Z'),(2,1,-30,'2026-01-02Z'),(3,3,5,'2026-01-01Z');
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 6 / 6

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

원본한국어 번역
1\set ON_ERROR_STOP onpsql이 첫 오류에서 실행을 멈추도록 ON_ERROR_STOP을 켠다.
2DROP SCHEMA IF EXISTS w18_project CASCADE; CREATE SCHEMA w18_project;기존 w18_project schema를 CASCADE drop하고 새 schema를 만든다.
3CREATE TABLE w18_project.account(id bigint PRIMARY KEY,balance bigint NOT NULL);id와 balance를 가진 account table을 만든다.
4CREATE TABLE w18_project.ledger(id bigint PRIMARY KEY,account_id bigint NOT NULL REFERENCES w18_project.account(id),signed_amount bigint NOT NULL,created_at timestamptz NOT NULL);account FK·signed amount·stable-order 시간을 가진 ledger table을 만든다.
5INSERT INTO w18_project.account VALUES(1,70),(2,0),(3,5);account 1=70, 2=0, 3=5 세 행을 넣는다.
6INSERT INTO w18_project.ledger VALUES(1,1,100,'2026-01-01Z'),(2,1,-30,'2026-01-02Z'),(3,3,5,'2026-01-01Z');account 1의 +100/-30과 account 3의 +5 ledger 세 행을 넣는다.
07

STEP 07 / 13

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

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

한 줄로 읽기

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다. 다만 시작할 때 w18_project를 CASCADE drop한다

문법 해부

  • psql meta-command와 PostgreSQL DDL/DML이 같은 input stream에서 순서대로 실행된다.
  • FK는 ledger.account_id가 존재하는 account를 참조하게 한다.
  • column list 없는 INSERT는 현재 table column order에 결합된다.

실행 순서

  1. psql fail-fast
  2. schema reset
  3. table DDL
  4. account seed
  5. ledger seed

원래 W6 수준의 조각별 정밀 해설

F03-C01 · fail-fast and schema reset
문법 해부
`fail-fast and schema reset` 범위는 psql이 첫 오류에서 실행을 멈추도록 ON_ERROR_STOP을 켠다. 이어서 기존 w18_project schema를 CASCADE drop하고 새 schema를 만든다.
실제 값 추적
범위 시작 입력은 sql/w18/fixture.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 이전 실험 data가 사라지고 빈 w18_project namespace가 생긴다.
정상 예
동결 source 1~2줄을 그대로 적용하면 이전 실험 data가 사라지고 빈 w18_project namespace가 생긴다.
틀린 예·반례
시작할 때 w18_project를 CASCADE drop한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 account=(1,70),(2,0),(3,5)이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: 시작할 때 w18_project를 CASCADE drop한다
다음 연결
끝 상태를 보존한 뒤 `account and ledger tables` 범위에서 다음 입력·결과를 확인한다.
F03-C02 · account and ledger tables
문법 해부
`account and ledger tables` 범위는 id와 balance를 가진 account table을 만든다. 이어서 account FK·signed amount·stable-order 시간을 가진 ledger table을 만든다.
실제 값 추적
범위 시작 입력은 sql/w18/fixture.sql의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 account FK를 가진 ledger relation이 생긴다.
정상 예
동결 source 3~4줄을 그대로 적용하면 account FK를 가진 ledger relation이 생긴다.
틀린 예·반례
INSERT에 column list가 없어 table column order에 결합된다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 ledger account1=100,-30이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
다음 연결
끝 상태를 보존한 뒤 `deterministic seed` 범위에서 다음 입력·결과를 확인한다.
F03-C03 · deterministic seed
문법 해부
`deterministic seed` 범위는 account 1=70, 2=0, 3=5 세 행을 넣는다. 이어서 account 1의 +100/-30과 account 3의 +5 ledger 세 행을 넣는다.
실제 값 추적
범위 시작 입력은 sql/w18/fixture.sql의 5줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 account1 합계 70과 account3 합계 5가 재현된다.
정상 예
동결 source 5~6줄을 그대로 적용하면 account1 합계 70과 account3 합계 5가 재현된다.
틀린 예·반례
Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 ledger account3=5이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: INSERT에 column list가 없어 table column order에 결합된다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 INSERT에 column list가 없어 table column order에 결합된다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1account=(1,70),(2,0),(3,5)F03의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `account=(1,70),(2,0),(3,5)`가 기록되거나 그 값으로 비교된다.시작할 때 w18_project를 CASCADE drop한다
2ledger account1=100,-30F03의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `ledger account1=100,-30`가 기록되거나 그 값으로 비교된다.INSERT에 column list가 없어 table column order에 결합된다
3ledger account3=5F03의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `ledger account3=5`가 기록되거나 그 값으로 비교된다.Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
4account2 no ledgerF03의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `account2 no ledger`가 기록되거나 그 값으로 비교된다.시작할 때 w18_project를 CASCADE drop한다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 account2 no ledger을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

psql client

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.에서 1번째 내부 책임을 수행한다.

시작할 때 w18_project를 CASCADE drop한다
PostgreSQL catalog

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.에서 2번째 내부 책임을 수행한다.

INSERT에 column list가 없어 table column order에 결합된다
constraint executor

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.에서 3번째 내부 책임을 수행한다.

Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다
transaction boundary

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.에서 4번째 내부 책임을 수행한다.

시작할 때 w18_project를 CASCADE drop한다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ account=(1,70),(2,0),(3,5)이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 시작할 때 w18_project를 CASCADE drop한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 시작할 때 w18_project를 CASCADE drop한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ ledger account1=100,-30이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 INSERT에 column list가 없어 table column order에 결합된다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 INSERT에 column list가 없어 table column order에 결합된다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ ledger account3=5이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ account2 no ledger이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 시작할 때 w18_project를 CASCADE drop한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 시작할 때 w18_project를 CASCADE drop한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ account=(1,70),(2,0),(3,5)이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 INSERT에 column list가 없어 table column order에 결합된다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 INSERT에 column list가 없어 table column order에 결합된다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

시작할 때 w18_project를 CASCADE drop한다

이 책임을 맡는 곳: caller path policy
result proof

INSERT에 column list가 없어 table column order에 결합된다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

Container mode에서는 supplied container의 같은 schema를 파괴적으로 교체한다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

시작할 때 w18_project를 CASCADE drop한다

이 책임을 맡는 곳: runtime owner
provenance

INSERT에 column list가 없어 table column order에 결합된다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

세 account와 세 ledger row를 w18_project schema에 고정해 Q1~Q5가 공유할 deterministic fixture를 만든다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. fail-fast and schema reset
  2. account and ledger tables
  3. deterministic seed

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

6개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 88a2509fe83b5a3044d2186a5c49211e328ae6023ae00ca9fac480ee40ce3b03와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/fixture.sqlSHA-256 88a2509fe83b5a3044d2186a5c49211e328ae6023ae00ca9fac480ee40ce3b03
fixture.sql — account·ledger 결정형 출발 상태 전체
\set ON_ERROR_STOP on
DROP SCHEMA IF EXISTS w18_project CASCADE; CREATE SCHEMA w18_project;
CREATE TABLE w18_project.account(id bigint PRIMARY KEY,balance bigint NOT NULL);
CREATE TABLE w18_project.ledger(id bigint PRIMARY KEY,account_id bigint NOT NULL REFERENCES w18_project.account(id),signed_amount bigint NOT NULL,created_at timestamptz NOT NULL);
INSERT INTO w18_project.account VALUES(1,70),(2,0),(3,5);
INSERT INTO w18_project.ledger VALUES(1,1,100,'2026-01-01Z'),(2,1,-30,'2026-01-02Z'),(3,3,5,'2026-01-01Z');
04

01-reconcile.sql — 저장 잔액과 원장 합계 대조

sql/w18/01-reconcile.sql

정본 SQL invariant · 정본 · W18-F04
1줄 연결1줄 번역1 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.

  1. mismatch=0은 어느 줄에서 만들어지거나 검사될까?
  2. account2 missing ledger -> 0은 실제 계산값인가 고정 marker인가?
  3. hardcoded marker는 실제 SELECT 결과 row가 아니다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값mismatch=0account2 missing ledger -> 0marker=W18_Q1 rows=0
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

01-reconcile.sql — 저장 잔액과 원장 합계 대조를 실행 전 검사표로 바꾸기

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.

핵심 관찰값 mismatch=0, account2 missing ledger -> 0, marker=W18_Q1 rows=0을 따라가되, hardcoded marker는 실제 SELECT 결과 row가 아니다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

reconcile invariant and marker

1~1줄을 한 덩어리로 읽어 account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.의 1번째 단계를 확인한다.

코드 연결
1~1줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
hardcoded marker는 실제 SELECT 결과 row가 아니다

전체 한 줄

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.

코드 연결
1줄
비유
한 장짜리 검사표를 처음부터 끝까지 읽는다.
비유의 끝
hardcoded marker는 실제 SELECT 결과 row가 아니다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. 저장 잔액과 원장 합계 대조의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 mismatch=0 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 account2 missing ledger -> 0을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL invariant에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.1 / 1 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F04-L01 DO $g$ BEGIN IF (SELECT count(*) FROM w18_project.account a LEFT JOIN(SELECT account_id,sum(signed_amount)b FROM w18_project.ledger GROUP BY account_id)l ON l.account_id=a.id WHERE a.balance<>coalesce(l.b,0))<>0 THEN RAISE EXCEPTION 'reconcile';END IF;END $g$; SELECT 'W18_Q1 rows=0'; 명단의 사람을 지우지 않고 옆 장부 칸만 연결한다. account별 ledger 합계를 LEFT JOIN하고 missing 합계를 0으로 바꿔 저장 balance와 다른 행이 0건인지 검사한 뒤 Q1 marker를 출력한다.
입력
sql/w18/01-reconcile.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
fixture에서 mismatch count 0이면 예외 없이 W18_Q1 rows=0 한 줄이 stdout에 나타난다.
비유의 한계
hardcoded marker는 실제 SELECT 결과 row가 아니다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 hardcoded marker는 실제 SELECT 결과 row가 아니다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F04-C01 · reconcile invariant and marker1–1줄
1–1줄 원본
DO $g$ BEGIN IF (SELECT count(*) FROM w18_project.account a LEFT JOIN(SELECT account_id,sum(signed_amount)b FROM w18_project.ledger GROUP BY account_id)l ON l.account_id=a.id WHERE a.balance<>coalesce(l.b,0))<>0 THEN RAISE EXCEPTION 'reconcile';END IF;END $g$; SELECT 'W18_Q1 rows=0';
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 1 / 1

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

원본한국어 번역
1DO $g$ BEGIN IF (SELECT count(*) FROM w18_project.account a LEFT JOIN(SELECT account_id,sum(signed_amount)b FROM w18_project.ledger GROUP BY account_id)l ON l.account_id=a.id WHERE a.balance<>coalesce(l.b,0))<>0 THEN RAISE EXCEPTION 'reconcile';END IF;END $g$; SELECT 'W18_Q1 rows=0';account별 ledger 합계를 LEFT JOIN하고 missing 합계를 0으로 바꿔 저장 balance와 다른 행이 0건인지 검사한 뒤 Q1 marker를 출력한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다. 다만 hardcoded marker는 실제 SELECT 결과 row가 아니다

문법 해부

  • DO block의 PL/pgSQL IF와 뒤의 SELECT marker는 서로 다른 statement다.
  • SQL NULL 비교는 false가 아니라 unknown이 될 수 있어 IF fail-open 여부를 확인해야 한다.
  • hardcoded marker 문자열과 실제 query 결과 row를 구분한다.

실행 순서

  1. DO block query
  2. fixture-bound invariant comparison
  3. optional exception
  4. hardcoded marker SELECT
  5. runner substring check

원래 W6 수준의 조각별 정밀 해설

F04-C01 · reconcile invariant and marker
문법 해부
`reconcile invariant and marker` 범위는 account별 ledger 합계를 LEFT JOIN하고 missing 합계를 0으로 바꿔 저장 balance와 다른 행이 0건인지 검사한 뒤 Q1 marker를 출력한다. 이어서 account별 ledger 합계를 LEFT JOIN하고 missing 합계를 0으로 바꿔 저장 balance와 다른 행이 0건인지 검사한 뒤 Q1 marker를 출력한다.
실제 값 추적
범위 시작 입력은 sql/w18/01-reconcile.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 fixture에서 mismatch count 0이면 예외 없이 W18_Q1 rows=0 한 줄이 stdout에 나타난다.
정상 예
동결 source 1~1줄을 그대로 적용하면 fixture에서 mismatch count 0이면 예외 없이 W18_Q1 rows=0 한 줄이 stdout에 나타난다.
틀린 예·반례
hardcoded marker는 실제 SELECT 결과 row가 아니다 / account가 0행이면 mismatch count 0으로 vacuous pass한다 / fixture 이후 변형된 shared schema에도 영향을 받는다 / 합계 일치만 double-entry 완전성을 증명하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 mismatch=0이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: hardcoded marker는 실제 SELECT 결과 row가 아니다 / account가 0행이면 mismatch count 0으로 vacuous pass한다 / fixture 이후 변형된 shared schema에도 영향을 받는다 / 합계 일치만 double-entry 완전성을 증명하지 않는다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 account가 0행이면 mismatch count 0으로 vacuous pass한다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1mismatch=0F04의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `mismatch=0`가 기록되거나 그 값으로 비교된다.hardcoded marker는 실제 SELECT 결과 row가 아니다
2account2 missing ledger -> 0F04의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `account2 missing ledger -> 0`가 기록되거나 그 값으로 비교된다.account가 0행이면 mismatch count 0으로 vacuous pass한다
3marker=W18_Q1 rows=0F04의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `marker=W18_Q1 rows=0`가 기록되거나 그 값으로 비교된다.fixture 이후 변형된 shared schema에도 영향을 받는다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 marker=W18_Q1 rows=0을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PL/pgSQL DO block

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.에서 1번째 내부 책임을 수행한다.

hardcoded marker는 실제 SELECT 결과 row가 아니다
PostgreSQL executor

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.에서 2번째 내부 책임을 수행한다.

account가 0행이면 mismatch count 0으로 vacuous pass한다
three-valued logic

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.에서 3번째 내부 책임을 수행한다.

fixture 이후 변형된 shared schema에도 영향을 받는다
psql marker stream

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.에서 4번째 내부 책임을 수행한다.

합계 일치만 double-entry 완전성을 증명하지 않는다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ mismatch=0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 hardcoded marker는 실제 SELECT 결과 row가 아니다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 hardcoded marker는 실제 SELECT 결과 row가 아니다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ account2 missing ledger -> 0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 account가 0행이면 mismatch count 0으로 vacuous pass한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 account가 0행이면 mismatch count 0으로 vacuous pass한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ marker=W18_Q1 rows=0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 fixture 이후 변형된 shared schema에도 영향을 받는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 fixture 이후 변형된 shared schema에도 영향을 받는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ mismatch=0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 합계 일치만 double-entry 완전성을 증명하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 합계 일치만 double-entry 완전성을 증명하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ account2 missing ledger -> 0이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 hardcoded marker는 실제 SELECT 결과 row가 아니다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 hardcoded marker는 실제 SELECT 결과 row가 아니다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

hardcoded marker는 실제 SELECT 결과 row가 아니다

이 책임을 맡는 곳: caller path policy
result proof

account가 0행이면 mismatch count 0으로 vacuous pass한다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

fixture 이후 변형된 shared schema에도 영향을 받는다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

합계 일치만 double-entry 완전성을 증명하지 않는다

이 책임을 맡는 곳: runtime owner
provenance

hardcoded marker는 실제 SELECT 결과 row가 아니다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

account 저장 balance와 account별 ledger 합계를 대조해 mismatch 0건을 요구한다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. reconcile invariant and marker
  2. NULL·empty·순서 반례와 Green 경계

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

1개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 f07165ad6fe0a5b0d4f9246e9be1a7786f3a0a02d0f6d6cd746b40bf1e0bf6f5와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/01-reconcile.sqlSHA-256 f07165ad6fe0a5b0d4f9246e9be1a7786f3a0a02d0f6d6cd746b40bf1e0bf6f5
01-reconcile.sql — 저장 잔액과 원장 합계 대조 전체
DO $g$ BEGIN IF (SELECT count(*) FROM w18_project.account a LEFT JOIN(SELECT account_id,sum(signed_amount)b FROM w18_project.ledger GROUP BY account_id)l ON l.account_id=a.id WHERE a.balance<>coalesce(l.b,0))<>0 THEN RAISE EXCEPTION 'reconcile';END IF;END $g$; SELECT 'W18_Q1 rows=0';
05

02-anti-join.sql — 원장 없는 계정 찾기

sql/w18/02-anti-join.sql

정본 SQL invariant · 정본 · W18-F05
1줄 연결1줄 번역1 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.

  1. missing id=2은 어느 줄에서 만들어지거나 검사될까?
  2. rows=1은 실제 계산값인가 고정 marker인가?
  3. array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값missing id=2rows=1marker=W18_Q2
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

02-anti-join.sql — 원장 없는 계정 찾기를 실행 전 검사표로 바꾸기

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.

핵심 관찰값 missing id=2, rows=1, marker=W18_Q2을 따라가되, array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

anti-join invariant and marker

1~1줄을 한 덩어리로 읽어 NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.의 1번째 단계를 확인한다.

코드 연결
1~1줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

전체 한 줄

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.

코드 연결
1줄
비유
한 장짜리 검사표를 처음부터 끝까지 읽는다.
비유의 끝
array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. 원장 없는 계정 찾기의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 missing id=2 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 rows=1을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL invariant에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.1 / 1 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F05-L01 DO $g$ BEGIN IF (SELECT array_agg(id ORDER BY id) FROM w18_project.account a WHERE NOT EXISTS(SELECT 1 FROM w18_project.ledger l WHERE l.account_id=a.id))<>ARRAY[2::bigint] THEN RAISE EXCEPTION 'anti join';END IF;END $g$; SELECT 'W18_Q2 rows=1 id=2'; 줄을 세울 반·번호표·창 범위를 차례로 붙인다. NOT EXISTS로 ledger가 없는 account id 배열이 [2]인지 검사한 뒤 Q2 marker를 출력한다.
입력
sql/w18/02-anti-join.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
현재 fixture에서는 missing id 배열 [2]가 통과해 W18_Q2 rows=1 id=2가 출력된다.
비유의 한계
array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F05-C01 · anti-join invariant and marker1–1줄
1–1줄 원본
DO $g$ BEGIN IF (SELECT array_agg(id ORDER BY id) FROM w18_project.account a WHERE NOT EXISTS(SELECT 1 FROM w18_project.ledger l WHERE l.account_id=a.id))<>ARRAY[2::bigint] THEN RAISE EXCEPTION 'anti join';END IF;END $g$; SELECT 'W18_Q2 rows=1 id=2';
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 1 / 1

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

원본한국어 번역
1DO $g$ BEGIN IF (SELECT array_agg(id ORDER BY id) FROM w18_project.account a WHERE NOT EXISTS(SELECT 1 FROM w18_project.ledger l WHERE l.account_id=a.id))<>ARRAY[2::bigint] THEN RAISE EXCEPTION 'anti join';END IF;END $g$; SELECT 'W18_Q2 rows=1 id=2';NOT EXISTS로 ledger가 없는 account id 배열이 [2]인지 검사한 뒤 Q2 marker를 출력한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다. 다만 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

문법 해부

  • DO block의 PL/pgSQL IF와 뒤의 SELECT marker는 서로 다른 statement다.
  • SQL NULL 비교는 false가 아니라 unknown이 될 수 있어 IF fail-open 여부를 확인해야 한다.
  • hardcoded marker 문자열과 실제 query 결과 row를 구분한다.

실행 순서

  1. DO block query
  2. fixture-bound invariant comparison
  3. optional exception
  4. hardcoded marker SELECT
  5. runner substring check

원래 W6 수준의 조각별 정밀 해설

F05-C01 · anti-join invariant and marker
문법 해부
`anti-join invariant and marker` 범위는 NOT EXISTS로 ledger가 없는 account id 배열이 [2]인지 검사한 뒤 Q2 marker를 출력한다. 이어서 NOT EXISTS로 ledger가 없는 account id 배열이 [2]인지 검사한 뒤 Q2 marker를 출력한다.
실제 값 추적
범위 시작 입력은 sql/w18/02-anti-join.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 현재 fixture에서는 missing id 배열 [2]가 통과해 W18_Q2 rows=1 id=2가 출력된다.
정상 예
동결 source 1~1줄을 그대로 적용하면 현재 fixture에서는 missing id 배열 [2]가 통과해 W18_Q2 rows=1 id=2가 출력된다.
틀린 예·반례
array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 / marker의 rows=1은 실제 row count에서 계산되지 않는다 / 현재 fixture에 묶인 oracle이다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 missing id=2이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 / marker의 rows=1은 실제 row count에서 계산되지 않는다 / 현재 fixture에 묶인 oracle이다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 marker의 rows=1은 실제 row count에서 계산되지 않는다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1missing id=2F05의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `missing id=2`가 기록되거나 그 값으로 비교된다.array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다
2rows=1F05의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `rows=1`가 기록되거나 그 값으로 비교된다.marker의 rows=1은 실제 row count에서 계산되지 않는다
3marker=W18_Q2F05의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `marker=W18_Q2`가 기록되거나 그 값으로 비교된다.현재 fixture에 묶인 oracle이다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 marker=W18_Q2을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PL/pgSQL DO block

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.에서 1번째 내부 책임을 수행한다.

array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다
PostgreSQL executor

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.에서 2번째 내부 책임을 수행한다.

marker의 rows=1은 실제 row count에서 계산되지 않는다
three-valued logic

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.에서 3번째 내부 책임을 수행한다.

현재 fixture에 묶인 oracle이다
psql marker stream

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.에서 4번째 내부 책임을 수행한다.

array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ missing id=2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ rows=1이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker의 rows=1은 실제 row count에서 계산되지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker의 rows=1은 실제 row count에서 계산되지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ marker=W18_Q2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 현재 fixture에 묶인 oracle이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 현재 fixture에 묶인 oracle이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ missing id=2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ rows=1이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker의 rows=1은 실제 row count에서 계산되지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker의 rows=1은 실제 row count에서 계산되지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

이 책임을 맡는 곳: caller path policy
result proof

marker의 rows=1은 실제 row count에서 계산되지 않는다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

현재 fixture에 묶인 oracle이다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

array_agg가 NULL이면 IF NULL 조건은 실행되지 않아 empty-result 변형이 fail-open이다

이 책임을 맡는 곳: runtime owner
provenance

marker의 rows=1은 실제 row count에서 계산되지 않는다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

NOT EXISTS anti-join으로 ledger가 없는 account id 배열이 정확히 [2]인지 확인한다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. anti-join invariant and marker
  2. NULL·empty·순서 반례와 Green 경계

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

1개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 8ea9c9c8ec2a81157fe6a400b35281c22c51219b62417f897a8fe6cc50b3bb64와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/02-anti-join.sqlSHA-256 8ea9c9c8ec2a81157fe6a400b35281c22c51219b62417f897a8fe6cc50b3bb64
02-anti-join.sql — 원장 없는 계정 찾기 전체
DO $g$ BEGIN IF (SELECT array_agg(id ORDER BY id) FROM w18_project.account a WHERE NOT EXISTS(SELECT 1 FROM w18_project.ledger l WHERE l.account_id=a.id))<>ARRAY[2::bigint] THEN RAISE EXCEPTION 'anti join';END IF;END $g$; SELECT 'W18_Q2 rows=1 id=2';
06

03-aggregation.sql — credit·debit 조건부 집계

sql/w18/03-aggregation.sql

정본 SQL invariant · 정본 · W18-F06
1줄 연결1줄 번역1 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.

  1. n=2은 어느 줄에서 만들어지거나 검사될까?
  2. credits=100은 실제 계산값인가 고정 marker인가?
  3. account_id=1에 고정된 fixture oracle이다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값n=2credits=100debits=30marker=W18_Q3
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

03-aggregation.sql — credit·debit 조건부 집계를 실행 전 검사표로 바꾸기

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.

핵심 관찰값 n=2, credits=100, debits=30, marker=W18_Q3을 따라가되, account_id=1에 고정된 fixture oracle이다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

conditional aggregation invariant and marker

1~1줄을 한 덩어리로 읽어 account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.의 1번째 단계를 확인한다.

코드 연결
1~1줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
account_id=1에 고정된 fixture oracle이다

전체 한 줄

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.

코드 연결
1줄
비유
한 장짜리 검사표를 처음부터 끝까지 읽는다.
비유의 끝
account_id=1에 고정된 fixture oracle이다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. credit·debit 조건부 집계의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 n=2 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 credits=100을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL invariant에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.1 / 1 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F06-L01 DO $g$ DECLARE r record;BEGIN SELECT count(*) n,coalesce(sum(signed_amount)FILTER(WHERE signed_amount>0),0)p,coalesce(-sum(signed_amount)FILTER(WHERE signed_amount<0),0)d INTO r FROM w18_project.ledger WHERE account_id=1;IF r.n<>2 OR r.p<>100 OR r.d<>30 THEN RAISE EXCEPTION 'aggregation';END IF;END $g$; SELECT 'W18_Q3 rows=1 credits=100 debits=30'; 검사표가 어긋나면 다음 방으로 가지 못하게 비상벨을 울린다. account 1의 행 수·양수 합·음수 절댓값 합을 record로 받아 2·100·30인지 검사한 뒤 Q3 marker를 출력한다.
입력
sql/w18/03-aggregation.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
record가 n=2,p=100,d=30이면 W18_Q3 credits=100 debits=30 marker가 출력된다.
비유의 한계
account_id=1에 고정된 fixture oracle이다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 account_id=1에 고정된 fixture oracle이다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F06-C01 · conditional aggregation invariant and marker1–1줄
1–1줄 원본
DO $g$ DECLARE r record;BEGIN SELECT count(*) n,coalesce(sum(signed_amount)FILTER(WHERE signed_amount>0),0)p,coalesce(-sum(signed_amount)FILTER(WHERE signed_amount<0),0)d INTO r FROM w18_project.ledger WHERE account_id=1;IF r.n<>2 OR r.p<>100 OR r.d<>30 THEN RAISE EXCEPTION 'aggregation';END IF;END $g$; SELECT 'W18_Q3 rows=1 credits=100 debits=30';
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 1 / 1

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

원본한국어 번역
1DO $g$ DECLARE r record;BEGIN SELECT count(*) n,coalesce(sum(signed_amount)FILTER(WHERE signed_amount>0),0)p,coalesce(-sum(signed_amount)FILTER(WHERE signed_amount<0),0)d INTO r FROM w18_project.ledger WHERE account_id=1;IF r.n<>2 OR r.p<>100 OR r.d<>30 THEN RAISE EXCEPTION 'aggregation';END IF;END $g$; SELECT 'W18_Q3 rows=1 credits=100 debits=30';account 1의 행 수·양수 합·음수 절댓값 합을 record로 받아 2·100·30인지 검사한 뒤 Q3 marker를 출력한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다. 다만 account_id=1에 고정된 fixture oracle이다

문법 해부

  • DO block의 PL/pgSQL IF와 뒤의 SELECT marker는 서로 다른 statement다.
  • SQL NULL 비교는 false가 아니라 unknown이 될 수 있어 IF fail-open 여부를 확인해야 한다.
  • hardcoded marker 문자열과 실제 query 결과 row를 구분한다.

실행 순서

  1. DO block query
  2. fixture-bound invariant comparison
  3. optional exception
  4. hardcoded marker SELECT
  5. runner substring check

원래 W6 수준의 조각별 정밀 해설

F06-C01 · conditional aggregation invariant and marker
문법 해부
`conditional aggregation invariant and marker` 범위는 account 1의 행 수·양수 합·음수 절댓값 합을 record로 받아 2·100·30인지 검사한 뒤 Q3 marker를 출력한다. 이어서 account 1의 행 수·양수 합·음수 절댓값 합을 record로 받아 2·100·30인지 검사한 뒤 Q3 marker를 출력한다.
실제 값 추적
범위 시작 입력은 sql/w18/03-aggregation.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 record가 n=2,p=100,d=30이면 W18_Q3 credits=100 debits=30 marker가 출력된다.
정상 예
동결 source 1~1줄을 그대로 적용하면 record가 n=2,p=100,d=30이면 W18_Q3 credits=100 debits=30 marker가 출력된다.
틀린 예·반례
account_id=1에 고정된 fixture oracle이다 / debit은 음수 합에 minus를 붙여 양수로 표시한다 / marker는 계산값을 직접 출력하지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 n=2이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: account_id=1에 고정된 fixture oracle이다 / debit은 음수 합에 minus를 붙여 양수로 표시한다 / marker는 계산값을 직접 출력하지 않는다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 debit은 음수 합에 minus를 붙여 양수로 표시한다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1n=2F06의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `n=2`가 기록되거나 그 값으로 비교된다.account_id=1에 고정된 fixture oracle이다
2credits=100F06의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `credits=100`가 기록되거나 그 값으로 비교된다.debit은 음수 합에 minus를 붙여 양수로 표시한다
3debits=30F06의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `debits=30`가 기록되거나 그 값으로 비교된다.marker는 계산값을 직접 출력하지 않는다
4marker=W18_Q3F06의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `marker=W18_Q3`가 기록되거나 그 값으로 비교된다.account_id=1에 고정된 fixture oracle이다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 marker=W18_Q3을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PL/pgSQL DO block

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.에서 1번째 내부 책임을 수행한다.

account_id=1에 고정된 fixture oracle이다
PostgreSQL executor

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.에서 2번째 내부 책임을 수행한다.

debit은 음수 합에 minus를 붙여 양수로 표시한다
three-valued logic

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.에서 3번째 내부 책임을 수행한다.

marker는 계산값을 직접 출력하지 않는다
psql marker stream

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.에서 4번째 내부 책임을 수행한다.

account_id=1에 고정된 fixture oracle이다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ n=2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 account_id=1에 고정된 fixture oracle이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 account_id=1에 고정된 fixture oracle이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ credits=100이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 debit은 음수 합에 minus를 붙여 양수로 표시한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 debit은 음수 합에 minus를 붙여 양수로 표시한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ debits=30이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker는 계산값을 직접 출력하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker는 계산값을 직접 출력하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ marker=W18_Q3이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 account_id=1에 고정된 fixture oracle이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 account_id=1에 고정된 fixture oracle이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ n=2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 debit은 음수 합에 minus를 붙여 양수로 표시한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 debit은 음수 합에 minus를 붙여 양수로 표시한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

account_id=1에 고정된 fixture oracle이다

이 책임을 맡는 곳: caller path policy
result proof

debit은 음수 합에 minus를 붙여 양수로 표시한다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

marker는 계산값을 직접 출력하지 않는다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

account_id=1에 고정된 fixture oracle이다

이 책임을 맡는 곳: runtime owner
provenance

debit은 음수 합에 minus를 붙여 양수로 표시한다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

account 1의 ledger 두 행을 조건부 집계해 credit 100과 debit 30을 요구한다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. conditional aggregation invariant and marker
  2. NULL·empty·순서 반례와 Green 경계

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

1개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 c07f6588ae5b431c733d84d097c2dec446470470405e57e0126e8e09dcd82c1d와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/03-aggregation.sqlSHA-256 c07f6588ae5b431c733d84d097c2dec446470470405e57e0126e8e09dcd82c1d
03-aggregation.sql — credit·debit 조건부 집계 전체
DO $g$ DECLARE r record;BEGIN SELECT count(*) n,coalesce(sum(signed_amount)FILTER(WHERE signed_amount>0),0)p,coalesce(-sum(signed_amount)FILTER(WHERE signed_amount<0),0)d INTO r FROM w18_project.ledger WHERE account_id=1;IF r.n<>2 OR r.p<>100 OR r.d<>30 THEN RAISE EXCEPTION 'aggregation';END IF;END $g$; SELECT 'W18_Q3 rows=1 credits=100 debits=30';
07

04-running-balance.sql — 안정 순서 누적 잔액

sql/w18/04-running-balance.sql

정본 SQL invariant · 정본 · W18-F07
1줄 연결1줄 번역1 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.

  1. 100 -> 70은 어느 줄에서 만들어지거나 검사될까?
  2. ORDER BY created_at,id은 실제 계산값인가 고정 marker인가?
  3. 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값100 -> 70ORDER BY created_at,idmarker rows=2 final=70
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

04-running-balance.sql — 안정 순서 누적 잔액를 실행 전 검사표로 바꾸기

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.

핵심 관찰값 100 -> 70, ORDER BY created_at,id, marker rows=2 final=70을 따라가되, 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

window running balance invariant and marker

1~1줄을 한 덩어리로 읽어 created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.의 1번째 단계를 확인한다.

코드 연결
1~1줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

전체 한 줄

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.

코드 연결
1줄
비유
한 장짜리 검사표를 처음부터 끝까지 읽는다.
비유의 끝
최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. 안정 순서 누적 잔액의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 100 -> 70 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 ORDER BY created_at,id을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL invariant에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.1 / 1 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F07-L01 DO $g$ DECLARE x bigint;BEGIN SELECT running INTO x FROM(SELECT id,sum(signed_amount)OVER(PARTITION BY account_id ORDER BY created_at,id)running FROM w18_project.ledger WHERE account_id=1)s ORDER BY id DESC LIMIT 1;IF x<>70 THEN RAISE EXCEPTION 'running';END IF;END $g$; SELECT 'W18_Q4 rows=2 final=70'; 줄을 세울 반·번호표·창 범위를 차례로 붙인다. account 1을 created_at,id 순서로 window sum한 뒤 id가 가장 큰 행의 running 값이 70인지 검사하고 Q4 marker를 출력한다.
입력
sql/w18/04-running-balance.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
현재 id/time 정렬에서는 100 다음 70이 되어 W18_Q4 final=70 marker가 출력된다.
비유의 한계
최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F07-C01 · window running balance invariant and marker1–1줄
1–1줄 원본
DO $g$ DECLARE x bigint;BEGIN SELECT running INTO x FROM(SELECT id,sum(signed_amount)OVER(PARTITION BY account_id ORDER BY created_at,id)running FROM w18_project.ledger WHERE account_id=1)s ORDER BY id DESC LIMIT 1;IF x<>70 THEN RAISE EXCEPTION 'running';END IF;END $g$; SELECT 'W18_Q4 rows=2 final=70';
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 1 / 1

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

원본한국어 번역
1DO $g$ DECLARE x bigint;BEGIN SELECT running INTO x FROM(SELECT id,sum(signed_amount)OVER(PARTITION BY account_id ORDER BY created_at,id)running FROM w18_project.ledger WHERE account_id=1)s ORDER BY id DESC LIMIT 1;IF x<>70 THEN RAISE EXCEPTION 'running';END IF;END $g$; SELECT 'W18_Q4 rows=2 final=70';account 1을 created_at,id 순서로 window sum한 뒤 id가 가장 큰 행의 running 값이 70인지 검사하고 Q4 marker를 출력한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다. 다만 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

문법 해부

  • DO block의 PL/pgSQL IF와 뒤의 SELECT marker는 서로 다른 statement다.
  • SQL NULL 비교는 false가 아니라 unknown이 될 수 있어 IF fail-open 여부를 확인해야 한다.
  • hardcoded marker 문자열과 실제 query 결과 row를 구분한다.

실행 순서

  1. DO block query
  2. fixture-bound invariant comparison
  3. optional exception
  4. hardcoded marker SELECT
  5. runner substring check

원래 W6 수준의 조각별 정밀 해설

F07-C01 · window running balance invariant and marker
문법 해부
`window running balance invariant and marker` 범위는 account 1을 created_at,id 순서로 window sum한 뒤 id가 가장 큰 행의 running 값이 70인지 검사하고 Q4 marker를 출력한다. 이어서 account 1을 created_at,id 순서로 window sum한 뒤 id가 가장 큰 행의 running 값이 70인지 검사하고 Q4 marker를 출력한다.
실제 값 추적
범위 시작 입력은 sql/w18/04-running-balance.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 현재 id/time 정렬에서는 100 다음 70이 되어 W18_Q4 final=70 marker가 출력된다.
정상 예
동결 source 1~1줄을 그대로 적용하면 현재 id/time 정렬에서는 100 다음 70이 되어 W18_Q4 final=70 marker가 출력된다.
틀린 예·반례
최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 / x가 NULL이면 IF x<>70이 NULL이라 fail-open이다 / marker rows=2는 실제로 세지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 100 -> 70이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 / x가 NULL이면 IF x<>70이 NULL이라 fail-open이다 / marker rows=2는 실제로 세지 않는다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 x가 NULL이면 IF x<>70이 NULL이라 fail-open이다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1100 -> 70F07의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `100 -> 70`가 기록되거나 그 값으로 비교된다.최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다
2ORDER BY created_at,idF07의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `ORDER BY created_at,id`가 기록되거나 그 값으로 비교된다.x가 NULL이면 IF x<>70이 NULL이라 fail-open이다
3marker rows=2 final=70F07의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `marker rows=2 final=70`가 기록되거나 그 값으로 비교된다.marker rows=2는 실제로 세지 않는다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 marker rows=2 final=70을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PL/pgSQL DO block

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.에서 1번째 내부 책임을 수행한다.

최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다
PostgreSQL executor

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.에서 2번째 내부 책임을 수행한다.

x가 NULL이면 IF x<>70이 NULL이라 fail-open이다
three-valued logic

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.에서 3번째 내부 책임을 수행한다.

marker rows=2는 실제로 세지 않는다
psql marker stream

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.에서 4번째 내부 책임을 수행한다.

최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ 100 -> 70이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ ORDER BY created_at,id이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 x가 NULL이면 IF x<>70이 NULL이라 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 x가 NULL이면 IF x<>70이 NULL이라 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ marker rows=2 final=70이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker rows=2는 실제로 세지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker rows=2는 실제로 세지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ 100 -> 70이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ ORDER BY created_at,id이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 x가 NULL이면 IF x<>70이 NULL이라 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 x가 NULL이면 IF x<>70이 NULL이라 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

이 책임을 맡는 곳: caller path policy
result proof

x가 NULL이면 IF x<>70이 NULL이라 fail-open이다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

marker rows=2는 실제로 세지 않는다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

최종 행 선택은 id DESC라 id와 시간 순서가 어긋나면 잘못된 행을 집을 수 있다

이 책임을 맡는 곳: runtime owner
provenance

x가 NULL이면 IF x<>70이 NULL이라 fail-open이다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

created_at,id 순서 window sum으로 account 1의 누적잔액을 만들고 최종값 70을 검사한다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. window running balance invariant and marker
  2. NULL·empty·순서 반례와 Green 경계

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

1개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 8353f5ce6de5b0e39ee265446e7fb8098db9d54849300e95db36e3bc052b5a19와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/04-running-balance.sqlSHA-256 8353f5ce6de5b0e39ee265446e7fb8098db9d54849300e95db36e3bc052b5a19
04-running-balance.sql — 안정 순서 누적 잔액 전체
DO $g$ DECLARE x bigint;BEGIN SELECT running INTO x FROM(SELECT id,sum(signed_amount)OVER(PARTITION BY account_id ORDER BY created_at,id)running FROM w18_project.ledger WHERE account_id=1)s ORDER BY id DESC LIMIT 1;IF x<>70 THEN RAISE EXCEPTION 'running';END IF;END $g$; SELECT 'W18_Q4 rows=2 final=70';
08

05-keyset.sql — 복합 cursor 다음 두 행

sql/w18/05-keyset.sql

정본 SQL invariant · 정본 · W18-F08
1줄 연결1줄 번역1 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.

  1. cursor=2026-01-03Z,99은 어느 줄에서 만들어지거나 검사될까?
  2. ids=2|1은 실제 계산값인가 고정 marker인가?
  3. ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값cursor=2026-01-03Z,99ids=2|1limit=2marker=W18_Q5
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

05-keyset.sql — 복합 cursor 다음 두 행를 실행 전 검사표로 바꾸기

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.

핵심 관찰값 cursor=2026-01-03Z,99, ids=2|1, limit=2, marker=W18_Q5을 따라가되, ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

keyset tuple cursor invariant and marker

1~1줄을 한 덩어리로 읽어 (created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.의 1번째 단계를 확인한다.

코드 연결
1~1줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

전체 한 줄

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.

코드 연결
1줄
비유
한 장짜리 검사표를 처음부터 끝까지 읽는다.
비유의 끝
ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. 복합 cursor 다음 두 행의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 cursor=2026-01-03Z,99 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 ids=2|1을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.정본 SQL invariant에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.1 / 1 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F08-L01 DO $g$ DECLARE ids bigint[];BEGIN SELECT array_agg(id ORDER BY created_at DESC,id DESC) INTO ids FROM(SELECT * FROM w18_project.ledger WHERE account_id=1 AND(created_at,id)<('2026-01-03Z'::timestamptz,99)ORDER BY created_at DESC,id DESC LIMIT 2)s;IF ids<>ARRAY[2::bigint,1::bigint] THEN RAISE EXCEPTION 'keyset';END IF;END $g$; SELECT 'W18_Q5 rows=2 ids=2|1'; 줄을 세울 반·번호표·창 범위를 차례로 붙인다. 복합 cursor보다 작은 account 1 ledger를 최신순 두 건으로 제한해 id 배열 [2,1]을 검사하고 Q5 marker를 출력한다.
입력
sql/w18/05-keyset.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
cursor 조건 아래 최신 두 id가 2,1이 되어 W18_Q5 ids=2|1 marker가 출력된다.
비유의 한계
ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F08-C01 · keyset tuple cursor invariant and marker1–1줄
1–1줄 원본
DO $g$ DECLARE ids bigint[];BEGIN SELECT array_agg(id ORDER BY created_at DESC,id DESC) INTO ids FROM(SELECT * FROM w18_project.ledger WHERE account_id=1 AND(created_at,id)<('2026-01-03Z'::timestamptz,99)ORDER BY created_at DESC,id DESC LIMIT 2)s;IF ids<>ARRAY[2::bigint,1::bigint] THEN RAISE EXCEPTION 'keyset';END IF;END $g$; SELECT 'W18_Q5 rows=2 ids=2|1';
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 1 / 1

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

원본한국어 번역
1DO $g$ DECLARE ids bigint[];BEGIN SELECT array_agg(id ORDER BY created_at DESC,id DESC) INTO ids FROM(SELECT * FROM w18_project.ledger WHERE account_id=1 AND(created_at,id)<('2026-01-03Z'::timestamptz,99)ORDER BY created_at DESC,id DESC LIMIT 2)s;IF ids<>ARRAY[2::bigint,1::bigint] THEN RAISE EXCEPTION 'keyset';END IF;END $g$; SELECT 'W18_Q5 rows=2 ids=2|1';복합 cursor보다 작은 account 1 ledger를 최신순 두 건으로 제한해 id 배열 [2,1]을 검사하고 Q5 marker를 출력한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다. 다만 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

문법 해부

  • DO block의 PL/pgSQL IF와 뒤의 SELECT marker는 서로 다른 statement다.
  • SQL NULL 비교는 false가 아니라 unknown이 될 수 있어 IF fail-open 여부를 확인해야 한다.
  • hardcoded marker 문자열과 실제 query 결과 row를 구분한다.

실행 순서

  1. DO block query
  2. fixture-bound invariant comparison
  3. optional exception
  4. hardcoded marker SELECT
  5. runner substring check

원래 W6 수준의 조각별 정밀 해설

F08-C01 · keyset tuple cursor invariant and marker
문법 해부
`keyset tuple cursor invariant and marker` 범위는 복합 cursor보다 작은 account 1 ledger를 최신순 두 건으로 제한해 id 배열 [2,1]을 검사하고 Q5 marker를 출력한다. 이어서 복합 cursor보다 작은 account 1 ledger를 최신순 두 건으로 제한해 id 배열 [2,1]을 검사하고 Q5 marker를 출력한다.
실제 값 추적
범위 시작 입력은 sql/w18/05-keyset.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 cursor 조건 아래 최신 두 id가 2,1이 되어 W18_Q5 ids=2|1 marker가 출력된다.
정상 예
동결 source 1~1줄을 그대로 적용하면 cursor 조건 아래 최신 두 id가 2,1이 되어 W18_Q5 ids=2|1 marker가 출력된다.
틀린 예·반례
ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 / marker rows=2는 실제 array 길이에서 계산하지 않는다 / tuple 비교 방향은 정렬 방향과 함께 유지해야 한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 cursor=2026-01-03Z,99이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 / marker rows=2는 실제 array 길이에서 계산하지 않는다 / tuple 비교 방향은 정렬 방향과 함께 유지해야 한다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 marker rows=2는 실제 array 길이에서 계산하지 않는다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1cursor=2026-01-03Z,99F08의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `cursor=2026-01-03Z,99`가 기록되거나 그 값으로 비교된다.ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
2ids=2|1F08의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `ids=2|1`가 기록되거나 그 값으로 비교된다.marker rows=2는 실제 array 길이에서 계산하지 않는다
3limit=2F08의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `limit=2`가 기록되거나 그 값으로 비교된다.tuple 비교 방향은 정렬 방향과 함께 유지해야 한다
4marker=W18_Q5F08의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `marker=W18_Q5`가 기록되거나 그 값으로 비교된다.ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 marker=W18_Q5을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

PL/pgSQL DO block

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.에서 1번째 내부 책임을 수행한다.

ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
PostgreSQL executor

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.에서 2번째 내부 책임을 수행한다.

marker rows=2는 실제 array 길이에서 계산하지 않는다
three-valued logic

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.에서 3번째 내부 책임을 수행한다.

tuple 비교 방향은 정렬 방향과 함께 유지해야 한다
psql marker stream

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.에서 4번째 내부 책임을 수행한다.

ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ cursor=2026-01-03Z,99이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ ids=2|1이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker rows=2는 실제 array 길이에서 계산하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker rows=2는 실제 array 길이에서 계산하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ limit=2이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 tuple 비교 방향은 정렬 방향과 함께 유지해야 한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 tuple 비교 방향은 정렬 방향과 함께 유지해야 한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ marker=W18_Q5이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ cursor=2026-01-03Z,99이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 marker rows=2는 실제 array 길이에서 계산하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 marker rows=2는 실제 array 길이에서 계산하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

이 책임을 맡는 곳: caller path policy
result proof

marker rows=2는 실제 array 길이에서 계산하지 않는다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

tuple 비교 방향은 정렬 방향과 함께 유지해야 한다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

ids가 NULL이면 IF ids<>expected가 NULL이라 fail-open이다

이 책임을 맡는 곳: runtime owner
provenance

marker rows=2는 실제 array 길이에서 계산하지 않는다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

(created_at,id) 복합 cursor보다 작은 account 1 ledger를 최신순 두 건 [2,1]로 제한한다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. keyset tuple cursor invariant and marker
  2. NULL·empty·순서 반례와 Green 경계

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

1개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 71316b719893b5af3342e0809420ee412309cddbc0c66f4f75c748c047117abc와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

정본 전체 코드 확인하기
hash로 고정한 native 정본 sourcesql/w18/05-keyset.sqlSHA-256 71316b719893b5af3342e0809420ee412309cddbc0c66f4f75c748c047117abc
05-keyset.sql — 복합 cursor 다음 두 행 전체
DO $g$ DECLARE ids bigint[];BEGIN SELECT array_agg(id ORDER BY created_at DESC,id DESC) INTO ids FROM(SELECT * FROM w18_project.ledger WHERE account_id=1 AND(created_at,id)<('2026-01-03Z'::timestamptz,99)ORDER BY created_at DESC,id DESC LIMIT 2)s;IF ids<>ARRAY[2::bigint,1::bigint] THEN RAISE EXCEPTION 'keyset';END IF;END $g$; SELECT 'W18_Q5 rows=2 ids=2|1';
09

W18-SQL-Q27.sql — 고객별 첫·마지막 거래일 예시

illustrative/sql/W18-SQL-Q27.sql

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

STEP 01 / 13

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

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

오늘의 한 문장

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.

  1. customers=6은 어느 줄에서 만들어지거나 검사될까?
  2. customer4/5=NULL,NULL은 실제 계산값인가 고정 marker인가?
  3. prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값customers=6customer4/5=NULL,NULLOPENING includedbusiness_date used
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

W18-SQL-Q27.sql — 고객별 첫·마지막 거래일 예시를 실행 전 검사표로 바꾸기

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.

핵심 관찰값 customers=6, customer4/5=NULL,NULL, OPENING included, business_date used을 따라가되, prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

provenance assumptions and schema

1~5줄을 한 덩어리로 읽어 customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.의 1번째 단계를 확인한다.

코드 연결
1~5줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

customer grain and MIN MAX

6~10줄을 한 덩어리로 읽어 customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.의 2번째 단계를 확인한다.

코드 연결
6~10줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
시각이 아니라 영업일의 첫·마지막을 답한다

zero-row preserving joins and order

11~14줄을 한 덩어리로 읽어 customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.의 3번째 단계를 확인한다.

코드 연결
11~14줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. 고객별 첫·마지막 거래일 예시의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 customers=6 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 customer4/5=NULL,NULL을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.학습용 SQL 예시 · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.16 / 16 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F09-L01 -- W18-SQL-Q27 illustrative example; not a shipped workbook answer. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
정본과 예시, 명시된 가정과 미보장 영역이 화면에서 구분된다.
비유의 한계
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
2줄F09-L02 -- Assumption: every business_tx status and tx_type, including OPENING, counts as a transaction. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. prompt가 비워 둔 순서·포함 범위를 이 예시의 명시적 가정으로 고정한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 2줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
정본과 예시, 명시된 가정과 미보장 영역이 화면에서 구분된다.
비유의 한계
시각이 아니라 영업일의 첫·마지막을 답한다
3줄F09-L03 -- No-transaction customers remain with NULL first/last dates. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 거래가 없는 고객을 남기고 날짜 둘을 NULL로 둘 정책을 선언한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
4줄F09-L04 SET search_path TO :"workbook_schema", public; 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 4줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
shipped workbook 정답이 아니다
6줄F09-L06 SELECT 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 최종 customer-grain projection 절을 시작한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 6줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
시각이 아니라 영업일의 첫·마지막을 답한다
7줄F09-L07 c.customer_id, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 결과 grain을 customer_id로 드러낸다.
입력
illustrative/sql/W18-SQL-Q27.sql의 7줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 7줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
8줄F09-L08 MIN(t.business_date) AS first_transaction_date, 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. customer에 연결된 모든 transaction의 최소 business_date를 첫 거래일로 계산한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 8줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 8줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
shipped workbook 정답이 아니다
9줄F09-L09 MAX(t.business_date) AS last_transaction_date 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. customer에 연결된 모든 transaction의 최대 business_date를 마지막 거래일로 계산한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 9줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 9줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
10줄F09-L10 FROM customer AS c 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. zero-transaction 고객을 보존하려고 customer에서 query를 시작한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 10줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q27 pipeline이 10줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
비유의 한계
시각이 아니라 영업일의 첫·마지막을 답한다
11줄F09-L11 LEFT JOIN account AS a ON a.customer_id = c.customer_id 명단의 사람을 지우지 않고 옆 장부 칸만 연결한다. account가 없는 customer도 남기는 outer join으로 account를 연결한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
customer 4와 5도 NULL 날짜를 가진 채 결과 후보 집합에 남는다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
12줄F09-L12 LEFT JOIN business_tx AS t ON t.account_id = a.account_id 명단의 사람을 지우지 않고 옆 장부 칸만 연결한다. 거래가 없는 account도 남기는 outer join으로 business_tx를 연결한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 12줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
customer 4와 5도 NULL 날짜를 가진 채 결과 후보 집합에 남는다.
비유의 한계
shipped workbook 정답이 아니다
13줄F09-L13 GROUP BY c.customer_id 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. MIN/MAX 집계를 customer 한 행으로 묶는다.
입력
illustrative/sql/W18-SQL-Q27.sql의 13줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
각 customer가 first/last date 두 값이 붙은 정확히 한 행으로 축약된다.
비유의 한계
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
14줄F09-L14 ORDER BY c.customer_id; 줄을 세울 반·번호표·창 범위를 차례로 붙인다. customer_id 순서로 여섯 결과 행을 안정화한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 14줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
결과가 customer 1부터 6까지 한눈에 비교 가능한 여섯 행으로 정렬된다.
비유의 한계
시각이 아니라 영업일의 첫·마지막을 답한다
15줄F09-L15 -- Oracle: customer 1 = 2026-06-30..2027-01-02; customer 2 = 2026-06-30..2027-01-02. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
16줄F09-L16 -- customer 3 = 2026-06-30..2026-06-30; customer 4 = NULL..NULL. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 16줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
비유의 한계
shipped workbook 정답이 아니다
17줄F09-L17 -- customer 5 = NULL..NULL; customer 6 = 2026-06-30..2026-06-30. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
입력
illustrative/sql/W18-SQL-Q27.sql의 17줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
비유의 한계
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F09-C01 · provenance assumptions and schema1–5줄
1–5줄 원본
-- W18-SQL-Q27 illustrative example; not a shipped workbook answer.
-- Assumption: every business_tx status and tx_type, including OPENING, counts as a transaction.
-- No-transaction customers remain with NULL first/last dates.
SET search_path TO :"workbook_schema", public;
F09-C02 · customer grain and MIN MAX6–10줄
6–10줄 원본
SELECT
    c.customer_id,
    MIN(t.business_date) AS first_transaction_date,
    MAX(t.business_date) AS last_transaction_date
FROM customer AS c
F09-C03 · zero-row preserving joins and order11–14줄
11–14줄 원본
LEFT JOIN account AS a ON a.customer_id = c.customer_id
LEFT JOIN business_tx AS t ON t.account_id = a.account_id
GROUP BY c.customer_id
ORDER BY c.customer_id;
F09-C04 · fixture oracle15–17줄
15–17줄 원본
-- Oracle: customer 1 = 2026-06-30..2027-01-02; customer 2 = 2026-06-30..2027-01-02.
-- customer 3 = 2026-06-30..2026-06-30; customer 4 = NULL..NULL.
-- customer 5 = NULL..NULL; customer 6 = 2026-06-30..2026-06-30.
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 16 / 16

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

원본한국어 번역
1-- W18-SQL-Q27 illustrative example; not a shipped workbook answer.이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다.
2-- Assumption: every business_tx status and tx_type, including OPENING, counts as a transaction.prompt가 비워 둔 순서·포함 범위를 이 예시의 명시적 가정으로 고정한다.
3-- No-transaction customers remain with NULL first/last dates.거래가 없는 고객을 남기고 날짜 둘을 NULL로 둘 정책을 선언한다.
4SET search_path TO :"workbook_schema", public;psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
6SELECT최종 customer-grain projection 절을 시작한다.
7 c.customer_id,결과 grain을 customer_id로 드러낸다.
8 MIN(t.business_date) AS first_transaction_date,customer에 연결된 모든 transaction의 최소 business_date를 첫 거래일로 계산한다.
9 MAX(t.business_date) AS last_transaction_datecustomer에 연결된 모든 transaction의 최대 business_date를 마지막 거래일로 계산한다.
10FROM customer AS czero-transaction 고객을 보존하려고 customer에서 query를 시작한다.
11LEFT JOIN account AS a ON a.customer_id = c.customer_idaccount가 없는 customer도 남기는 outer join으로 account를 연결한다.
12LEFT JOIN business_tx AS t ON t.account_id = a.account_id거래가 없는 account도 남기는 outer join으로 business_tx를 연결한다.
13GROUP BY c.customer_idMIN/MAX 집계를 customer 한 행으로 묶는다.
14ORDER BY c.customer_id;customer_id 순서로 여섯 결과 행을 안정화한다.
15-- Oracle: customer 1 = 2026-06-30..2027-01-02; customer 2 = 2026-06-30..2027-01-02.동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
16-- customer 3 = 2026-06-30..2026-06-30; customer 4 = NULL..NULL.동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
17-- customer 5 = NULL..NULL; customer 6 = 2026-06-30..2026-06-30.동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다. 다만 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

문법 해부

  • CTE는 이름 붙인 중간 relation을 statement 안에서 연결한다.
  • GROUP BY grain과 window PARTITION/ORDER/frame은 서로 다른 역할이다.
  • LEFT JOIN 이후 predicate 위치가 zero-row 보존 여부를 바꾼다.

실행 순서

  1. search_path
  2. customer start
  3. two LEFT JOINs
  4. customer GROUP BY
  5. MIN/MAX dates
  6. stable display order

원래 W6 수준의 조각별 정밀 해설

F09-C01 · provenance assumptions and schema
문법 해부
`provenance assumptions and schema` 범위는 이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다. 이어서 psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q27.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Q27 pipeline이 4줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
정상 예
동결 source 1~5줄을 그대로 적용하면 Q27 pipeline이 4줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
틀린 예·반례
prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 customers=6이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
다음 연결
끝 상태를 보존한 뒤 `customer grain and MIN MAX` 범위에서 다음 입력·결과를 확인한다.
F09-C02 · customer grain and MIN MAX
문법 해부
`customer grain and MIN MAX` 범위는 최종 customer-grain projection 절을 시작한다. 이어서 zero-transaction 고객을 보존하려고 customer에서 query를 시작한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q27.sql의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Q27 pipeline이 10줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
정상 예
동결 source 6~10줄을 그대로 적용하면 Q27 pipeline이 10줄 이후 customer-grain 날짜 계산에 필요한 상태를 얻는다.
틀린 예·반례
시각이 아니라 영업일의 첫·마지막을 답한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 customer4/5=NULL,NULL이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: 시각이 아니라 영업일의 첫·마지막을 답한다
다음 연결
끝 상태를 보존한 뒤 `zero-row preserving joins and order` 범위에서 다음 입력·결과를 확인한다.
F09-C03 · zero-row preserving joins and order
문법 해부
`zero-row preserving joins and order` 범위는 account가 없는 customer도 남기는 outer join으로 account를 연결한다. 이어서 customer_id 순서로 여섯 결과 행을 안정화한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q27.sql의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 결과가 customer 1부터 6까지 한눈에 비교 가능한 여섯 행으로 정렬된다.
정상 예
동결 source 11~14줄을 그대로 적용하면 결과가 customer 1부터 6까지 한눈에 비교 가능한 여섯 행으로 정렬된다.
틀린 예·반례
psql 변수 workbook_schema가 실행 전에 주입되어야 한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 OPENING included이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: psql 변수 workbook_schema가 실행 전에 주입되어야 한다
다음 연결
끝 상태를 보존한 뒤 `fixture oracle` 범위에서 다음 입력·결과를 확인한다.
F09-C04 · fixture oracle
문법 해부
`fixture oracle` 범위는 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다. 이어서 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q27.sql의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
정상 예
동결 source 15~17줄을 그대로 적용하면 독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
틀린 예·반례
shipped workbook 정답이 아니다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 business_date used이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: psql 변수 workbook_schema가 실행 전에 주입되어야 한다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 시각이 아니라 영업일의 첫·마지막을 답한다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1customers=6F09의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `customers=6`가 기록되거나 그 값으로 비교된다.prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
2customer4/5=NULL,NULLF09의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `customer4/5=NULL,NULL`가 기록되거나 그 값으로 비교된다.시각이 아니라 영업일의 첫·마지막을 답한다
3OPENING includedF09의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `OPENING included`가 기록되거나 그 값으로 비교된다.psql 변수 workbook_schema가 실행 전에 주입되어야 한다
4business_date usedF09의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `business_date used`가 기록되거나 그 값으로 비교된다.shipped workbook 정답이 아니다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 business_date used을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

psql search_path

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.에서 1번째 내부 책임을 수행한다.

prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다
PostgreSQL planner

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.에서 2번째 내부 책임을 수행한다.

시각이 아니라 영업일의 첫·마지막을 답한다
join/aggregate executor

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.에서 3번째 내부 책임을 수행한다.

psql 변수 workbook_schema가 실행 전에 주입되어야 한다
window executor

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.에서 4번째 내부 책임을 수행한다.

shipped workbook 정답이 아니다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ customers=6이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ customer4/5=NULL,NULL이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 시각이 아니라 영업일의 첫·마지막을 답한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 시각이 아니라 영업일의 첫·마지막을 답한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ OPENING included이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 psql 변수 workbook_schema가 실행 전에 주입되어야 한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 psql 변수 workbook_schema가 실행 전에 주입되어야 한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ business_date used이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 shipped workbook 정답이 아니다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 shipped workbook 정답이 아니다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ customers=6이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

이 책임을 맡는 곳: caller path policy
result proof

시각이 아니라 영업일의 첫·마지막을 답한다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

psql 변수 workbook_schema가 실행 전에 주입되어야 한다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

shipped workbook 정답이 아니다

이 책임을 맡는 곳: runtime owner
provenance

prompt가 status·tx_type 포함범위를 고정하지 않아 모든 row 포함 가정을 주석으로 고정했다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

customer를 시작점으로 거래가 없는 고객까지 보존하면서 첫·마지막 business_date를 MIN/MAX로 계산하는 Q27 비정답 예시다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. provenance assumptions and schema
  2. customer grain and MIN MAX
  3. zero-row preserving joins and order
  4. fixture oracle

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

17개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 b4d1c9be2731e41a4c5ef8dd3b60e621878a3443ca2455f9e544958ff1fd076f와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 정본 답안 아님illustrative/sql/W18-SQL-Q27.sqlSHA-256 b4d1c9be2731e41a4c5ef8dd3b60e621878a3443ca2455f9e544958ff1fd076f
W18-SQL-Q27.sql — 고객별 첫·마지막 거래일 예시 전체
-- W18-SQL-Q27 illustrative example; not a shipped workbook answer.
-- Assumption: every business_tx status and tx_type, including OPENING, counts as a transaction.
-- No-transaction customers remain with NULL first/last dates.
SET search_path TO :"workbook_schema", public;

SELECT
    c.customer_id,
    MIN(t.business_date) AS first_transaction_date,
    MAX(t.business_date) AS last_transaction_date
FROM customer AS c
LEFT JOIN account AS a ON a.customer_id = c.customer_id
LEFT JOIN business_tx AS t ON t.account_id = a.account_id
GROUP BY c.customer_id
ORDER BY c.customer_id;
-- Oracle: customer 1 = 2026-06-30..2027-01-02; customer 2 = 2026-06-30..2027-01-02.
-- customer 3 = 2026-06-30..2026-06-30; customer 4 = NULL..NULL.
-- customer 5 = NULL..NULL; customer 6 = 2026-06-30..2026-06-30.
10

W18-SQL-Q28.sql — actor별 연속 실패 island 예시

illustrative/sql/W18-SQL-Q28.sql

학습용 SQL 예시 · 정본 답안 아님 · 학습용 예시 · 정본 답안 아님 · W18-F10
32줄 연결32줄 번역4 chunks
01

STEP 01 / 13

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

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

오늘의 한 문장

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.

  1. actor=USER-FAIL은 어느 줄에서 만들어지거나 검사될까?
  2. streak=3은 실제 계산값인가 고정 marker인가?
  3. 연속의 순서 기준은 prompt 보강 가정이다 경계에서 첫 실패는 어디일까?
  4. 빈 집합·NULL·동률·순서 역전 중 어떤 반례가 중요한가?
  5. 이 source가 책임지지 않는 lifecycle·provenance는 무엇인가?
이 파일에서 끝까지 다시 쓰는 값actor=USER-FAILstreak=3order=occurred_at,tx_id10:00..10:02+09
02

STEP 02 / 13

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

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

STARRY가 source를 값·순서·증명 경계가 적힌 작은 실험 카드로 나눈다.

W18-SQL-Q28.sql — actor별 연속 실패 island 예시를 실행 전 검사표로 바꾸기

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.

핵심 관찰값 actor=USER-FAIL, streak=3, order=occurred_at,tx_id, 10:00..10:02+09을 따라가되, 연속의 순서 기준은 prompt 보강 가정이다까지 함께 표시해 Green 문구를 과대해석하지 않는다.

딱 여기까지만 비유는 순서를 기억하게 할 뿐 SQL NULL, native exit, hash, cleanup의 실제 proof를 대신하지 않는다.

03

STEP 03 / 13

초등학생도 이해하는 설명

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

provenance order assumptions and schema

1~5줄을 한 덩어리로 읽어 actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.의 1번째 단계를 확인한다.

코드 연결
1~5줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
연속의 순서 기준은 prompt 보강 가정이다

stable order and reset-group window

6~17줄을 한 덩어리로 읽어 actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.의 2번째 단계를 확인한다.

코드 연결
6~17줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
psql 변수 workbook_schema가 실행 전에 주입되어야 한다

failed islands and threshold

18~29줄을 한 덩어리로 읽어 actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.의 3번째 단계를 확인한다.

코드 연결
18~29줄
비유
긴 조립 설명서에서 같은 일을 하는 부품만 한 봉투에 담아 확인한다.
비유의 끝
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
왜 먼저 보는가
  1. 이 파일은 왜 필요한가요?

  2. actor별 연속 실패 island 예시의 출발 계약을 먼저 고정해야 해.

  3. 직접 보장하는 값은 actor=USER-FAIL 범위다.

  4. 목적과 결과를 같은 문장으로 섞지 않겠습니다.

고정값 읽기
  1. 숫자나 marker는 그냥 외우면 되나요?

  2. 아니, fixture·순서와 함께 streak=3을 읽어야 해.

  3. 고정값은 현재 source의 관찰 계약이지 모든 입력의 일반 법칙은 아니다.

  4. 입력→처리→결과 표로 다시 적어 볼게요.

04

STEP 04 / 13

비유 ↔ 코드 전체 연결표

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

단어 몇 개만 뽑은 표가 아닙니다.학습용 SQL 예시 · 정본 답안 아님에서 비어 있지 않은 모든 줄을 원본 줄 번호 그대로 연결했습니다.32 / 32 연결
정확한 원본 줄STARRY 비유실제 뜻·입력·결과·한계
1줄F10-L01 -- W18-SQL-Q28 illustrative example; not a shipped workbook answer. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
정본과 예시, 명시된 가정과 미보장 영역이 화면에서 구분된다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
2줄F10-L02 -- Assumption: a streak is consecutive inside actor_id order (occurred_at, tx_id). 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. prompt가 비워 둔 순서·포함 범위를 이 예시의 명시적 가정으로 고정한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 2줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
정본과 예시, 명시된 가정과 미보장 영역이 화면에서 구분된다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
3줄F10-L03 -- Every non-FAILED row closes the preceding FAILED island. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. FAILED가 아닌 행이 앞선 실패 연속 구간을 끝낸다는 reset 규칙을 선언한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 3줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
정본과 예시, 명시된 가정과 미보장 영역이 화면에서 구분된다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
4줄F10-L04 SET search_path TO :"workbook_schema", public; 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 4줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 4줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
6줄F10-L06 WITH ordered AS ( 다음 설정이나 계산을 담을 새 서랍을 연다. actor별 안정 순서와 reset group을 계산할 첫 CTE를 연다.
입력
illustrative/sql/W18-SQL-Q28.sql의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 6줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
shipped workbook 정답이 아니다
7줄F10-L07 SELECT 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 최종 customer-grain projection 절을 시작한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 7줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 7줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
8줄F10-L08 actor_id, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. window partition과 최종 후보의 actor 식별자를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 8줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 8줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
9줄F10-L09 tx_id, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. occurred_at 동률을 깨는 stable tie-breaker UUID를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 9줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 9줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
10줄F10-L10 status, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 실패 여부와 reset 여부를 판단할 status를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 10줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 10줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
11줄F10-L11 occurred_at, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. actor 내부 사건 순서를 정할 timestamp를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 11줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 11줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
후보 탐지는 장애 원인이나 사기를 판정하지 않는다
12줄F10-L12 COUNT(*) FILTER (WHERE status <> 'FAILED') OVER ( 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. 현재 행까지 나타난 non-FAILED 개수를 window로 누적해 실패 island 번호를 만든다.
입력
illustrative/sql/W18-SQL-Q28.sql의 12줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
같은 reset_group을 공유하는 연속 FAILED 행들이 후속 GROUP BY에서 한 island가 된다.
비유의 한계
shipped workbook 정답이 아니다
13줄F10-L13 PARTITION BY actor_id 줄을 세울 반·번호표·창 범위를 차례로 붙인다. reset group window를 actor별로 독립 계산한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 13줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 13줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
14줄F10-L14 ORDER BY occurred_at, tx_id 줄을 세울 반·번호표·창 범위를 차례로 붙인다. timestamp 다음 UUID로 actor 내부 total order를 만든다.
입력
illustrative/sql/W18-SQL-Q28.sql의 14줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 14줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
15줄F10-L15 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 줄을 세울 반·번호표·창 범위를 차례로 붙인다. 현재 행까지의 물리 window frame을 명시한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 15줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 15줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
16줄F10-L16 ) AS reset_group 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 누적 non-FAILED count에 reset_group 이름을 붙인다.
입력
illustrative/sql/W18-SQL-Q28.sql의 16줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 16줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
17줄F10-L17 FROM business_tx 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 동결 workbook의 business_tx 행을 ordered CTE 입력으로 읽는다.
입력
illustrative/sql/W18-SQL-Q28.sql의 17줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 17줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
후보 탐지는 장애 원인이나 사기를 판정하지 않는다
18줄F10-L18 ), failed_streaks AS ( 다음 설정이나 계산을 담을 새 서랍을 연다. ordered 결과에서 실패 island를 집계할 두 번째 CTE를 연다.
입력
illustrative/sql/W18-SQL-Q28.sql의 18줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 18줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
shipped workbook 정답이 아니다
19줄F10-L19 SELECT 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 최종 customer-grain projection 절을 시작한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 19줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 19줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
20줄F10-L20 actor_id, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. window partition과 최종 후보의 actor 식별자를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 20줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 20줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
21줄F10-L21 reset_group, 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 같은 actor 안에서 실패 연속 구간을 구분할 island id를 전달한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 21줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 21줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
22줄F10-L22 COUNT(*) AS streak_length, 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. 각 실패 island의 행 수를 streak 길이로 계산한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 22줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 22줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
23줄F10-L23 MIN(occurred_at) AS first_failed_at, 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. 실패 island의 첫 timestamp를 계산한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 23줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 23줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
후보 탐지는 장애 원인이나 사기를 판정하지 않는다
24줄F10-L24 MAX(occurred_at) AS last_failed_at 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. 실패 island의 마지막 timestamp를 계산한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 24줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 24줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
shipped workbook 정답이 아니다
25줄F10-L25 FROM ordered 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 안정 순서와 reset group을 가진 ordered CTE를 읽는다.
입력
illustrative/sql/W18-SQL-Q28.sql의 25줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 25줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
26줄F10-L26 WHERE status = 'FAILED' 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. FAILED 행만 island 집계 대상으로 남긴다.
입력
illustrative/sql/W18-SQL-Q28.sql의 26줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 26줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
27줄F10-L27 GROUP BY actor_id, reset_group 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. actor와 reset group마다 한 실패 island로 묶는다.
입력
illustrative/sql/W18-SQL-Q28.sql의 27줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 27줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
28줄F10-L28 HAVING COUNT(*) >= 3 같은 바구니의 개수·합계·양끝 값을 재서 기록한다. 실패가 세 번 이상 연속된 island만 후보로 남긴다.
입력
illustrative/sql/W18-SQL-Q28.sql의 28줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
USER-FAIL의 길이 3 island만 threshold를 통과하고 짧은 실패 구간은 제거된다.
비유의 한계
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
29줄F10-L29 ) 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 현재 CTE 또는 window 표현식의 범위를 닫는다.
입력
illustrative/sql/W18-SQL-Q28.sql의 29줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 29줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
후보 탐지는 장애 원인이나 사기를 판정하지 않는다
30줄F10-L30 SELECT actor_id, streak_length, first_failed_at, last_failed_at 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. 후보 actor와 streak 길이·시작·끝 시각을 최종 출력 열로 고른다.
입력
illustrative/sql/W18-SQL-Q28.sql의 30줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 30줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
shipped workbook 정답이 아니다
31줄F10-L31 FROM failed_streaks 한 단계의 안내표를 읽어 다음 상태표로 넘긴다. threshold를 통과한 failed_streaks CTE를 최종 입력으로 사용한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 31줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
Q28 pipeline이 31줄 이후 stable-order 실패 island 계산 상태로 이동한다.
비유의 한계
연속의 순서 기준은 prompt 보강 가정이다
32줄F10-L32 ORDER BY actor_id, first_failed_at; 줄을 세울 반·번호표·창 범위를 차례로 붙인다. actor와 실패 시작 시각으로 최종 행 순서를 안정화한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 32줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
USER-FAIL|3|10:00|10:02 후보가 결정적 위치에 놓인다.
비유의 한계
psql 변수 workbook_schema가 실행 전에 주입되어야 한다
33줄F10-L33 -- Oracle: USER-FAIL|3|2026-12-28 10:00:00+09|2026-12-28 10:02:00+09. 문제 봉투 겉면에 ‘가정’ 또는 ‘예상 답’ 딱지를 붙인다. 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
입력
illustrative/sql/W18-SQL-Q28.sql의 33줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다.
결과·효과
독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
비유의 한계
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
Green의 작은 범위
  1. Green 문구가 보이면 전부 안전한가요?

  2. 그 문구가 직접 검사한 조건까지만 안전해.

  3. 특히 연속의 순서 기준은 prompt 보강 가정이다 경계는 별도 proof가 필요하다.

  4. 증명한 것과 안 한 것을 두 칸으로 나누겠습니다.

05

STEP 05 / 13

원본 코드 조각

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

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

F10-C01 · provenance order assumptions and schema1–5줄
1–5줄 원본
-- W18-SQL-Q28 illustrative example; not a shipped workbook answer.
-- Assumption: a streak is consecutive inside actor_id order (occurred_at, tx_id).
-- Every non-FAILED row closes the preceding FAILED island.
SET search_path TO :"workbook_schema", public;
F10-C02 · stable order and reset-group window6–17줄
6–17줄 원본
WITH ordered AS (
    SELECT
        actor_id,
        tx_id,
        status,
        occurred_at,
        COUNT(*) FILTER (WHERE status <> 'FAILED') OVER (
            PARTITION BY actor_id
            ORDER BY occurred_at, tx_id
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS reset_group
    FROM business_tx
F10-C03 · failed islands and threshold18–29줄
18–29줄 원본
), failed_streaks AS (
    SELECT
        actor_id,
        reset_group,
        COUNT(*) AS streak_length,
        MIN(occurred_at) AS first_failed_at,
        MAX(occurred_at) AS last_failed_at
    FROM ordered
    WHERE status = 'FAILED'
    GROUP BY actor_id, reset_group
    HAVING COUNT(*) >= 3
)
F10-C04 · candidate projection and oracle30–33줄
30–33줄 원본
SELECT actor_id, streak_length, first_failed_at, last_failed_at
FROM failed_streaks
ORDER BY actor_id, first_failed_at;
-- Oracle: USER-FAIL|3|2026-12-28 10:00:00+09|2026-12-28 10:02:00+09.
06

STEP 06 / 13

코드 한 줄씩 한국어로 번역

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

전체 번역 32 / 32

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

원본한국어 번역
1-- W18-SQL-Q28 illustrative example; not a shipped workbook answer.이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다.
2-- Assumption: a streak is consecutive inside actor_id order (occurred_at, tx_id).prompt가 비워 둔 순서·포함 범위를 이 예시의 명시적 가정으로 고정한다.
3-- Every non-FAILED row closes the preceding FAILED island.FAILED가 아닌 행이 앞선 실패 연속 구간을 끝낸다는 reset 규칙을 선언한다.
4SET search_path TO :"workbook_schema", public;psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
6WITH ordered AS (actor별 안정 순서와 reset group을 계산할 첫 CTE를 연다.
7 SELECT최종 customer-grain projection 절을 시작한다.
8 actor_id,window partition과 최종 후보의 actor 식별자를 전달한다.
9 tx_id,occurred_at 동률을 깨는 stable tie-breaker UUID를 전달한다.
10 status,실패 여부와 reset 여부를 판단할 status를 전달한다.
11 occurred_at,actor 내부 사건 순서를 정할 timestamp를 전달한다.
12 COUNT(*) FILTER (WHERE status <> 'FAILED') OVER (현재 행까지 나타난 non-FAILED 개수를 window로 누적해 실패 island 번호를 만든다.
13 PARTITION BY actor_idreset group window를 actor별로 독립 계산한다.
14 ORDER BY occurred_at, tx_idtimestamp 다음 UUID로 actor 내부 total order를 만든다.
15 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW현재 행까지의 물리 window frame을 명시한다.
16 ) AS reset_group누적 non-FAILED count에 reset_group 이름을 붙인다.
17 FROM business_tx동결 workbook의 business_tx 행을 ordered CTE 입력으로 읽는다.
18), failed_streaks AS (ordered 결과에서 실패 island를 집계할 두 번째 CTE를 연다.
19 SELECT최종 customer-grain projection 절을 시작한다.
20 actor_id,window partition과 최종 후보의 actor 식별자를 전달한다.
21 reset_group,같은 actor 안에서 실패 연속 구간을 구분할 island id를 전달한다.
22 COUNT(*) AS streak_length,각 실패 island의 행 수를 streak 길이로 계산한다.
23 MIN(occurred_at) AS first_failed_at,실패 island의 첫 timestamp를 계산한다.
24 MAX(occurred_at) AS last_failed_at실패 island의 마지막 timestamp를 계산한다.
25 FROM ordered안정 순서와 reset group을 가진 ordered CTE를 읽는다.
26 WHERE status = 'FAILED'FAILED 행만 island 집계 대상으로 남긴다.
27 GROUP BY actor_id, reset_groupactor와 reset group마다 한 실패 island로 묶는다.
28 HAVING COUNT(*) >= 3실패가 세 번 이상 연속된 island만 후보로 남긴다.
29)현재 CTE 또는 window 표현식의 범위를 닫는다.
30SELECT actor_id, streak_length, first_failed_at, last_failed_at후보 actor와 streak 길이·시작·끝 시각을 최종 출력 열로 고른다.
31FROM failed_streaksthreshold를 통과한 failed_streaks CTE를 최종 입력으로 사용한다.
32ORDER BY actor_id, first_failed_at;actor와 실패 시작 시각으로 최종 행 순서를 안정화한다.
33-- Oracle: USER-FAIL|3|2026-12-28 10:00:00+09|2026-12-28 10:02:00+09.동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
07

STEP 07 / 13

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

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

한 줄로 읽기

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다. 다만 연속의 순서 기준은 prompt 보강 가정이다

문법 해부

  • CTE는 이름 붙인 중간 relation을 statement 안에서 연결한다.
  • GROUP BY grain과 window PARTITION/ORDER/frame은 서로 다른 역할이다.
  • LEFT JOIN 이후 predicate 위치가 zero-row 보존 여부를 바꾼다.

실행 순서

  1. search_path
  2. actor stable order
  3. non-FAILED cumulative reset
  4. FAILED-only islands
  5. HAVING >=3
  6. candidate order

원래 W6 수준의 조각별 정밀 해설

F10-C01 · provenance order assumptions and schema
문법 해부
`provenance order assumptions and schema` 범위는 이 SQL이 배포된 정답이 아니라 가정을 드러낸 학습용 예시임을 선언한다. 이어서 psql 변수 workbook_schema를 먼저 탐색하고 그다음 public을 보도록 search_path를 설정한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q28.sql의 1줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Q28 pipeline이 4줄 이후 stable-order 실패 island 계산 상태로 이동한다.
정상 예
동결 source 1~5줄을 그대로 적용하면 Q28 pipeline이 4줄 이후 stable-order 실패 island 계산 상태로 이동한다.
틀린 예·반례
연속의 순서 기준은 prompt 보강 가정이다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 actor=USER-FAIL이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: 연속의 순서 기준은 prompt 보강 가정이다
다음 연결
끝 상태를 보존한 뒤 `stable order and reset-group window` 범위에서 다음 입력·결과를 확인한다.
F10-C02 · stable order and reset-group window
문법 해부
`stable order and reset-group window` 범위는 actor별 안정 순서와 reset group을 계산할 첫 CTE를 연다. 이어서 동결 workbook의 business_tx 행을 ordered CTE 입력으로 읽는다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q28.sql의 6줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Q28 pipeline이 17줄 이후 stable-order 실패 island 계산 상태로 이동한다.
정상 예
동결 source 6~17줄을 그대로 적용하면 Q28 pipeline이 17줄 이후 stable-order 실패 island 계산 상태로 이동한다.
틀린 예·반례
psql 변수 workbook_schema가 실행 전에 주입되어야 한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 streak=3이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: shipped workbook 정답이 아니다
다음 연결
끝 상태를 보존한 뒤 `failed islands and threshold` 범위에서 다음 입력·결과를 확인한다.
F10-C03 · failed islands and threshold
문법 해부
`failed islands and threshold` 범위는 ordered 결과에서 실패 island를 집계할 두 번째 CTE를 연다. 이어서 현재 CTE 또는 window 표현식의 범위를 닫는다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q28.sql의 18줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 Q28 pipeline이 29줄 이후 stable-order 실패 island 계산 상태로 이동한다.
정상 예
동결 source 18~29줄을 그대로 적용하면 Q28 pipeline이 29줄 이후 stable-order 실패 island 계산 상태로 이동한다.
틀린 예·반례
TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 order=occurred_at,tx_id이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: shipped workbook 정답이 아니다
다음 연결
끝 상태를 보존한 뒤 `candidate projection and oracle` 범위에서 다음 입력·결과를 확인한다.
F10-C04 · candidate projection and oracle
문법 해부
`candidate projection and oracle` 범위는 후보 actor와 streak 길이·시작·끝 시각을 최종 출력 열로 고른다. 이어서 동결 fixture에서 대조할 예상 결과 일부를 주석으로 기록한다.
실제 값 추적
범위 시작 입력은 illustrative/sql/W18-SQL-Q28.sql의 30줄 직전까지 형성된 parser·실행 상태를 입력으로 받는다. 끝에서 관찰할 상태는 독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
정상 예
동결 source 30~33줄을 그대로 적용하면 독자가 실행 결과를 동결 fixture의 날짜·actor sentinel과 직접 대조할 기준을 얻는다.
틀린 예·반례
fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다 상황을 정상 예로 간주하면 이 chunk의 Green 범위를 넘는다.
착각 방지
이 범위의 고정값은 10:00..10:02+09이지만 marker·형식만으로 전체 실행을 인증하지 않는다.
하지 않는 일
현재 chunk가 직접 보장하지 않는 경계: shipped workbook 정답이 아니다
다음 연결
끝 상태를 보존한 뒤 `파일 전체 source와 책임 경계` 범위에서 다음 입력·결과를 확인한다.
반례 먼저
  1. 정상 fixture만 보면 충분하지 않나요?

  2. 빈 집합·NULL·순서 역전 같은 반례도 넣어야 해.

  3. 여기서는 psql 변수 workbook_schema가 실행 전에 주입되어야 한다을 먼저 흔들어 본다.

  4. 첫 실패 지점을 줄 번호와 함께 기록하겠습니다.

08

STEP 08 / 13

실제 값 따라가기

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

순서들어온 값코드가 하는 일나온 값·상태경계
1actor=USER-FAILF10의 실행 순서 1단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `actor=USER-FAIL`가 기록되거나 그 값으로 비교된다.연속의 순서 기준은 prompt 보강 가정이다
2streak=3F10의 실행 순서 2단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `streak=3`가 기록되거나 그 값으로 비교된다.psql 변수 workbook_schema가 실행 전에 주입되어야 한다
3order=occurred_at,tx_idF10의 실행 순서 3단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `order=occurred_at,tx_id`가 기록되거나 그 값으로 비교된다.TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
410:00..10:02+09F10의 실행 순서 4단계에 넣어 source 조건을 적용한다.관찰 가능한 W18 상태에 `10:00..10:02+09`가 기록되거나 그 값으로 비교된다.fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
전체 다시 쓰기
  1. 한 줄이 너무 길어서 읽기 힘들어요.

  2. 화면에서는 줄바꿈해 보되 source bytes와 실행 순서는 그대로 보존해.

  3. 마지막에는 10:00..10:02+09을 source와 다시 대조한다.

  4. 뜻→chunk→전체 코드 순서로 복원하겠습니다.

09

STEP 09 / 13

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

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

psql search_path

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.에서 1번째 내부 책임을 수행한다.

연속의 순서 기준은 prompt 보강 가정이다
PostgreSQL planner

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.에서 2번째 내부 책임을 수행한다.

psql 변수 workbook_schema가 실행 전에 주입되어야 한다
join/aggregate executor

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.에서 3번째 내부 책임을 수행한다.

TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다
window executor

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.에서 4번째 내부 책임을 수행한다.

fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다
10

STEP 10 / 13

흔한 착각과 틀린 예

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

❌ actor=USER-FAIL이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 연속의 순서 기준은 prompt 보강 가정이다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 연속의 순서 기준은 prompt 보강 가정이다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ streak=3이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 psql 변수 workbook_schema가 실행 전에 주입되어야 한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 psql 변수 workbook_schema가 실행 전에 주입되어야 한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ order=occurred_at,tx_id이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ 10:00..10:02+09이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

❌ actor=USER-FAIL이 보이면 관련 실행 전체가 완전하게 증명된다.

왜 틀리나 후보 탐지는 장애 원인이나 사기를 판정하지 않는다

바르게 읽기 고정 fixture에서 직접 검사한 값과 owner·DB·운영 계층의 나머지 책임을 분리한다.

반례 후보 탐지는 장애 원인이나 사기를 판정하지 않는다 상황에서는 같은 marker·형식이 보여도 의도한 전체 의미가 성립하지 않을 수 있다.

11

STEP 11 / 13

이 코드가 보장하지 않는 것

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

input ownership

연속의 순서 기준은 prompt 보강 가정이다

이 책임을 맡는 곳: caller path policy
result proof

psql 변수 workbook_schema가 실행 전에 주입되어야 한다

이 책임을 맡는 곳: exact oracle gate
NULL/order edge

TIMESTAMPTZ stdout offset은 session TimeZone에 따라 달라져 +09 문자열 자체는 고정되지 않는다

이 책임을 맡는 곳: SQL/parser counterexample test
lifecycle

fixture contract는 USER-FAIL 실패가 3건 이상임만 검사하고 연속성은 pinned seed에서 별도 재계산해야 한다

이 책임을 맡는 곳: runtime owner
provenance

후보 탐지는 장애 원인이나 사기를 판정하지 않는다

이 책임을 맡는 곳: byte-pinned source audit
12

STEP 12 / 13

직접 다시 써보기

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

1단계 · 뜻부터 복원

actor별 안정 순서에서 non-FAILED 누적 횟수로 island를 나눠 FAILED 연속 3회 이상 후보를 찾는 Q28 비정답 예시다.를 고정값과 미보장 경계까지 한 문장으로 말한다.

2단계 · 코드 조각 재조립

  1. provenance order assumptions and schema
  2. stable order and reset-group window
  3. failed islands and threshold
  4. candidate projection and oracle

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

33개 물리 줄을 원본 순서로 다시 쓰고 SHA-256 7b88be81fa7a9031d90307c6376ae71e3dc607379a031594553a8369098f6378와 대조한다.

자가 점검
  • 정본과 학습용 예시 label을 바꾸지 않는다.
  • 긴 한 줄은 화면에서 감싸도 source exact text를 바꾸지 않는다.
  • marker literal과 실제 결과 row를 구분한다.
  • NULL·순서·empty fixture 반례를 하나 이상 말한다.
  • owner mode와 cleanup 책임을 분리한다.
13

STEP 13 / 13

전체 원본 정답

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

학습용 예시 전체 확인하기 · 정본 답안 아님
학습용 예시 · 정본 답안 아님illustrative/sql/W18-SQL-Q28.sqlSHA-256 7b88be81fa7a9031d90307c6376ae71e3dc607379a031594553a8369098f6378
W18-SQL-Q28.sql — actor별 연속 실패 island 예시 전체
-- W18-SQL-Q28 illustrative example; not a shipped workbook answer.
-- Assumption: a streak is consecutive inside actor_id order (occurred_at, tx_id).
-- Every non-FAILED row closes the preceding FAILED island.
SET search_path TO :"workbook_schema", public;

WITH ordered AS (
    SELECT
        actor_id,
        tx_id,
        status,
        occurred_at,
        COUNT(*) FILTER (WHERE status <> 'FAILED') OVER (
            PARTITION BY actor_id
            ORDER BY occurred_at, tx_id
            ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
        ) AS reset_group
    FROM business_tx
), failed_streaks AS (
    SELECT
        actor_id,
        reset_group,
        COUNT(*) AS streak_length,
        MIN(occurred_at) AS first_failed_at,
        MAX(occurred_at) AS last_failed_at
    FROM ordered
    WHERE status = 'FAILED'
    GROUP BY actor_id, reset_group
    HAVING COUNT(*) >= 3
)
SELECT actor_id, streak_length, first_failed_at, last_failed_at
FROM failed_streaks
ORDER BY actor_id, first_failed_at;
-- Oracle: USER-FAIL|3|2026-12-28 10:00:00+09|2026-12-28 10:02:00+09.