14주차 미리보기
W14 미리보기 · 웹소설 본편

STARRY PASS 10 — 여섯 장의 점검표와 아직 찍지 않은 초록 도장

시험 주에는 새 기능보다 selector·증거·미실행 범위를 지키는 법

범위D1–D6 · 월–토p459 L13–30 + p460–481
정본 상태SOURCE AUDIT PASSPDF·source·stage byte 폐쇄
실행 상태SMOKE NOT_RUNtestsExecutedByAudit=0

이 이야기는 원문의 D1∼D6 수행 계약을 이해하기 위한 모형극입니다. 공통 코어 주차이며 트랙 선택 시 증권을 기본으로 표시하지만, 증권 전용 기능을 구현하는 주차는 아닙니다. 현재 source audit은 통과했어도 smoke 실행은 CURRENT NOT RUN, testsExecutedByAudit=0이므로 초록 도장을 이미 얻었다고 서술하지 않습니다.

STARRY의 문을 닫을 시간이 가까워지자 키타가 반짝이는 새 간판 상자를 들고 들어왔다. 시험 기간에도 무대가 허전해 보이지 않게 기능을 하나만 더 붙이면 어떻겠느냐는 제안이었다. 니지카는 상자를 열어 보지도 않고 계산대 아래에 넣었다. 그 대신 얇은 모래시계와 여섯 장의 점검표를 탁자 위에 놓았다.

“이번 주에는 새 간판을 달지 않아.”

“고장 난 것도 아닌데요?” 키타가 물었다.

“고장 나지 않았다는 말을 짧게 확인하는 주야. 학교시험을 밀어내면서 기능을 늘리면, 프로젝트를 지킨 게 아니라 둘 다 놓친 거니까.”

료는 모래시계 옆의 숫자를 읽었다. 전체 활동 시간은 여섯 시간에서 여덟 시간, 오늘 점검은 사십오 분 상한이었다. 빨리 끝났다면 남은 시간을 채우지 않는다는 문장도 있었다. 히토리는 시간을 덜 쓰면 성의가 부족해 보이지 않겠느냐고 걱정했지만, 니지카는 이번 주의 성실함은 오래 앉아 있는 시간이 아니라 정한 범위를 넘지 않는 데 있다고 답했다.

니지카가 태블릿을 보며 여섯 선택 실행과 시험 주 범위를 점검하는 공식 장면
그림 한눈에: 공식 장면컷은 시험 주 점검 분위기를 위한 전환이며, selector와 수치는 원문·source 감사 결과를 따릅니다.

탁자에는 월요일부터 토요일까지 여섯 칸이 그려진 지도가 펼쳐졌다. 첫 칸은 핵심 smoke, 둘째는 원자성 2분 답변, 셋째는 동시성 답변과 SQL Q19, 넷째는 멱등성 답변, 다섯째는 W13 실행계획 회수와 SQL Q20, 여섯째는 smoke 재확인과 evidence 링크 점검이었다. 지도 끝에는 굵은 선이 그어져 있었다. 오늘 이야기의 계약은 그 선을 넘지 않았다.

키타는 각 칸에 작은 종을 달려 했다. 니지카는 종 대신 상태 표식을 놓았다. source와 PDF의 범위를 맞춘 표식은 SOURCE AUDIT PASS, 실제 learner root에서 test를 돌린 표식은 비어 있는 CURRENT NOT RUN, 사람이 녹음 내용을 들어야 하는 칸은 MANUAL REVIEW REQUIRED였다. 세 표식은 서로 대신할 수 없었다.

“감사에 통과했으면 실행도 통과한 거 아닌가요?” 히토리가 물었다.

“악보의 모든 마디가 인쇄됐는지 확인한 것과 실제 공연이 끝까지 성공한 건 다른 일이야.” 니지카가 답했다. “이번 감사에서 실행한 test는 영 개야. 그러니 지금 우리가 가진 건 검사법을 정확히 읽은 결과지, 오늘의 Green 결과가 아니야.”

PDF p459의 W14 개요와 p460부터 p481까지 D1–D6만 포함하고 D7 이후를 제외한 범위 지도
그림 한눈에: strict 범위는 D1∼D6이며, source 감사·실제 실행·사람 검토 상태를 서로 다른 칸으로 둡니다.

첫 번째 칸에서 료가 여섯 장의 점검표를 펼쳤다. 다섯 장에는 반 전체를 부르는 이름이 있었고, 마지막 한 장에는 반 이름 뒤에 특정 학생 이름까지 적혀 있었다. 니지카는 그것을 class selector와 method selector라고 설명했다.

첫 카드는 com.example.financialcore.account.api.AccountControllerTest, 둘째는 com.example.financialcore.transfer.TransferFailurePointIT, 셋째는 com.example.financialcore.transfer.SortedLockTransferIT였다. 넷째는 길어서 두 줄을 차지했다. com.example.financialcore.transfer.TransferIntegrationTest.same_key_with_different_semantic_request_conflicts_without_extra_effect. 다섯째는 com.example.financialcore.CoreSchemaIT, 여섯째는 com.example.financialcore.account.OpeningIntegrationTest였다.

“점검표 여섯 장이면 test도 여섯 개인가요?” 히토리가 물었다.

“아니. class를 부르면 그 class에서 발견된 test가 모두 대상이 될 수 있어. 우리가 감사한 staged reference 기준으로는 첫째 네 개, 둘째 두 개, 셋째 한 개, 넷째 한 method, 다섯째 한 개, 여섯째 한 개야.”

키타가 순서대로 4+2+1+1+1+1을 더해 십이라고 적었다. 여섯 historical Java source 전체에 선언된 @Test는 열여덟 개였지만, exact staged smoke envelope에 들어가는 것은 십 개였다. 그러나 니지카는 그 숫자 옆에 굵은 괄호를 쳤다.

“십은 staged source가 정확히 조립됐을 때의 역사적 계산이야. 오늘 실제 XML에서 십이 나왔다는 뜻이 아니야. 실행하지 않았고, runner도 source hash를 marker에 묶지 않아.”

다섯 class selector와 한 exact method selector, staged test 10개를 구분한 선택표
그림 한눈에: selector는 여섯 개, staged reference의 선택 test는 십 개, 전체 선언은 열여덟 개이며 서로 같은 수가 아닙니다.

여섯 카드의 출처도 한 선반에 있지 않았다. CoreSchemaITOpeningIntegrationTest는 옛 학습 stage 안에만 있었고 기본 reference project의 활성 test source set에는 자동으로 들어오지 않았다. AccountControllerTest는 더 까다로웠다. 같은 전체 이름을 가진 현재 root 파일이 있었지만, W14 시점의 W6D7 staged source와 method와 assertion이 달랐다.

료가 카드에 이름이 같으니 같은 사람으로 치면 되지 않느냐고 하자, 니지카는 두 장의 신분증을 나란히 놓았다. 이름은 같아도 사진과 발급일과 내용이 다르면 같은 byte라고 할 수 없었다. selector 문자열은 찾아갈 주소일 뿐, 어느 stage source의 SHA가 compile되어 실행됐는지를 자동으로 증명하지 않았다.

“그럼 기본 선반에서 runner를 켜면 여섯 카드가 모두 정확히 돌아간다는 말도 못 하네요.” 키타가 말했다.

“맞아. stage-only 두 class를 learner root에 조립하고, 충돌하는 이름은 어느 source를 택했는지 고정하고, fresh XML과 source hash를 연결해야 그런 말을 할 수 있어.”

runner를 읽기 전에 네 사람은 여섯 historical card가 무엇을 보는지 빠르게 대조했다. CoreSchemaITinformation_schema.tables에서 public schema의 BASE TABLE만 읽고 flyway_schema_history를 제외했다. 결과 목록은 account, business_tx, ledger_entry, idempotency_request 네 이름과 순서 없이 정확히 같아야 했다. 추가 table 하나나 누락 하나는 잡지만, column type·nullability·PK·FK·CHECK·index가 올바른지는 전혀 묻지 않았다.

“서랍 이름 네 개가 맞아도 서랍 안 칸막이가 틀릴 수 있다는 거네요.” 히토리가 말했다.

“그렇지. 이름 inventory를 전체 schema 검증이라고 부풀리면 안 돼.”

AccountControllerTest의 staged card에는 네 경로가 있었다. A-100 생성 요청은 201과 account 한 행을, 음수 opening은 400과 account 영 행을, owner 조회는 200과 일부 body 문자열을, 없는 account 조회는 404를 확인했다. MockMvc를 쓰므로 실제 network port를 연 배포 환경은 아니었고, body 일부가 들어 있다는 검사는 전체 JSON schema equality도 아니었다. 인증·권한과 audit 기록은 이 네 method의 assertion에 없었다.

키타는 카드 겉면에 ‘HTTP 성공’이라고 쓰려다가 ‘staged MockMvc 대표 경로’로 고쳤다. 짧은 표현일수록 관찰 층을 빠뜨리기 쉬웠다. status code, 일부 body, 특정 DB count를 확인했다는 사실을 그대로 남기면 충분했다.

TransferFailurePointITTransferIntegrationTest는 같은 이체 이야기를 다루지만 smoke에서 맡은 장면이 달랐다. 전자는 class selector라 두 failure method를 고르고, 후자는 exact method selector라 아홉 method 중 amount-conflict 하나만 고른다. source 파일 전체를 읽으며 배운 내용과 runner가 이번에 실행하도록 요청한 내용을 같은 색으로 칠하지 않는 것이 첫 번째 점검의 핵심이었다.

히토리는 여섯 점검표 위에 ‘전체 회귀’라고 적힌 큰 표찰을 올렸다. 시험 주에 이만큼 중요한 것만 모았으니 사실상 전체라고 생각한 것이다. 료는 표찰을 뒤집어 targeted smoke라고 고쳐 썼다.

“이 여섯 장은 위험이 큰 길을 빠르게 보는 묶음이야. 선택하지 않은 test가 실패해도 이 실행에는 안 나타나.”

“토요일 설명에 전체라는 말이 있더라도요?”

“실제 owner source는 같은 여섯 --tests filter만 넘겨. 실행 범위의 이름은 source에 맞춰야 해.”

니지카는 큰 원 안에 전체 suite를 그리고, 그 안쪽에 작은 육각형으로 six-selector smoke를 그렸다. 작은 육각형이 초록이어도 원의 나머지가 초록이라는 결론은 나오지 않았다. 반대로 smoke가 빨리 깨졌다면 전체 suite까지 기다리지 않고 첫 failure를 기록할 수 있었다. 시험 주 budget에서 smoke가 필요한 이유는 넓은 확신을 주기 때문이 아니라, 좁은 경보를 빨리 울리기 때문이었다.

여섯 선택 경로의 targeted smoke와 선택 밖 전체 회귀 suite를 분리한 그림
그림 한눈에: exact six-selector smoke는 전체 Gradle suite의 일부이며, 선택 밖 test 상태를 대신 말하지 않습니다.

료는 Green 도장을 찍기 전에 runner의 아홉 줄을 읽었다. runner는 여섯 selector를 gradlew.bat test --no-daemon--tests로 차례로 넘겼다. native exit가 0이 아니면 즉시 멈췄다. 그 뒤 build/test-results/test 아래의 TEST-*.xml을 모두 읽고 각 suite의 class, tests, failures, errors, skipped를 모았다.

기대 class 집합과 실제 XML class 집합은 exact 비교였다. 하나가 빠지거나 예상 밖 class가 끼면 실패했다. 하지만 히토리가 test 최소 수 조건을 소리 내 읽다가 멈췄다. 벽의 공개 계약에는 tests>=6이라고 적혀 있었지만 source에는 if($tests-lt 5 ...)가 있었다.

tests=55-lt 5가 거짓이니까 통과하잖아요.”

“그래. 교재 문장은 여섯 이상을 요구하지만 현재 구현은 다섯도 받아들여. marker에 tests=5가 찍힐 수 있다는 뜻이야.”

료가 실제 staged envelope는 십인데 왜 여섯과 다섯을 따지느냐고 묻자, 니지카는 세 숫자의 역할을 다시 갈랐다. 십은 감사한 staged source에서 selector가 고르는 method 계산이고, 여섯은 공개 최소 계약이며, 다섯은 현재 runner가 실수로 허용하는 구현 하한이었다. 어느 숫자도 오늘 실행 결과가 아니었다.

교재 tests 6개 이상 계약과 실행기 tests 5개 이상 구현의 차이를 나란히 보인 그림
그림 한눈에: 공개 계약은 tests>=6, 현재 source는 tests>=5를 허용해 test 수 5도 Green marker로 갈 수 있습니다.

XML 검사대에는 다른 틈도 있었다. runner는 실행 전에 오래된 TEST-*.xml을 지우지 않았다. 예상 밖 class의 오래된 XML은 exact class 집합 비교에서 들킬 수 있지만, 기대 class 이름과 같은 오래된 XML은 합계에 섞일 가능성이 있었다. --rerun-tasks도 없었고, XML이나 marker에 source SHA와 실행 시각을 함께 기록하지 않았다.

마지막에는 Set-Contentsmoke.txt를 바로 썼다. 임시 파일을 완성한 뒤 원자 교체하는 방식이 아니었고, 실행 시작 전에 이전 smoke.txt를 지우지도 않았다. 새 실행이 중간에 실패했을 때 옛 파일이 남아 있을 수 있으므로, 파일 존재만으로 오늘 Green을 판단하면 안 됐다. caller의 exit code, fresh XML, source identity, marker 내용을 함께 묶어야 했다.

기대·실제 class 집합과 tests failures errors skipped 합계를 검사하는 JUnit XML gate
그림 한눈에: XML class 집합과 네 합계는 검사하지만 stale 제거·source SHA·fresh 실행·원자 marker 저장은 별도 경계입니다.
네 사람이 원자성·동시성·멱등성 답변의 증거 경계를 함께 검토하는 공식 장면
그림 한눈에: 공식 장면컷은 점검표를 함께 읽는 분위기용이며, 현재 smoke를 실제 실행했다는 장면이 아닙니다.

두 번째 칸에서는 네 사람이 원자성 답변을 이 분 안에 말하는 연습을 했다. 키타가 먼저 “@Transactional을 붙여서 모두 안전합니다”라고 말했다. 니지카는 초시계를 멈추고, annotation 이름 대신 실제로 본 경계를 말해 보라고 했다.

첫 번째 historical card인 OpeningIntegrationTestcustomer-1OPEN-100 계좌를 12_345로 개설한 뒤 반환 account ID를 받는다. 그 뒤 account 한 행, OPENING business transaction 한 행, 같은 account의 OPENING ledger 한 행을 서로 다른 COUNT(*) query로 세어 각각 1인지 확인한다. 그러나 amount와 balance column을 다시 읽지 않고, business transaction을 반환 account와 join하지 않으며, 세 SELECT가 하나의 transaction snapshot이라는 선언도 없다.

“method 이름에 Atomically가 있잖아요.” 히토리가 말했다.

“이름은 질문의 의도를 알려 주지만 assertion을 늘리지 않아. 이 test는 정상 호출 뒤 세 count를 봤어. 중간 실패를 넣어 rollback을 직접 재현한 test는 아니야.”

두 번째 원자성 card인 TransferFailurePointITAFTER_CLAIMAFTER_BUSINESS 두 곳에서 RuntimeException을 주입한다. 각 실패 뒤 idempotency_request, TRANSFER business_tx, TRANSFER ledger의 수가 모두 0인지 본다. 하지만 두 account balance를 직접 assertion하지 않는다. audit row나 외부 시스템 effect, checked exception, process hard-kill도 이 두 test의 관찰 범위 밖이었다.

키타는 교재 답변 뼈대에 failure point 세 개라는 문장이 있는 것을 발견했다. 니지카는 그 숫자를 현재 smoke의 직접 증거에 붙이지 말라고 했다. 답변 과제는 이전 학습과 evidence를 회수하는 말하기 계약이고, exact smoke source가 직접 선택한 test 범위와 같지 않을 수 있었다.

녹음 파일 validator도 내용을 듣지 않았다. 파일이 존재하는지, 128 byte 이상인지, SHA-256이 무엇인지만 출력하고 MANUAL_REVIEW_REQUIRED라고 남겼다. 빈 파일을 거르는 기술 gate일 뿐, 위험·설계·실험·결과·한계가 정확히 들어갔는지는 사람이 들어야 했다.

개설 성공 뒤 세 행 확인과 두 실패 지점 뒤 세 종류 transfer 행 0을 구분한 원자성 경계
그림 한눈에: 성공 후 세 count, 두 failure point의 세 업무 표 0행, balance·외부 효과·오디오 의미 검토를 서로 분리합니다.

료는 원자성 답변을 다섯 문장으로 다시 구성했다. 위험은 이체 중간 실패가 일부 행만 남기는 것, 설계 의도는 한 transaction 경계 안에서 effect를 묶는 것, 직접 test는 두 RuntimeException 지점에서 세 업무 표의 row count를 본다는 것, 관찰 결과는 source assertion이 기대하는 영 행, 한계는 account balance와 외부 effect를 이 card가 직접 보지 않는다는 것이었다.

니지카는 “변경 0”이라는 짧은 말을 그대로 허용하지 않았다. 무엇의 변경이 영인지 table 이름을 붙이게 했다. idempotency claim row, TRANSFER business transaction row, TRANSFER ledger row가 각각 영이라는 문장은 source와 연결되지만, 모든 database column과 외부 메시지가 영이라는 문장은 연결되지 않았다.

새 transaction으로 사후 조회했다는 면접 문장도 현재 staged source 본문에서 직접 확인되지 않으면 단정하지 않았다. annotation과 test 이름, 답변 template, 실제 assertion은 서로 다른 증거 층이었다. 이 구분을 한 뒤에야 히토리의 답변은 이 분보다 짧아졌고, 오히려 더 정확해졌다.

세 번째 칸에는 서로 반대 방향으로 달리는 스무 개의 작은 수레가 놓였다. 열 개는 A에서 B로, 열 개는 B에서 A로 향했다. SortedLockTransferIT는 열 쌍, 총 스무 task를 한 번 동시에 출발시키고 준비 latch를 최대 십 초, 각 future를 최대 삼십 초 기다렸다. 모든 task가 끝난 뒤 두 계좌 balance 합이 이만이고, transfer ledger의 signed amount 합이 영인지 확인했다.

히토리는 보고서 제목을 ‘deadlock 백 회 영 건’이라고 쓰려 했다. 료가 연필을 빼앗았다.

“우리가 본 source는 스무 task 한 번이야. test 이름에 FinishWithoutDeadlock이 있어도 모든 scheduling과 production load에서 deadlock이 불가능하다는 증명은 아니지.”

처리량, p95, retry 횟수도 재지 않았다. Future.get이 task exception을 밖으로 전달하므로 숨은 기술 실패를 성공으로 세지는 않지만, 한 fixture와 한 실행의 종료·보존 범위만 말할 수 있었다. 교재의 2분 답변에는 versionless lost update나 concurrent withdraw 20개의 성공 10·업무 거절 10·기술 실패 0 같은 이전 학습 수치도 등장했지만, 그것들은 run-w14-smoke.ps1에 추가 selector로 들어 있지 않았다.

반대 방향 10쌍 20 task를 한 번 실행해 합계 20000과 signed sum 0을 확인하는 범위
그림 한눈에: 직접 selector의 범위는 반대 방향 10쌍·20 task·1회와 두 보존식이며, 100회·모든 부하·처리량은 미검증입니다.

동시성 답변을 마친 뒤 키타는 SQL Q19 문제 카드를 꺼냈다. reversal 영수증과 그 원거래 영수증을 한 줄에 보이라는 과제였다. 히토리는 reversal만 먼저 골라 INNER JOIN하면 된다고 생각했지만, 문제의 반례는 원거래가 없는 정상 transaction도 결과에서 잃지 않는 것이었다.

그들은 business_txcurrent_txoriginal_tx 두 별칭으로 놓고, current side의 모든 스물한 행을 왼쪽에 유지했다. current_tx.original_tx_id = original_tx.tx_id인 경우에만 원거래가 붙었다. fixture에서는 REQ-211REQ-212 두 reversal이 모두 REQ-201을 가리켰고, 나머지 열아홉 행은 NO_ORIGINAL이었다.

BROKEN_REFERENCE라는 분류 칸도 있었지만 현재 fixture의 FK가 없는 원거래 ID를 막으므로 예상 결과는 영 행이었다. 이 SQL은 W14 Ver2가 문제 계약과 격리 fixture를 바탕으로 만든 illustrative example이었다. packaged workbook이 제공한 canonical answer가 아니고, 어떤 reversal이 업무상 허용되는지를 판정하지도 않았다.

21개 거래를 유지하며 reversal 두 행을 REQ-201 원거래와 잇는 학습용 LEFT self join
그림 한눈에: Q19 예시는 current 21행을 보존하고 두 reversal만 REQ-201에 연결하며, 정본 답안이나 운영 reversal 정책이 아닙니다.

네 번째 칸에는 같은 접수번호가 적힌 두 장의 이체 신청서가 있었다. 첫 신청서의 amount는 천, 둘째는 이천이었다. TransferIntegrationTest source에는 아홉 개의 @Test가 있었지만 exact selector는 same_key_with_different_semantic_request_conflicts_without_extra_effect 한 method만 불렀다.

fixture는 두 계좌를 각각 만으로 시작했다. conflict-key와 amount 천인 첫 요청은 한 번의 이체 효과를 만들었다. 같은 key에 amount만 이천으로 바꾼 두 번째 요청은 semantic conflict가 되어 추가 효과를 만들지 않아야 했다. 최종 관찰값은 balance 구천과 만천, transfer business transaction 한 행, ledger 두 행, idempotency claim 한 행이었다.

“그럼 같은 key 스무 요청도 업무 효과 한 번이 Green이네요.” 히토리가 말했다.

“그 test는 같은 source 안에는 있지만 이 exact method selector가 실행하지 않아.” 니지카가 답했다. “JSON field 순서 replay, changed from, changed to, 반대 방향, rollback test도 마찬가지야. 파일 안에 있다는 사실과 이번 selector가 실행한다는 사실을 나눠.”

교재의 멱등성 답변에는 AtomicClaim50의 owner 1·existing 49와 same-key concurrent 20개의 업무 효과 1도 들어갔다. 그것들은 이전 evidence를 정확한 workload와 함께 회수하는 학습 문장이지 W14 smoke가 추가로 실행하는 selector가 아니었다. burst 100, stale PROCESSING recovery, 모든 semantic field conflict도 현재 selected method에서 일반화할 수 없었다.

같은 key에서 amount 1000 성공 뒤 2000 요청을 conflict로 막고 효과 하나만 남기는 범위
그림 한눈에: 현재 smoke는 amount 1000→2000 conflict method 하나이며, AtomicClaim50·same-key20·stale recovery는 별도 증거 범위입니다.

다섯 번째 칸에서 료는 W13의 실행계획 봉투 세 개를 가져왔다. before-plan.txt, after-plan.txt, perf-manifest.json이었다. W14는 이 파일을 읽기 전용으로 다시 펼쳐 node, estimated/actual rows, buffers, sort, planning/execution time을 설명하는 날이었다. 같은 fixture와 query, parameter, machine이라는 주장 경계도 다시 말해야 했다.

“이번에 읽으면 W14 성능 개선 결과가 하나 더 생기나요?” 키타가 물었다.

“아니. predecessor evidence를 회수하는 거야. 파일이 존재하고 hash와 내용이 올바르다는 조건 아래 이전 결과를 설명할 뿐, 새 benchmark를 실행하거나 새 production claim을 만들지 않아.”

니지카는 특히 top node 이름 하나만 외우지 말라고 했다. plan tree의 node와 actual rows, buffers를 query shape과 데이터 분포에 연결해야 했다. Seq Scan은 많은 행을 읽는 선택도에서 합리적일 수 있었고, 한 parameter의 plan을 모든 account에 일반화하면 안 됐다. W13의 현재 증거 연속성과 계산 계약 문제도 W14 회수만으로 고쳐지지 않았다.

회수 worksheet에는 before_top_node, after_top_node, chosen_index, before_samples, after_samples, claim_boundary를 한 표에 놓는 예가 있었다. 목표 sample 표기는 전 삼십, 후 삼십이었다. 하지만 이 표는 원 파일을 다시 읽어 실제 값을 채울 때만 의미가 있었다. 예시 이름 idx_ledger_account_created_id나 기대 sample 수를 기억에서 복사하는 것은 current predecessor hash를 확인하는 일을 대신하지 못했다.

히토리는 “index를 썼으니 빨라졌다”라는 답을 지웠다. 대신 같은 query와 fixture에서 어떤 node가 actual rows와 buffers를 내보냈는지, median과 p95가 각각 어떻게 되었는지, hot account 같은 반례에서 왜 planner 선택이 달라질 수 있는지를 묻는 문장으로 바꿨다. W14가 하는 일은 새 숫자를 만드는 것이 아니라 기존 주장에 붙어 있던 조건을 회수하는 일이었다.

그 옆에는 SQL Q20 카드가 있었다. 업무 transaction 하나당 ledger가 몇 행인지 세는 문제였다. 히토리는 ledger_entry에서 시작해 group하면 간단하다고 생각했지만, 그러면 ledger가 영 개인 transaction은 아예 사라졌다. 그래서 business_tx 스물한 행을 왼쪽에 두고 ledger_entryLEFT JOIN해야 했다.

count에도 함정이 있었다. COUNT(*)는 ledger가 없어도 LEFT JOIN이 만든 NULL 확장행 하나를 셀 수 있었다. COUNT(ledger.entry_id)를 써야 실제 ledger 영 건을 영으로 유지할 수 있었다. fixture 교육 정책은 성공한 transfer라면 2, 다른 지원 success라면 1, 나머지는 0을 기대했고, REQ-302만 actual 1 대 expected 2로 어긋났다.

료는 cardinality가 맞으면 원장도 완전히 맞는 것 아니냐고 물었다. 키타가 ledger 두 장의 금액과 방향을 서로 바꾼 예를 그렸다. 잘못된 두 행도 수만 2면 cardinality_ok=true가 될 수 있었다. 이 illustrative SQL은 행 수 이상을 증명하지 않았고, 2·1·0 정책도 모든 금융 시스템의 보편 규칙이 아니었다.

business transaction 21행을 유지하고 ledger 수를 세어 REQ-302 불일치를 찾는 학습용 도식
그림 한눈에: Q20은 transaction 21행을 보존해 REQ-302의 1 대 2를 찾지만, 금액·방향·복식부기 정합성이나 정본 답안을 뜻하지 않습니다.

여섯 번째 칸에서 네 사람은 다시 six-selector source를 읽고 evidence 연결표를 설계했다. 표의 행은 정확히 여섯 개였다. W9, W10, W11, W12의 day-7-green-gate.txt 네 경로, W13의 perf-manifest.json, W14의 smoke.txt였다. 각 행에는 relative path, byte 수, SHA-256을 같은 시점에 적어야 했다.

한 경로라도 없거나 너무 작으면 표를 쓰지 않았다. W14 smoke text에는 W14_SMOKE_GREEN, failures=0, errors=0, skipped=0 token이 있어야 했다. 임시 CSV를 만든 뒤 다시 읽어 행 수가 여섯이고 hash가 64자리인지 확인한 다음 목적지로 옮기는 것이 D6 링크 계약이었다.

그러나 키타가 빈 표에 미리 rows=6 broken=0 smoke=GREEN이라고 적자 니지카가 지웠다.

“그건 기대 marker야. 이번 source audit은 learner root smoke도, links writer도 실행하지 않았어. 계약을 정확히 설명할 수 있다는 것과 현재 파일 여섯 개가 같은 실행에 묶였다는 건 달라.”

여섯 행의 이름도 임의로 바꿀 수 없었다. evidence/w9/day-7-green-gate.txt, evidence/w10/day-7-green-gate.txt, evidence/w11/day-7-green-gate.txt, evidence/w12/day-7-green-gate.txt, evidence/w13/perf-manifest.json, evidence/w14/smoke.txt가 계약의 상대 경로였다. 경로가 존재하더라도 byte가 스무 미만이면 비어 있는 evidence로 처리했고, 저장한 CSV를 다시 읽었을 때 행 수가 여섯이 아니면 목적지로 옮기지 않았다.

그런데 이 D6 inline writer 역시 Ver2의 아홉 source teaching unit에 새 canonical file로 더해진 것은 아니었다. PDF가 제시한 workflow gate를 이해하는 장면이었다. 코드 뒤풀이에서 byte 단위로 다룬 W14 신규 owner는 scripts/run-w14-smoke.ps1 하나였고, 여섯 Java source는 historical reference, Q19와 Q20은 illustrative였다. workflow 계약과 source closure 수량을 섞지 않아야 “파일이 몇 개인가”라는 질문에도 같은 답을 할 수 있었다.

료는 links.csv가 여섯 hash를 담으면 full suite까지 증명하는지 물었다. 히토리가 먼저 고개를 저었다. 링크 표는 주어진 여섯 evidence path가 존재하고 그 byte를 다시 찾을 수 있게 할 뿐이었다. 각 predecessor claim이 올바른지, W14 smoke가 full suite인지, 오디오 내용이 좋은지는 각각의 producer와 검토 경계로 돌아가야 했다.

카운터에 모인 네 사람이 토요일 링크 점검과 현재 미실행 상태를 정리하는 공식 장면
그림 한눈에: 공식 장면컷은 D6 링크 점검의 마무리 분위기용이며, 현재 links.csv Green을 주장하지 않습니다.

마지막으로 니지카는 초록 도장을 투명 상자에 넣고 잠갔다. 상자 바깥에는 세 줄만 남았다. SOURCE/PDF/STAGE CLOSURE PASS. TESTS EXECUTED BY AUDIT = 0. CURRENT SIX-SELECTOR SMOKE = NOT RUN.

히토리는 처음보다 빈 도장이 덜 불안해 보인다고 말했다. 비어 있다는 사실을 정확히 표시하면 과거 marker를 오늘 결과로 착각하지 않을 수 있었다. 반대로 기대 output을 미리 Green으로 색칠하면, 실행하지 않은 범위와 실패한 범위를 찾기 어려워졌다.

“이번 주에 지켜야 할 건 초록색이 아니었네요.” 키타가 말했다.

“응. 정확한 범위야.” 니지카가 여섯 점검표를 다시 묶었다. “class와 method, historical reference와 current root, source 감사와 실제 실행, targeted smoke와 full suite, artifact hash와 사람의 의미 검토. 그 경계를 지켜야 다음에 찍는 초록 도장이 진짜가 돼.”

투명 상자 안에는 실행 날짜, learner root, source manifest, fresh XML hash를 적을 빈 칸도 남겨 두었다. 다음 실행이 그 칸들을 실제 값으로 채우기 전까지, 도장은 깨끗한 면을 위로 둔 채 잠겨 있었다.

W14 미리보기 · 히토리 질문 노트

히토리의 질문 노트 — W14 여섯 selector와 증거의 경계

이 노트는 strict D1∼D6에서 source가 직접 검사하는 것, PDF가 요구하는 학습 artifact, 아직 실행하지 않은 현재 상태를 분리합니다. 공통 코어 주차이며 트랙 선택 시 증권을 기본으로 표시하지만, 아래 test와 SQL을 증권 전용 구현으로 바꾸어 설명하지 않습니다.

1. 이번 미리보기의 정확한 원문 범위는 어디까지인가요?

W14 시작은 p459 L13–30 + p460–481입니다. p459의 W14 제목과 목적이 나오는 13∼30행 뒤, p460의 D1부터 p481의 D6 최종 Gate까지를 잇습니다. strict preview는 D1∼D6만 다룹니다. D7은 p482에서 시작하고 p486은 주간 통합, p487은 W14 recovery tail과 W15가 섞인 페이지이므로 모두 제외합니다.

페이지 수만 보고 p459 전체를 W14로 가져오면 앞쪽에 남아 있는 W13 마무리 문장이 섞입니다. 반대로 p481을 빼면 D6의 반환값·제약·완료 Gate가 끊깁니다. 시작 행과 끝 day를 함께 고정해야 합니다.

실제 기술 이름PDF line-bounded W14 D1–D6 curriculum closure — p459 L13–L30 plus p460–p481

PDF p459의 W14 개요와 p460부터 p481까지 D1–D6만 포함하고 D7 이후를 제외한 범위 지도
그림 한눈에: D1∼D6의 여섯 칸과 source audit·runtime·manual review 상태를 나눕니다.

2. W14의 목적과 시간 budget은 무엇인가요?

시험 집중 주에는 신규 기능을 멈추고 핵심 회귀, 세 2분 답변, W13 plan 회수, evidence 링크만 유지합니다. 전체 권장 범위는 6∼8시간이고, 월요일 targeted smoke는 45분 상한입니다. Gate를 일찍 통과했다면 남은 시간을 채우기 위한 기능을 추가하지 않습니다.

이 시간표가 technical Green을 만들지는 않습니다. 여덟 시간을 썼다는 사실은 test가 통과했다는 증거가 아니고, source audit이 PASS라는 사실도 learner가 시간을 지켰다는 증거가 아닙니다. W14는 공통 코어이고, UI에서 트랙을 골라야 할 때만 증권을 기본값으로 표시합니다.

실제 기술 이름exam-protected regression budget and scope control for a common-core week

3. Ver2가 표시하는 teaching unit 아홉 개는 어떻게 나뉘나요?

F01∼F06은 W6·W10·W12에서 누적된 historical Java test reference 여섯 개입니다. F07은 W14 신규 hash-bound PowerShell owner scripts/run-w14-smoke.ps1입니다. F08·F09는 Q19·Q20 문제 계약과 격리 fixture를 바탕으로 만든 illustrative SQL 두 개입니다.

따라서 canonical/reference unit은 일곱 개, illustrative unit은 두 개, 합계 아홉 개입니다. PDF의 audio validator, W13 recall validator, D6 links writer는 수행 workflow를 설명하지만 이 source-line teaching unit 수에 새 canonical file로 더하지 않습니다.

실제 기술 이름nine-unit dependency-first source closure — six historical Java references, one PowerShell owner, two illustrative SQL examples

4. exact six-selector는 무엇이며 순서는 중요한가요?

runner array는 AccountControllerTest, TransferFailurePointIT, SortedLockTransferIT, TransferIntegrationTest.same_key_with_different_semantic_request_conflicts_without_extra_effect, CoreSchemaIT, account.OpeningIntegrationTest를 담습니다. 앞의 다섯 항목 중 네 개와 뒤의 두 항목은 class selector이고, 긴 네 번째 항목만 exact method selector입니다.

Gradle filter 전달 순서는 source 독해와 재현에 중요하지만, XML Gate는 class 이름을 정렬한 집합으로 비교합니다. 따라서 XML class set의 순서는 성공 조건이 아닙니다. selector 문자열을 줄이거나 비슷한 다른 method로 바꾸면 exact smoke 계약이 달라집니다.

실제 기술 이름six exact Gradle --tests filters with five class selectors and one method selector

다섯 class selector와 한 exact method selector, staged test 10개를 구분한 선택표
그림 한눈에: 여섯 filter와 staged source에서 선택되는 test 10개의 관계를 보여 줍니다.

5. selector 6개·선택 test 10개·전체 @Test 18개는 왜 다른 숫자인가요?

여섯 historical Java source에는 @Test가 합계 18개 선언되어 있습니다. exact staged reference를 조립했다고 가정하면 class selector가 F01 1개, F02 1개, F03 4개, F04 1개, F05 2개를 고르고 F06 method selector가 1개를 골라 10개가 됩니다. Gradle에 넘기는 filter 문자열은 여섯 개입니다.

그러나 10은 이번 audit의 runtime 결과가 아닙니다. testsExecutedByAudit=0이고, 기본 root의 active source 구성도 staged closure와 다릅니다. 따라서 “현재 XML tests=10”이 아니라 “감사한 staged envelope를 계산하면 selected methods=10”이라고 말해야 합니다.

실제 기술 이름selector-count, staged selected-test count, and source-local declaration count separation

6. 왜 six-selector Green을 full regression Green이라고 부르면 안 되나요?

runner는 test task에 정확한 여섯 --tests filter를 붙입니다. 선택하지 않은 class와 method는 이 실행의 관찰 대상이 아닙니다. 따라서 여섯 selector의 XML이 Green이어도 전체 project test suite의 나머지가 Green이라는 논리적 결론은 나오지 않습니다.

PDF 일부 제목에 전체 회귀라는 표현이 있어도 실행 owner의 범위는 targeted smoke입니다. “핵심 위험 경로를 짧게 점검했다”는 문장은 맞지만, “모든 회귀 test가 통과했다”는 문장은 source가 지지하지 않습니다.

실제 기술 이름targeted six-selector smoke versus unexecuted full Gradle regression suite

여섯 선택 경로의 targeted smoke와 선택 밖 전체 회귀 suite를 분리한 그림
그림 한눈에: six-selector 육각형은 전체 suite 원의 일부이며 나머지 상태를 대신하지 않습니다.

7. 왜 기본 reference root가 historical source 여섯 개를 그대로 실행한다고 할 수 없나요?

CoreSchemaITOpeningIntegrationTestlearning_stages 아래에만 있고 기본 test source set에는 자동으로 들어오지 않습니다. AccountControllerTest는 현재 root에도 같은 FQCN이 있지만 W6D7 staged file과 method·assertion이 다른 evolved source입니다.

selector는 class 이름을 가리킬 뿐 source path와 SHA를 marker에 쓰지 않습니다. exact historical bytes를 실행했다고 주장하려면 stage source를 learner root에 조립하고, name collision을 해소하고, compiled source manifest와 fresh XML을 같은 실행에 묶어야 합니다.

실제 기술 이름learner-root stage composition and class-name-to-source-hash binding boundary

8. 교재의 tests>=6과 현재 runner의 최소 test 조건은 어떻게 다른가요?

공개 계약과 expected marker는 classes=6 tests>=6 failures=0 errors=0 skipped=0입니다. 하지만 source의 조건은 $tests-lt 5일 때만 실패하므로 tests=5는 거절되지 않습니다. 즉 구현 하한은 실질적으로 5입니다.

staged envelope 계산값 10, PDF 최소 계약 6, 구현 허용 하한 5를 섞으면 안 됩니다. 특히 runner를 고치고 재실행하기 전에는 “현재 source가 tests>=6을 enforce한다”고 쓸 수 없습니다.

실제 기술 이름minimum-test threshold defect — printed >=6 versus implemented >=5

교재 tests 6개 이상 계약과 실행기 tests 5개 이상 구현의 차이를 나란히 보인 그림
그림 한눈에: 10은 staged 계산, 6은 공개 최소, 5는 현재 source가 허용하는 하한입니다.

9. JUnit XML Gate가 직접 확인하는 값과 놓치는 값은 무엇인가요?

runner는 TEST-*.xml마다 suite name, tests, failures, errors, skipped를 읽습니다. expected class set과 actual unique class set을 exact 비교하고, 합계 failure·error·skip이 모두 0인지 봅니다. class 집합이 같더라도 각 class가 어느 source SHA에서 compile됐는지는 읽지 않습니다.

실행 전에 오래된 XML을 삭제하지 않고 --rerun-tasks도 쓰지 않습니다. 같은 기대 class 이름의 stale XML이 합계에 섞일 수 있고, smoke.txt도 사전 삭제나 temporary-file atomic replace 없이 Set-Content됩니다. file 존재 하나만으로 freshness를 증명할 수 없습니다.

실제 기술 이름JUnit XML class-set and aggregate gate with stale-result and source-identity gaps

기대·실제 class 집합과 tests failures errors skipped 합계를 검사하는 JUnit XML gate
그림 한눈에: 검사하는 class·tests·failure 합계와 검사하지 않는 freshness·source SHA를 갈라 봅니다.

10. 현재 W14 evidence 상태를 Green이라고 말할 수 있나요?

말할 수 없습니다. source_audit.status=PASS의 의미는 PDF, packaged source, stage provenance, file SHA closure를 감사했다는 것입니다. 같은 audit의 commandsAndTests.testsExecutedByAudit는 0입니다. learner root에서 six-selector를 fresh 실행하거나 현재 links.csv를 생성한 결과가 아닙니다.

workspace에 과거 smoke.txt가 존재하더라도 현재 staged composition·source hash·실행 시각에 묶이지 않으면 오늘 Green으로 승격할 수 없습니다. 미리보기의 올바른 상태 배지는 SOURCE AUDIT PASS, CURRENT SMOKE NOT_RUN입니다.

실제 기술 이름source-closure PASS with runtime status NOT_RUN and zero audit-executed tests

11. CoreSchemaIT이 직접 증명하는 schema 범위는 어디까지인가요?

information_schema.tables에서 table_schema='public', table_type='BASE TABLE'을 적용하고 flyway_schema_history를 제외합니다. 반환 이름이 account, business_tx, ledger_entry, idempotency_requestcontainsExactlyInAnyOrder로 같아야 합니다.

이 assertion은 table 이름의 exact membership만 봅니다. column type, nullability, PK·FK·UNIQUE·CHECK, index 정의, 다른 schema와 view는 관찰하지 않습니다. .as("W6D1_RED...")는 failure label이지 일부러 Red를 만드는 명령도 아닙니다.

실제 기술 이름exact public base-table name-set assertion excluding Flyway history

12. OpeningIntegrationTest의 method 이름에 Atomically가 있어도 무엇을 단정할 수 없나요?

fixture를 비운 뒤 customer-1, OPEN-100, 12_345로 account를 열고, 반환 ID의 account count 1, 전체 OPENING business transaction count 1, 같은 account의 OPENING ledger count 1을 차례로 확인합니다. 세 SQL은 모두 count projection입니다.

중간 failure injection이 없고 amount·balance·business_tx_id를 다시 읽지 않습니다. 세 SELECT가 하나의 명시적 transaction snapshot에 묶였다는 code도 없습니다. 따라서 직접 증명은 정상 성공 후 세 persistence 흔적의 count로 제한됩니다.

실제 기술 이름success-path opening persistence counts without direct rollback or single-snapshot proof

13. staged AccountControllerTest의 네 HTTP 경로는 무엇을 보나요?

계좌 생성은 201과 A-100 account 한 행, 음수 opening은 400과 account 영 행, owner 조회는 200과 일부 body 문자열, 없는 account 조회는 404를 확인합니다. class selector가 정확한 W6D7 staged source를 조립했다면 이 네 method가 대상입니다.

MockMvc test이므로 실제 network port·배포 환경을 통과한 end-to-end 증거가 아닙니다. body containsString은 전체 structured JSON equality가 아니며 인증·권한 전체와 audit_event도 assertion하지 않습니다. 현재 root의 evolved same-FQCN file과도 구분해야 합니다.

실제 기술 이름four-path staged MockMvc account API reference with historical-source boundary

14. TransferFailurePointIT은 무엇을 rollback했다고 직접 검사하나요?

AFTER_CLAIMAFTER_BUSINESS 두 지점에서 RuntimeException을 던집니다. 각 test는 exception 뒤 idempotency_request, TRANSFER business transaction, TRANSFER ledger entry가 모두 0행인지 확인합니다.

이 staged class는 실패 뒤 account balance 두 값을 직접 assert하지 않습니다. checked exception, process hard-kill, 외부 시스템, audit row도 범위 밖입니다. “모든 effect 0” 대신 “세 query가 세 업무 표의 row count 0을 확인”이라고 쓰는 것이 정확합니다.

실제 기술 이름two injected RuntimeException rollback checks over three database row-count categories

15. 원자성 2분 오디오의 byte와 SHA가 답변 의미를 증명하나요?

아닙니다. D2 validator는 atomicity-answer.m4a가 file인지, 128 byte 이상인지 확인하고 SHA-256을 출력합니다. expected output도 MANUAL_REVIEW_REQUIRED W14D2 ...입니다. 음성을 재생해 위험·설계·test·결과·한계가 맞는지 해석하지 않습니다.

교재 답변 template의 failure point 수치와 exact smoke source의 선택 test도 자동으로 같지 않습니다. 사람이 녹음을 듣고 F02 정상 count, F05 두 failure point, account balance 미assertion 같은 경계를 확인해야 합니다.

실제 기술 이름artifact existence/hash validation with mandatory semantic human review

개설 성공 뒤 세 행 확인과 두 실패 지점 뒤 세 종류 transfer 행 0을 구분한 원자성 경계
그림 한눈에: 정상 count·failure row count·balance 미검증·오디오 사람 검토를 한 표에서 분리합니다.

16. SortedLockTransferIT의 exact concurrency envelope는 무엇인가요?

두 account를 각각 10,000으로 만들고 A→B와 B→A를 열 쌍, 총 20 task로 한 번 시작합니다. ready latch는 10초, 각 future는 30초 안에 끝나야 합니다. 마지막에는 두 account balance 합 20,000과 TRANSFER ledger signed amount 합 0을 확인합니다.

이 한 실행은 모든 scheduling에서 deadlock이 불가능함을 증명하지 않습니다. 100회 반복, 처리량, p95, retry count, production의 모든 account 조합도 측정하지 않습니다. Future.get이 task exception을 전달한다는 것과 성능을 측정했다는 것도 다른 주장입니다.

실제 기술 이름one-run 10-pair/20-task sorted-lock completion and conservation envelope

17. 동시성 2분 답변의 다른 수치를 W14 smoke 결과라고 불러도 되나요?

안 됩니다. curriculum 답변에는 versionless lost-update 기준 결함과 ConcurrentWithdraw20IT의 success 10·business reject 10·technical failure 0 같은 이전 학습 envelope도 등장합니다. 그러나 run-w14-smoke.ps1의 여섯 filter에는 그 두 selector가 없습니다.

W14 smoke가 직접 고르는 concurrency class는 SortedLockTransferIT입니다. 오디오에서는 이전 evidence를 출처와 함께 회수할 수 있지만, 현재 smoke가 모두 다시 실행했다고 표현하면 범위를 부풀립니다.

실제 기술 이름interview-envelope recall separated from the exact W14 concurrency smoke selector

반대 방향 10쌍 20 task를 한 번 실행해 합계 20000과 signed sum 0을 확인하는 범위
그림 한눈에: 직접 smoke의 20-task 1회와 이전 학습의 다른 workload 수치를 다른 색으로 둡니다.

18. TransferIntegrationTest의 exact method selector는 어떤 충돌을 보나요?

두 account를 10,000/10,000으로 시작하고 conflict-key로 amount 1,000인 첫 요청을 성공시킵니다. 같은 key에 amount만 2,000으로 바꾼 두 번째 요청은 semantic conflict가 되어야 하며 추가 업무 effect가 없어야 합니다.

최종 assertion은 balance 9,000/11,000, transfer business transaction 1행, ledger 2행, idempotency claim 1행입니다. source 파일에는 test가 9개 있지만 exact selector는 이 method 하나뿐이므로 same-key 20, JSON 표현 replay, changed from/to, rollback method를 실행 결과에 더하지 않습니다.

실제 기술 이름same-key changed-amount conflict without extra business effect

19. AtomicClaim50과 same-key 20은 현재 W14 smoke가 다시 실행하나요?

아닙니다. D4의 2분 답변은 AtomicClaim50의 owner 1·existing 49와 same-key concurrent 20의 업무 effect 1을 이전 evidence에서 회수하도록 요구합니다. 둘은 claim owner 경쟁과 통합 업무 effect 경쟁이라는 서로 다른 workload입니다.

exact W14 smoke의 멱등 관련 selector는 changed-amount conflict method 하나입니다. burst 100, stale PROCESSING recovery, 모든 semantic field conflict도 그 method가 보장하지 않습니다. D4 오디오 hash 역시 내용 Green이 아니라 manual-review marker입니다.

실제 기술 이름prior idempotency stress-envelope recall versus one exact semantic-conflict smoke method

같은 key에서 amount 1000 성공 뒤 2000 요청을 conflict로 막고 효과 하나만 남기는 범위
그림 한눈에: amount-conflict 직접 selector와 AtomicClaim50·same-key20 회수 범위를 갈라 표시합니다.

20. SQL Q19의 입력 grain과 출력 grain은 무엇인가요?

입력은 business_tx 한 행을 current transaction 한 건으로 보는 grain입니다. 같은 table을 current_txoriginal_tx로 별칭하고 current_tx.original_tx_id = original_tx.tx_id로 self LEFT JOIN합니다. 출력도 current transaction 한 건당 한 행을 유지하면서 원거래 column을 옆에 붙입니다.

INNER JOIN을 쓰면 원거래가 없는 정상 transaction이 사라집니다. Q19의 필수 반례가 바로 그 행을 보존하는지 확인하는 것입니다. query를 쓰기 전 시작 table, 출력 grain, 예상 cardinality를 주석으로 적어야 합니다.

실제 기술 이름current-transaction-grain self LEFT JOIN preserving no-original rows

21. Q19 fixture의 정확한 oracle과 provenance는 무엇인가요?

current transaction은 21행입니다. REQ-211REQ-212 두 reversal이 REQ-201에 연결되어 LINKED가 되고, 나머지 19행은 original_tx_id가 없어 NO_ORIGINAL입니다. fixture의 FK가 유지되므로 BROKEN_REFERENCE는 0행이 예상됩니다.

이 SQL은 W14 Ver2가 prompt와 격리 workbook fixture를 바탕으로 만든 fixture-bound 학습용 예시(illustrative example)입니다. shipped canonical answer가 아니며 어떤 reversal이 업무적으로 유효한지 판정하지 않습니다. reversal만 필요한 report라면 별도 filter 정책도 필요합니다.

실제 기술 이름fixture-bound Q19 oracle — 21 current rows, 2 linked reversals, 19 no-original rows

21개 거래를 유지하며 reversal 두 행을 REQ-201 원거래와 잇는 학습용 LEFT self join
그림 한눈에: 두 reversal의 연결과 보존되는 19개 일반 transaction을 함께 보여 줍니다.

22. D2∼D4의 오디오 validator가 technical teaching unit 수에 들어가지 않는 이유는 무엇인가요?

PDF의 validator는 학습자가 만든 .m4a file의 존재·최소 byte·SHA를 검사하는 workflow gate입니다. Java test나 W14 신규 owner source의 동작을 한 줄씩 번역하는 teaching unit은 아닙니다. 그래서 source closure의 아홉 항목에 별도 canonical file로 세지 않습니다.

그렇다고 과제를 무시한다는 뜻도 아닙니다. 수행 계약에는 존재하지만 technical proof는 사람이 audio를 검토해야 합니다. workflow inclusion과 source-line inventory inclusion을 서로 다른 수량으로 관리합니다.

실제 기술 이름workflow artifact validation versus source-line teaching-unit inventory

23. D5의 W13 plan review가 새 W14 성능 증거가 아닌 이유는 무엇인가요?

D5는 before-plan.txt, after-plan.txt, perf-manifest.json을 read-only로 읽어 top node, actual rows, buffers, chosen index, before/after sample 수와 claim boundary를 회수합니다. query·fixture·parameter를 바꾸어 새 benchmark를 실행하는 단계가 아닙니다.

세 predecessor file이 실제로 존재하고 올바른 hash에 묶여 있어야 설명 입력이 됩니다. W14 source audit이 그것들을 fresh 실행했다는 뜻은 아니며, W13의 현재 evidence 연속성이나 통계 계산 문제도 회수 worksheet가 고치지 않습니다.

실제 기술 이름read-only predecessor EXPLAIN recall with no new performance claim

24. SQL Q20에서 왜 LEFT JOINCOUNT(ledger.entry_id)가 함께 필요한가요?

business_tx 21행을 왼쪽에 두어 ledger가 0개인 transaction도 결과에 남깁니다. COUNT(*)는 LEFT JOIN의 NULL 확장행을 한 건으로 셀 수 있으므로 non-NULL ledger key인 COUNT(ledger.entry_id)를 써야 0을 정확히 보존합니다.

fixture policy는 성공 transfer 2행, 다른 지원 success 1행, 그 밖 0행을 기대합니다. REQ-302만 actual 1, expected 2여서 cardinality_ok=false입니다. 출력 grain은 business transaction 한 건당 한 행입니다.

실제 기술 이름transaction-grain ledger cardinality audit preserving zero-entry transactions

business transaction 21행을 유지하고 ledger 수를 세어 REQ-302 불일치를 찾는 학습용 도식
그림 한눈에: 21개 transaction을 보존하고 REQ-302의 1 대 2 불일치를 찾는 흐름입니다.

25. Q20에서 cardinality_ok=true면 원장이 완전히 맞다는 뜻인가요?

아닙니다. Q20은 ledger 행 수만 비교합니다. 금액, debit/credit 방향, signed 합, status, 원거래 연결이 틀려도 행 수가 expected와 같으면 true가 될 수 있습니다. 두 개의 잘못된 ledger가 있는 반례가 대표적입니다.

2·1·0 expected CASE도 이 격리 fixture의 교육 정책입니다. 모든 금융 시스템의 보편적인 double-entry 법칙으로 일반화하면 안 됩니다. Q20 파일 역시 illustrative이며 packaged canonical workbook answer가 아닙니다.

실제 기술 이름fixture-specific cardinality check separated from ledger amount and balancing invariants

26. D6 links.csv가 묶으려는 여섯 경로는 무엇인가요?

evidence/w9/day-7-green-gate.txt, W10·W11·W12의 같은 day-7 path 네 개, evidence/w13/perf-manifest.json, evidence/w14/smoke.txt입니다. 각 file의 relative path, byte 수, SHA-256을 같은 CSV 행에 둡니다.

한 file이라도 없거나 20 byte 미만이면 쓰기를 멈춥니다. temporary CSV를 다시 읽어 행 수 6, 64자리 lower-case hash, byte 하한을 확인한 뒤 목적지로 옮깁니다. 이는 PDF D6 workflow contract이며 Ver2 source teaching unit에 두 번째 W14 신규 owner로 세지 않습니다.

실제 기술 이름six-row predecessor evidence traceability contract with path, byte size, and SHA-256

27. D6 expected marker를 현재 완료 결과처럼 보여 줘도 되나요?

안 됩니다. 기대 결과는 W14D6_PRIMARY_EVIDENCE_GREEN rows=6 broken=0 smoke=GREEN sha256=<64-hex>이지만, 이번 source audit은 smoke와 links writer를 실행하지 않았습니다. CURRENT NOT RUN 상태에서 예시 marker를 실제 값으로 채우면 증거를 제작한 것처럼 보입니다.

links.csv가 존재하더라도 그 여섯 predecessor claim의 의미가 자동으로 다시 검증되는 것도 아닙니다. traceability는 어느 byte를 가리켰는지 찾게 해 주고, 각 producer의 technical claim과 사람 검토는 별도로 남습니다.

실제 기술 이름expected D6 marker contract versus absent current execution evidence

28. strict D1∼D6이 최종적으로 보장하는 것과 보장하지 않는 것은 무엇인가요?

정적으로 보장할 수 있는 것은 PDF 범위, six-selector 문자열, historical source의 direct assertions, runner의 XML 조건과 threshold 결함, Q19·Q20 fixture oracle, W13 read-only recall과 six-link workflow 계약입니다. source audit PASS는 이 closure를 byte와 provenance로 확인했다는 뜻입니다.

보장하지 않는 것은 current learner-root Green, exact staged SHA의 실제 compile·execution, full suite Green, tests>=6 enforcement, stale-free XML, account balance까지 포함한 F05 rollback, audio 의미 품질, Q19·Q20 canonical 정답, 새 W14 성능 개선, 현재 links.csv 완료입니다.

실제 기술 이름evidence-bounded W14 D1–D6 contract with explicit runtime and semantic non-guarantees