1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
# 108 — 경계에서 값이 그대로인가: 한 값, 모든 길
2026-09-30. [외부 리뷰 004](../outside-feedback/feedback-004.md)의 맺음말에서 시작한다. 리뷰는 "앱 코드와 저장·전달·화면
사이의 중복 상태와 변환을 실제로 줄인 것"을 가장 중요한 성과로 꼽고, "그 경계에서 의미가 유지되는지부터 고정하는 일"을 다음
연구의 기반으로 권했다.
변환은 줄었지만 없어지지는 않았다.
- 들어올 때: HTTP의 JSON 본문과 쿼리가 메시지의 필드가 된다(JSON 문자열의 이스케이프를 풀고, 수의 글자를 읽는다).
- 저장할 때: 상태가 저장 형식이 된다(위치는 varint, 2^53 미만의 정수는 zigzag varint, 그 밖의 수는 표시와 8바이트).
체크포인트는 그것을 다시 이미지로 쓴다.
- 보일 때: 스냅숏의 상태가 보기의 JSON이 된다(수는 가장 짧은 글자로, text는 JSON 문자열로).
- 보낼 때: 보냄이 outbox의 기록이 되고, 링크의 기록이 되고, 받는 쪽 입력의 배치로 옮겨진다(모양이 다르면 이름으로,
[014](../design/014-fields-by-name.md)).
변환마다 단위 검사는 있다. 그러나 한 값이 한 길로 들어가 다른 모든 길로 나올 때 같은지를 보는 검사는 없다.
착수 기준(검사를 짜기 전에 정해 커밋했다):
- **바꿀 결정.**
- 어떤 길이 값을 바꾸면(수의 비트, text의 바이트, 필드가 있고 없음) 다른 일보다 먼저 고친다.
- 형식이 싣지 못해 바꿀 수 없는 것은 계약으로 적는다.
- 경계마다의 계약을 설계 문서로 두고, 이 검사를 `make check`에 넣는다.
- **길.**
- 들어가는 길: HTTP의 JSON 본문, HTTP의 쿼리와 날 본문(text), 링크 전달, HTTP 전달.
- 나오는 길: 보기(연결 스레드가 스냅숏에서), 등록한 질의, 재시작(저장소의 로그에서), 체크포인트 뒤의 재시작(이미지에서),
모듈 교체(필드 순서를 바꾸고 u32를 number로 넓힌 모듈), 중계(A에서 B로 링크, B에서 C로 HTTP).
- **값.**
- 수: 0, −0, 2^53 부근, 가장 큰 수, 가장 작은 정규 수와 비정규 수, 1/3, 글자가 지수로 바뀌는 1e21 부근.
- u32: 0, 2^31, 2^32 − 1.
- text: 빈 것, 한글, 4바이트 UTF-8, 따옴표·역슬래시·제어 문자, NUL, U+2028, 4 KiB.
- 그룹 안의 필드.
- **통과 조건.**
- 받아들인 값은 모든 나오는 길에서 같아야 한다. 수는 비트까지(보기의 글자를 다시 읽은 binary64), text는 바이트까지.
- 거절할 값(넘치는 수, 음수·소수·2^32 이상의 u32, UTF-8이 아닌 text, 짝 없는 서로게이트)은 모든 들어가는 길이 거절해야
한다. 거절한 사건은 아무것도 남기지 않는다(보기에도, 보냄에도).
- **범위.** Pure의 경계다. 화면(Vega가 보기를 그리는 것)은 다음 단계다.
## 쉬운 말로
- **어긋난 곳은 하나였고, 고쳤다.** 입력 필드의 쿼리 값은 퍼센트 인코딩을 풀지 않았다. 같은 pure-serve가 보기의 인자는
풀었다. 그래서 파이썬의 `urlencode`나 브라우저처럼 지수의 `+`를 `%2B`로 보내는 클라이언트는 `?n=1e%2B21`이 거절됐다. 이제
입력 필드도 보기의 인자처럼 푼다.
- **0에는 부호가 없다는 것을 계약으로 적었다.** `-0`은 받아들이지만 보기에는 `0`으로 나온다. 글자로 쓰는 함수가 그렇게
정해져 있고, `distinct`도 둘을 한 번 센다. aup에는 둘을 가르는 연산이 없으니 −0은 aup의 별개 값이 아니다.
- **aup 문서의 예가 컴파일되지 않았다.** 문서는 `message { title: text, points: number }`처럼 선언의 필드를 쉼표로 적었는데,
컴파일러는 줄바꿈이나 세미콜론만 받았다. 값(`Kept { id: 1 }`)은 쉼표로 나누므로, 이제 선언도 쉼표를 받는다(aup `be1122a`).
- **나머지는 모든 길에서 같았다.**
- 수는 어려운 경우까지 파이썬의 정확한 반올림과 비트가 같았다(2^53+1의 짝수 반올림, `2.2250738585072011e-308`, 비정규 수,
반쯤에서의 언더플로).
- text는 NUL, 제어 문자, U+2028, BOM, 4 KiB까지 바이트가 같았다.
- 거절할 값은 JSON, 쿼리, 날 본문, 행이 모두 거절했다.
- 재시작, 체크포인트, 모듈 교체, 두 번의 중계를 지나도 같았다.
- **검사는 `make check`에 들어갔다.** `server_meaning`이 1.3초에 값 67개를 나가는 길 7개에서 보고, 거절할 값 39가지를 본다.
경계마다의 계약은 [015](../design/015-boundary-meaning.md)에 적었다.
## 한 것
1. **값을 하나씩 더듬었다.** 노드 하나에 경계값을 넣고 보기로 읽었다. 수 40가지(거절할 것 포함), text 25가지(서로게이트와
UTF-8이 아닌 바이트 포함), 쿼리와 날 본문 47가지다. 수는 파이썬의 `float()`이 기대값이다.
2. **어긋난 곳을 찾아 고쳤다.** 쿼리 값의 해독이 보기와 입력에서 달랐다(pure-serve `serve.c`의 `resolve`).
3. **한 번에 보는 검사를 짰다** ([`tests/check.py`](../../tests/check.py)의 `server_meaning`).
- A가 JSON(ASCII로 이스케이프한 것과 UTF-8), 행, 쿼리와 날 본문으로 받아 쥐고, 링크로 B에 보낸다.
- B는 필드 순서를 바꾸고 u32를 number로 넓힌 입력으로 받아 HTTP로 C에 보낸다.
- 나오는 길 일곱: A의 보기, A의 질의, B와 C의 보기, 재시작한 A, 체크포인트 뒤에 재시작한 A, 모듈을 바꾼 B. 그리고 `put`의
답은 들어온 text의 바이트다.
- A에서만 한 행을 4 KiB로 400번 고쳐, 목록의 이미지를 새로 쓰게 했다(체크포인트 1번).
4. **검사가 헛돌지 않는지 봤다.** 고치기 전의 pure-serve로 돌리면 쿼리 경로(`1E+2`를 `1E%2B2`로 보낸 것)에서 실패한다.
B가 받은 수에 1 ulp를 곱하게 앱을 바꾸면 B에서 잡힌다.
## 판정
- 통과 조건을 채웠다. 받아들인 값은 나가는 길 일곱에서 수는 비트까지(0의 부호만 빼고), text는 바이트까지 같다. 거절할 값은
모든 들어가는 길이 거절하고, 어디에도 남지 않는다.
- 착수 기준은 −0을 내다보지 못했다(수를 비트까지 비교한다고만 적었다). 0의 부호는 aup에서 뜻이 없으므로(가르는 연산이
없고, 글자와 `distinct`가 이미 같게 본다) 바꾸지 않고 계약으로 적었다(015). 검사는 0끼리를 같게 본다.
## 다음
- **화면.** Vega가 보기를 그리는 경계다. 터미널과 CLI와 문장이 같은 값을 어떻게 보이는지(제어 문자, U+2028, 넓은 글자를
그대로 보일지, 이스케이프할지)를 정하고 같은 방식으로 고정한다.
- bytes 필드는 목록의 레코드에 담기지 않으므로 여기서 보지 않았다. 문서 모델의 검사(`server_parity`)가 본다.
|