docs(TechLog): 글감 56개를 기록으로 쓴다

주제 13개 · Case 28 · Concept 5 · Reference 15 · Question 4 · Decision 4.
계약의 노드마다 종류가 요구하는 칸을 채우고, 본문이 있는 두 종류에는 SSOT 가 이미
그려 둔 도식 셋(value-boundaries · decision-path-404 · topic-variant-model)을
tech-log-studio/ 로 옮겨 붙였다. 새로 그린 그림은 없다.

검사 셋 전부 통과한다.
  check_body.mjs      56 편 중 본문이 있는 33 편 PASS
  check_prose.mjs     56 편 error 0
  check_evidence.mjs  --repo 포함 문제 없음
  verify-tech-log-tree.py  프로젝트 5 · error 0 · warn 0

인용한 코드블록은 전부 SSOT 에서 찾아 대조했다. check_evidence.mjs 가 본문의 각 줄과
source 앵커와 계약 제목을 다시 확인한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 15:29:33 +09:00
co-authored by Claude Opus 5
parent 6955611439
commit f6c825e858
62 changed files with 6381 additions and 186 deletions
@@ -0,0 +1,86 @@
---
kind: CASE
slug: it-said-there-were-no-open-questions
title: 「이 프로젝트에 열린 질문이 없습니다」 — 실제로는 넷이 있었다
topic: failure-drawn-as-absence
topicName: 실패를 없음으로 그린다
project: TechLog
status: 게시 전
lastVerifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§10.1
---
# 「이 프로젝트에 열린 질문이 없습니다」 — 실제로는 넷이 있었다
홈 「지금 집중하는 것」 편집기가 「이 프로젝트에 열린 질문이 없습니다」라고 적었다. 실제로는 넷이 있었고 공개 사이트에도 나오고 있었다. 편집기가 질문과 결정을 못 읽으면 빈 배열로 삼키고 있었다.
## 관계
- **화면은 못 읽은 것을 없다고 말하지 않는다**
이 사건에서 굳힌 규칙이다.
- **계약에 선언만 있고 구현이 없어 화면 다섯 곳이 비어 있었다**
못 읽은 원인이 그 기록에 있다 — 컨트롤러가 없었다.
- **한 칸의 실패가 옆 칸을 끌고 내려갔다**
같은 편집기에서 난 다른 부류의 실패다.
## 문제
홈 편집기에서 「지금 집중하는 것」에 걸 질문을 고르려 했다. 목록이 비어 있고 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있었다.
같은 프로젝트의 공개 사이트에는 질문 넷이 나오고 있었다.
## 결론
편집기가 질문과 결정 목록을 못 읽으면 빈 배열로 삼키고 있었다. 서버는 404 를 주고 있었다 — 그 두 연산에 컨트롤러가 없었다.
작성자에게는 「아직 안 쓴 것」으로 읽힌다. 실제로는 쓴 것을 못 읽은 것이다.
> 거짓말을 하느니 못 읽었다고 말한다.
못 읽었을 때 못 읽었다고 적게 고쳤다. 같은 판단을 주제 탭에도 적용했다 — 탭 하나를 못 받아도 탭 줄과 나머지는 그대로 남고, 못 받은 탭에는 못 받았다고 적는다.
## 검증 환경
tech-log-frontend : 7acde27 · 3bb724b
tech-log-backend : 911e8ba — 없던 두 컨트롤러를 구현
확인 방식 : 편집기 목록과 공개 사이트의 같은 프로젝트를 대조
## 재현 조건
1. 프로젝트에 Open Question 을 하나 이상 게시한다
2. 그 목록을 주는 연산을 서버에서 막는다
3. 홈 편집기를 연다 — 옛 코드에서는 「없습니다」가, 지금은 못 읽었다는 문구가 나온다
## 본문
<!-- body:start -->
## 빈 배열이 두 가지를 뜻했다
편집기는 질문 목록을 받아 고를 수 있게 그린다. 목록이 비면 「이 프로젝트에 열린 질문이 없습니다」를 적는다.
요청이 실패했을 때도 빈 배열이 되고 있었다. 그래서 「없다」와 「못 읽었다」가 같은 화면이 됐다.
## 작성자가 무엇으로 읽었나
작성자는 자기가 쓴 것과 화면을 대조한다. 화면이 「없습니다」라고 하면 아직 안 썼거나 게시하지 않았다고 읽는다.
이 경우에는 넷이 있었고 공개 사이트에도 나오고 있었다. 편집기만 못 읽고 있었다.
## 못 읽었다고 적는다
> 거짓말을 하느니 못 읽었다고 말한다.
요청이 실패하면 실패했다고 적는다. 0건은 0건이라고 적는다. 이 둘을 구분할 수 있어야 작성자가 다음에 무엇을 할지 정할 수 있다.
## 같은 판단을 다른 화면에
주제 탭에도 같은 판단을 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그려지고, 못 받은 탭에는 못 받았다고 적는다.
## 확인하지 못한 것
화면이 못 읽은 것을 없다고 그리는 곳을 전수로 세지 않았다. 고친 것은 홈 편집기와 주제 탭 둘이다.
<!-- body:end -->
@@ -0,0 +1,79 @@
---
kind: CASE
slug: one-cell-failing-took-its-neighbour-down
title: 한 칸의 실패가 옆 칸을 끌고 내려갔다
topic: failure-drawn-as-absence
topicName: 실패를 없음으로 그린다
project: TechLog
status: 게시 전
lastVerifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§10.2
---
# 한 칸의 실패가 옆 칸을 끌고 내려갔다
편집기가 질문과 결정을 하나로 묶어 읽고 있었다. 결정만 터지는데 멀쩡히 오던 질문 목록까지 「불러오지 못했습니다」가 됐다. 더 미묘한 변종도 있었다 — 호출이 동기적으로 던지면 묶는 중에 터져 거절 처리를 지나지도 못한다.
## 관계
- **화면은 못 읽은 것을 없다고 말하지 않는다**
같은 편집기에서 나온 짝이 되는 규칙이다.
- **「이 프로젝트에 열린 질문이 없습니다」 — 실제로는 넷이 있었다**
같은 편집기의 다른 부류 실패다.
- **홈의 비교 구역이 세 번 바뀌었다**
같은 판단을 주제 탭에 적용한 기록이다.
## 문제
홈 편집기가 질문과 결정을 한 번에 읽는다. 결정 쪽만 실패해도 화면 전체가 「불러오지 못했습니다」가 됐다.
질문 목록은 정상적으로 오고 있었다.
## 결론
두 요청을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 둘을 따로 읽도록 갈랐다.
더 미묘한 변종이 하나 더 있었다.
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
같은 판단을 주제 탭에도 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지는 그대로 남고, 못 받은 탭에는 못 받았다고 적는다.
## 검증 환경
tech-log-frontend : 6e784ed · fd73bc8 · 3bb724b
확인 방식 : 한쪽 연산만 실패시키고 나머지가 그려지는지 확인
## 재현 조건
1. 편집기가 부르는 두 연산 중 하나만 실패시킨다
2. 화면에서 나머지 하나가 그려지는지 본다
3. 실패하는 쪽을 동기적으로 던지게 바꾸고 다시 본다
## 본문
<!-- body:start -->
## 묶어 읽으면 한쪽이 전체를 끌고 내려간다
편집기가 질문 목록과 결정 목록을 하나로 묶어 기다리고 있었다. 결정 쪽 연산에 컨트롤러가 없어 404 가 났고, 화면은 두 목록을 다 못 받은 것으로 그렸다.
질문 목록은 정상적으로 오고 있었다. 둘을 따로 읽도록 갈랐다.
## 동기적으로 던지면 거절 처리를 지나지 않는다
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
거절과 던짐이 다른 경로를 탄다. 배열을 만드는 표현식 안에서 던지면 그 표현식이 완성되지 않으므로 거절 처리기가 붙을 대상이 없다.
## 탭에도 같은 판단을
탭 줄은 목록 하나로 그리고 상세는 고른 탭만 받는다. 상세 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
## 확인하지 못한 것
같은 모양으로 묶어 읽는 곳을 전수로 세지 않았다. 갈라 놓은 것은 홈 편집기와 주제 탭 둘이다.
<!-- body:end -->
@@ -0,0 +1,83 @@
---
kind: CASE
slug: records-disappeared-without-a-trace
title: 매퍼가 null 을 돌려주고 호출부가 걸러 내, 기록이 조용히 사라졌다
topic: failure-drawn-as-absence
topicName: 실패를 없음으로 그린다
project: TechLog
status: 게시 전
lastVerifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§10.6
---
# 매퍼가 null 을 돌려주고 호출부가 걸러 내, 기록이 조용히 사라졌다
프로젝트 기록 목록에서 Open Question 이 보이지 않았다. 이 목록은 탐색의 지식 목록과 응답 모양이 다른데 그쪽 매퍼를 그대로 쓰고 있었다. 그 매퍼는 두 종류가 아니면 `null` 을 돌려주고 호출부가 걸러 내므로, 질문과 개념은 오류도 빈 줄도 남기지 않고 사라진다.
## 관계
- **화면은 못 읽은 것을 없다고 말하지 않는다**
같은 부류를 다루는 규칙이다.
- **개념을 하나 더하자 열세 곳이 그것을 조용히 삼켰다**
이 매퍼가 그 열세 곳 중 하나다.
- **합성 루트에 테스트가 없어 공개 사이트 전체가 오류 화면이었다**
스텁 때문에 매핑이 검사되지 않던 다른 사건이다.
## 문제
프로젝트 화면의 기록 목록에 CASE 와 REFERENCE 만 나왔다. 같은 프로젝트에 게시된 질문과 개념이 목록에 없었다.
오류는 없었다. 목록이 한 줄 짧아질 뿐이라 눈으로는 알아채기 어렵다.
## 결론
프로젝트 기록 목록은 탐색의 지식 목록과 응답 모양이 다른데 지식 목록의 매퍼를 그대로 쓰고 있었다.
그 매퍼는 CASE 나 REFERENCE 가 아니면 `null` 을 돌려준다. 호출부가 `filter` 로 걸러 내므로 그 항목은 목록에서 없어진다.
증상 : 목록이 한 줄 짧아진다
오류 : 없음
빈 줄 : 없음
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 고쳤다. 요약과 주제와 게시일도 함께 실었다.
## 검증 환경
tech-log-frontend : 77125d1
tech-log-backend : f0407d9 — 목록이 요약·주제·게시일을 싣게 함
tech-log-design-package : 76a7ccb
확인 방식 : 프로젝트에 종류별로 기록을 게시하고 목록에 전부 나오는지 확인
## 재현 조건
1. 한 프로젝트에 CASE·REFERENCE·QUESTION·CONCEPT 을 각각 하나씩 게시한다
2. 프로젝트 화면의 기록 목록을 연다
3. 네 종류가 다 나오는지 센다
## 본문
<!-- body:start -->
## null 을 돌려주고 걸러 내면 흔적이 없다
매퍼가 아는 종류가 아니면 `null` 을 돌려준다. 호출부는 그 목록에서 `null` 을 걸러 낸다.
이 조합에서는 오류가 나지 않고 빈 줄도 생기지 않는다. 목록의 길이만 줄어든다. 목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있다.
## 응답 모양이 다른 목록에 다른 매퍼를 썼다
프로젝트 기록 목록과 탐색의 지식 목록은 응답 모양이 다르다. 프로젝트 쪽은 관계 항목을 그대로 실어 요약도 주제도 게시일도 없었고, 지식 목록은 처음부터 그 칸들을 갖고 있었다.
모양이 다른데 매퍼를 공유하면서 종류 판정까지 그쪽 것을 따랐다.
## 고친 것
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 했다. 목록 항목에 요약과 주제와 게시일을 더해 「제목만 있고 가운뎃점만 남은」 줄을 없앴다.
## 확인하지 못한 것
`null` 을 돌려주고 호출부가 거르는 매퍼가 다른 목록에도 남아 있는지는 세지 않았다.
<!-- body:end -->
@@ -0,0 +1,60 @@
---
kind: REFERENCE
slug: an-expected-failure-must-not-be-counted-as-a-failure
title: 매번 우는 검사는 읽히지 않는다 — 기대된 실패는 조건을 적어 빼고 나머지는 전부 실패시킨다
topic: failure-drawn-as-absence
topicName: 실패를 없음으로 그린다
project: TechLog
status: 게시 전
verifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§10.5
---
# 매번 우는 검사는 읽히지 않는다 — 기대된 실패는 조건을 적어 빼고 나머지는 전부 실패시킨다
배포 뒤 전 화면을 훑는 스윕이 기대된 404 를 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았고, 그 줄은 곧 읽히지 않게 됐다. 기대된 실패는 조건을 적어 빼고 나머지는 전부 실패시킨다.
## 관계
- **화면은 못 읽은 것을 없다고 말하지 않는다**
같은 프로젝트에서 실패를 어떻게 다룰지 정한 짝이 되는 기준이다.
- **가드는 결함을 되돌려 실제로 멈추는 것을 확인한 뒤 커밋한다**
검사가 실제로 무엇을 잡는지 확인하는 기준이다.
- **계약은 앵커라고 적었고 만드는 쪽은 경로를 만들었다**
같은 스윕이 실제 결함을 잡아야 했던 사건이다.
## 목적
매번 우는 검사가 읽히지 않게 되는 것을 막는다. 진짜 실패가 그 옆에 앉아 있어도 아무도 보지 않는다.
## 규칙
**기대된 실패는 조건을 적어 뺀다**
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이런 응답은 실패가 아니다.
**뺀 나머지는 전부 실패시킨다**
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다.
**조건을 적을 수 없으면 빼지 않는다**
조건 없이 빼면 진짜 실패도 같이 빠진다.
**뺀 조건을 사람이 읽을 수 있는 곳에 남긴다**
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다.
## 적용 조건
배포 뒤 전 화면을 훑는 스윕처럼 결과를 사람이 훑어보는 검사. 정상 동작이 오류 상태 코드로 나타나는 화면이 있는 서비스에서 걸린다.
## 예외
기계가 판정하고 사람이 결과를 읽지 않는 검사라면 빨간 줄이 쌓여도 무뎌지지 않는다. 그래도 통과 기준은 정해야 한다.
## 예시
미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다.
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외하고, 나머지 4xx·5xx 는 전부 스윕을 실패시킨다.
@@ -0,0 +1,63 @@
---
kind: REFERENCE
slug: say-you-could-not-read-it
title: 화면은 못 읽은 것을 없다고 말하지 않는다
topic: failure-drawn-as-absence
topicName: 실패를 없음으로 그린다
project: TechLog
status: 게시 전
verifiedOn: 2026-09-04
sourceRevision: tech-log@2026-09-02
source:
- final/document.md#§10.1
- final/document.md#§17.3
---
# 화면은 못 읽은 것을 없다고 말하지 않는다
목록이나 요약처럼 「비어 있음」이 정상값인 화면에서는 실패와 0건이 같은 모양으로 그려진다. 작성자는 그것을 자기가 아직 쓰지 않은 것으로 읽는다. 못 읽었으면 못 읽었다고 적는다.
## 관계
- **「이 프로젝트에 열린 질문이 없습니다」 — 실제로는 넷이 있었다**
이 규칙의 근거 사건이다.
- **한 칸의 실패가 옆 칸을 끌고 내려갔다**
실패가 어디까지 번지는지를 다룬 사건이다.
- **매퍼가 null 을 돌려주고 호출부가 걸러 내, 기록이 조용히 사라졌다**
실패가 아니라 항목이 사라진 변종이다.
## 목적
작성자가 「아직 안 썼다」와 「못 읽었다」를 구분할 수 있게 한다. 이 둘이 같은 화면이면 작성자는 다음에 무엇을 할지 정할 수 없다.
## 규칙
**요청이 실패하면 실패했다고 적는다**
빈 배열로 삼키지 않는다. 0건과 실패는 다른 문구를 쓴다.
**한 칸의 실패가 옆 칸을 끌고 내려가지 않게 한다**
여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다.
**거절만 잡는 처리로는 부족하다**
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 함께 잡는다.
**항목을 걸러 낼 때 걸러 낸 것을 세어 둔다**
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다.
## 적용 조건
목록·요약·카운트처럼 「비어 있음」이 정상값이라 실패와 구분되지 않는 화면. 작성 도구에서 특히 걸린다 — 작성자가 자기 작업물과 화면을 대조하기 때문이다.
## 예외
정말로 0건인 것과 못 읽은 것을 구분할 수 없는 화면이라면 그 구분을 먼저 만든다. 구분 없이 문구만 바꾸면 0건이 실패로 읽힌다.
읽는 사람이 그 데이터를 만들지 않는 화면 — 공개 조회 — 에서는 실패를 화면 전체의 오류로 다뤄도 된다.
## 예시
「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다.
탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다.