aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design/015-boundary-meaning.md
blob: e11ddc48a3402c665925eb54e79dfcc5f46d20ad (plain)
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 015 — 경계에서의 값: 들어온 값은 모든 길에서 같게 나온다

작성일: 2026-09-30. 상태: 구현, 검사로 고정. [108](../research/108-one-value-every-path.md)에서 정했다.

앱의 값은 여러 경계를 지난다. 들어올 때 HTTP의 JSON이나 쿼리에서 메시지의 필드가 되고, 저장할 때 저장 형식이 되고,
보일 때 보기의 글자가 되고, 보낼 때 링크나 HTTP의 기록이 되어 다른 노드의 입력으로 옮겨진다. 이 문서는 그 모든 경계에서
값의 뜻이 무엇이고, 무엇을 그대로 옮기고 무엇을 거절하는지를 정한다.

한 줄로는 이렇다. **받아들인 값은 어느 길로 나오든 같다. 수는 비트까지(0의 부호만 빼고), text는 바이트까지, bool은
`true`나 `false`로, i64는 자릿수 하나까지. 받아들일 수 없는 값은 모든 들어오는 길이 거절하고, 거절한 사건은 아무것도 남기지
않는다.**

## 수

- **들어올 때.** JSON의 수와 쿼리의 값(퍼센트 인코딩을 푼 뒤)은 JSON 수의 문법으로 읽는다. 부호 `+`, 앞의 0(`01`),
  점만 있는 것(`.5`, `1.`), 16진, `NaN`, `Infinity`는 받지 않는다. 글자가 뜻하는 값에 가장 가까운 binary64로 읽는다(정확히
  반올림하고, 가운데는 짝수로). 그 값이 유한하지 않으면(`1e999`) 거절한다.
- **0에는 부호가 없다.** aup에서 −0과 0은 같은 값이다. 비교는 같고, `distinct`는 한 번 세고, 보기와 글자는 둘 다 `0`으로
  쓴다. 저장과 전달은 비트를 그대로 옮기므로 −0이 −0으로 남을 수 있지만, aup 코드에서는 보이지 않는다.
- **저장.** 비트 그대로 돌아온다. 크기가 2^53 미만인 정수는 zigzag varint, 그 밖은 표시와 8바이트다.
- **보일 때.** 다시 읽으면 같은 binary64가 되는 가장 짧은 글자다. 1e15 미만의 정수는 점과 지수 없이 쓰고, 그 밖은 `%g`
  모양에 지수를 줄여 쓴다.
- **보낼 때.** 기록의 비트가 그대로 간다. 받는 입력의 모양이 다르면 이름으로 옮긴다([014](014-fields-by-name.md)). u32를
  number로 넓히는 것은 정확하다.

## u32

- **들어올 때.** 숫자만 받는다. 부호, 소수, 지수(`1e3`도), 앞의 0은 안 되고, 0부터 4,294,967,295까지다. 그 밖은 거절한다.
  JSON과 쿼리가 같다.
- 상태에서는 수다.
- **보낼 때.** 출력의 u32 필드에 준 수는 범위 안의 정수여야 한다. 아니면 그 사건이 실패한다. 아무것도 남지 않고, 보내지도
  않는다.

## bool

[016](016-bool-and-i64.md)이 더한 참·거짓이다. 수가 아니다.

- **들어올 때.** JSON의 `true`와 `false`만 받는다. `1`, `0`, `"true"`, `null`은 `json_type`으로, `True`는 JSON이 아니라서
  `json`으로 거절한다. 쿼리의 값(퍼센트 인코딩을 푼 뒤)도 `true`와 `false`만이고, 그 밖은 `query`로 거절한다. 메시지의 행도
  같다. 페이로드에서는 접두부의 1바이트이고, 0이나 1이 아니면 그 메시지가 `limit`으로 실패한다.
- **저장.** 상태와 목록의 bool은 수의 칸에 0이나 1로 담는다. 목록의 이미지는 그 필드의 종류를 bool(3)로 적는다.
- **보일 때.** 보기의 글자는 `true`나 `false`이고(보간과 같다), 표의 JSON에서는 JSON의 `true`와 `false`다. 화면도 같은
  글자다.
- **보낼 때.** 링크는 bool을 bool인 필드로만 옮긴다(수로 넓히지 않는다). 받는 입력의 필드가 bool이 아니면 그 기록을 받지
  않고 기다린다. HTTP 전달의 JSON은 `true`와 `false`다.
- **모듈 교체와 다시 열기.** 수가 bool이 되면 0(−0도)과 1은 `false`와 `true`가 되고, 다른 수는 새 기본값이 된다. bool이 수가
  되면 0과 1이다. 교체가 출력의 필드를 바꾸면 기다리던 보냄의 값도 같은 규칙으로 옮긴다.

## i64

[016](016-bool-and-i64.md)이 더한 정확한 64비트 정수다. 수가 아니다.

- **들어올 때.** JSON과 쿼리의 값은 정수의 자릿수만이다(`-?(0|[1-9][0-9]*)`). 부호 `+`, 앞의 0, 점(`1.0`도), 지수(`1e3`도),
  문자열(`"1"`)은 받지 않고, −2^63부터 2^63−1까지다. `-0`은 0이다. JSON의 수이지만 i64가 아니면 `json_type`, JSON이 아니면
  (`+1`, `01`) `json`, 쿼리는 `query`로 거절한다. 메시지의 행도 같다. 페이로드에서는 접두부의 8바이트(부호 있는
  little-endian)다.
- **저장.** 상태는 `values` 문서의 종류 6과 8바이트 정수다. 목록은 종류 4(i64)의 열이다. 이미지는 8바이트, 로그는 zigzag
  varint다.
- **보일 때.** 모든 10진 자릿수다. 보기의 글자, 표의 JSON(JSON의 수로), HTTP 전달의 JSON, `pure-run`, 화면이 같다. JSON을
  binary64로 읽는 쪽(자바스크립트의 `JSON.parse`)은 2^53 너머에서 값을 잃는다. 그런 쪽에 보일 i64는 앱이 글자로 보인다(보간).
- **보낼 때.** 링크는 i64를 i64인 필드로 옮기고, u32는 i64로 넓힌다(정확하다). i64와 수, bool 사이는 넓히지 않는다(받지 않고
  기다린다). HTTP 전달의 JSON은 자릿수다.
- **모듈 교체와 다시 열기.** 수가 i64가 되면 정수이고 범위 안일 때 그 값(−0은 0), 아니면 새 기본값이다. i64가 수가 되면
  |i| ≤ 2^53일 때 그 값, 아니면 새 기본값이다. bool과 i64 사이는 0과 1만 옮긴다. 기다리던 보냄의 값도 같은 규칙으로 옮긴다.

## text

- **들어올 때.** JSON 문자열은 이스케이프를 풀고 서로게이트 짝을 합친다. 날 본문은 바이트 그대로다. 결과는 UTF-8이어야
  한다. 짝 없는 서로게이트, 너무 긴 인코딩, U+10FFFF 너머, 잘린 바이트는 거절한다. JSON이면 400 `json`, 날 본문이면
  422 `encoding`이다.
- 유효한 스칼라는 무엇이든 그대로 둔다. NUL, 제어 문자, U+2028과 U+2029, BOM, 비문자가 모두 그렇다.
- **저장과 전달.** 바이트 그대로다.
- **보일 때.** JSON 문자열(따옴표, 역슬래시, 제어 문자를 이스케이프)이다. 다시 읽으면 같은 바이트다.
- **답할 때.** `reply 메시지.필드`의 답은 그 필드의 바이트다.

## 메시지의 모양

- **그룹.** JSON에서는 안의 객체이고, 쿼리에서는 점으로 이은 이름(`at.x`)이다. 모듈과 전달에서는 `at.x` 같은 이름의 필드다.
- **행(`list of 레코드`).** JSON 배열의 객체 하나가 행 하나이고, 목록의 저장 형식으로 옮겨진다. 수와 text는 위와 같다.
  메시지의 행은 모두 받거나 하나도 받지 않는다.
- **사건.** 사건은 모두 되거나 아무것도 되지 않는다. 실패한 사건은 상태를 바꾸지 않고 보내지 않는다.
- **전달.** 필드를 이름으로 받는다. 같은 종류이거나 넓히는 것이어야 하고, 빠진 필드는 0이나 빈 값이며, 모르는 필드는
  받지 않고 기다린다([014](014-fields-by-name.md)). 한 번씩, 순서대로 적용한다.

## 저장과 다시 읽기

- 재시작(로그에서), 체크포인트 뒤의 재시작(이미지에서), 모듈 교체(상태를 이름으로 잇는다)가 모두 같은 값을 낸다.
- 보기와 질의는 마지막 저장의 스냅숏에서 읽는다. 그 저장의 상태와 같다.

## 검사

[`tests/check.py`](../../tests/check.py)의 `server_meaning`이 한 번에 본다.

- 값: 수 22개(0과 −0, 2^53 부근, 가장 큰 수, 정규와 비정규의 경계, 반쯤에서의 언더플로, 1e21 부근), u32 넷, text 12개(한글,
  4바이트 UTF-8, 이스케이프, NUL, U+2028, BOM, 비문자, 4 KiB), 그리고 모든 값에 `true`나 `false`인 bool 필드 하나와 i64 필드
  하나(i64 13개: 0과 −0, ±1, 2^53과 2^53+1, −(2^53+1), 2^63−1과 −2^63과 그 옆, 2^32, 19자리).
- 들어오는 길: JSON(ASCII로 이스케이프한 것과 UTF-8), 행, 쿼리와 날 본문, 링크(다른 순서, 넓힌 필드: u32를 수로, u32를
  i64로), HTTP 전달.
- 나오는 길: 보기, 질의, 답, 재시작, 체크포인트 뒤의 재시작, 모듈 교체, 두 번 중계한 노드.
- 거절할 값 84가지(수, u32, text, bool, i64, 행)가 모든 들어오는 길에서 거절되고, 어디에도 남지 않는다. bool과 i64는 JSON,
  쿼리, 행에서 거절의 종류까지 본다.
- bool 보기의 글자(`true`)와 bool 인자(`?want=true`, `?want=false`, 그 밖은 400 `invalid`).
- i64 보기의 글자(`max`는 2^63−1, `min`은 −2^63)와 i64 인자(끝값마다 센 수가 맞고, 소수·지수·범위 밖·`+1`·`01`·빈 값은 400
  `invalid`).

전달이 한 번씩, 순서대로 적용되는 것은 링크의 검사들(`server_links_udp_lossy`, `server_links_moved`, `server_upgrade`)과
[012](012-link-protocol.md)이 지킨다. 필드를 이름으로 받는 규칙은 `server_fields`가 지킨다.

## 화면

보기를 그리는 것은 Vega다. 규칙은 Vega의 `docs/design/001-first-component.md`("값이 보이는 방식")에 있고, 요약하면
이렇다.

- 값은 제 자리 안에서 제 글자로 보인다.
- 화면에 작용할 글자만 그림으로 보인다. 제어 문자, DEL, C1 제어, 줄과 문단의 구분자가 그렇고, 터미널에서는 방향 제어도
  그렇다.
- 문장(NUI)에서는 그것을 공백으로 말하거나 말하지 않는다.
- 웹의 JSON은 값을 그대로 싣는다.
- 값의 어떤 바이트도 터미널에 제어로 닿지 않는다.

Vega의 `screen_meaning`과 `browser` 검사가 지킨다.