19주차 미리보기
W19 미리보기 · 웹소설 본편

STARRY PASS 15 — 수상한 추적표와 가려진 카드번호

추적표는 이어지고, 민감한 값은 가려진다

범위개요 + D1–D6 · 월–토p607 W19 heading 아래 + p608–637 + p638 D6 최종 Gate 상단 체크 2개
정본 상태SOURCE AUDIT PASSPDF·package·display byte 폐쇄
실행 상태W19 D1–D6 INTEGRATION NOT_RUNtestsExecutedByAudit=0

이 이야기는 p607 W19 heading 아래 + p608–637 + p638 D6 최종 Gate 상단 체크 2개만 다룹니다. p607 상단의 W18 공식 참고는 제외하고, p638 하단 D7, p643 주간 통합, p644 공식 참고, p645 W20도 포함하지 않습니다. strict D1–D6 초회 예상시간은 7시간 50분–14시간 50분입니다. 공통 코어 · 트랙 선택 시 증권 기본이며 증권 주문·체결 전용 주차는 아닙니다. SOURCE AUDIT PASS는 PDF·package·display byte 폐쇄를 확인한 정적 판정일 뿐입니다. 현재 상태는 W19 D1–D6 INTEGRATION NOT_RUN이고, 이 preview의 파생 표지는 testsExecutedByAudit=0입니다. 원문 감사의 정확한 실행 경계는 No Docker/Testcontainers integration execution is claimed by this source audit; deterministic test and SQL oracles are read from pinned source/seed.이며 learner runtime Green이나 learner authorship을 주장하지 않습니다.

월요일 저녁, STARRY의 카드 보관함에서 가느다란 종이띠가 끝없이 흘러나왔다. 종이마다 X-Request-Id라고 적혀 있었지만 어떤 것은 비어 있었고, 어떤 것은 공백과 줄바꿈을 품고 있었다. 카운터 아래에서는 손님 계좌번호가 원문 그대로 찍힌 영수증까지 발견됐다. 히토리는 종이를 주워 들고 자신이 어제 처리한 요청이 다음 손님의 기록에 붙어 버린 것은 아닌지 걱정했다.

니지카는 새 기능부터 만들지 않았다. 먼저 reference와 learner 작업장을 서로 다른 절대경로로 나누고, 학습자가 직접 고칠 두 파일만 빈 무대에 올렸다. RequestIdFilter.java는 받은 header를 검증 없이 되돌려 주고 MDC를 쓰거나 정리하지 않는 starter였다. SensitiveDataMasker.java는 민감한 식별자를 그대로 반환했다. 둘은 고장 난 완성품이 아니라 일부러 빠진 동작을 드러내는 LEARNER_TARGET_STARTER였다.

니지카가 태블릿으로 요청 추적표와 보안 점검 순서를 확인하는 공식 장면
그림 한눈에: 공식 장면컷은 니지카가 두 작업장과 source 역할을 확인하는 분위기용이며, 학습 사건·코드·수치는 원문 감사에 따른 비공식 구성입니다.
ReferenceRoot의 제공 테스트와 닫힌 솔루션을 bridge를 거쳐 LearnerRoot의 두 intentional starter와 분리한 지도
그림 한눈에: ReferenceRoot의 제공 test와 LearnerRoot의 intentional starter 두 개를 분리하고, solution은 D3까지 닫아 둡니다.

키타가 starter 설치 표지에 초록 스티커를 붙이려 하자 료가 떼어 냈다. 예상 문구 W19 Starter; provided tests + intentionally incomplete targets installed는 설치와 ownership을 말할 뿐 기능 Green이 아니었다. evidence/w19/bridge-starter.json도 실제로 새로 실행해 만들어야 할 주 증거였다. 이 미리보기는 그 실행을 하지 않았고 source block 자체도 실행 명령이 아니었다.

“설치됐다는 말과 안전하다는 말 사이에 문 하나가 더 있어.” 니지카가 말했다. “오늘은 그 문에 이름을 붙인 거야.”

둘째 날에는 세 장의 test 계약서가 무대에 펼쳐졌다. RequestIdFilterTest에는 유효한 req-w19-1이 response header·request attribute·filter 안 MDC에서 같고, chain이 끝난 뒤 MDC가 NULL이어야 한다는 test와 위험한 header가 36자 UUID로 교체돼야 한다는 test가 있었다. MaskingTest에는 긴 값·NULL·짧은 값·resource ID의 정확한 문자열 계약 두 개가 있었다. AuditPropagationIT에는 타인 계좌 조회가 403으로 끝난 뒤 같은 requestId를 가진 DENIED audit 한 행이 남는 test 하나가 있었다. 인쇄된 세 test source에는 모두 다섯 @Test가 있었지만, D2의 intentional Red target은 learner 파일 두 개뿐이었다.

테스트가 실행되어 exact assertion 두 개에 도달한 intentional Red와 compile import test zero 사고를 나눈 흐름
그림 한눈에: 지정 assertion 두 개가 실패한 intentional Red와 import·compile 오류 같은 우연한 실패를 갈라 봅니다.

Red의 exact marker는 validRequestIdIsReturnedStoredForTheRequestAndRemovedFromMdcaccountNumberKeepsAtMostTheLastFourCharacters였다. tests>0, 지정 marker 두 개, native_exit!=0이 함께 있어야 했다. 아무 compile 오류나 Red로 세면 test 계약을 읽었다는 증거가 되지 않았다. 반대로 AuditPropagationIT가 이 단계에서 통과하거나 실패한다는 사실만으로 학습자 구현의 저작권을 만들 수도 없었다. 그것은 PROVIDED_TEST_CONTRACTPROVIDED_FIXTURE_VERIFIED로 분리되는 제공 audit fixture의 경계였다.

D2의 전체 expected 문구는 W19 LearnerRed; native_exit!=0; exact markers=validRequestIdIsReturnedStoredForTheRequestAndRemovedFromMdc;accountNumberKeepsAtMostTheLastFourCharacters였다. 두 method 사이의 세미콜론까지 포함한 한 묶음이며, 문구를 읽는 것만으로 실제 Red evidence가 생기지는 않았다.

히토리는 실패가 빨간색이면 모두 같은 줄 알았다고 털어놓았다. 니지카는 “의도한 assertion까지 도착한 실패”와 “그 전에 넘어져 버린 실패”를 다른 상자에 넣었다. evidence/w19/bridge-recordred.json에 들어갈 expected marker는 지도였고, 지금 그 지도를 읽은 것과 실제 JUnit XML을 새로 만든 것은 구분했다.

셋째 날, 네 사람은 드디어 두 starter를 직접 고치는 연습을 했다. 안전한 requestId는 [A-Za-z0-9._:-]{1,64}에 맞을 때만 그대로 썼다. 누락·공백·줄바꿈·길이초과 입력은 신뢰하지 않고 서버가 UUID를 새로 만들었다. 그 선택을 request attribute, response header, MDC 세 위치에 같은 값으로 넣고 chain이 정상 종료하든 예외로 끝나든 finally에서 MDC key를 제거하는 것이 목표였다.

네 사람이 request ID와 masking, 감사 경계를 토론하는 공식 장면
그림 한눈에: 공식 장면컷은 네 사람이 안전한 입력과 교체할 입력, cleanup 경계를 토론하는 분위기용입니다.
X Request Id 입력을 안전한 allowlist 값은 보존하고 누락 공백 개행 길이초과 값은 UUID로 교체하는 분기
그림 한눈에: allowlist에 맞는 ID는 echo하고, 누락·공백·개행·길이초과 ID는 36자 UUID로 교체합니다.
header에서 선택한 requestId가 request attribute response header MDC를 지나 finally remove되는 lifecycle
그림 한눈에: 하나의 requestId가 attribute·response·MDC로 흐르고 chain 뒤 finally에서 MDC가 제거됩니다.

료는 cleanup 상자 옆에 작은 경고를 붙였다. 현재 solution은 pre-existing MDC 값을 저장했다가 복원하지 않고 remove한다. 비동기 thread로 MDC가 자동 전파되는 것도 아니다. requestId는 사건을 연결하는 좌석표이지 인증 token이나 권한 판단 근거가 아니며, UUID regex가 uniqueness나 entropy까지 증명하지도 않는다.

masker는 NULL이나 blank를 *로, 123-456-7890**7890으로, 1212로, ACCOUNT/123456ACCOUNT:**3456으로 바꾸는 계약을 가졌다. 하지만 짧은 값은 별표 뒤에 원문 전체가 남는다. ASCII가 아닌 문자와 구두점을 제거하는 정규화 때문에 서로 다른 입력이 같은 표시값이 될 수 있고, resource type은 별도 검증 없이 prefix에 붙는다. 마스킹은 암호화·인가·credential rotation을 대신하지 않았다.

긴 계좌번호 null 짧은 값 resource id의 정확한 마스킹 결과와 짧은 원문 Unicode collision type 미검증 경계
그림 한눈에: 네 exact masking 결과와 함께 짧은 값 노출·Unicode collision·resource type 미검증 경계를 표시합니다.

같은 날의 Q29는 “업무일 기준 전일 대비 거래액 차이”였다. shipped workbook 정본 답안은 없었다. 학습용 illustrative SQL은 모든 SUCCESS business_tx의 amount를 합치고 OPENING·REVERSAL도 포함한다는 가정을 먼저 적었다. business_date - 1의 전 달력일과 self join하므로 휴일을 건너뛰는 영업일 calendar가 아니다. 2026-07-01은 total 1500, previous 2129999, difference -2128499이고, 2026-12-29는 전날 SUCCESS 집계가 없어 previous와 difference가 NULL이다. 나머지는 2026-12-30 difference=1; 2026-12-31 difference=900000; 2027-01-01 difference=-999500; 2027-01-02 difference=700이며, 축약 exact difference는 2026-12-30=1, 12-31=900000, 2027-01-01=-999500, 01-02=700이다.

SUCCESS 일별 집계를 전 달력일과 self join해 2026년 7월 1일 차이와 12월 29일 NULL을 보여 주는 도식
그림 한눈에: SUCCESS 일별 합계를 전 달력일과 self join하고, 전일 집계가 없으면 NULL로 남기는 Q29 가정을 봅니다.

니지카는 W19 LearnerSolutionVerified; exact target hashes=2라는 expected 문구도 실행 결과처럼 읽지 않았다. 학습자가 Red 뒤 두 파일을 직접 쓴 경로만 learner-authored가 될 수 있었다. solution을 복사하는 수동 복구 경로는 QA_OVERLAY/VerifiedGreen일 뿐이며 QA_OVERLAY ≠ learner-authored Green이었다. 어느 경로든 source hash와 실제 실행 증거, 사람의 의미 검토가 따로 필요했다.

넷째 날에는 두 learner target과 제공 denied-audit fixture를 한 무대에서 연결하는 계획표가 나왔다. manifest의 Green selector는 RequestIdFilterTest, MaskingTest, AuditPropagationIT 세 개였다. 실제 RecordGreen이라면 각 class가 tests>0이고 failures·errors·skipped가 0인 새 JUnit XML과 native exit, source mode를 확인해야 했다.

RequestIdFilter Masking AuditPropagation 세 selector 실행 열과 두 learner target 및 제공 audit fixture ownership 열의 행렬
그림 한눈에: selector 세 개의 실행 상태와 learner target 두 개·provided audit fixture 한 개의 ownership을 서로 다른 열로 봅니다.

“세 개가 함께 Green이면 세 개를 우리가 만든 건가요?” 키타가 물었다.

“실행 상태와 저작 상태는 다른 열이야.” 니지카가 답했다. “RequestIdFilterSensitiveDataMasker만 learner가 Red 뒤 직접 쓴 target이고, denied-audit 구현과 integration test는 provided fixture야. recovery가 QA overlay면 상태 이름도 VerifiedGreen으로 남겨야 하고.”

D4의 source-mode 분기까지 포함한 exact expected 문구는 W19 LearnerGreen for learner-typed path OR VerifiedGreen source_mode=QA_OVERLAY for recovery path였다. 앞 경로의 실행 성공을 뒤 경로의 learner authorship으로 옮겨 적을 수 없었다.

타인 계좌를 customer-2가 조회하면 응답은 403이고, audit-denied-1로 찾은 event는 정확히 한 행이어야 했다. actor는 customer-2, result는 DENIED, error는 ACCESS_DENIED, masked resource는 ACCOUNT:로 시작하지만 원래 식별자 전체와 같지 않아야 했다. 같은 requestId가 고객 응답과 audit row를 이어 주었다.

customer two의 타인 계좌 조회가 403으로 rollback되고 같은 requestId의 masked denied audit 한 행이 남는 흐름
그림 한눈에: 권한 없는 GET은 403으로 끝나고 업무 transaction은 되돌아가지만, 같은 requestId의 가려진 DENIED audit 한 행은 별도 경계에 남습니다.

그러나 이 한 test는 success audit, exactly-once 전 경로, 보관 기간, tamper resistance, audit 저장 실패 때의 transaction 정책을 증명하지 않는다. masked 값이 원문과 다르다는 assertion도 모든 포맷을 고정하지 않는다. SOURCE AUDIT PASS가 이 integrated Gate를 실행했다는 뜻도 아니었다.

다섯째 날에는 긴 계좌번호가 찍힌 카드와 짧은 두 자리 카드가 나란히 놓였다. 오늘 exact selector는 MaskingTest 하나였다. 실제 완료라면 EXACT_SELECTOR_GREEN selectors=1 classes>=1 tests>=1 failures=0 errors=0 skipped=0 native_exit=0과 새 evidence를 확인해야 하지만, 이 문서에서 그 명령을 실행하지 않았다.

카운터에 모인 네 사람이 Green 증거와 실행 한계를 정리하는 공식 장면
그림 한눈에: 공식 장면컷은 카운터에서 공개해도 되는 표시값과 숨길 원문을 함께 점검하는 분위기용입니다.

secret scan과 credential rotation은 masker test와 다른 일이다. 오늘 새로 쓴 파일의 scan report가 깨끗하더라도 Git 과거 이력 전체가 깨끗하다고 말할 수 없고, 실제 credential 노출 가능성이 있으면 값을 가리는 대신 폐기하고 교체해야 했다. 계좌번호 마지막 네 글자가 남는 정책도 조직의 privacy 요구와 다시 맞춰야 했다.

Q30은 “계좌별 최근 성공 거래 1건”이었다. illustrative SQL은 account를 시작 table로 삼고 LEFT JOIN LATERAL을 써 성공 거래가 없는 계좌도 남겼다. direct business_tx.account_id만 보고 counterparty로 참여한 거래는 포함하지 않았다. SUCCESS라면 OPENING·REVERSAL도 포함했으며, 동률은 occurred_at DESC, tx_id DESC로 고정했다. oracle은 8개 account 행, account 101은 ...212, 103과 104는 NULL, 105는 ...210이었다. 나머지는 102→...102 amount 20000; 106→...106 amount499999; 107→...107 amount500000; 108→...108 amount1000000이며, 모두 SUCCESS OPENING이다. 같은 값을 축약 없이 묶으면 account 102 ...102 amount20000, 106 ...106 amount499999,107 ...107 amount500000,108 ...108 amount1000000 all SUCCESS OPENING이다.

account를 grain으로 LEFT LATERAL 최신 성공 거래를 찾고 account 101 103 104 105 oracle을 보여 주는 도식
그림 한눈에: account 한 행마다 LEFT LATERAL로 최근 SUCCESS 한 건을 고르고, 동률은 occurred_at·tx_id로 안정 정렬합니다.

Q29와 Q30 모두 학습용 예시 · 정본 답안 아님이었다. prompt가 고정하지 않은 status·tx_type·금액 부호·calendar·counterparty·0건 보존 정책을 주석으로 공개했기 때문에 유용한 예시일 뿐, learner가 제출해야 할 shipped canonical answer가 되지 않았다.

여섯째 날의 마지막 상자에는 고객에게 보낼 오류 봉투와 내부 조사 기록이 나란히 들어 있었다. exact selector는 RequestIdFilterTestApiExceptionHandlerTest 두 개였다. 유효 ID echo, 누락·invalid ID의 서버 생성, 다음 요청 MDC 누수 0, 외부 response에서 SQL·exception class·stack trace 원문 노출 0만 오늘의 claim이었다.

D6 실행 성공의 exact 형식은 EXACT_SELECTOR_GREEN selectors=2 classes>=1 tests>=1 failures=0 errors=0 skipped=0 native_exit=0이었다. 이것도 실제 새 JUnit XML과 evidence가 동반될 때만 관찰 결과가 된다.

그런데 PDF가 D6에 전문으로 보여 주는 source는 RequestIdFilterTest, AuditPropagationIT, MaskingTest 세 개였다. 실행 selector 두 개와 displayed source 세 개는 서로 같은 집합이 아니었다. selector인 ApiExceptionHandlerTest 전문은 인쇄되지 않았고, displayed source 중 audit와 masking test는 D6 exact selector가 아니었다. 숫자가 2와 3이라고 어느 쪽을 다른 쪽의 목록으로 바꾸면 안 됐다.

외부 ApiError의 error code safe message request id와 내부 SQL exception stack trace 사이의 disclosure boundary
그림 한눈에: 고객 body에는 stable errorCode·message·requestId만 두고 SQL·exception·stack trace는 내부 경계 밖으로 내보내지 않습니다.

고객에게 보이는 requestId와 내부 진단이 같은 사건을 가리켜도 requestId는 인증 근거가 아니다. 외부 입력은 검증해야 하고, 안전하지 않은 값은 그대로 반사하지 않는다. 비노출 token scan 하나로 전체 log-injection 방어를 완료했다고 주장할 수도 없다. error body와 내부 log의 목적을 분리하고, test하지 않은 공격 벡터는 claim에서 제외했다.

p637의 D6 최종 Gate가 p638 상단으로 넘어가면서 마지막 두 체크가 이어졌다. 첫째, ApiError에 SQL·exception text가 노출되지 않는다. 둘째, 전체 log-injection 방어를 검증했다고 과장하지 않는다. 그 아래 일 모듈 | W19 감사·masking Gate부터는 D7이므로 니지카는 문을 닫았다.

여섯 장의 계획표 아래 적힌 초회 예상시간을 더하면 D1 55분–1시간 45분, D2 1시간 5분–2시간 5분, D3 1시간 40분–3시간 10분, D4 1시간 5분–2시간 10분, D5 1시간 30분–2시간 50분, D6 1시간 35분–2시간 50분이다. strict D1–D6 합계는 정확히 7시간 50분–14시간 50분이다. 막힘 중단선과 실제 소요시간은 별도로 기록하고, 예상보다 빨리 통과했다고 시간을 채우지 않는다.

현대 코드 뒤풀이의 display closure는 canonical 7개illustrative 2개다. canonical은 intentional Red starter 2개, test source 3개, Green solution 2개다. illustrative는 Q29·Q30 두 SQL이다. content aggregate vector 9/237/208/45/163/208/31/5는 차례로 teaching units 9개 / physical lines 237줄 / nonblank lines 208줄 / setup lines 45줄 / mapping rows 163개 / translation rows 208개 / source chunks 31개 / source-local tests 5개를 뜻한다. source-local @Test 5개는 source inventory이지 이 미리보기 감사가 실행한 test 수가 아니다. 전체 코드 문서의 source audit 범위는 W19 전체 p607–644라 strict preview subset과 다르고, 이 preview에서는 p644를 제외한다.

니지카는 마지막으로 세 표찰을 벽에 붙였다. SOURCE AUDIT PASS는 byte와 분류의 폐쇄, No Docker/Testcontainers integration execution is claimed by this source audit; deterministic test and SQL oracles are read from pinned source/seed.는 실행 claim 없음, HUMAN_SEMANTIC_REVIEW_REQUIRED는 ownership·업무 가정·보안 경계를 사람이 다시 판단해야 한다는 뜻이었다. 히토리는 초록색 펜을 내려놓고 대신 다음 실행에서 확인할 evidence path를 적었다.

종이띠는 더 이상 유령처럼 보이지 않았다. 안전한 추적표는 같은 요청의 세 위치를 이어 주고 끝나면 메모판에서 사라졌다. 수상한 표는 새 번호로 바뀌었다. 카드번호는 필요한 끝부분만 남았고, 거절된 행동은 원문을 가린 채 한 행으로 이어졌다. 무엇을 실행했고 누가 썼는지까지 정확히 구분하자 STARRY의 밤은 조용해졌다.

이미지 출처와 이용 안내: 공식 장면컷은 TV 애니메이션 《봇치 더 록!》 공식 사이트 자료입니다. ©はまじあき/芳文社・アニプレックス. 이 문서는 개인 학습용 비공식 구성으로, 원작의 공식 설정과 별개의 학습 비유입니다. 개념도는 W19 source audit의 수치·ownership·미보장 경계에 맞춰 새로 만든 교육용 SVG입니다.

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

히토리의 질문 노트 — STARRY PASS 15

본편의 비유를 W19 requestId·masking·denied audit의 정확한 source 역할과 증거 경계로 다시 연결합니다. 모든 답은 p607 W19 heading 아래 + p608–637 + p638 D6 최종 Gate 상단 체크 2개에 한정합니다. 현재 상태는 W19 D1–D6 INTEGRATION NOT_RUN이고 이 preview의 파생 표지는 testsExecutedByAudit=0입니다. expected marker를 실제 Green으로 바꾸어 읽지 않습니다.

1. 이번 미리보기의 strict PDF 범위는 어디인가요?

답: p607에서 W19 heading 아래의 개요를 포함하고 p608–637 전체, p638 상단의 D6 최종 Gate 체크 두 개까지만 포함합니다.

근거: 정확한 범위 표기는 p607 W19 heading 아래 + p608–637 + p638 D6 최종 Gate 상단 체크 2개입니다. p607 상단 W18 공식 참고, p638 하단 D7, p643 주간 통합, p644 공식 참고, p645 W20은 제외합니다.

오답 함정: p607이나 p638이 혼합 페이지라는 사실을 무시하고 페이지 전체를 한 주차에 넣으면 W18 tail 또는 D7이 섞입니다.

범위 한계: modern 코드 문서의 source audit은 W19 전체 p607–644를 닫으므로 이 preview subset과 동일하지 않습니다.

실제 기술 이름mixed-page semantic boundary for W19 overview and D1–D6

2. D1–D6의 초회 예상시간 합계는 얼마인가요?

답: strict D1–D6 합계는 7시간 50분–14시간 50분, 즉 470–890분입니다.

근거: D1 55–105분, D2 65–125분, D3 100–190분, D4 65–130분, D5 90–170분, D6 95–170분을 각각 더합니다.

오답 함정: p607 주간 전체 시간이나 D7 45–90분을 더해 D1–D6 시간이라고 부르면 범위가 달라집니다.

범위 한계: 예상시간은 active-time 계획값입니다. 실제 소요시간·막힘 중단선·24–72시간 재검증 시간과 같지 않습니다.

실제 기술 이름bounded active-time sum for six curriculum modules

3. SOURCE AUDIT PASS를 현재 Green이라고 말할 수 있나요?

답: 아니요. SOURCE AUDIT PASS는 PDF·package·display byte 폐쇄를 확인한 정적 결과이고 현재 상태는 W19 D1–D6 INTEGRATION NOT_RUN입니다.

근거: testsExecutedByAudit=0은 이 preview의 파생 표지입니다. source audit의 정확한 문구는 No Docker/Testcontainers integration execution is claimed by this source audit; deterministic test and SQL oracles are read from pinned source/seed.입니다. 공통 코어 · 트랙 선택 시 증권 기본이라는 트랙 표기도 실행 상태를 바꾸지 않습니다.

오답 함정: source hash가 맞거나 expected stdout이 문서에 있다는 이유만으로 learner runtime Green 또는 learner authorship을 주장하면 안 됩니다.

범위 한계: 실제 Green은 learner workspace에서 새 JUnit XML·native exit·source hash·evidence를 다시 확인해야 합니다.

실제 기술 이름byte-verified source closure versus unexecuted runtime evidence

4. D1의 두 starter는 왜 일부러 불완전한가요?

답: learner가 test 계약의 Red를 보고 직접 복구할 target과 제공된 source의 ownership을 분리하기 위해서입니다.

근거: RequestIdFilter starter는 header를 검증 없이 반사하고 MDC lifecycle이 없으며, SensitiveDataMasker starter는 raw 식별자를 그대로 반환합니다.

오답 함정: starter 설치 marker를 기능 완료나 secure implementation으로 읽으면 intentional Red의 목적이 사라집니다.

범위 한계: starter source block은 그 자체로 실행 결과가 아니고, D3 전 solution을 복사해서도 learner-authored 증거가 되지 않습니다.

실제 기술 이름intentional Red starter with learner-owned production targets

ReferenceRoot의 제공 테스트와 닫힌 솔루션을 bridge를 거쳐 LearnerRoot의 두 intentional starter와 분리한 지도
그림 한눈에: 제공 test가 있는 ReferenceRoot와 학습자가 고칠 starter 두 개가 있는 LearnerRoot를 분리합니다.

5. ReferenceRoot와 LearnerRoot를 왜 다른 절대경로로 두나요?

답: 정본 reference를 읽는 작업과 learner가 직접 입력한 결과를 파일·hash·ownership 수준에서 구분하기 위해서입니다.

근거: D1 Gate는 두 root가 다른지, 두 starter가 learner 상대경로에 설치됐는지, 제공 test와 target ownership이 evidence에 기록됐는지를 봅니다.

오답 함정: 같은 폴더를 두 변수에 넣으면 reference 복사와 learner 작성이 구분되지 않고 target hash도 저작 증거가 될 수 없습니다.

범위 한계: 경로가 다르다는 사실만으로 learner가 source를 직접 썼거나 기능이 Green이라는 뜻은 아닙니다.

실제 기술 이름reference-versus-learner workspace isolation

6. bridge-starter.json은 무엇을 증명하나요?

답: 실제 D1 실행에서 제공 test와 intentional starter 두 target의 설치·hash·ownership을 기록하는 주 evidence입니다.

근거: expected output은 W19 Starter; provided tests + intentionally incomplete targets installed이고 주 경로는 evidence/w19/bridge-starter.json입니다.

오답 함정: 파일 이름만 존재하거나 문서의 expected 문자열을 복사한 것을 새 실행 evidence로 세면 안 됩니다.

범위 한계: 이 미리보기는 해당 Gate를 실행하지 않았으므로 evidence의 현재 내용·시각·native exit를 보증하지 않습니다.

실제 기술 이름phase-specific starter provenance artifact

7. D2에서 읽는 test source와 @Test 수는 어떻게 되나요?

답: RequestIdFilterTest, AuditPropagationIT, MaskingTest 세 source이며 source-local @Test 5개입니다.

근거: requestId 두 test, denied audit 한 test, masking 두 test가 인쇄돼 있습니다. 세 파일의 역할은 모두 PROVIDED_TEST_CONTRACT입니다.

오답 함정: source-local test 다섯 개를 이번 audit에서 실행한 test 다섯 개라고 읽으면 testsExecutedByAudit=0과 충돌합니다.

범위 한계: 다섯 test는 각 assertion의 좁은 계약만 증명하며 보안 정책 전체를 증명하지 않습니다.

실제 기술 이름provided test inventory with five source-local methods

8. intentional Red와 우연한 compile 실패는 어떻게 구분하나요?

답: 지정된 두 assertion marker에 실제로 도달해 실패하고 tests>0·native_exit!=0인지를 함께 봅니다.

근거: 전체 expected 문구는 W19 LearnerRed; native_exit!=0; exact markers=validRequestIdIsReturnedStoredForTheRequestAndRemovedFromMdc;accountNumberKeepsAtMostTheLastFourCharacters입니다.

오답 함정: import 오류·문법 오류·test 0건 같은 조기 실패를 Red success로 세면 test 계약의 누락 동작을 관찰하지 못합니다.

범위 한계: expected marker를 문서에서 읽은 것과 bridge-recordred.json 및 JUnit XML을 새로 만든 것은 다릅니다.

실제 기술 이름assertion-reached intentional Red gate

테스트가 실행되어 exact assertion 두 개에 도달한 intentional Red와 compile import test zero 사고를 나눈 흐름
그림 한눈에: 지정 assertion Red는 evidence로 가고, compile accident와 test 0건은 복구 대상으로 빠집니다.

9. AuditPropagationIT가 D2 learner Red target이 아닌 이유는 무엇인가요?

답: denied-audit integration test와 구현은 제공 fixture 계약이고 learner가 직접 고치는 target은 두 production file뿐이기 때문입니다.

근거: audit 쪽 ownership은 PROVIDED_TEST_CONTRACT와 이후 PROVIDED_FIXTURE_VERIFIED로 기록되고, learner target은 RequestIdFilter와 SensitiveDataMasker입니다.

오답 함정: 제공 integration fixture가 통과했다는 사실을 learner가 audit subsystem 전체를 작성했다는 증거로 바꾸면 안 됩니다.

범위 한계: 제공 fixture 여부와 test 실행 Green 여부도 별개의 열입니다. 둘 다 실제 evidence로 확인해야 합니다.

실제 기술 이름provided fixture ownership boundary

10. test contract를 먼저 읽을 때 무엇을 추출해야 하나요?

답: Given 입력, When 호출, Then 관찰값과 cleanup, 그리고 test가 직접 보장하지 않는 반례를 추출합니다.

근거: requestId test는 response·attribute·MDC inside·MDC after를, masking test는 exact 문자열을, audit test는 403과 같은 requestId의 한 event를 봅니다.

오답 함정: class 이름이나 성공 문구만 외우고 assertion을 읽지 않으면 Green이 무엇을 뜻하는지 설명할 수 없습니다.

범위 한계: missing header, 1·64자 경계, chain exception cleanup, async MDC, success audit 등은 인쇄 test 일부에서 직접 확인되지 않습니다.

실제 기술 이름Given-When-Then observable contract extraction

11. D3에서 안전한 requestId와 위험한 requestId는 어떻게 갈라지나요?

답: [A-Za-z0-9._:-]{1,64} allowlist에 맞는 값만 echo하고, 누락·invalid 값은 서버가 36자 UUID로 교체합니다.

근거: req-w19-1은 보존되고 공백·개행이 든 입력은 response에 반사되지 않아야 합니다.

오답 함정: client가 준 값을 trim만 하거나 오류 body·log에 원문을 되돌려 주면 header injection과 정보 노출 경계가 무너집니다.

범위 한계: UUID regex가 36자 형식을 확인해도 uniqueness·entropy·인증 신뢰성을 증명하지는 않습니다.

실제 기술 이름request-ID allowlist and UUID replacement

X Request Id 입력을 안전한 allowlist 값은 보존하고 누락 공백 개행 길이초과 값은 UUID로 교체하는 분기
그림 한눈에: 안전한 ID는 그대로 흐르고, 누락·공백·개행·길이초과 값은 UUID로 갈라집니다.

12. 하나의 requestId는 어느 위치를 지나고 어디서 정리되나요?

답: 선택된 ID가 request attribute, response header, MDC에 들어가고 chain 종료 뒤 finally에서 MDC key가 제거됩니다.

근거: valid test는 세 위치에서 req-w19-1을 보고, filter 호출이 끝난 뒤 MDC.get("requestId")가 NULL인지 확인합니다.

오답 함정: 정상 return 뒤에만 cleanup을 두면 downstream exception 경로에서 worker thread에 이전 ID가 남을 수 있습니다.

범위 한계: 현재 solution은 기존 MDC 값을 복원하지 않고 제거하며 async thread로 context를 자동 전파하지 않습니다.

실제 기술 이름request correlation lifecycle with finally cleanup

header에서 선택한 requestId가 request attribute response header MDC를 지나 finally remove되는 lifecycle
그림 한눈에: requestId가 세 관찰 위치로 이어지고 다음 요청 전에 MDC에서 제거됩니다.

13. requestId를 인증이나 권한 판단에 써도 되나요?

답: 안 됩니다. requestId는 고객 응답·log·audit를 같은 사건으로 연결하는 correlation 식별자입니다.

근거: 외부 값은 allowlist로 검증하거나 교체하지만, 사용자의 principal·소유권·권한은 별도 보안 계층이 판단합니다.

오답 함정: 추적 가능하다는 사실을 신뢰 가능하다는 뜻으로 바꾸면 공격자가 고른 ID를 권한 근거로 쓸 수 있습니다.

범위 한계: 이 주차는 authentication token 발급·서명 검증·세션 보안을 구현하는 범위가 아닙니다.

실제 기술 이름correlation identifier versus authorization credential

14. exact masking 결과와 남는 위험은 무엇인가요?

답: 123-456-7890→****7890, null→*, 12→**12, ACCOUNT/123456→ACCOUNT:**3456이 exact oracle입니다.

근거: ASCII 영숫자만 남긴 뒤 최대 마지막 네 글자를 보이고 최소 네 별표를 붙입니다.

오답 함정: 짧은 값 12는 별표 뒤에 원문 전체가 남고, Unicode·구두점 정규화는 서로 다른 입력을 같은 표시값으로 만들 수 있습니다.

범위 한계: resource type은 별도 sanitize 없이 prefix에 붙으며 마스킹은 암호화·인가·token 보호를 대신하지 않습니다.

실제 기술 이름deterministic suffix masking with normalization limits

긴 계좌번호 null 짧은 값 resource id의 정확한 마스킹 결과와 짧은 원문 Unicode collision type 미검증 경계
그림 한눈에: exact 네 결과와 short-value disclosure·Unicode collision·type trust 경계를 한 표에서 봅니다.

15. Q29 illustrative SQL은 어떤 가정과 oracle을 사용하나요?

답: 모든 SUCCESS business_tx amount를 business_date별로 합치고 전 달력일 집계와 self join해 차이를 계산합니다.

근거: OPENING·REVERSAL도 포함하며 2026-07-01은 total 1500, previous 2129999, difference -2128499입니다. 2026-12-29는 전일 SUCCESS 합이 없어 previous/difference가 NULL입니다. 추가 oracle은 2026-12-30 difference=1; 2026-12-31 difference=900000; 2027-01-01 difference=-999500; 2027-01-02 difference=700이고 축약하면 2026-12-30=1, 12-31=900000, 2027-01-01=-999500, 01-02=700입니다.

Q29 remaining vector: 2026-12-30 difference=1; 2026-12-31 difference=900000; 2027-01-01 difference=-999500; 2027-01-02 difference=700

오답 함정: business_date - 1을 휴일을 건너뛰는 실제 업무일 calendar라고 부르거나 missing day를 0으로 바꾸면 예시의 가정이 달라집니다.

범위 한계: Q29는 학습용 예시 · 정본 답안 아님이며 status·tx_type·amount sign·holiday 정책을 prompt가 완전히 고정하지 않았습니다.

실제 기술 이름daily SUCCESS aggregation with prior-calendar-day self join

SUCCESS 일별 집계를 전 달력일과 self join해 2026년 7월 1일 차이와 12월 29일 NULL을 보여 주는 도식
그림 한눈에: 오늘 합계가 전 달력일 합계와 만나고 missing prior SUCCESS date는 NULL로 남습니다.

16. D4 integrated Green의 최소 실행 조건은 무엇인가요?

답: RequestIdFilterTest, MaskingTest, AuditPropagationIT 세 selector가 tests>0이고 failures·errors·skipped=0인 새 JUnit XML과 native_exit=0을 가져야 합니다.

근거: RecordGreen Gate는 bridge-recordgreen.json, 두 learner target hash, source_mode, provided audit ownership을 함께 기록합니다.

오답 함정: 예전 state 파일이나 예상 문자열만 읽고 새 selector 실행 없이 integrated Green이라고 쓰면 안 됩니다.

범위 한계: 이 미리보기의 source audit은 해당 세 selector를 Docker/Testcontainers 환경에서 실행하지 않았습니다.

실제 기술 이름three-selector integrated Green evidence contract

17. Green status와 learner authorship은 어떻게 분리하나요?

답: 두 target을 learner가 Red 뒤 직접 입력한 LEARNER_TYPED 경로만 LearnerGreen authorship 후보이고, provided audit fixture는 별도 ownership입니다.

근거: 전체 expected 결과는 W19 LearnerGreen for learner-typed path OR VerifiedGreen source_mode=QA_OVERLAY for recovery path로 두 경로를 구분합니다.

오답 함정: QA_OVERLAY ≠ learner-authored Green입니다. 복구 solution을 사용한 통과를 포트폴리오 저작 증거로 바꾸면 안 됩니다.

범위 한계: 직접 입력했다는 주장도 target hash·phase evidence·workspace 분리 없이 자동 입증되지 않습니다.

실제 기술 이름execution-status and source-ownership matrix

RequestIdFilter Masking AuditPropagation 세 selector 실행 열과 두 learner target 및 제공 audit fixture ownership 열의 행렬
그림 한눈에: 세 selector의 Green 열과 learner 두 target·provided audit의 ownership 열을 나눕니다.

18. denied audit의 고정 관찰값은 무엇인가요?

답: customer-2의 타인 계좌 GET은 403이고, audit-denied-1로 찾은 event는 한 행이며 actor customer-2, result DENIED, error ACCESS_DENIED입니다.

근거: masked resource는 ACCOUNT:로 시작하지만 ACCOUNT:<원래 account id> 전체와 같지 않고 같은 requestId를 보존합니다.

오답 함정: 업무 요청이 rollback됐으니 audit도 0행이어야 한다고 생각하거나 actor를 account owner customer-1로 기록하면 사건 의미가 바뀝니다.

범위 한계: 한 denied case는 success audit·중복 방지·retention·tamper resistance를 증명하지 않습니다.

실제 기술 이름rejected-request audit propagation with masked resource

customer two의 타인 계좌 조회가 403으로 rollback되고 같은 requestId의 masked denied audit 한 행이 남는 흐름
그림 한눈에: 403 응답과 별도 DENIED audit 한 행이 같은 requestId로 이어집니다.

19. AuditPropagationIT 하나로 무엇까지 말할 수 없나요?

답: 관찰한 한 denied path 밖의 exactly-once, success·failure 전 경로, 보관·삭제, 위변조 방지, audit 저장 장애 정책은 말할 수 없습니다.

근거: test는 event size 1, actor/result/error/requestId, masked resource의 prefix와 원문 불일치만 직접 assertion합니다.

오답 함정: integration test라는 이름만 보고 audit subsystem의 모든 품질 속성이 검증됐다고 확대하면 안 됩니다.

범위 한계: Testcontainers PostgreSQL과 전체 Spring context가 필요한 실행 여부 자체도 이번 audit에서는 확인하지 않았습니다.

실제 기술 이름partial integration-test oracle boundary

20. D5 masking 회귀의 exact selector와 결과 계약은 무엇인가요?

답: selector는 com.example.financialcore.security.MaskingTest 하나이고 실제 실행 시 classes>=1, tests>=1, failures/errors/skipped=0, native_exit=0이어야 합니다.

근거: 주 evidence는 evidence/w19/day-5-selector-gate.json이며 exact masking 문자열과 source hash를 다시 연결합니다.

오답 함정: state 조회나 오래된 XML, 반대 문자열 결과를 새 mask regression Green으로 세면 안 됩니다.

범위 한계: 이 selector는 credential 저장소·Git history·Authorization·Cookie·password·token 전체를 검사하지 않습니다.

실제 기술 이름single-selector masking regression gate

21. secret scan과 credential rotation은 masking test와 어떻게 다른가요?

답: secret scan은 key·token·password 패턴을 찾는 검사이고 rotation은 노출 가능성이 있는 비밀을 폐기하고 새 값으로 교체하는 절차입니다.

근거: D5는 오늘 새로 쓴 파일의 scan 기록을 남기되 자동 검증 핵심은 SensitiveDataMasker의 원문 비노출 계약으로 제한합니다.

오답 함정: scan report가 깨끗하다고 과거 Git 이력까지 깨끗하다고 말하거나 실제 credential을 마스킹만 하고 계속 쓰면 안 됩니다.

범위 한계: 전체 history scan과 외부 vault·provider의 revoke 상태는 별도 수동·운영 evidence가 필요합니다.

실제 기술 이름secret detection versus credential revocation and replacement

22. 짧은 값과 Unicode 입력에서 masking 정책을 다시 봐야 하는 이유는 무엇인가요?

답: 현재 알고리즘은 짧은 normalized value 전체를 suffix로 남기고 ASCII 밖 문자를 제거하므로 privacy·collision 정책이 별도로 필요합니다.

근거: 12→****12는 test 계약과 일치하지만 원문 두 글자는 그대로 보입니다. Unicode-only 입력은 normalized empty가 되어 서로 같은 표시가 될 수 있습니다.

오답 함정: 별표가 붙었다는 사실만으로 reversible risk·식별 collision·resource type injection이 모두 해결됐다고 생각하면 안 됩니다.

범위 한계: 마지막 네 글자를 허용할지, Unicode를 어떻게 정규화할지는 조직·도메인 privacy owner가 정해야 합니다.

실제 기술 이름short-value disclosure and normalization-collision risk

23. Q30 illustrative SQL은 어떤 grain·정렬·oracle을 사용하나요?

답: account 한 행을 output grain으로 두고 LEFT JOIN LATERAL로 direct account_id의 최근 SUCCESS 한 건을 찾습니다.

근거: 동률은 occurred_at DESC, tx_id DESC이며 8행을 반환합니다. account101은 ...212, 103·104는 NULL, 105는 ...210입니다. 나머지는 102→...102 amount 20000; 106→...106 amount499999; 107→...107 amount500000; 108→...108 amount1000000이고 모두 SUCCESS OPENING입니다. 축약 없는 묶음은 account 102 ...102 amount20000, 106 ...106 amount499999,107 ...107 amount500000,108 ...108 amount1000000 all SUCCESS OPENING입니다.

Q30 full oracle: account101=...212/600; account102=...102/20000; account103=NULL; account104=NULL; account105=...210/1000000; account106=...106/499999; account107=...107/500000; account108=...108/1000000

오답 함정: counterparty_account_id 참여를 포함하거나 INNER LATERAL로 0건 account를 지우거나 timestamp만 정렬하면 결과 계약이 달라집니다.

범위 한계: SUCCESS의 OPENING·REVERSAL 포함과 0건 account 보존은 prompt가 고정하지 않아 예시가 공개한 가정입니다.

실제 기술 이름per-account latest-row LEFT LATERAL with total order

account를 grain으로 LEFT LATERAL 최신 성공 거래를 찾고 account 101 103 104 105 oracle을 보여 주는 도식
그림 한눈에: 계좌를 보존한 채 최신 SUCCESS 한 건을 고르고 동률은 tx_id로 끊습니다.

24. Q29·Q30을 정본 답안이라고 부르면 안 되는 이유는 무엇인가요?

답: workbook에 shipped canonical answer가 없고 두 SQL은 pinned schema·seed와 명시한 가정으로 만든 illustrative 자료이기 때문입니다.

근거: 두 파일 모두 canonicalAnswerShipped=false이며 status·tx_type·calendar·counterparty·zero-row 정책의 prompt gap을 주석으로 공개합니다.

오답 함정: oracle 숫자가 맞는다는 이유로 업무 요구사항 전체가 확정됐거나 learner 제출 정답이라고 부르면 안 됩니다.

범위 한계: 실제 과제에서는 requirement owner가 정책을 정하고 learner가 자기 SQL과 실행 결과를 evidence로 남겨야 합니다.

실제 기술 이름noncanonical illustrative SQL with explicit assumptions

25. D6에서 실제로 실행하도록 지정된 exact selector 두 개는 무엇인가요?

답: RequestIdFilterTestApiExceptionHandlerTest 두 개입니다.

근거: D6 contract는 valid ID echo, missing/invalid ID server generation, MDC cleanup, 외부 body의 SQL·stack·exception text 노출 0을 확인합니다. exact 성공 형식은 EXACT_SELECTOR_GREEN selectors=2 classes>=1 tests>=1 failures=0 errors=0 skipped=0 native_exit=0입니다.

오답 함정: D4의 세-selector 목록을 그대로 D6에 재사용하거나 MaskingTest를 D6 exact selector에 넣으면 원문 명세와 달라집니다.

범위 한계: 두 selector Green만으로 전체 log injection, 모든 exception mapping, async context propagation을 증명하지 않습니다.

실제 기술 이름two-selector request-correlation and ApiError regression

26. D6 selector 2개와 displayed source 3개는 왜 서로 다르나요?

답: selector는 RequestIdFilterTest·ApiExceptionHandlerTest지만 PDF가 D6에 전문으로 보여 주는 source는 RequestIdFilterTest·AuditPropagationIT·MaskingTest 세 개입니다.

근거: ApiExceptionHandlerTest는 selector dependency인데 full source listing이 없고, displayed audit·masking test는 D6 selector가 아닙니다.

오답 함정: “2개 selector”와 “3개 displayed source”를 같은 집합의 다른 표현으로 읽으면 실제 실행 대상과 인쇄 source가 뒤바뀝니다.

범위 한계: source가 인쇄되지 않았다는 사실은 test가 없다는 뜻이 아니며, 반대로 source가 인쇄됐다고 D6에서 실행됐다는 뜻도 아닙니다.

실제 기술 이름selector-to-displayed-source set mismatch

외부 ApiError의 error code safe message request id와 내부 SQL exception stack trace 사이의 disclosure boundary
그림 한눈에: 실행 selector와 인쇄 source를 구분하면서 외부 ApiError의 공개 필드와 내부 진단 경계를 봅니다.

27. ApiError에 공개할 값과 숨길 값은 무엇인가요?

답: 외부 body에는 stable errorCode·message·requestId만 공개하고 SQL text·exception class/message·stack trace 같은 내부 정보는 노출하지 않습니다.

근거: p638 상단 D6 최종 Gate는 ApiError의 SQL·exception text 비노출과 전체 log-injection 방어를 검증했다고 과장하지 않았는지 확인합니다.

오답 함정: 디버깅을 쉽게 하려고 내부 exception 원문을 고객 body에 넣거나 token scan 하나를 전체 방어 proof로 부르면 안 됩니다.

범위 한계: 고객 body 비노출과 내부 log encoding·retention·access control은 서로 다른 보안 책임입니다.

실제 기술 이름stable public error envelope and information-disclosure boundary

28. modern 코드 인벤토리와 최종 검토 표찰은 어떻게 읽나요?

답: display closure는 canonical 7개와 illustrative 2개입니다. canonical은 Red starter 2, test source 3, Green solution 2이고 illustrative는 Q29·Q30입니다. aggregate vector 9/237/208/45/163/208/31/5의 semantic labels는 teaching units / physical lines / nonblank lines / setup lines / mapping rows / translation rows / source chunks / source-local tests입니다.

source aggregate: 9/237/208/45/163/208/31/5

근거: source-local @Test 5개는 인쇄 source inventory이고 실행 수는 0입니다. SOURCE AUDIT PASS, No Docker/Testcontainers integration execution is claimed by this source audit; deterministic test and SQL oracles are read from pinned source/seed., HUMAN_SEMANTIC_REVIEW_REQUIRED를 함께 읽어야 합니다.

오답 함정: 9개 display item을 learner-authored 9개로 세거나 static HTML QA를 Docker/Testcontainers integration Green으로 바꾸면 안 됩니다.

범위 한계: source audit은 byte·분류·display closure를 보증하지만 현재 learner workspace의 실행·authorship·업무 의미는 새 evidence와 사람 검토가 필요합니다.

실제 기술 이름canonical-versus-illustrative provenance with semantic review boundary