aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/research/103-review-004-defects.md61
1 files changed, 61 insertions, 0 deletions
diff --git a/docs/research/103-review-004-defects.md b/docs/research/103-review-004-defects.md
new file mode 100644
index 0000000..3273ab3
--- /dev/null
+++ b/docs/research/103-review-004-defects.md
@@ -0,0 +1,61 @@
+# 103 — 외부 리뷰 004의 결함 일곱: 의미를 지키는 경계
+
+2026-09-29. [외부 리뷰 004](../outside-feedback/feedback-004.md)가 찾은 결함을 고친다. 데이터의 의미를 깨는 P1이 셋이고, 기능
+오류인 P2가 넷이다(리뷰가 새로 찾은 둘, 앞 리뷰에서 남은 둘). 리뷰는 다음 확장보다 이 경계를 먼저 고치라고 권했다.
+
+착수 기준:
+
+- **바꿀 결정.** 없다. 계약을 고치는 것이다. 최적화 전후에 결과가 같고, 실패 뒤 복구 상태가 같고, 스키마가 바뀐 뒤에도 메시지의
+ 뜻이 같아야 한다.
+- **최소 확인.** 리뷰의 재현 사례를 회귀 검사로 넣는다. 그 검사가 옛 코드에서 실패하고 새 코드에서 통과하는 것을 본다.
+- **중단 기준.** 고친 것이 기존 검사(Pure `make check`, Vega `make check`)를 하나라도 깨면 되돌린다.
+
+## 쉬운 말로
+
+- 알아낸 것:
+ - 일곱 모두 리뷰가 말한 그대로 재현됐고, 모두 고쳤다.
+ - 일곱 모두 검사가 있다. 옛 코드에서 실패하고 새 코드에서 통과한다.
+ - 두 저장소의 `make check`가 통과한다.
+- 공통점: 셋은 "암묵적인 것"을 세지 않은 데서 나왔다.
+ - F1: 삽입이 옮기는 index
+ - F3: 필드의 이름
+ - F4: outbox도 상태라는 것
+- 아직 하지 않은 것: 리뷰의 권고 가운데 앱 전체 메모리 예산, 실제 앱 하나를 키우는 일, 조건을 맞춘 성능 비교의 확대는 다음
+ 일이다(아래 "다음").
+
+## 한 것
+
+| | 결함 | 원인 | 고침 | 검사(옛 코드 → 새 코드) |
+|---|---|---|---|---|
+| F1 · P1 | 범위 최적화가 반복 결과를 바꾼다 | 반복 몸체의 쓰기에 삽입·삭제가 옮기는 index(와 텍스트 편집이 옮기는 커서)를 세지 않았다 | 그 상태도 쓴 것으로 센다. index를 옮기는 경계는 몸체가 어느 리스트도 바꾸지 않을 때만 범위를 쓴다 | `build/module-probe`가 범위를 쓴 실행과 안 쓴 실행을 비교한다. 리뷰의 앱: done 1 대 5로 다름 → 같음(10, 10) |
+| F2 · P1 | 트리만 바꾼 저장의 불확실성이 앱을 멈추지 않는다 | `uncertain` 표시가 리스트 로그가 있을 때만 섰다 | 트리 커밋이 불확실하면 늘 세운다 | test-module `uncertain_numbers`: 리스트만 바꾸는 다음 사건이 저장됨 → 거절됨 |
+| F3 · P1 | 출력 필드의 순서를 바꾸면 대기 메시지의 뜻이 바뀐다 | 교체와 재시작이 필드 개수와 타입만 비교했고, 저장된 links(PLK3)에 이름이 없었다 | 이름도 비교하고, links를 PLK4로 올려 필드 이름을 둔다. PLK3은 전처럼 타입만 본다 | test-module `output_meaning`: 교체와 재시작 모두 PURE_OK → CONFLICT |
+| F4 · P2 | 상태 없이 출력만 보내는 타이머가 돌지 않는다 | 타이머가 상태가 있는 앱에서만 돌았다 | 상태나 출력이 있으면 돈다 | check.py `server_timers`: 틱 0 → 22 |
+| F5 · P2 | Vega TUI가 나뉘어 온 방향키를 잃는다 | read 한 번 안에서 끝난 escape 시퀀스만 찾았다 | 끝나지 않은 시퀀스, 마우스 보고, UTF-8 문자는 다음 read까지 둔다. ESC 하나는 100 ms, 시작된 키는 1초까지 기다린다 | Vega check.py `interactive(cut=True)`: 초점이 안 움직여 엉뚱한 줄이 완료됨 → 옳게 움직임 |
+| 앞 리뷰 P2 | 입력 이름 edit/show가 문서 모드를 강제한다 | 입력 이름으로 문서 앱을 판정했다 | 모듈이 키로 여는 collection을 선언했을 때만 문서 앱이다 | check.py `server_plain_edit`: 시작 거부 → 시작, POST 적용 |
+| 앞 리뷰 P2 | 주기 타이머의 마감을 wall clock으로 셈한다 | Pure `run_timers`와 Vega `vega_timers_run`이 마감과 앱의 now를 같은 wall clock으로 셌다 | 마감은 monotonic clock으로 세고, 앱의 now만 wall clock으로 둔다 | `build/clock-back.so`(clock_gettime의 LD_PRELOAD)로 wall clock을 0.2초에 한 시간 되돌린다. Pure 틱 9에서 멈춤 → 29–39, Vega 9 → 40 |
+
+- **F1의 범위.** 범위를 쓰는 `where`에 들어올 수 있는 연산은 `has_lanes`가 정한다. 그래서 경계가 읽을 수 있는 상태는 수, index,
+ 지역 변수, 리스트 읽기, INDEX_MOVE(피연산자 index의 리스트 길이)뿐이다. 커서는 그 안에 들어오지 못하지만, 같은 규칙으로 함께
+ 센다. 차등 탐침은 범위가 여전히 붙는 반복도 함께 돌려(`ranged` 1), 최적화가 필요한 곳에서 꺼지지 않았음을 본다.
+- **F3의 옛 저장.** PLK3 저장에는 필드 이름이 없어 이름까지는 비교하지 못한다. 새 코드가 한 번 저장하면 PLK4가 된다. 저장을
+ 이어받은 뒤 필드를 재배치한 모듈로 바꾸는 경우만 여전히 타입까지만 본다.
+- **시계 검사의 방법.** 이 기기에서 시스템 시계를 바꿀 권한은 없고, 바꿔서도 안 된다. 그래서 `CLOCK_REALTIME`만 한 시간
+ 뒤로 돌리는 LD_PRELOAD 라이브러리를 썼다. Vega의 검사도 Pure가 만든 이 라이브러리를 쓴다(`make check`가 넘긴다).
+
+## 리뷰의 다른 지적
+
+- **micro-runtime의 전체 자원 한도.** 맞다. `--budget`은 입력과 반환의 payload admission만 묶는다. 앱의 리스트, outbox, 스냅숏,
+ 체크포인트까지 합친 한도와 넘칠 때의 정책(거절, 대기, 저장으로 넘김)은 아직 없다. 다음 일의 첫째다.
+- **실행 모델.** 맞다. aup 앱은 C로 쓴 실행기가 모듈 명령을 해석해 돈다. 성능과 코드 크기는 이 비용을 포함해 말한다.
+- **이벤트의 실행량.** 모듈이 유한해도 EACH와 fold의 일은 리스트 크기를 따른다. 앱별 시간 한도는 아직 없다.
+- **전송 비교.** 이 리뷰 뒤 M6를 ngtcp2와도 쟀다([102](102-adoption-and-spec.md)). 어느 네트워크에서나 QUIC보다 낫다고
+ 말하지 않는다. 102도 조건과 차이의 원인을 함께 적는다.
+
+## 다음
+
+리뷰의 순서를 따른다.
+
+1. 앱 전체 메모리 예산: 느린 수신자와 오래 열린 스냅숏이 있을 때 앱 전체 사용량을 재고, 넘칠 때의 정책을 정한다.
+2. 보드·메신저 계열의 실제 앱 하나를 키운다. 필드 추가와 재배치, 연결 중단, 재시작과 재동기화를 반복하며 작성 경험을 기록한다.
+3. 조건(저장 완료 기준, 지연, 메모리)을 먼저 정한 성능 비교를 넓힌다.