aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/results/2026-09-23-prepared-connections
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-24 17:14:54 +0100
committereunhhu <[email protected]>2026-09-24 17:14:54 +0100
commitb1ce1927022d374198441afafeb27199fc8a052a (patch)
tree6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/results/2026-09-23-prepared-connections
parentd34f9426d5317a1948f12f229439946728bc342a (diff)
downloadpure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz
pure-b1ce1927022d374198441afafeb27199fc8a052a.zip
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/results/2026-09-23-prepared-connections')
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/README.md31
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/native.json269
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sanitized.json269
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/Makefile24
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/check.py419
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/excerpt.pure13
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/storage.pure11
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.c41
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.h25
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.c233
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.h26
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/server.c385
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.c227
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.h39
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-flow.c183
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-program.c111
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/cli.mjs127
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/examples/direct.pure47
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/remote.mjs89
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/compiler.mjs496
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/core.wasmbin0 -> 61072 bytes
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/project.mjs47
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/runtime.mjs350
-rw-r--r--prototypes/connections/results/2026-09-23-prepared-connections/verification.json51
24 files changed, 3513 insertions, 0 deletions
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/README.md b/prototypes/connections/results/2026-09-23-prepared-connections/README.md
new file mode 100644
index 0000000..c79e954
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/README.md
@@ -0,0 +1,31 @@
+# 020 검증 자료
+
+> 필드와 데이터 의존 범위 도입 후 기록은 [021](../2026-09-23-field-connections/README.md)에 있다.
+> 이 디렉터리의 원자료·소스 사본은 상수 구간 프로파일 당시의 결과로 보존한다.
+
+작성한 구간·저장·응답 관계를 준비하고 실제 네이티브 TCP 경로에서 실행한 기록이다.
+속도·처리량·RSS 비교는 하지 않았다.
+
+- [native.json](native.json): 일반 빌드의 22개 통합 시나리오, 준비 결과, 종료 자원, 실행 파일·소스 해시.
+- [sanitized.json](sanitized.json): ASan·UBSan 빌드의 같은 검사. 누수 검사를 끄지 않았다.
+- [verification.json](verification.json): 명령, 저장/전송 및 준비 단위 검사 결과, 소스·바이너리 일치 확인.
+- `sources/`: 기록된 파일의 사본. 현재 작업 트리가 바뀐 뒤에는 별도 작업 트리에 배치해 재현한다.
+
+`prepared_connections`는 검사 시 작성한 정의에서 나온 구간·길이 조건이다.
+`runtime_view_steps`는 요청별 뷰 노드 실행기가 없다는 구조 설명값이고 시간이나 CPU 명령
+계측값은 아니다. `prepared_program_bytes`는 준비 구조체 하나의 `sizeof`다. 전체 메모리와
+혼동하지 않는다. 부모 구간이나 쓰지 않는 구간도 유효해야 하므로 최소 원본 길이가 최종
+응답 범위보다 클 수 있다.
+
+논리적 본문 예산 16 B로 1 MiB 파일에서 작성한 16 B를 응답한 검사는 요구 범위 전달을
+확인한다. mmap의 페이지·정렬 비용, 파일 보유 공간, OS 버퍼, 전체 RSS는 이 예산 밖이다.
+입력의 일부를 응답하는 경우에는 원본 수신 할당 전체를 유지·계상한다.
+
+기존 저장 단위 검사 외에 `test-program`은 준비 결과를 순차 범위 검사와 대조하며,
+잘못된 이름·타입·효과 위치·상수 범위·소스 한도 등을 거절하는지 확인한다. 기본 작성 파일로
+기존 Workbench 조작·저장·새 실행기 복원이 유지되는 것도 통합 검사에 포함했다.
+
+재현은 [구현 README](../../README.md)의 `make check`를 따른다.
+`EVIDENCE=/새/경로.json`으로 원자료를 기록하며 기존 파일을 덮어쓰지 않는다. 이 환경은
+소켓·자식 프로세스·LeakSanitizer 제한 때문에 승인된 환경에서 로컬 검사를 실행했다.
+외부 서비스는 사용하지 않았으며 macOS와 실제 전원 장애는 확인하지 않았다.
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/native.json b/prototypes/connections/results/2026-09-23-prepared-connections/native.json
new file mode 100644
index 0000000..4e04783
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/native.json
@@ -0,0 +1,269 @@
+{
+ "kind": "prepared-connection-correctness-not-performance",
+ "checks": [
+ "fragmented request and binary bytes round trip",
+ "conditional create and stale revision preserve committed bytes",
+ "writer contention and invalid extent fail without overwriting data",
+ "two native writers cannot both create the same expected revision",
+ "incomplete request never commits and releases its allocation",
+ "borrowed read remains the same snapshot during another commit",
+ "receiver reset aborts an outstanding borrowed transfer",
+ "unsupported framing and oversized payload are rejected before effects",
+ "existing workbench manipulation restores in a fresh runtime and reports conflict",
+ "invalid or non-idle restore preserves local state and does not adopt remote revision",
+ "revision above JavaScript safe integer round trips without numeric coercion",
+ "unread acknowledgment does not undo commit or permit a stale retry",
+ "empty body has a valid lifetime and stored revision",
+ "committed authoring state survives process restart",
+ "slow consumer retains its lease; budget rejects another; deadline releases it",
+ "connection slots stay bounded and recover after header deadlines",
+ "authored nested read maps only final demand while preserving parent bounds and revision",
+ "unused checked view still fails even when response range would be valid",
+ "authored write and reply share selected input; invalid parent prevents the storage effect",
+ "body-free response preserves read existence and malformed-object errors",
+ "input-backed response survives partial send and releases on finish or abort without undoing commit",
+ "invalid authored program fails preparation before creating storage or accepting requests"
+ ],
+ "prepared_connections": {
+ "excerpt": {
+ "read": {
+ "whole": false,
+ "offset": 32,
+ "length": 16,
+ "minimum_source_bytes": 80
+ },
+ "write": {
+ "whole": true,
+ "offset": 0,
+ "length": 0,
+ "minimum_source_bytes": 0
+ },
+ "reply_input": false,
+ "view_definitions": 2,
+ "range_definitions": 2,
+ "runtime_view_steps": 0,
+ "prepared_program_bytes": 112
+ },
+ "write": {
+ "read": {
+ "whole": true,
+ "offset": 0,
+ "length": 0,
+ "minimum_source_bytes": 0
+ },
+ "write": {
+ "whole": false,
+ "offset": 5,
+ "length": 4,
+ "minimum_source_bytes": 12
+ },
+ "reply_input": true,
+ "view_definitions": 3,
+ "range_definitions": 2,
+ "runtime_view_steps": 0,
+ "prepared_program_bytes": 112
+ }
+ },
+ "server_runs": [
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 6,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 1,
+ "commits": 1,
+ "gets": 0,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048583,
+ "payload_budget": 4194304,
+ "peak_connections": 2,
+ "accepted": 41,
+ "commits": 11,
+ "gets": 14,
+ "conflicts": 4,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 32,
+ "ingress_prefix_copy_bytes": 11674,
+ "borrowed_output_bytes": 2115417,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 2553,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 0,
+ "gets": 2,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 2,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 2556,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048576,
+ "payload_budget": 1048576,
+ "peak_connections": 8,
+ "accepted": 12,
+ "commits": 0,
+ "gets": 3,
+ "conflicts": 0,
+ "budget_rejections": 1,
+ "expired": 9,
+ "releases": 3,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 1048582,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 80,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 2,
+ "gets": 0,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 2,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 16,
+ "payload_budget": 16,
+ "peak_connections": 1,
+ "accepted": 3,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 16,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 16,
+ "payload_budget": 16,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 16,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 12,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 5,
+ "commits": 1,
+ "gets": 2,
+ "conflicts": 1,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 5,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 12,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 0,
+ "payload_budget": 1,
+ "peak_connections": 1,
+ "accepted": 3,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 0,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048576,
+ "payload_budget": 1048576,
+ "peak_connections": 2,
+ "accepted": 7,
+ "commits": 2,
+ "gets": 3,
+ "conflicts": 0,
+ "budget_rejections": 2,
+ "expired": 0,
+ "releases": 5,
+ "ingress_prefix_copy_bytes": 8006,
+ "borrowed_output_bytes": 3145734,
+ "server_structure_bytes": 39288
+ }
+ ],
+ "storage": "immutable-byte-extents-v1",
+ "binary_sha256": "ce76b1ff164623c8575c8c313a97ec4083c8a500eef88ab49072bc2e04d7520d",
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "python": "3.14.7",
+ "node": "v24.20.0"
+ },
+ "source_sha256": {
+ "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749",
+ "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421",
+ "prototypes/connections/store.c": "7a506fb79a074f0d90eaa6f047a364e4fee28eea9ebea1f869dbd3770edc6a2e",
+ "prototypes/connections/store.h": "05c5a4e35e27a4d309c3209c84112fd38d65093ece79ddc4eb1b68adb8e5741f",
+ "prototypes/connections/program.c": "b6b848b261aa9da455126ec25fff0494b4eefb649ce0fd036f075c1efb41f9c8",
+ "prototypes/connections/program.h": "700880cba88b18b08c1b87aa4da46fedc81f3d665d74e40fc659095fe4c3f014",
+ "prototypes/connections/server.c": "d2cb3cc8d47cb5e6bf258cf62c70c039b5c5e945fa5c4d5edb7917d50395fe73",
+ "prototypes/connections/check.py": "7321ba3a48a10a7d1344bbf61df3fb693e9ac95e00d26d3d247bd6a39997505f",
+ "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b",
+ "prototypes/connections/test-program.c": "d58248c65d59fa31f37ba50aa9ad761d3e9698d1e17d61307b7e88089c70d385",
+ "prototypes/connections/Makefile": "85bc73e364fb5a273efd7bfbd2e8118091f634d0e263d0729bea445252dcd7f0",
+ "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74",
+ "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16",
+ "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f",
+ "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da",
+ "prototypes/workbench/web/core.wasm": "9f760e4a2ce6ea11f2be07693fceca74088884885cc545a70ceef5003d0b2479",
+ "prototypes/workbench/web/runtime.mjs": "9cf12270bb39b3d6ce0384c56df8bdfabfd9459d3375cc7819290a5fe3e5db54",
+ "prototypes/workbench/web/compiler.mjs": "349139612d1f7da5f75ef21c65aa697963b760cb35321150fc36f67f3b40adf1",
+ "prototypes/workbench/web/project.mjs": "8af4cd1a60bbb217c896be20c7c6b8d33989f2166d06e736bf1479d8ecdc8b35",
+ "prototypes/workbench/examples/direct.pure": "57177e5197d75a6185cd3ac5df2be920ddba9a9b0fcc2dddab3560b815249f0a"
+ }
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sanitized.json b/prototypes/connections/results/2026-09-23-prepared-connections/sanitized.json
new file mode 100644
index 0000000..d5cc604
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sanitized.json
@@ -0,0 +1,269 @@
+{
+ "kind": "prepared-connection-correctness-not-performance",
+ "checks": [
+ "fragmented request and binary bytes round trip",
+ "conditional create and stale revision preserve committed bytes",
+ "writer contention and invalid extent fail without overwriting data",
+ "two native writers cannot both create the same expected revision",
+ "incomplete request never commits and releases its allocation",
+ "borrowed read remains the same snapshot during another commit",
+ "receiver reset aborts an outstanding borrowed transfer",
+ "unsupported framing and oversized payload are rejected before effects",
+ "existing workbench manipulation restores in a fresh runtime and reports conflict",
+ "invalid or non-idle restore preserves local state and does not adopt remote revision",
+ "revision above JavaScript safe integer round trips without numeric coercion",
+ "unread acknowledgment does not undo commit or permit a stale retry",
+ "empty body has a valid lifetime and stored revision",
+ "committed authoring state survives process restart",
+ "slow consumer retains its lease; budget rejects another; deadline releases it",
+ "connection slots stay bounded and recover after header deadlines",
+ "authored nested read maps only final demand while preserving parent bounds and revision",
+ "unused checked view still fails even when response range would be valid",
+ "authored write and reply share selected input; invalid parent prevents the storage effect",
+ "body-free response preserves read existence and malformed-object errors",
+ "input-backed response survives partial send and releases on finish or abort without undoing commit",
+ "invalid authored program fails preparation before creating storage or accepting requests"
+ ],
+ "prepared_connections": {
+ "excerpt": {
+ "read": {
+ "whole": false,
+ "offset": 32,
+ "length": 16,
+ "minimum_source_bytes": 80
+ },
+ "write": {
+ "whole": true,
+ "offset": 0,
+ "length": 0,
+ "minimum_source_bytes": 0
+ },
+ "reply_input": false,
+ "view_definitions": 2,
+ "range_definitions": 2,
+ "runtime_view_steps": 0,
+ "prepared_program_bytes": 112
+ },
+ "write": {
+ "read": {
+ "whole": true,
+ "offset": 0,
+ "length": 0,
+ "minimum_source_bytes": 0
+ },
+ "write": {
+ "whole": false,
+ "offset": 5,
+ "length": 4,
+ "minimum_source_bytes": 12
+ },
+ "reply_input": true,
+ "view_definitions": 3,
+ "range_definitions": 2,
+ "runtime_view_steps": 0,
+ "prepared_program_bytes": 112
+ }
+ },
+ "server_runs": [
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 6,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 1,
+ "commits": 0,
+ "gets": 0,
+ "conflicts": 1,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048583,
+ "payload_budget": 4194304,
+ "peak_connections": 2,
+ "accepted": 41,
+ "commits": 12,
+ "gets": 14,
+ "conflicts": 4,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 32,
+ "ingress_prefix_copy_bytes": 11674,
+ "borrowed_output_bytes": 2115416,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 2553,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 0,
+ "gets": 2,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 2,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 2556,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048576,
+ "payload_budget": 1048576,
+ "peak_connections": 8,
+ "accepted": 12,
+ "commits": 0,
+ "gets": 3,
+ "conflicts": 0,
+ "budget_rejections": 1,
+ "expired": 9,
+ "releases": 3,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 1048582,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 80,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 2,
+ "gets": 0,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 2,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 16,
+ "payload_budget": 16,
+ "peak_connections": 1,
+ "accepted": 3,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 16,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 16,
+ "payload_budget": 16,
+ "peak_connections": 1,
+ "accepted": 2,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 1,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 16,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 12,
+ "payload_budget": 4194304,
+ "peak_connections": 1,
+ "accepted": 5,
+ "commits": 1,
+ "gets": 2,
+ "conflicts": 1,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 5,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 12,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 0,
+ "payload_budget": 1,
+ "peak_connections": 1,
+ "accepted": 3,
+ "commits": 0,
+ "gets": 1,
+ "conflicts": 0,
+ "budget_rejections": 0,
+ "expired": 0,
+ "releases": 0,
+ "ingress_prefix_copy_bytes": 0,
+ "borrowed_output_bytes": 0,
+ "server_structure_bytes": 39288
+ },
+ {
+ "active": 0,
+ "retained_payload_bytes": 0,
+ "peak_retained_payload_bytes": 1048576,
+ "payload_budget": 1048576,
+ "peak_connections": 2,
+ "accepted": 7,
+ "commits": 2,
+ "gets": 3,
+ "conflicts": 0,
+ "budget_rejections": 2,
+ "expired": 0,
+ "releases": 5,
+ "ingress_prefix_copy_bytes": 8006,
+ "borrowed_output_bytes": 3145734,
+ "server_structure_bytes": 39288
+ }
+ ],
+ "storage": "immutable-byte-extents-v1",
+ "binary_sha256": "3cb873b98d64992cc89f11186de1e97baf19fd7aa3fcb5784b061bea7828b206",
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "python": "3.14.7",
+ "node": "v24.20.0"
+ },
+ "source_sha256": {
+ "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749",
+ "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421",
+ "prototypes/connections/store.c": "7a506fb79a074f0d90eaa6f047a364e4fee28eea9ebea1f869dbd3770edc6a2e",
+ "prototypes/connections/store.h": "05c5a4e35e27a4d309c3209c84112fd38d65093ece79ddc4eb1b68adb8e5741f",
+ "prototypes/connections/program.c": "b6b848b261aa9da455126ec25fff0494b4eefb649ce0fd036f075c1efb41f9c8",
+ "prototypes/connections/program.h": "700880cba88b18b08c1b87aa4da46fedc81f3d665d74e40fc659095fe4c3f014",
+ "prototypes/connections/server.c": "d2cb3cc8d47cb5e6bf258cf62c70c039b5c5e945fa5c4d5edb7917d50395fe73",
+ "prototypes/connections/check.py": "7321ba3a48a10a7d1344bbf61df3fb693e9ac95e00d26d3d247bd6a39997505f",
+ "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b",
+ "prototypes/connections/test-program.c": "d58248c65d59fa31f37ba50aa9ad761d3e9698d1e17d61307b7e88089c70d385",
+ "prototypes/connections/Makefile": "85bc73e364fb5a273efd7bfbd2e8118091f634d0e263d0729bea445252dcd7f0",
+ "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74",
+ "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16",
+ "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f",
+ "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da",
+ "prototypes/workbench/web/core.wasm": "9f760e4a2ce6ea11f2be07693fceca74088884885cc545a70ceef5003d0b2479",
+ "prototypes/workbench/web/runtime.mjs": "9cf12270bb39b3d6ce0384c56df8bdfabfd9459d3375cc7819290a5fe3e5db54",
+ "prototypes/workbench/web/compiler.mjs": "349139612d1f7da5f75ef21c65aa697963b760cb35321150fc36f67f3b40adf1",
+ "prototypes/workbench/web/project.mjs": "8af4cd1a60bbb217c896be20c7c6b8d33989f2166d06e736bf1479d8ecdc8b35",
+ "prototypes/workbench/examples/direct.pure": "57177e5197d75a6185cd3ac5df2be920ddba9a9b0fcc2dddab3560b815249f0a"
+ }
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/Makefile
new file mode 100644
index 0000000..50ad1cb
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/Makefile
@@ -0,0 +1,24 @@
+CC ?= cc
+CFLAGS ?= -O2 -g
+CFLAGS += -std=c17 -Wall -Wextra -Wpedantic -Werror
+BUILD ?= build
+
+.PHONY: all check
+all: $(BUILD)/pure-connections
+
+$(BUILD):
+ mkdir -p $@
+
+$(BUILD)/pure-connections: server.c store.c flow.c program.c store.h flow.h program.h | $(BUILD)
+ $(CC) $(CPPFLAGS) $(CFLAGS) server.c store.c flow.c program.c $(LDFLAGS) $(LDLIBS) -o $@
+
+$(BUILD)/test-flow: test-flow.c store.c flow.c store.h flow.h | $(BUILD)
+ $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_STORE_TEST test-flow.c store.c flow.c $(LDFLAGS) $(LDLIBS) -o $@
+
+$(BUILD)/test-program: test-program.c program.c program.h store.h flow.h | $(BUILD)
+ $(CC) $(CPPFLAGS) $(CFLAGS) test-program.c program.c $(LDFLAGS) $(LDLIBS) -o $@
+
+check: $(BUILD)/test-flow $(BUILD)/test-program $(BUILD)/pure-connections
+ $(BUILD)/test-flow
+ $(BUILD)/test-program
+ python3 check.py --server $(BUILD)/pure-connections $(if $(EVIDENCE),--output "$(EVIDENCE)")
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/check.py
new file mode 100644
index 0000000..c547ebf
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/check.py
@@ -0,0 +1,419 @@
+#!/usr/bin/env python3
+"""Exercise local TCP, immutable byte extents, and the existing workbench CLI."""
+import argparse
+from contextlib import contextmanager
+from concurrent.futures import ThreadPoolExecutor
+import fcntl
+import hashlib
+import http.client
+import json
+import platform
+from pathlib import Path
+import select
+import socket
+import struct
+import subprocess
+import tempfile
+import time
+
+ROOT = Path(__file__).resolve().parent
+WORKBENCH = ROOT.parent / 'workbench'
+OBJECT_LIMIT = (2 * 6 + 32) * 32768 + 8192
+
+
+class Server:
+ def __init__(self, binary, storage, budget=4 * 1024 * 1024, timeout=5000, program=None):
+ self.process = subprocess.Popen([str(binary), '--store', str(storage), '--port', '0',
+ '--program', str(program or ROOT / 'examples/storage.pure'),
+ '--budget', str(budget), '--timeout-ms', str(timeout), '--send-buffer', '4096'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
+ if not select.select([self.process.stdout], [], [], 15)[0]:
+ self.process.kill()
+ raise AssertionError('server readiness timed out')
+ line = self.process.stdout.readline()
+ if not line:
+ raise AssertionError(self.process.communicate(timeout=5))
+ self.ready = json.loads(line)
+ self.port = self.ready['port']
+ self.stats = None
+
+ def close(self):
+ if self.process.poll() is None:
+ self.process.terminate()
+ try:
+ stdout, stderr = self.process.communicate(timeout=10)
+ except subprocess.TimeoutExpired:
+ self.process.kill()
+ self.process.communicate()
+ raise
+ assert self.process.returncode == 0, (stdout, stderr)
+ self.stats = json.loads(stderr)
+ assert self.stats['active'] == self.stats['retained_payload_bytes'] == 0
+ assert self.stats['peak_retained_payload_bytes'] <= self.stats['payload_budget']
+ assert self.stats['peak_connections'] <= 8
+
+
+@contextmanager
+def serving(*args, **kwargs):
+ server = Server(*args, **kwargs)
+ try:
+ yield server
+ finally:
+ server.close()
+
+
+def rpc(server, method, key, body=b'', expected=None):
+ headers = {'Content-Length': str(len(body))}
+ if expected is not None:
+ headers['If-None-Match' if expected == 0 else 'If-Match'] = '*' if expected == 0 else f'"{expected}"'
+ connection = http.client.HTTPConnection('127.0.0.1', server.port, timeout=8)
+ try:
+ connection.request(method, f'/objects/{key}', body, headers)
+ response = connection.getresponse()
+ return response.status, dict(response.getheaders()), response.read()
+ finally:
+ connection.close()
+
+
+def wire(server, request, chunks=None):
+ with socket.create_connection(('127.0.0.1', server.port), timeout=8) as sock:
+ if chunks:
+ for chunk in chunks:
+ sock.sendall(chunk)
+ else:
+ sock.sendall(request)
+ response = http.client.HTTPResponse(sock)
+ response.begin()
+ return response.status, dict(response.getheaders()), response.read()
+
+
+def header(method, key, size=0, condition=b''):
+ return (f'{method} /objects/{key} HTTP/1.1\r\nHost: localhost\r\n'
+ f'Content-Length: {size}\r\n'.encode() + condition + b'\r\n')
+
+
+def cli(server, commands, source=None, expected_code=0):
+ result = subprocess.run(['node', str(WORKBENCH / 'cli.mjs'), *([str(source)] if source else [])],
+ input=commands.replace('$URL', f'http://127.0.0.1:{server.port}'), text=True,
+ capture_output=True, timeout=30)
+ assert result.returncode == expected_code, (result.stdout, result.stderr)
+ return [json.loads(line) for line in result.stdout.splitlines()], result.stderr
+
+
+def check(binary, directory):
+ storage = directory / 'objects'
+ checks, records = [], []
+ with serving(binary, storage) as server:
+ assert rpc(server, 'GET', 'absent')[0] == 404
+ payload = b'\x00\xffbinary\r\n\r\n' + bytes(range(256)) * 41
+ request = header('PUT', 'binary', len(payload), b'If-None-Match: *\r\n') + payload
+ status, headers, _ = wire(server, request, [request[:7], request[7:51], request[51:57], request[57:]])
+ assert status == 200 and headers['ETag'] == '"1"'
+ assert rpc(server, 'GET', 'binary')[2] == payload
+ checks.append('fragmented request and binary bytes round trip')
+ assert rpc(server, 'PUT', 'binary', b'new', 0)[0] == 412
+ assert rpc(server, 'PUT', 'binary', b'new', 1)[0] == 200
+ assert rpc(server, 'PUT', 'binary', b'stale', 1)[0] == 412
+ assert rpc(server, 'GET', 'binary')[2] == b'new'
+ checks.append('conditional create and stale revision preserve committed bytes')
+
+ with (storage / '.writer.lock').open('r+b') as lock:
+ fcntl.lockf(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ assert rpc(server, 'PUT', 'binary', b'blocked', 2)[0] == 503
+ fcntl.lockf(lock, fcntl.LOCK_UN)
+ broken = storage / 'obj-broken'
+ broken.write_bytes(b'incomplete')
+ assert rpc(server, 'GET', 'broken')[0] == 500
+ assert rpc(server, 'PUT', 'broken', b'failure', 0)[0] == 500
+ assert broken.read_bytes() == b'incomplete'
+ assert rpc(server, 'GET', 'binary')[2] == b'new'
+ checks.append('writer contention and invalid extent fail without overwriting data')
+
+ with serving(binary, storage) as second_writer:
+ with ThreadPoolExecutor(max_workers=2) as pool:
+ tasks = [pool.submit(rpc, target, 'PUT', 'race', value, 0)
+ for target, value in [(server, b'first'), (second_writer, b'second')]]
+ statuses = [task.result()[0] for task in tasks]
+ assert statuses.count(200) == 1 and all(status in [200, 412, 503] for status in statuses)
+ status, headers, body = rpc(server, 'GET', 'race')
+ assert status == 200 and headers['ETag'] == '"1"' and body in [b'first', b'second']
+ records.append(second_writer.stats)
+ checks.append('two native writers cannot both create the same expected revision')
+
+ sock = socket.create_connection(('127.0.0.1', server.port), timeout=8)
+ sock.sendall(header('PUT', 'incomplete', 100, b'If-None-Match: *\r\n') + b'partial')
+ sock.shutdown(socket.SHUT_WR)
+ assert sock.recv(1) == b''
+ sock.close()
+ assert rpc(server, 'GET', 'incomplete')[0] == 404
+ checks.append('incomplete request never commits and releases its allocation')
+
+ # Force genuine partial socket sends. A write on another connection must
+ # not alter the immutable file whose bytes are still being transmitted.
+ large = bytes(range(256)) * 4096
+ assert rpc(server, 'PUT', 'large', large, 0)[0] == 200
+ sock = socket.create_connection(('127.0.0.1', server.port), timeout=8)
+ sock.sendall(header('GET', 'large'))
+ response = http.client.HTTPResponse(sock)
+ response.begin()
+ assert response.status == 200 and response.getheader('ETag') == '"1"'
+ assert response.read(17) == large[:17]
+ assert rpc(server, 'PUT', 'large', b'changed', 1)[0] == 200
+ assert response.read() == large[17:]
+ sock.close()
+ assert rpc(server, 'GET', 'large')[2] == b'changed'
+ checks.append('borrowed read remains the same snapshot during another commit')
+
+ assert rpc(server, 'PUT', 'large', large, 2)[0] == 200
+ sock = socket.create_connection(('127.0.0.1', server.port), timeout=8)
+ sock.sendall(header('GET', 'large'))
+ assert sock.recv(1)
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
+ sock.close()
+ assert rpc(server, 'GET', 'binary')[2] == b'new'
+ checks.append('receiver reset aborts an outstanding borrowed transfer')
+
+ assert wire(server, header('PUT', 'too_large', OBJECT_LIMIT + 1, b'If-None-Match: *\r\n'))[0] == 413
+ malformed = [
+ b'PUT /objects/bad HTTP/1.1\r\nHost: x\r\nContent-Length: 1\r\nContent-Length: 1\r\nIf-None-Match: *\r\n\r\nx',
+ b'PUT /objects/bad HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: chunked\r\n\r\n',
+ b'GET /objects/bad HTTP/1.1\r\nHost: x\x00hidden\r\n\r\n',
+ ]
+ for request in malformed:
+ assert wire(server, request)[0] == 400
+ assert rpc(server, 'GET', 'bad')[0] == 404
+ checks.append('unsupported framing and oversized payload are rejected before effects')
+
+ # Existing authoring path: define, manipulate, save remotely, reconstruct
+ # in a fresh runtime. No fabricated project fixture or mock storage.
+ source = WORKBENCH / 'examples' / 'direct.pure'
+ saved = directory / 'restored.json'
+ lines, _ = cli(server, 'point drag down 80 180\npoint drag move 210 210\npoint drag up 210 210\n'
+ 'connect $URL authored\npush 0\nquit\n', source)
+ snapshots = [line for line in lines if 'states' in line]
+ assert snapshots
+ expected_states = snapshots[-1]['states']
+ assert expected_states['x'] == 210 and expected_states['y'] == 190 and expected_states['dragging'] == 0
+ lines, _ = cli(server, f'connect $URL authored\npull\nsave-project {saved}\nquit\n')
+ assert [line for line in lines if 'states' in line][-1]['states'] == expected_states
+ document = json.loads(saved.read_text())
+ assert document['version'] == 2 and document['source'] == source.read_text()
+ lines, error = cli(server, 'connect $URL authored\npush 0\nquit\n', source, 1)
+ assert '412' in error
+ checks.append('existing workbench manipulation restores in a fresh runtime and reports conflict')
+
+ assert rpc(server, 'PUT', 'invalid_project', b'{"format":"pure-workbench","version":2,"source":"state x=1","states":[]}', 0)[0] == 200
+ lines, error = cli(server, 'show\nconnect $URL invalid_project\npull\nshow\nquit\n', source, 1)
+ states = [line['states'] for line in lines if 'states' in line]
+ assert states[0] == states[-1] and error
+ lines, error = cli(server, 'point drag down 90 170\nconnect $URL authored\npull\npush\nshow\nquit\n', source, 1)
+ states = [line['states'] for line in lines if 'states' in line]
+ assert states[0] == states[-1] and states[-1]['dragging'] == 1
+ assert len(error.strip().splitlines()) == 2
+ checks.append('invalid or non-idle restore preserves local state and does not adopt remote revision')
+
+ assert rpc(server, 'PUT', 'wide_revision', rpc_bytes(saved), 0)[0] == 200
+ wide = storage / 'obj-wide_revision'
+ raw = wide.read_bytes()
+ wide.write_bytes(raw[:8] + struct.pack('<Q', 9007199254740993) + raw[16:])
+ lines, _ = cli(server, 'connect $URL wide_revision\npull\npush\nquit\n')
+ revisions = [line['revision'] for line in lines if 'remote' in line and 'revision' in line]
+ assert revisions == ['9007199254740993', '9007199254740994']
+ checks.append('revision above JavaScript safe integer round trips without numeric coercion')
+
+ sock = socket.create_connection(('127.0.0.1', server.port), timeout=8)
+ sock.sendall(header('PUT', 'unread_ack', 4, b'If-None-Match: *\r\n') + b'done')
+ try:
+ deadline = time.monotonic() + 3
+ while True:
+ committed = storage / 'obj-unread_ack'
+ if committed.exists():
+ assert committed.read_bytes()[24:] == b'done'
+ break
+ assert time.monotonic() < deadline
+ time.sleep(0.01)
+ finally:
+ sock.close() # Client never consumes the successful response.
+ assert rpc(server, 'PUT', 'unread_ack', b'again', 0)[0] == 412
+ assert rpc(server, 'GET', 'unread_ack')[2] == b'done'
+ checks.append('unread acknowledgment does not undo commit or permit a stale retry')
+
+ # A disconnect after commit can hide the ACK; retries remain conditional.
+ assert rpc(server, 'PUT', 'empty', b'', 0)[0] == 200
+ assert rpc(server, 'GET', 'empty')[2] == b''
+ checks.append('empty body has a valid lifetime and stored revision')
+ records.append(server.stats)
+
+ with serving(binary, storage) as restarted:
+ assert rpc(restarted, 'GET', 'binary')[2] == b'new'
+ assert rpc(restarted, 'GET', 'authored')[2] == rpc_bytes(saved)
+ checks.append('committed authoring state survives process restart')
+ records.append(restarted.stats)
+
+ # Fixed small payload budget is an admission test, not a throughput workload.
+ with serving(binary, storage, budget=1024 * 1024, timeout=1200) as limited:
+ sock = socket.create_connection(('127.0.0.1', limited.port), timeout=8)
+ sock.sendall(header('GET', 'large'))
+ response = http.client.HTTPResponse(sock)
+ response.begin()
+ assert response.status == 200
+ assert response.read(1) == large[:1]
+ status, headers, _ = rpc(limited, 'GET', 'large')
+ assert status == 503 and headers['X-Pure-Error'] == 'payload_budget'
+ # Leave the first receiver stalled until the absolute connection deadline.
+ time.sleep(1.4)
+ assert rpc(limited, 'GET', 'binary')[2] == b'new'
+ response.close()
+ sock.close()
+ checks.append('slow consumer retains its lease; budget rejects another; deadline releases it')
+
+ idle = [socket.create_connection(('127.0.0.1', limited.port), timeout=8) for _ in range(8)]
+ try:
+ for s in idle:
+ s.sendall(b'G')
+ for s in idle:
+ assert s.recv(1) == b''
+ finally:
+ for s in idle:
+ s.close()
+ assert rpc(limited, 'GET', 'binary')[0] == 200
+ checks.append('connection slots stay bounded and recover after header deadlines')
+ assert limited.stats['budget_rejections'] >= 1 and limited.stats['expired'] >= 9
+ records.append(limited.stats)
+ prepared = authored_connections(binary, directory, storage, records, checks)
+ return {'kind': 'prepared-connection-correctness-not-performance', 'checks': checks,
+ 'prepared_connections': prepared,
+ 'server_runs': records, 'storage': server.ready['storage']}
+
+
+def authored_connections(binary, directory, storage, records, checks):
+ get = 'on get { let o = store.read(request.key); reply(o, o.body); }'
+ put = 'on put { let s = store.write(request.key, request.revision, request.body); reply(s); }'
+
+ def author(name, source):
+ path = directory / name
+ path.write_text(source)
+ return path
+
+ def explain(path):
+ return json.loads(subprocess.check_output([str(binary), '--check-program', str(path)], text=True))
+
+ with serving(binary, storage) as writer:
+ assert rpc(writer, 'PUT', 'short_parent', bytes(range(48)), 0)[0] == 200
+ assert rpc(writer, 'PUT', 'short_unused', bytes(range(80)), 0)[0] == 200
+ records.append(writer.stats)
+ excerpt = ROOT / 'examples/excerpt.pure'
+ prepared = {'excerpt': explain(excerpt)}
+ assert prepared['excerpt']['read'] == {
+ 'whole': False, 'offset': 32, 'length': 16, 'minimum_source_bytes': 80}
+ with serving(binary, storage, budget=16, program=excerpt) as selected:
+ status, headers, body = rpc(selected, 'GET', 'large')
+ assert status == 200 and body == bytes(range(32, 48)) and headers['ETag'] == '"3"'
+ # Final [32,48) fits, but the authored parent [16,80) does not.
+ assert rpc(selected, 'GET', 'short_parent')[0] == 413
+ assert rpc(selected, 'GET', 'missing')[0] == 404
+ assert selected.stats['peak_retained_payload_bytes'] == 16
+ records.append(selected.stats)
+ checks.append('authored nested read maps only final demand while preserving parent bounds and revision')
+
+ unused = author('unused.pure', 'on get { let o = store.read(request.key); '
+ 'let unused = range(o.body, 0, 96); let body = range(o.body, 32, 16); reply(o, body); } ' + put)
+ with serving(binary, storage, budget=16, program=unused) as guarded:
+ assert rpc(guarded, 'GET', 'short_unused')[0] == 413
+ assert rpc(guarded, 'GET', 'large')[2] == bytes(range(32, 48))
+ records.append(guarded.stats)
+ checks.append('unused checked view still fails even when response range would be valid')
+
+ select_write = author('write.pure', get + ' on put { let a = range(request.body, 2, 10); '
+ 'let b = range(a, 3, 4); let alias = b; '
+ 'let saved = store.write(request.key, request.revision, alias); reply(saved, b); }')
+ prepared['write'] = explain(select_write)
+ with serving(binary, storage, program=select_write) as writer:
+ status, headers, body = rpc(writer, 'PUT', 'selected', b'0123456789AB', 0)
+ assert status == 200 and body == b'5678' and headers['ETag'] == '"1"'
+ assert rpc(writer, 'GET', 'selected')[2] == b'5678'
+ assert rpc(writer, 'PUT', 'selected', b'012345678', 1)[0] == 413
+ status, headers, body = rpc(writer, 'GET', 'selected')
+ assert status == 200 and body == b'5678' and headers['ETag'] == '"1"'
+ assert rpc(writer, 'PUT', 'selected', b'0123456789AB', 0)[0] == 412
+ records.append(writer.stats)
+ checks.append('authored write and reply share selected input; invalid parent prevents the storage effect')
+
+ metadata = author('metadata.pure', 'on get { let o = store.read(request.key); reply(o); } ' + put)
+ with serving(binary, storage, budget=1, program=metadata) as meta:
+ status, headers, body = rpc(meta, 'GET', 'large')
+ assert status == 200 and headers['ETag'] == '"3"' and body == b''
+ assert rpc(meta, 'GET', 'absent')[0] == 404
+ assert rpc(meta, 'GET', 'broken')[0] == 500
+ assert meta.stats['peak_retained_payload_bytes'] == 0
+ records.append(meta.stats)
+ checks.append('body-free response preserves read existence and malformed-object errors')
+
+ echo = author('echo.pure', get + ' on put { let body = request.body; '
+ 'let saved = store.write(request.key, request.revision, body); reply(saved, body); }')
+ payload = bytes(range(256)) * 4096
+ with serving(binary, storage, budget=len(payload), program=echo) as echoed:
+ with socket.create_connection(('127.0.0.1', echoed.port), timeout=8) as sock:
+ sock.sendall(header('PUT', 'echo', len(payload), b'If-None-Match: *\r\n') + payload)
+ response = http.client.HTTPResponse(sock)
+ response.begin()
+ assert response.status == 200 and response.read(1) == payload[:1]
+ assert rpc(echoed, 'GET', 'binary')[0] == 503
+ assert response.read() == payload[1:]
+ response.close()
+ assert rpc(echoed, 'GET', 'binary')[2] == b'new'
+ with socket.create_connection(('127.0.0.1', echoed.port), timeout=8) as sock:
+ sock.sendall(header('PUT', 'echo_abort', len(payload), b'If-None-Match: *\r\n') + payload)
+ assert sock.recv(1)
+ assert rpc(echoed, 'GET', 'binary')[0] == 503
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 0))
+ deadline = time.monotonic() + 3
+ while rpc(echoed, 'GET', 'binary')[0] == 503:
+ assert time.monotonic() < deadline
+ time.sleep(0.01)
+ assert rpc(echoed, 'GET', 'echo_abort')[2] == payload
+ records.append(echoed.stats)
+ checks.append('input-backed response survives partial send and releases on finish or abort without undoing commit')
+
+ bad = author('invalid.pure', get + get + put)
+ untouched = directory / 'must_not_exist'
+ rejected = subprocess.run([str(binary), '--program', str(bad), '--store', str(untouched)],
+ capture_output=True, text=True, timeout=10)
+ assert rejected.returncode == 2 and 'duplicate handler' in rejected.stderr and not untouched.exists()
+ checks.append('invalid authored program fails preparation before creating storage or accepting requests')
+ return prepared
+
+
+def rpc_bytes(path):
+ # CLI export is pretty JSON; wire export is compact JSON with the same model.
+ return json.dumps(json.loads(path.read_text()), ensure_ascii=False, separators=(',', ':')).encode()
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--server', type=Path, default=ROOT / 'build/pure-connections')
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args()
+ if args.output and args.output.exists():
+ parser.error('evidence file already exists')
+ binary = args.server.resolve()
+ with tempfile.TemporaryDirectory(prefix='pure-connections-check-') as temporary:
+ result = check(binary, Path(temporary))
+ result['binary_sha256'] = hashlib.sha256(binary.read_bytes()).hexdigest()
+ result['environment'] = {'platform': platform.platform(), 'machine': platform.machine(),
+ 'python': platform.python_version(), 'node': subprocess.check_output(['node', '--version'], text=True).strip()}
+ result['source_sha256'] = {str(path.relative_to(ROOT.parent.parent)): hashlib.sha256(path.read_bytes()).hexdigest()
+ for path in [*(ROOT / name for name in ('flow.c', 'flow.h', 'store.c', 'store.h', 'program.c', 'program.h',
+ 'server.c', 'check.py', 'test-flow.c', 'test-program.c', 'Makefile', 'examples/storage.pure', 'examples/excerpt.pure')),
+ *(WORKBENCH / name for name in ('remote.mjs', 'cli.mjs', 'web/core.wasm', 'web/runtime.mjs',
+ 'web/compiler.mjs', 'web/project.mjs', 'examples/direct.pure'))]}
+ output = json.dumps(result, ensure_ascii=False, indent=2) + '\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ with args.output.open('x') as destination:
+ destination.write(output)
+ print(output, end='')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/excerpt.pure b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/excerpt.pure
new file mode 100644
index 0000000..622f9bb
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/excerpt.pure
@@ -0,0 +1,13 @@
+// Read bytes 32..47 of an object that must contain the complete 64-byte window.
+// Preparation keeps the size check for 16..79, and maps only 32..47 for reply.
+on get {
+ let object = store.read(request.key);
+ let window = range(object.body, 16, 64);
+ let excerpt = range(window, 16, 16);
+ reply(object, excerpt);
+}
+
+on put {
+ let saved = store.write(request.key, request.revision, request.body);
+ reply(saved);
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/storage.pure b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/storage.pure
new file mode 100644
index 0000000..ff54ea3
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/examples/storage.pure
@@ -0,0 +1,11 @@
+// The host supplies request bytes, an object name, and the expected revision.
+// Failed storage results propagate to the host's response without executing reply.
+on get {
+ let object = store.read(request.key);
+ reply(object, object.body);
+}
+
+on put {
+ let saved = store.write(request.key, request.revision, request.body);
+ reply(saved);
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.c
new file mode 100644
index 0000000..1070c70
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.c
@@ -0,0 +1,41 @@
+#include "flow.h"
+#include <assert.h>
+
+void pure_transfer_abort(PureTransfer *t) {
+ if (!t->active) return;
+ PureRelease release = t->release;
+ void *owner = t->owner;
+ *t = (PureTransfer){0};
+ if (release) release(owner);
+}
+
+static void advance(PureTransfer *t) {
+ while (t->active && t->part < 2 && t->offset == t->spans[t->part].size) {
+ t->part++;
+ t->offset = 0;
+ }
+ if (t->active && t->part == 2) pure_transfer_abort(t);
+}
+
+void pure_transfer_init(PureTransfer *t, PureSpan header, PureSpan body,
+ void *owner, PureRelease release) {
+ assert(!t->active);
+ assert((!header.size || header.data) && (!body.size || body.data));
+ *t = (PureTransfer){.spans = {header, body}, .owner = owner,
+ .release = release, .active = true};
+ advance(t);
+}
+
+PureSpan pure_transfer_next(const PureTransfer *t, size_t limit) {
+ if (!t->active) return (PureSpan){0};
+ PureSpan span = t->spans[t->part];
+ size_t remaining = span.size - t->offset;
+ return (PureSpan){span.data + t->offset, remaining < limit ? remaining : limit};
+}
+
+bool pure_transfer_consume(PureTransfer *t, size_t bytes) {
+ if (!t->active || bytes > t->spans[t->part].size - t->offset) return false;
+ t->offset += bytes;
+ advance(t);
+ return true;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.h
new file mode 100644
index 0000000..6e27c58
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/flow.h
@@ -0,0 +1,25 @@
+#ifndef PURE_FLOW_H
+#define PURE_FLOW_H
+#include <stdbool.h>
+#include <stddef.h>
+
+typedef struct { const unsigned char *data; size_t size; } PureSpan;
+typedef void (*PureRelease)(void *owner);
+
+/* A borrowed body stays alive until every byte is consumed or the transfer is
+ * aborted. A successful send() consumes bytes; peer receipt is a separate fact.
+ * Single owner thread; callbacks must not reenter or reuse this transfer. */
+typedef struct {
+ PureSpan spans[2];
+ size_t part, offset;
+ void *owner;
+ PureRelease release;
+ bool active;
+} PureTransfer;
+
+void pure_transfer_init(PureTransfer *, PureSpan header, PureSpan body,
+ void *owner, PureRelease);
+PureSpan pure_transfer_next(const PureTransfer *, size_t limit);
+bool pure_transfer_consume(PureTransfer *, size_t bytes);
+void pure_transfer_abort(PureTransfer *);
+#endif
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.c
new file mode 100644
index 0000000..79101db
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.c
@@ -0,0 +1,233 @@
+#include "program.h"
+#include "store.h"
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+enum { NAME = 256, NUMBER, SYMBOLS = 32 };
+typedef struct {
+ char name[32];
+ bool result;
+ PureRegion region;
+} Symbol;
+typedef struct {
+ const char *source;
+ size_t length, at, line, token_line;
+ int token;
+ char name[32];
+ size_t number;
+ char *error;
+ size_t error_size;
+ bool failed, put, effect;
+ Symbol symbols[SYMBOLS];
+ size_t count;
+ PureProgram program;
+} Parser;
+
+static bool reject(Parser *p, const char *message) {
+ if (!p->failed && p->error_size) snprintf(p->error, p->error_size, "line %zu: %s", p->token_line, message);
+ p->failed = true;
+ return false;
+}
+
+static bool letter(char c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
+}
+
+static bool next(Parser *p) {
+ while (p->at < p->length) {
+ char c = p->source[p->at];
+ if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
+ if (c == '\n') ++p->line;
+ ++p->at;
+ } else if (c == '/' && p->at + 1 < p->length && p->source[p->at + 1] == '/') {
+ while (p->at < p->length && p->source[p->at] != '\n') ++p->at;
+ } else break;
+ }
+ p->token_line = p->line;
+ p->name[0] = 0;
+ p->token = 0;
+ if (p->at == p->length) return true;
+ char c = p->source[p->at++];
+ if (letter(c)) {
+ size_t start = p->at - 1;
+ while (p->at < p->length && (letter(p->source[p->at]) ||
+ (p->source[p->at] >= '0' && p->source[p->at] <= '9'))) ++p->at;
+ size_t size = p->at - start;
+ if (size >= sizeof(p->name)) return reject(p, "name exceeds 31 bytes");
+ memcpy(p->name, p->source + start, size);
+ p->name[size] = 0;
+ p->token = NAME;
+ } else if (c >= '0' && c <= '9') {
+ size_t value = (size_t)(c - '0');
+ while (p->at < p->length && p->source[p->at] >= '0' && p->source[p->at] <= '9') {
+ unsigned digit = (unsigned)(p->source[p->at++] - '0');
+ if (value > (SIZE_MAX - digit) / 10) return reject(p, "integer overflow");
+ value = value * 10 + digit;
+ }
+ p->number = value;
+ p->token = NUMBER;
+ } else if (c && strchr("{}().,=;", c)) p->token = (unsigned char)c;
+ else return reject(p, "unexpected character");
+ return true;
+}
+
+static bool is_word(Parser *p, const char *word) {
+ return p->token == NAME && !strcmp(p->name, word);
+}
+
+static bool word(Parser *p, const char *expected) {
+ return is_word(p, expected) ? next(p) : reject(p, expected);
+}
+
+static bool token(Parser *p, int expected) {
+ return p->token == expected ? next(p) : reject(p, "unexpected token");
+}
+
+static Symbol *find(Parser *p, const char *name) {
+ for (size_t i = 0; i < p->count; ++i)
+ if (!strcmp(name, p->symbols[i].name)) return &p->symbols[i];
+ return NULL;
+}
+
+static bool field(Parser *p, const char *object, const char *name) {
+ return word(p, object) && token(p, '.') && word(p, name);
+}
+
+static bool reference(Parser *p, PureRegion *out) {
+ if (is_word(p, "request")) {
+ if (!p->put) return reject(p, "request.body is only available in on put");
+ *out = (PureRegion){.whole = true};
+ return field(p, "request", "body");
+ }
+ Symbol *s = p->token == NAME ? find(p, p->name) : NULL;
+ if (!s) return reject(p, "unknown byte source");
+ bool result = s->result;
+ *out = s->region;
+ if (!next(p)) return false;
+ if (!result) return true;
+ if (p->put) return reject(p, "write result has no body; use the input value");
+ *out = (PureRegion){.whole = true};
+ return token(p, '.') && word(p, "body");
+}
+
+static bool view(Parser *p, PureRegion *out) {
+ if (!is_word(p, "range")) return reference(p, out);
+ PureRegion parent;
+ size_t offset, length;
+ if (!next(p) || !token(p, '(') || !reference(p, &parent) || !token(p, ',')) return false;
+ if (p->token != NUMBER) return reject(p, "range offset must be a constant integer");
+ offset = p->number;
+ if (!next(p) || !token(p, ',')) return false;
+ if (p->token != NUMBER) return reject(p, "range length must be a constant integer");
+ length = p->number;
+ if (!next(p) || !token(p, ')')) return false;
+ size_t bound = parent.whole ? PURE_OBJECT_LIMIT : parent.length;
+ if (offset > bound || length > bound - offset) return reject(p, "range exceeds its source bound");
+ *out = (PureRegion){.offset = parent.offset + offset, .length = length};
+ size_t required = out->offset + out->length;
+ size_t *minimum = p->put ? &p->program.input_required : &p->program.read_required;
+ if (required > *minimum) *minimum = required;
+ ++p->program.range_definitions;
+ return true;
+}
+
+static bool effect(Parser *p, Symbol *out) {
+ if (p->effect) return reject(p, "only one storage effect is supported per handler");
+ if (!word(p, "store") || !token(p, '.') || !word(p, p->put ? "write" : "read") ||
+ !token(p, '(') || !field(p, "request", "key")) return false;
+ if (p->put) {
+ if (!token(p, ',') || !field(p, "request", "revision") || !token(p, ',') ||
+ !reference(p, &p->program.write)) return false;
+ }
+ if (!token(p, ')')) return false;
+ out->result = true;
+ p->effect = true;
+ return true;
+}
+
+static bool handler(Parser *p) {
+ p->count = 0;
+ p->effect = false;
+ if (!token(p, '{')) return false;
+ while (is_word(p, "let")) {
+ if (p->put && p->effect) return reject(p, "all views must precede store.write; reply must follow it");
+ if (p->count == SYMBOLS) return reject(p, "handler exceeds 32 definitions");
+ if (!next(p)) return false;
+ if (p->token != NAME || is_word(p, "request") || is_word(p, "store") || is_word(p, "range"))
+ return reject(p, "expected a non-reserved definition name");
+ if (find(p, p->name)) return reject(p, "duplicate definition");
+ Symbol symbol = {0};
+ strcpy(symbol.name, p->name);
+ if (!next(p) || !token(p, '=')) return false;
+ if (is_word(p, "store")) {
+ if (!effect(p, &symbol)) return false;
+ } else {
+ if (!view(p, &symbol.region)) return false;
+ ++p->program.view_definitions;
+ }
+ if (!token(p, ';')) return false;
+ p->symbols[p->count++] = symbol;
+ }
+ if (!p->effect) return reject(p, "handler requires its storage effect");
+ if (!word(p, "reply") || !token(p, '(')) return false;
+ Symbol *result = p->token == NAME ? find(p, p->name) : NULL;
+ if (!result || !result->result) return reject(p, "reply requires this handler's storage result");
+ if (!next(p)) return false;
+ PureRegion body = {0};
+ bool has_body = p->token == ',';
+ if (has_body && (!next(p) || !reference(p, &body))) return false;
+ if (!token(p, ')') || !token(p, ';') || !token(p, '}')) return false;
+ if (p->put) {
+ p->program.reply = body;
+ p->program.reply_input = has_body;
+ } else p->program.read = body;
+ return true;
+}
+
+bool pure_program_parse(const char *source, size_t length, PureProgram *out,
+ char *error, size_t error_size) {
+ Parser p = {.source = source, .length = length, .line = 1, .token_line = 1,
+ .error = error, .error_size = error_size};
+ if (error_size) error[0] = 0;
+ if (length > PURE_PROGRAM_LIMIT) return reject(&p, "program exceeds 16 KiB");
+ if (!next(&p)) return false;
+ unsigned handlers = 0;
+ while (p.token) {
+ if (!word(&p, "on")) return false;
+ if (!is_word(&p, "get") && !is_word(&p, "put")) return reject(&p, "expected get or put handler");
+ p.put = is_word(&p, "put");
+ unsigned bit = p.put ? 2 : 1;
+ if (handlers & bit) return reject(&p, "duplicate handler");
+ handlers |= bit;
+ if (!next(&p) || !handler(&p)) return false;
+ }
+ if (handlers != 3) return reject(&p, "both get and put handlers are required");
+ *out = p.program; /* Invalid preparation never changes the installed plan. */
+ return true;
+}
+
+bool pure_program_load(const char *path, PureProgram *out, char *error, size_t error_size) {
+ FILE *file = fopen(path, "rb");
+ if (!file) {
+ if (error_size) snprintf(error, error_size, "cannot open program");
+ return false;
+ }
+ char source[PURE_PROGRAM_LIMIT + 1];
+ size_t length = fread(source, 1, sizeof(source), file);
+ bool bad = ferror(file) != 0;
+ if (fclose(file)) bad = true;
+ if (bad) {
+ if (error_size) snprintf(error, error_size, "cannot read program");
+ return false;
+ }
+ return pure_program_parse(source, length, out, error, error_size);
+}
+
+bool pure_region_span(PureRegion region, PureSpan source, PureSpan *out) {
+ *out = (PureSpan){0};
+ if (region.whole) { *out = source; return true; }
+ if (region.offset > source.size || region.length > source.size - region.offset) return false;
+ *out = (PureSpan){region.length ? source.data + region.offset : NULL, region.length};
+ return true;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.h b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.h
new file mode 100644
index 0000000..29764db
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/program.h
@@ -0,0 +1,26 @@
+#ifndef PURE_PROGRAM_H
+#define PURE_PROGRAM_H
+#include "flow.h"
+
+#define PURE_PROGRAM_LIMIT 16384u
+typedef struct {
+ size_t offset, length;
+ bool whole;
+} PureRegion;
+
+/* Prepared connections, not an instruction stream. All view definitions are
+ * checked at preparation; their root-length requirements survive elimination.
+ * Input views are evaluated before the write effect. A read still opens and
+ * validates the object even if its response has no body. */
+typedef struct {
+ PureRegion read, write, reply;
+ size_t read_required, input_required;
+ size_t view_definitions, range_definitions;
+ bool reply_input;
+} PureProgram;
+
+bool pure_program_parse(const char *source, size_t length, PureProgram *out,
+ char *error, size_t error_size);
+bool pure_program_load(const char *path, PureProgram *out, char *error, size_t error_size);
+bool pure_region_span(PureRegion region, PureSpan source, PureSpan *out);
+#endif
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/server.c
new file mode 100644
index 0000000..3822c66
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/server.c
@@ -0,0 +1,385 @@
+#define _POSIX_C_SOURCE 200809L
+#include "store.h"
+#include "program.h"
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/socket.h>
+#include <time.h>
+#include <unistd.h>
+
+enum { SLOTS = 8, HEADER_LIMIT = 4096, CHUNK = 65536 };
+typedef enum { READING_HEADER, READING_BODY, WRITING } Phase;
+typedef struct Server Server;
+typedef struct {
+ int fd;
+ Phase phase;
+ uint64_t deadline;
+ char request[HEADER_LIMIT + 1], response[512], key[65];
+ size_t header_size, body_size, received, charged;
+ unsigned char *body;
+ bool put;
+ int64_t expected;
+ PureStoredView stored;
+ PureTransfer transfer;
+ Server *server;
+} Connection;
+struct Server {
+ const char *path;
+ PureProgram program;
+ size_t budget, retained, peak_retained, active, peak_active;
+ uint64_t accepted, commits, gets, conflicts, budget_rejections, expired;
+ uint64_t releases, prefix_copy_bytes, borrowed_output_bytes;
+ uint64_t timeout_ms;
+ int send_buffer;
+ Connection connections[SLOTS];
+};
+static volatile sig_atomic_t stopping;
+static void stop_server(int sig) { (void)sig; stopping = 1; }
+
+static uint64_t now_ms(void) {
+ struct timespec t;
+ if (clock_gettime(CLOCK_MONOTONIC, &t)) abort();
+ return (uint64_t)t.tv_sec * 1000 + (uint64_t)t.tv_nsec / 1000000;
+}
+
+static bool number(const char *text, uint64_t maximum, uint64_t *value) {
+ if (!*text) return false;
+ uint64_t n = 0;
+ for (; *text; ++text) {
+ if (*text < '0' || *text > '9') return false;
+ unsigned digit = (unsigned)(*text - '0');
+ if (digit > maximum || n > (maximum - digit) / 10) return false;
+ n = n * 10 + digit;
+ }
+ *value = n;
+ return true;
+}
+
+static void release_payload(void *owner) {
+ Connection *c = owner;
+ bool owned = c->body || c->stored.mapped_size || c->charged;
+ pure_store_release(&c->stored);
+ free(c->body);
+ c->body = NULL;
+ assert(c->charged <= c->server->retained);
+ c->server->retained -= c->charged;
+ c->charged = 0;
+ if (owned) ++c->server->releases;
+}
+
+static void close_connection(Connection *c) {
+ if (c->fd < 0) return;
+ if (c->transfer.active) pure_transfer_abort(&c->transfer);
+ else if (c->body || c->stored.mapped_size || c->charged) release_payload(c);
+ close(c->fd);
+ --c->server->active;
+ *c = (Connection){.fd = -1};
+}
+
+static bool charge(Connection *c, size_t size) {
+ Server *s = c->server;
+ if (size > s->budget - s->retained) { ++s->budget_rejections; return false; }
+ s->retained += size;
+ c->charged += size;
+ if (s->retained > s->peak_retained) s->peak_retained = s->retained;
+ return true;
+}
+
+static void respond(Connection *c, int status, const char *reason, const char *error,
+ int64_t revision, PureSpan body) {
+ char tag[80] = "";
+ if (revision > 0) snprintf(tag, sizeof(tag), "ETag: \"%" PRId64 "\"\r\n", revision);
+ int length = snprintf(c->response, sizeof(c->response),
+ "HTTP/1.1 %d %s\r\nContent-Length: %zu\r\nConnection: close\r\n"
+ "Content-Type: application/octet-stream\r\nCache-Control: no-store\r\n"
+ "X-Pure-Error: %s\r\n%s\r\n", status, reason, body.size, error, tag);
+ assert(length > 0 && (size_t)length < sizeof(c->response));
+ c->phase = WRITING;
+ pure_transfer_init(&c->transfer,
+ (PureSpan){(const unsigned char *)c->response, (size_t)length}, body, c, release_payload);
+}
+
+static void fail(Connection *c, int status, const char *reason, const char *error) {
+ /* No outstanding backend access on the owner thread at this point. */
+ if (c->body || c->stored.mapped_size || c->charged) release_payload(c);
+ respond(c, status, reason, error, 0, (PureSpan){0});
+}
+
+static void run_request(Connection *c) {
+ PureResult result;
+ int64_t revision = 0;
+ PureProgram *program = &c->server->program;
+ PureSpan output = {0};
+ if (c->put) {
+ PureSpan source = {c->body, c->body_size}, value;
+ /* Validate every authored view before performing an external effect,
+ * including unused views. Static preparation preserves these guards. */
+ if (source.size < program->input_required ||
+ !pure_region_span(program->write, source, &value) ||
+ (program->reply_input && !pure_region_span(program->reply, source, &output))) {
+ fail(c, 413, "Content Too Large", "limit");
+ return;
+ }
+ result = pure_store_put(c->server->path, c->key, c->expected, value, &revision);
+ /* A reply can consume the same input. Its owner lives until transfer
+ * completion; otherwise storage completion ends the input lifetime. */
+ if (result != PURE_OK || !output.size) { release_payload(c); output = (PureSpan){0}; }
+ if (result == PURE_OK) {
+ ++c->server->commits;
+ c->server->borrowed_output_bytes += output.size;
+ }
+ } else {
+ result = pure_store_select(c->server->path, c->key, program->read.offset,
+ program->read.length, program->read.whole, program->read_required,
+ c->server->budget - c->server->retained, &c->stored);
+ if (result == PURE_OK) {
+ if (!charge(c, c->stored.body.size)) abort(); /* Same owner thread. */
+ revision = c->stored.revision;
+ output = c->stored.body;
+ ++c->server->gets;
+ c->server->borrowed_output_bytes += c->stored.body.size;
+ } else if (result == PURE_BUDGET) ++c->server->budget_rejections;
+ }
+ switch (result) {
+ case PURE_OK: respond(c, 200, "OK", "none", revision, output); break;
+ case PURE_NOT_FOUND: fail(c, 404, "Not Found", "not_found"); break;
+ case PURE_CONFLICT:
+ ++c->server->conflicts;
+ respond(c, 412, "Precondition Failed", "conflict", revision, (PureSpan){0}); break;
+ case PURE_BUSY: fail(c, 503, "Service Unavailable", "store_busy"); break;
+ case PURE_BUDGET: fail(c, 503, "Service Unavailable", "payload_budget"); break;
+ case PURE_LIMIT: fail(c, 413, "Content Too Large", "limit"); break;
+ case PURE_UNCERTAIN: fail(c, 500, "Internal Server Error", "durability_unknown"); break;
+ default: fail(c, 500, "Internal Server Error", "storage"); break;
+ }
+}
+
+/* Deliberately bounded HTTP/1.1 subset: one GET/PUT, Content-Length, close.
+ * No transfer coding, pipelining, trailers, Expect, URL decoding, or proxying. */
+static bool parse_header(Connection *c, size_t end) {
+ for (size_t i = 0; i < end; ++i) {
+ unsigned char b = (unsigned char)c->request[i];
+ if ((b < 32 && b != '\r' && b != '\n' && b != '\t') || b == 127) return false;
+ }
+ char *line = c->request, *next = strstr(line, "\r\n");
+ if (!next) return false;
+ *next = 0;
+ char *path = strchr(line, ' ');
+ if (!path) return false;
+ *path++ = 0;
+ if (strcmp(line, "PUT") == 0) c->put = true;
+ else if (strcmp(line, "GET") != 0) return false;
+ char *version = strchr(path, ' ');
+ if (!version) return false;
+ *version++ = 0;
+ if (strcmp(version, "HTTP/1.1") || strncmp(path, "/objects/", 9)) return false;
+ const char *key = path + 9;
+ size_t key_size = strlen(key);
+ if (!key_size || key_size > 64) return false;
+ for (size_t i = 0; i < key_size; ++i) {
+ char b = key[i];
+ if (!((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') ||
+ (b >= '0' && b <= '9') || b == '-' || b == '_')) return false;
+ }
+ memcpy(c->key, key, key_size + 1);
+ bool length_seen = false, condition_seen = false, host_seen = false;
+ line = next + 2;
+ while (*line != '\r') {
+ next = strstr(line, "\r\n");
+ if (!next) return false;
+ *next = 0;
+ char *value = strchr(line, ':');
+ if (!value || value == line) return false;
+ *value++ = 0;
+ for (char *p = line; *p; ++p)
+ if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
+ (*p >= '0' && *p <= '9') || *p == '-')) return false;
+ while (*value == ' ' || *value == '\t') ++value;
+ char *tail = value + strlen(value);
+ while (tail > value && (tail[-1] == ' ' || tail[-1] == '\t')) *--tail = 0;
+ if (strchr(value, '\r') || strchr(value, '\n')) return false;
+ uint64_t n;
+ if (!strcasecmp(line, "Content-Length")) {
+ if (length_seen || !number(value, SIZE_MAX, &n)) return false;
+ length_seen = true;
+ c->body_size = (size_t)n;
+ } else if (!strcasecmp(line, "Host")) {
+ if (host_seen || !*value) return false;
+ host_seen = true;
+ } else if (!strcasecmp(line, "If-Match")) {
+ if (condition_seen || tail - value < 3 || *value != '"' || tail[-1] != '"') return false;
+ tail[-1] = 0;
+ if (!number(value + 1, INT64_MAX, &n) || !n) return false;
+ c->expected = (int64_t)n;
+ condition_seen = true;
+ } else if (!strcasecmp(line, "If-None-Match")) {
+ if (condition_seen || strcmp(value, "*")) return false;
+ c->expected = 0;
+ condition_seen = true;
+ } else if (!strcasecmp(line, "Transfer-Encoding") || !strcasecmp(line, "Expect")) return false;
+ line = next + 2;
+ }
+ return host_seen && (c->put ? length_seen && condition_seen : !condition_seen && c->body_size == 0);
+}
+
+static void read_connection(Connection *c) {
+ if (c->phase == READING_HEADER) {
+ ssize_t n = recv(c->fd, c->request + c->header_size, HEADER_LIMIT - c->header_size, 0);
+ if (n < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) close_connection(c); return; }
+ if (!n) { close_connection(c); return; }
+ c->header_size += (size_t)n;
+ c->request[c->header_size] = 0;
+ /* Scan by byte count, so embedded NUL cannot hide a terminator. */
+ size_t end = 0;
+ for (size_t i = 3; i < c->header_size; ++i)
+ if (!memcmp(c->request + i - 3, "\r\n\r\n", 4)) { end = i + 1; break; }
+ if (!end) {
+ if (c->header_size == HEADER_LIMIT) fail(c, 431, "Request Header Fields Too Large", "headers");
+ return;
+ }
+ size_t prefix = c->header_size - end;
+ if (!parse_header(c, end)) { fail(c, 400, "Bad Request", "request"); return; }
+ if (c->body_size > PURE_OBJECT_LIMIT) { fail(c, 413, "Content Too Large", "limit"); return; }
+ if (prefix > c->body_size) { fail(c, 400, "Bad Request", "extra_body"); return; }
+ if (!c->put) { run_request(c); return; }
+ if (!charge(c, c->body_size)) { fail(c, 503, "Service Unavailable", "payload_budget"); return; }
+ c->body = malloc(c->body_size ? c->body_size : 1);
+ if (!c->body) { fail(c, 503, "Service Unavailable", "allocation"); return; }
+ if (prefix) memcpy(c->body, c->request + end, prefix);
+ c->server->prefix_copy_bytes += prefix;
+ c->received = prefix;
+ c->phase = READING_BODY;
+ if (c->received == c->body_size) run_request(c);
+ } else {
+ size_t remaining = c->body_size - c->received;
+ ssize_t n = recv(c->fd, c->body + c->received, remaining < CHUNK ? remaining : CHUNK, 0);
+ if (n < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) close_connection(c); return; }
+ if (!n) { close_connection(c); return; }
+ c->received += (size_t)n;
+ if (c->received == c->body_size) run_request(c);
+ }
+}
+
+static void write_connection(Connection *c) {
+ PureSpan span = pure_transfer_next(&c->transfer, CHUNK);
+ ssize_t n = send(c->fd, span.data, span.size, 0);
+ if (n < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) close_connection(c); return; }
+ if (!n) { close_connection(c); return; }
+ if (!pure_transfer_consume(&c->transfer, (size_t)n)) abort();
+ if (!c->transfer.active) close_connection(c);
+}
+
+static bool nonblocking(int fd) {
+ int flags = fcntl(fd, F_GETFL, 0);
+ return flags >= 0 && fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
+}
+
+int main(int argc, char **argv) {
+ Server server = {.budget = 4u * 1024u * 1024u, .timeout_ms = 10000};
+ unsigned port = 8767;
+ const char *program_path = NULL;
+ bool explain = argc == 3 && !strcmp(argv[1], "--check-program");
+ char program_error[160];
+ if (explain) program_path = argv[2];
+ for (int i = 1; !explain && i < argc; i += 2) {
+ if (i + 1 == argc) goto usage;
+ uint64_t value;
+ if (!strcmp(argv[i], "--store")) server.path = argv[i + 1];
+ else if (!strcmp(argv[i], "--program")) program_path = argv[i + 1];
+ else if (!strcmp(argv[i], "--port") && number(argv[i + 1], 65535, &value)) port = (unsigned)value;
+ else if (!strcmp(argv[i], "--budget") && number(argv[i + 1], SIZE_MAX, &value) && value) server.budget = (size_t)value;
+ else if (!strcmp(argv[i], "--timeout-ms") && number(argv[i + 1], 3600000, &value) && value) server.timeout_ms = value;
+ else if (!strcmp(argv[i], "--send-buffer") && number(argv[i + 1], INT32_MAX, &value)) server.send_buffer = (int)value;
+ else goto usage;
+ }
+ if (!program_path || (!explain && !server.path)) goto usage;
+ if (!pure_program_load(program_path, &server.program, program_error, sizeof(program_error))) {
+ fprintf(stderr, "Program: %s\n", program_error); return 2;
+ }
+ if (explain) {
+ PureProgram *p = &server.program;
+ printf("{\"read\":{\"whole\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu},"
+ "\"write\":{\"whole\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu},"
+ "\"reply_input\":%s,\"view_definitions\":%zu,\"range_definitions\":%zu,"
+ "\"runtime_view_steps\":0,\"prepared_program_bytes\":%zu}\n",
+ p->read.whole ? "true" : "false", p->read.offset, p->read.length, p->read_required,
+ p->write.whole ? "true" : "false", p->write.offset, p->write.length, p->input_required,
+ p->reply_input ? "true" : "false", p->view_definitions, p->range_definitions, sizeof(*p));
+ return 0;
+ }
+ if (pure_store_init(server.path) != PURE_OK) { fputs("Cannot initialize byte store directory\n", stderr); return 1; }
+ signal(SIGPIPE, SIG_IGN);
+ signal(SIGINT, stop_server);
+ signal(SIGTERM, stop_server);
+ int listener = socket(AF_INET, SOCK_STREAM, 0);
+ if (listener < 0) { perror("socket"); return 1; }
+ int one = 1;
+ setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
+ struct sockaddr_in address = {.sin_family = AF_INET, .sin_port = htons((uint16_t)port),
+ .sin_addr.s_addr = htonl(INADDR_LOOPBACK)};
+ if (bind(listener, (struct sockaddr *)&address, sizeof(address)) || listen(listener, SLOTS) || !nonblocking(listener)) {
+ perror("listen"); close(listener); return 1;
+ }
+ socklen_t address_size = sizeof(address);
+ if (getsockname(listener, (struct sockaddr *)&address, &address_size)) { close(listener); return 1; }
+ for (size_t i = 0; i < SLOTS; ++i) server.connections[i].fd = -1;
+ printf("{\"port\":%u,\"storage\":\"immutable-byte-extents-v1\"}\n", ntohs(address.sin_port));
+ fflush(stdout);
+ while (!stopping) {
+ struct pollfd polls[SLOTS + 1] = {{.fd = server.active < SLOTS ? listener : -1, .events = POLLIN}};
+ for (size_t i = 0; i < SLOTS; ++i) {
+ Connection *c = &server.connections[i];
+ polls[i + 1] = (struct pollfd){.fd = c->fd, .events = c->phase == WRITING ? POLLOUT : POLLIN};
+ }
+ int ready = poll(polls, SLOTS + 1, 50);
+ if (ready < 0 && errno != EINTR) { perror("poll"); stopping = 1; }
+ uint64_t now = now_ms();
+ for (size_t i = 0; i < SLOTS; ++i) {
+ Connection *c = &server.connections[i];
+ if (c->fd < 0) continue;
+ if (now >= c->deadline) { ++server.expired; close_connection(c); continue; }
+ short events = polls[i + 1].revents;
+ if (events & (POLLERR | POLLNVAL)) { close_connection(c); continue; }
+ if (events & (POLLIN | POLLOUT | POLLHUP)) {
+ if (c->phase == WRITING) write_connection(c); else read_connection(c);
+ }
+ }
+ if (!stopping && (polls[0].revents & POLLIN)) {
+ int fd = accept(listener, NULL, NULL);
+ if (fd < 0) continue;
+ if (!nonblocking(fd)) { close(fd); continue; }
+ if (server.send_buffer) setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &server.send_buffer, sizeof(server.send_buffer));
+ for (size_t i = 0; i < SLOTS; ++i) if (server.connections[i].fd < 0) {
+ server.connections[i] = (Connection){.fd = fd, .server = &server, .deadline = now_ms() + server.timeout_ms};
+ ++server.active;
+ ++server.accepted;
+ if (server.active > server.peak_active) server.peak_active = server.active;
+ break;
+ }
+ }
+ }
+ close(listener);
+ for (size_t i = 0; i < SLOTS; ++i) close_connection(&server.connections[i]);
+ fprintf(stderr, "{\"active\":%zu,\"retained_payload_bytes\":%zu,\"peak_retained_payload_bytes\":%zu,"
+ "\"payload_budget\":%zu,\"peak_connections\":%zu,\"accepted\":%" PRIu64 ",\"commits\":%" PRIu64 ","
+ "\"gets\":%" PRIu64 ",\"conflicts\":%" PRIu64 ",\"budget_rejections\":%" PRIu64 ","
+ "\"expired\":%" PRIu64 ",\"releases\":%" PRIu64 ",\"ingress_prefix_copy_bytes\":%" PRIu64 ","
+ "\"borrowed_output_bytes\":%" PRIu64 ",\"server_structure_bytes\":%zu}\n",
+ server.active, server.retained, server.peak_retained, server.budget, server.peak_active,
+ server.accepted, server.commits, server.gets, server.conflicts, server.budget_rejections,
+ server.expired, server.releases, server.prefix_copy_bytes, server.borrowed_output_bytes, sizeof(server));
+ return 0;
+usage:
+ fputs("pure-connections --program FILE --store DIRECTORY [--port 8767] [--budget BYTES] [--timeout-ms 10000] [--send-buffer BYTES]\n"
+ "pure-connections --check-program FILE\n", stderr);
+ return 2;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.c
new file mode 100644
index 0000000..759f210
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.c
@@ -0,0 +1,227 @@
+#define _POSIX_C_SOURCE 200809L
+#include "store.h"
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* Immutable extent envelope: marker, little-endian u64 revision and payload
+ * length, then unchanged caller bytes. Single-threaded control API; cooperating
+ * processes serialize publication using .writer.lock. */
+enum { HEADER_BYTES = 24 };
+static const unsigned char magic[8] = {'P','U','R','E','B','0','0','1'};
+static uint64_t temporary_sequence;
+
+static bool fault(PureStoreStage stage) {
+#ifdef PURE_STORE_TEST
+ if (pure_store_test_fault(stage)) { errno = EIO; return true; }
+#else
+ (void)stage;
+#endif
+ return false;
+}
+
+static bool file_name(const char *key, char name[72]) {
+ if (!key) return false;
+ size_t length = strlen(key);
+ if (!length || length > 64) return false;
+ for (size_t i = 0; i < length; ++i) {
+ char b = key[i];
+ if (!((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') ||
+ (b >= '0' && b <= '9') || b == '-' || b == '_')) return false;
+ }
+ snprintf(name, 72, "obj-%s", key);
+ return true;
+}
+
+static uint64_t decode64(const unsigned char *bytes) {
+ uint64_t value = 0;
+ for (unsigned i = 0; i < 8; ++i) value |= (uint64_t)bytes[i] << (8 * i);
+ return value;
+}
+
+static void encode64(unsigned char *bytes, uint64_t value) {
+ for (unsigned i = 0; i < 8; ++i) bytes[i] = (unsigned char)(value >> (8 * i));
+}
+
+static bool sync_fd(int fd) {
+ int rc;
+ do { rc = fsync(fd); } while (rc && errno == EINTR);
+ return rc == 0;
+}
+
+static bool write_bytes(int fd, const unsigned char *bytes, size_t length) {
+ while (length) {
+ ssize_t n = write(fd, bytes, length);
+ if (n < 0 && errno == EINTR) continue;
+ if (n <= 0) return false;
+ bytes += (size_t)n;
+ length -= (size_t)n;
+ }
+ return true;
+}
+
+static bool read_header(int fd, int64_t *revision, size_t *length) {
+ struct stat info;
+ if (fstat(fd, &info) || !S_ISREG(info.st_mode)) return false;
+ unsigned char header[HEADER_BYTES];
+ size_t used = 0;
+ while (used < sizeof(header)) {
+ ssize_t n = pread(fd, header + used, sizeof(header) - used, (off_t)used);
+ if (n < 0 && errno == EINTR) continue;
+ if (n <= 0) return false;
+ used += (size_t)n;
+ }
+ uint64_t version = decode64(header + 8), size = decode64(header + 16);
+ if (memcmp(header, magic, sizeof(magic)) || !version || version > INT64_MAX ||
+ size > PURE_OBJECT_LIMIT || info.st_size != (off_t)(HEADER_BYTES + size)) return false;
+ *revision = (int64_t)version;
+ *length = (size_t)size;
+ return true;
+}
+
+PureResult pure_store_init(const char *path) {
+ if (!path || !*path) return PURE_STORE_ERROR;
+ bool created = mkdir(path, 0700) == 0;
+ if (!created && errno != EEXIST) return PURE_STORE_ERROR;
+ int directory = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (directory < 0) return PURE_STORE_ERROR; /* Never reinterpret an old DB file. */
+ int lock = openat(directory, ".writer.lock", O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC, 0600);
+ struct stat info;
+ bool ok = lock >= 0 && fstat(lock, &info) == 0 && S_ISREG(info.st_mode) && sync_fd(lock);
+ if (lock >= 0) close(lock);
+ if (ok) ok = sync_fd(directory);
+ close(directory);
+ if (!ok) return PURE_STORE_ERROR;
+ if (created) {
+ char *parent = strdup(path);
+ if (!parent) return PURE_STORE_ERROR;
+ size_t length = strlen(parent);
+ while (length > 1 && parent[length - 1] == '/') parent[--length] = 0;
+ char *slash = strrchr(parent, '/');
+ if (!slash) strcpy(parent, ".");
+ else if (slash == parent) parent[1] = 0;
+ else *slash = 0;
+ int fd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ free(parent);
+ ok = fd >= 0 && sync_fd(fd);
+ if (fd >= 0) close(fd);
+ }
+ return ok ? PURE_OK : PURE_STORE_ERROR;
+}
+
+void pure_store_release(PureStoredView *view) {
+ if (view->mapped_size) munmap(view->mapping, view->mapped_size);
+ *view = (PureStoredView){0};
+}
+
+PureResult pure_store_select(const char *path, const char *key, size_t offset,
+ size_t length, bool whole, size_t minimum,
+ size_t available, PureStoredView *out) {
+ *out = (PureStoredView){0};
+ char name[72];
+ if (!file_name(key, name)) return PURE_STORE_ERROR;
+ int directory = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (directory < 0) return PURE_STORE_ERROR;
+ int fd = openat(directory, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+ int cause = errno;
+ close(directory);
+ if (fd < 0) return cause == ENOENT ? PURE_NOT_FOUND : PURE_STORE_ERROR;
+ int64_t revision;
+ size_t size;
+ PureResult result = PURE_STORE_ERROR;
+ if (!read_header(fd, &revision, &size)) goto finish;
+ if (size < minimum) { result = PURE_LIMIT; goto finish; }
+ if (whole) { offset = 0; length = size; }
+ if (offset > size || length > size - offset) { result = PURE_LIMIT; goto finish; }
+ if (length > available) { result = PURE_BUDGET; goto finish; }
+ if (length) {
+ long page = sysconf(_SC_PAGESIZE);
+ if (page <= 0) goto finish;
+ size_t start = HEADER_BYTES + offset;
+ size_t aligned = start - start % (size_t)page;
+ size_t prefix = start - aligned;
+ size_t mapped_size = prefix + length;
+ void *mapping = mmap(NULL, mapped_size, PROT_READ, MAP_PRIVATE, fd, (off_t)aligned);
+ if (mapping == MAP_FAILED) goto finish;
+ out->mapping = mapping;
+ out->mapped_size = mapped_size;
+ out->body = (PureSpan){(const unsigned char *)mapping + prefix, length};
+ }
+ out->revision = revision;
+ out->object_size = size;
+ result = PURE_OK;
+finish:
+ close(fd); /* The mapping retains this immutable file version after rename. */
+ return result;
+}
+
+PureResult pure_store_get(const char *path, const char *key, size_t available, PureStoredView *out) {
+ return pure_store_select(path, key, 0, 0, true, 0, available, out);
+}
+
+PureResult pure_store_get_range(const char *path, const char *key, size_t offset,
+ size_t length, size_t available, PureStoredView *out) {
+ return pure_store_select(path, key, offset, length, false, 0, available, out);
+}
+
+PureResult pure_store_put(const char *path, const char *key, int64_t expected,
+ PureSpan body, int64_t *revision) {
+ *revision = 0;
+ char name[72], temporary[96] = "";
+ if (!file_name(key, name) || (body.size && !body.data)) return PURE_STORE_ERROR;
+ if (body.size > PURE_OBJECT_LIMIT || expected < 0) return PURE_LIMIT;
+ int directory = open(path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
+ if (directory < 0) return PURE_STORE_ERROR;
+ int lock = openat(directory, ".writer.lock", O_RDWR | O_NOFOLLOW | O_CLOEXEC);
+ int fd = -1;
+ PureResult result = PURE_STORE_ERROR;
+ struct stat info;
+ if (lock < 0 || fstat(lock, &info) || !S_ISREG(info.st_mode)) goto finish;
+ struct flock hold = {.l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0};
+ if (fcntl(lock, F_SETLK, &hold)) {
+ if (errno == EACCES || errno == EAGAIN) result = PURE_BUSY;
+ goto finish;
+ }
+ fd = openat(directory, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC);
+ if (fd >= 0) {
+ size_t size;
+ if (!read_header(fd, revision, &size)) goto finish;
+ close(fd);
+ fd = -1;
+ } else if (errno != ENOENT) goto finish;
+ if (*revision != expected) { result = PURE_CONFLICT; goto finish; }
+ if (*revision == INT64_MAX) { result = PURE_LIMIT; goto finish; }
+ for (unsigned attempt = 0; attempt < 64; ++attempt) {
+ if (temporary_sequence == UINT64_MAX) goto finish;
+ snprintf(temporary, sizeof(temporary), ".pending-%ld-%" PRIu64, (long)getpid(), ++temporary_sequence);
+ fd = openat(directory, temporary, O_WRONLY | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
+ if (fd >= 0) break;
+ temporary[0] = 0; /* Never remove an existing file on a name collision. */
+ if (errno != EEXIST) goto finish;
+ }
+ if (fd < 0) goto finish;
+ unsigned char header[HEADER_BYTES];
+ memcpy(header, magic, sizeof(magic));
+ encode64(header + 8, (uint64_t)*revision + 1);
+ encode64(header + 16, body.size);
+ if (!write_bytes(fd, header, sizeof(header)) || fault(PURE_AFTER_HEADER) ||
+ !write_bytes(fd, body.data, body.size) || fault(PURE_BEFORE_FILE_SYNC) || !sync_fd(fd)) goto finish;
+ int close_result = close(fd);
+ fd = -1;
+ if (close_result || fault(PURE_BEFORE_RENAME) || renameat(directory, temporary, directory, name)) goto finish;
+ temporary[0] = 0; /* Published: a later sync failure does not roll it back. */
+ ++*revision;
+ result = fault(PURE_BEFORE_DIR_SYNC) || !sync_fd(directory) ? PURE_UNCERTAIN : PURE_OK;
+finish:
+ if (fd >= 0) close(fd);
+ if (*temporary) unlinkat(directory, temporary, 0);
+ if (lock >= 0) close(lock);
+ close(directory);
+ return result;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.h
new file mode 100644
index 0000000..b1fbea7
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/store.h
@@ -0,0 +1,39 @@
+#ifndef PURE_STORE_H
+#define PURE_STORE_H
+#include "flow.h"
+#include <stdint.h>
+
+#define PURE_OBJECT_LIMIT ((2u * 6u + 32u) * 32768u + 8192u)
+typedef enum {
+ PURE_OK, PURE_NOT_FOUND, PURE_CONFLICT, PURE_BUSY, PURE_BUDGET,
+ PURE_LIMIT, PURE_STORE_ERROR, PURE_UNCERTAIN
+} PureResult;
+typedef struct {
+ void *mapping;
+ size_t mapped_size, object_size;
+ PureSpan body;
+ int64_t revision;
+} PureStoredView;
+
+PureResult pure_store_init(const char *path);
+PureResult pure_store_get(const char *path, const char *key, size_t available,
+ PureStoredView *out);
+PureResult pure_store_get_range(const char *path, const char *key, size_t offset,
+ size_t length, size_t available, PureStoredView *out);
+/* Inspect once: guards and mapping refer to the same immutable file version.
+ * minimum preserves bounds checks from eliminated intermediate views. */
+PureResult pure_store_select(const char *path, const char *key, size_t offset,
+ size_t length, bool whole, size_t minimum,
+ size_t available, PureStoredView *out);
+void pure_store_release(PureStoredView *);
+PureResult pure_store_put(const char *path, const char *key, int64_t expected,
+ PureSpan body, int64_t *revision);
+
+/* Fault hooks exist only in the separately built unit-test binary. */
+typedef enum {
+ PURE_AFTER_HEADER, PURE_BEFORE_FILE_SYNC, PURE_BEFORE_RENAME, PURE_BEFORE_DIR_SYNC
+} PureStoreStage;
+#ifdef PURE_STORE_TEST
+int pure_store_test_fault(PureStoreStage stage);
+#endif
+#endif
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-flow.c
new file mode 100644
index 0000000..5ff028d
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-flow.c
@@ -0,0 +1,183 @@
+#define _POSIX_C_SOURCE 200809L
+#include "store.h"
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "line %d: %s\n", __LINE__, #expr); exit(1); } } while (0)
+static int failed_stage = -1;
+static bool crash_at_stage;
+int pure_store_test_fault(PureStoreStage stage) {
+ if ((int)stage != failed_stage) return 0;
+ if (crash_at_stage) _exit(77);
+ return 1;
+}
+typedef struct { unsigned char *bytes; unsigned releases; } Owner;
+static void release(void *raw) {
+ Owner *owner = raw;
+ free(owner->bytes);
+ owner->bytes = NULL;
+ ++owner->releases;
+}
+
+static void transfers(void) {
+ Owner owner = {.bytes = malloc(5)};
+ CHECK(owner.bytes);
+ memcpy(owner.bytes, "a\0bcd", 5);
+ PureTransfer transfer = {0};
+ pure_transfer_init(&transfer, (PureSpan){(const unsigned char *)"head", 4},
+ (PureSpan){owner.bytes, 5}, &owner, release);
+ CHECK(!pure_transfer_consume(&transfer, 5));
+ CHECK(transfer.active && !owner.releases);
+ CHECK(pure_transfer_consume(&transfer, 0));
+ unsigned char received[9];
+ size_t used = 0;
+ while (transfer.active) {
+ PureSpan span = pure_transfer_next(&transfer, 2);
+ CHECK(span.size && span.size <= 2);
+ memcpy(received + used, span.data, span.size);
+ used += span.size;
+ CHECK(pure_transfer_consume(&transfer, span.size));
+ CHECK((used == 9) == (owner.releases == 1));
+ }
+ CHECK(used == 9 && !memcmp(received, "heada\0bcd", 9));
+ pure_transfer_abort(&transfer);
+ CHECK(owner.releases == 1 && !pure_transfer_consume(&transfer, 0));
+ owner.bytes = malloc(3);
+ CHECK(owner.bytes);
+ pure_transfer_init(&transfer, (PureSpan){0}, (PureSpan){owner.bytes, 3}, &owner, release);
+ CHECK(pure_transfer_consume(&transfer, 1));
+ pure_transfer_abort(&transfer);
+ pure_transfer_abort(&transfer);
+ CHECK(owner.releases == 2 && !transfer.active);
+ pure_transfer_init(&transfer, (PureSpan){0}, (PureSpan){0}, &owner, release);
+ CHECK(owner.releases == 3 && !transfer.active);
+}
+
+static void storage(const char *path) {
+ CHECK(pure_store_init(path) == PURE_OK);
+ PureStoredView first, second;
+ CHECK(pure_store_get(path, "missing", 100, &first) == PURE_NOT_FOUND);
+ CHECK(!first.mapping && !first.mapped_size);
+ static const unsigned char original[] = {0, 1, 2, 255, 17};
+ static const unsigned char updated[] = {9, 8, 7};
+ int64_t revision;
+ CHECK(pure_store_put(path, "work", 0, (PureSpan){original, sizeof(original)}, &revision) == PURE_OK);
+ CHECK(revision == 1);
+ CHECK(pure_store_get(path, "work", sizeof(original) - 1, &first) == PURE_BUDGET);
+ CHECK(!first.mapping && !first.mapped_size);
+ CHECK(pure_store_get_range(path, "work", 1, 3, 3, &first) == PURE_OK);
+ CHECK(first.object_size == sizeof(original) && first.body.size == 3 && !memcmp(first.body.data, original + 1, 3));
+ pure_store_release(&first);
+ CHECK(pure_store_get_range(path, "work", SIZE_MAX, 1, 10, &first) == PURE_LIMIT);
+ CHECK(pure_store_get_range(path, "work", 1, SIZE_MAX, 10, &first) == PURE_LIMIT);
+ CHECK(pure_store_get_range(path, "work", 1, 3, 2, &first) == PURE_BUDGET);
+ CHECK(pure_store_get_range(path, "work", sizeof(original), 0, 0, &first) == PURE_OK);
+ CHECK(!first.mapped_size && !first.body.size);
+ pure_store_release(&first);
+ CHECK(pure_store_get(path, "work", sizeof(original), &first) == PURE_OK);
+ CHECK(first.body.size == sizeof(original) && first.revision == 1);
+ CHECK(pure_store_put(path, "work", 1, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_OK);
+ CHECK(revision == 2);
+ CHECK(!memcmp(first.body.data, original, sizeof(original)));
+ CHECK(pure_store_get(path, "work", 100, &second) == PURE_OK);
+ CHECK(second.revision == 2 && second.body.size == sizeof(updated));
+ CHECK(!memcmp(second.body.data, updated, sizeof(updated)));
+ pure_store_release(&first);
+ pure_store_release(&first);
+ pure_store_release(&second);
+ CHECK(pure_store_put(path, "work", 1, (PureSpan){original, sizeof(original)}, &revision) == PURE_CONFLICT);
+ CHECK(revision == 2);
+
+ for (failed_stage = PURE_AFTER_HEADER; failed_stage <= PURE_BEFORE_RENAME; ++failed_stage) {
+ CHECK(pure_store_put(path, "work", 2, (PureSpan){original, sizeof(original)}, &revision) == PURE_STORE_ERROR);
+ CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK);
+ CHECK(first.revision == 2 && !memcmp(first.body.data, updated, sizeof(updated)));
+ pure_store_release(&first);
+ }
+ failed_stage = PURE_BEFORE_DIR_SYNC;
+ CHECK(pure_store_put(path, "work", 2, (PureSpan){original, sizeof(original)}, &revision) == PURE_UNCERTAIN);
+ CHECK(revision == 3);
+ failed_stage = -1;
+ CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK);
+ CHECK(first.revision == 3 && !memcmp(first.body.data, original, sizeof(original)));
+ pure_store_release(&first);
+ CHECK(pure_store_put(path, "work", 2, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_CONFLICT);
+
+ /* A crashed unpublished writer leaves only a private file. A new publisher
+ * must still use the previous committed version, never an incomplete body. */
+ pid_t child = fork();
+ CHECK(child >= 0);
+ if (!child) {
+ failed_stage = PURE_AFTER_HEADER;
+ crash_at_stage = true;
+ pure_store_put(path, "work", 3, (PureSpan){updated, sizeof(updated)}, &revision);
+ _exit(78);
+ }
+ int status;
+ CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 77);
+ CHECK(pure_store_init(path) == PURE_OK);
+ CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK);
+ CHECK(first.revision == 3 && !memcmp(first.body.data, original, sizeof(original)));
+ pure_store_release(&first);
+ CHECK(pure_store_put(path, "work", 3, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_OK);
+ CHECK(revision == 4);
+
+ char file[320];
+ snprintf(file, sizeof(file), "%s/obj-work", path);
+ int fd = open(file, O_WRONLY);
+ CHECK(fd >= 0);
+ const unsigned char maximum[] = {255,255,255,255,255,255,255,127};
+ CHECK(pwrite(fd, maximum, 8, 8) == 8 && close(fd) == 0);
+ CHECK(pure_store_put(path, "work", INT64_MAX, (PureSpan){original, sizeof(original)}, &revision) == PURE_LIMIT);
+ CHECK(pure_store_put(path, "empty", 0, (PureSpan){0}, &revision) == PURE_OK);
+ CHECK(pure_store_get(path, "empty", 0, &first) == PURE_OK && first.body.size == 0);
+ pure_store_release(&first);
+
+ /* Range mapping crosses page boundaries without copying a whole object. */
+ long page = sysconf(_SC_PAGESIZE);
+ CHECK(page >= 128 && (size_t)page <= PURE_OBJECT_LIMIT / 3);
+ size_t size = (size_t)page * 3, offset = (size_t)page + 13, length = (size_t)page + 105;
+ unsigned char *bytes = malloc(size);
+ CHECK(bytes);
+ for (size_t i = 0; i < size; ++i) bytes[i] = (unsigned char)((i * 17u) ^ (i >> 8) ^ (i >> 16));
+ CHECK(pure_store_put(path, "range", 0, (PureSpan){bytes, size}, &revision) == PURE_OK);
+ CHECK(pure_store_get_range(path, "range", offset, length, length, &first) == PURE_OK);
+ CHECK(first.body.size == length && !memcmp(first.body.data, bytes + offset, length));
+ pure_store_release(&first);
+ free(bytes);
+ snprintf(file, sizeof(file), "%s/obj-broken", path);
+ fd = open(file, O_CREAT | O_WRONLY | O_EXCL, 0600);
+ CHECK(fd >= 0 && write(fd, "incomplete", 10) == 10 && close(fd) == 0);
+ CHECK(pure_store_get(path, "broken", 100, &first) == PURE_STORE_ERROR && !first.mapped_size);
+ CHECK(pure_store_put(path, "broken", 0, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_STORE_ERROR);
+ CHECK(pure_store_init(file) == PURE_STORE_ERROR);
+ struct stat info;
+ CHECK(stat(file, &info) == 0 && info.st_size == 10);
+}
+
+int main(void) {
+ transfers();
+ char directory[] = "/tmp/pure-connection-unit-XXXXXX";
+ CHECK(mkdtemp(directory));
+ char path[256];
+ snprintf(path, sizeof(path), "%s/store", directory);
+ storage(path);
+ DIR *entries = opendir(path);
+ CHECK(entries);
+ struct dirent *entry;
+ while ((entry = readdir(entries))) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+ CHECK(unlinkat(dirfd(entries), entry->d_name, 0) == 0);
+ }
+ CHECK(closedir(entries) == 0 && rmdir(path) == 0);
+ CHECK(rmdir(directory) == 0);
+ puts("{\"transfer\":\"partial-consumption-and-single-release\","
+ "\"storage\":\"mapped-ranges-snapshot-budget-conflict-failure-uncertain-crash-overflow-empty\"}");
+ return 0;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-program.c b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-program.c
new file mode 100644
index 0000000..b8deeba
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/connections/test-program.c
@@ -0,0 +1,111 @@
+#include "program.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "line %d: %s\n", __LINE__, #expr); exit(1); } } while (0)
+static const char get[] = "on get { let o = store.read(request.key); reply(o, o.body); }";
+static const char put[] = "on put { let s = store.write(request.key, request.revision, request.body); reply(s); }";
+static PureProgram parse(const char *source) {
+ PureProgram program;
+ char error[160];
+ if (!pure_program_parse(source, strlen(source), &program, error, sizeof(error))) {
+ fprintf(stderr, "%s\n", error); exit(1);
+ }
+ return program;
+}
+static void invalid(const char *source) {
+ PureProgram program;
+ memset(&program, 0x5a, sizeof(program));
+ unsigned char original[sizeof(program)];
+ memcpy(original, &program, sizeof(program));
+ char error[160];
+ CHECK(!pure_program_parse(source, strlen(source), &program, error, sizeof(error)));
+ CHECK(*error && !memcmp(original, &program, sizeof(program)));
+}
+int main(void) {
+ char source[4096];
+ snprintf(source, sizeof(source), "%s %s", get, put);
+ PureProgram plan = parse(source);
+ CHECK(plan.read.whole && plan.write.whole && !plan.reply_input);
+ CHECK(!plan.view_definitions && !plan.read_required && !plan.input_required);
+ snprintf(source, sizeof(source), "%s on put { let a = range(request.body, 2, 10); "
+ "let b = range(a, 3, 4); let alias = b; "
+ "let saved = store.write(request.key, request.revision, alias); reply(saved, b); }", get);
+ plan = parse(source);
+ CHECK(plan.write.offset == 5 && plan.write.length == 4 && !plan.write.whole);
+ CHECK(plan.reply.offset == 5 && plan.reply.length == 4 && plan.reply_input);
+ CHECK(plan.input_required == 12 && plan.range_definitions == 2 && plan.view_definitions == 3);
+ /* Ordered reference views keep even unused bounds failures observable. */
+ unsigned char bytes[160];
+ for (size_t i = 0; i < sizeof(bytes); ++i) bytes[i] = (unsigned char)(i * 7);
+ size_t comparisons = 0;
+ for (size_t a = 0; a < 4; ++a) for (size_t b = 0; b < 6; ++b)
+ for (size_t c = 0; c <= b; ++c) for (size_t d = 0; d <= b - c; ++d) {
+ size_t unused_end = (a + b + c) % 12;
+ snprintf(source, sizeof(source), "on get { let o = store.read(request.key); "
+ "let unused = range(o.body, %zu, 0); let first = range(o.body, %zu, %zu); "
+ "let second = range(first, %zu, %zu); reply(o, second); } %s",
+ unused_end, a, b, c, d, put);
+ plan = parse(source);
+ for (size_t n = 0; n < 16; ++n) {
+ bool reference_ok = unused_end <= n && a <= n && b <= n - a;
+ PureSpan selected;
+ bool prepared_ok = n >= plan.read_required &&
+ pure_region_span(plan.read, (PureSpan){bytes, n}, &selected);
+ CHECK(reference_ok == prepared_ok);
+ if (reference_ok) {
+ CHECK(selected.size == d);
+ CHECK(!d || !memcmp(selected.data, bytes + a + c, d));
+ }
+ ++comparisons;
+ }
+ }
+ snprintf(source, sizeof(source), "on get { let o = store.read(request.key); reply(o); } %s", put);
+ plan = parse(source);
+ CHECK(!plan.read.whole && !plan.read.length);
+ const char *bad_get[] = {
+ "on get { let o = store.read(request.key); let x = range(o.body, 0, 2); let y = range(x, 2, 1); reply(o, y); }",
+ "on get { let o = store.read(request.key); let x = range(o.body, 184467440737095516160, 1); reply(o, x); }",
+ "on get { let o = store.read(request.key); let x = range(o.body, 1449984, 1); reply(o, x); }",
+ "on get { let o = store.read(request.key); let o = o.body; reply(o); }",
+ "on get { let o = store.read(request.key); let x = x; reply(o, x); }",
+ "on get { let o = store.read(request.key); let x = store.read(request.key); reply(x); }",
+ "on get { let o = store.read(request.key); reply(o, request.body); }",
+ "on get { let o = store.read(request.key); let x = o.body; reply(x); }",
+ "on get { let o = store.read(request.key); reply(o.body); }",
+ "on get { let o = store.read(request.key); reply(o); let x = o.body; }",
+ "on get { let o = store.read(request.key); reply(o);", "on get {}"
+ };
+ for (size_t i = 0; i < sizeof(bad_get) / sizeof(*bad_get); ++i) {
+ snprintf(source, sizeof(source), "%s %s", bad_get[i], put); invalid(source);
+ }
+ const char *bad_put[] = {
+ "on put { let s = store.write(request.key, request.revision, request.body); let b = range(request.body, 0, 1); reply(s, b); }",
+ "on put { let s = store.write(request.key, request.revision, request.body); reply(s, s.body); }",
+ "on put { let s = store.read(request.key); reply(s); }"
+ };
+ for (size_t i = 0; i < sizeof(bad_put) / sizeof(*bad_put); ++i) {
+ snprintf(source, sizeof(source), "%s %s", get, bad_put[i]); invalid(source);
+ }
+ invalid(get);
+ snprintf(source, sizeof(source), "%s %s %s", get, get, put); invalid(source);
+ snprintf(source, sizeof(source), "%s %s garbage", get, put); invalid(source);
+ snprintf(source, sizeof(source), "on get { let o = store.read(request.key); ");
+ for (size_t i = 0; i < 32; ++i) {
+ size_t used = strlen(source);
+ snprintf(source + used, sizeof(source) - used, "let x%zu = o.body; ", i);
+ }
+ size_t used = strlen(source);
+ snprintf(source + used, sizeof(source) - used, "reply(o); } %s", put); invalid(source);
+ char oversized[PURE_PROGRAM_LIMIT + 1];
+ memset(oversized, ' ', sizeof(oversized));
+ char error[160];
+ CHECK(!pure_program_parse(oversized, sizeof(oversized), &plan, error, sizeof(error)));
+ const char embedded[] = "on get\0{}";
+ CHECK(!pure_program_parse(embedded, sizeof(embedded) - 1, &plan, error, sizeof(error)));
+ PureSpan selected;
+ CHECK(pure_region_span((PureRegion){0}, (PureSpan){0}, &selected) && !selected.data && !selected.size);
+ printf("{\"program\":\"prepare-views-preserve-guards-types-effects-and-atomic-install\","
+ "\"boundary_semantic_comparisons\":%zu}\n", comparisons);
+ return 0;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/cli.mjs
new file mode 100644
index 0000000..b67f4d3
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/cli.mjs
@@ -0,0 +1,127 @@
+#!/usr/bin/env node
+import { readFile, writeFile } from 'node:fs/promises';
+import { createInterface } from 'node:readline';
+import { createRuntime } from './web/runtime.mjs';
+import { parseProject } from './web/project.mjs';
+import { remoteTarget, pushProject, pullProject } from './remote.mjs';
+
+const help = `Pure authoring workbench · 같은 정의와 Zig 실행기
+ load PATH .pure 파일을 적용 (같은 이름의 상태 보존)
+ down NAME 정의한 입력 누르기
+ up NAME 정의한 입력 놓기
+ point NAME PHASE X Y 좌표 입력 (down / move / up / cancel)
+ point-target NAME VIEW PHASE X Y 그룹의 대상에 좌표 입력
+ change NAME VALUE 숫자 입력 전달
+ frame [1..600] 시간 진행, 1프레임 = 1/60초
+ show 상태와 도형 출력
+ limits C H W 표시 캐시(0..32)·변경 기록(0..8)·작업량(0..4294967295) 설정
+ forget [PART] 표시 기록 삭제: all, values, history (상태는 유지)
+ costs 마지막 표시 계산의 작업량·명령 수·캐시 적중 출력
+ reset 현재 정의의 초기 상태로
+ save PATH 적용한 정의를 저장
+ save-project PATH 적용 코드와 현재 배치를 JSON으로 저장
+ restore PATH 저장한 배치를 복원 (초안이 있으면 파일에 보존)
+ connect URL KEY 로컬 네이티브 저장 서버의 작업물에 연결
+ push [REVISION] 현재 배치를 저장 (첫 생성은 push 0, 충돌 시 기존 값 유지)
+ pull 원격 배치를 검증·복원하고 revision 기억
+ help / quit
+브라우저와 별도 인스턴스입니다. 임의 JavaScript는 실행하지 않습니다.`;
+
+async function main() {
+ const args = process.argv.slice(2);
+ if (args.includes('--help')) { console.log(help); return; }
+ if (args.length > 1) throw new Error('사용법: node cli.mjs [project.pure]');
+ const runtime = await createRuntime(await readFile(new URL('./web/core.wasm', import.meta.url)));
+ let savedDraft = null;
+ let remote = null, remoteRevision = null;
+ async function loadFile(path, restoreOnly = false) {
+ const content = await readFile(path, 'utf8');
+ if (restoreOnly || path.toLowerCase().endsWith('.json')) {
+ const project = parseProject(content);
+ if (project.version === 2) {
+ savedDraft = runtime.restoreDocument(project).draft;
+ return;
+ }
+ if (restoreOnly) throw new Error('현재 배치가 포함된 version 2 파일이 필요합니다.');
+ runtime.apply(project.source);
+ } else runtime.apply(content);
+ savedDraft = null;
+ }
+ if (args[0]) await loadFile(args[0]); else runtime.apply('');
+ const terminal = createInterface({ input: process.stdin, output: process.stdout, terminal: Boolean(process.stdin.isTTY) });
+ if (process.stdin.isTTY) console.log(help);
+ terminal.setPrompt('pure> ');
+ if (process.stdin.isTTY) terminal.prompt();
+ for await (const line of terminal) {
+ try {
+ const match = /^(\S+)(?:\s+(.+))?$/.exec(line.trim());
+ const name = match?.[1] || 'show';
+ const arg = match?.[2];
+ if (name === 'quit') { terminal.close(); break; }
+ if (name === 'help') console.log(help);
+ else if (name === 'costs' && !arg) console.log(JSON.stringify({ evaluation: runtime.evaluationStats() }));
+ else {
+ if (name === 'load' && arg) await loadFile(arg);
+ else if (name === 'restore' && arg) await loadFile(arg, true);
+ else if (name === 'connect' && arg) {
+ const parts = arg.split(/\s+/);
+ if (parts.length !== 2) throw new Error('connect URL KEY 형식을 사용하세요.');
+ remote = remoteTarget(parts[0], parts[1]);
+ remoteRevision = null;
+ console.log(JSON.stringify({ remote: remote.href }));
+ } else if (name === 'push') {
+ if (!remote) throw new Error('먼저 connect URL KEY로 연결하세요.');
+ const expected = arg ?? remoteRevision;
+ if (expected === null) throw new Error('먼저 pull로 버전을 읽거나 push 0으로 새 작업물을 만드세요.');
+ remoteRevision = await pushProject(remote, expected, runtime.exportDocument(savedDraft ?? undefined));
+ console.log(JSON.stringify({ remote: remote.href, revision: remoteRevision, stored: true }));
+ } else if (name === 'pull' && !arg) {
+ if (!remote) throw new Error('먼저 connect URL KEY로 연결하세요.');
+ const loaded = await pullProject(remote);
+ savedDraft = runtime.restoreDocument(loaded.project).draft;
+ remoteRevision = loaded.revision;
+ console.log(JSON.stringify({ remote: remote.href, revision: remoteRevision, restored: true }));
+ }
+ else if (name === 'save-project' && arg) await writeFile(arg, JSON.stringify(runtime.exportDocument(savedDraft ?? undefined), null, 2) + '\n', {flag:'wx'});
+ else if (name === 'save' && arg) await writeFile(arg, runtime.exportProject().source, { flag: 'wx' });
+ else if (name === 'down' && arg) runtime.down(arg, 'terminal');
+ else if (name === 'up' && arg) runtime.up(arg, 'terminal');
+ else if (name === 'point' && arg) {
+ const parts = arg.split(/\s+/);
+ if (parts.length !== 4) throw new Error('point NAME PHASE X Y 형식을 사용하세요.');
+ runtime.point(parts[0], parts[1], Number(parts[2]), Number(parts[3]), 'terminal');
+ } else if (name === 'point-target' && arg) {
+ const parts = arg.split(/\s+/);
+ if (parts.length !== 5) throw new Error('point-target NAME VIEW PHASE X Y 형식을 사용하세요.');
+ runtime.point(parts[0], parts[2], Number(parts[3]), Number(parts[4]), 'terminal', parts[1]);
+ } else if (name === 'change' && arg) {
+ const parts = arg.split(/\s+/);
+ if (parts.length !== 2) throw new Error('change NAME VALUE 형식을 사용하세요.');
+ runtime.change(parts[0], Number(parts[1]), 'terminal');
+ } else if (name === 'frame') {
+ if (arg !== undefined && !/^\d+$/.test(arg)) throw new Error('frame 1..600 정수를 입력하세요.');
+ const count = arg === undefined ? 1 : Number(arg);
+ if (count < 1 || count > 600) throw new Error('frame 1..600 정수를 입력하세요.');
+ runtime.frame(count);
+ } else if (name === 'reset' && !arg) runtime.reset();
+ else if (name === 'limits' && arg) {
+ const parts = arg.split(/\s+/);
+ if (parts.length !== 3 || parts.some(part => !/^\d+$/.test(part)))
+ throw new Error('limits C H W 정수 세 개를 입력하세요.');
+ const [cacheEntries, historyEntries, workLimit] = parts.map(Number);
+ runtime.configureEvaluation({ cacheEntries, historyEntries, workLimit });
+ } else if (name === 'forget') runtime.forgetEvaluation(arg || 'all');
+ else if (name !== 'show' || arg) throw new Error('명령 형식을 확인하세요. help로 안내를 볼 수 있습니다.');
+ const result = runtime.snapshot();
+ console.log(JSON.stringify(result));
+ if (result.lastError && !process.stdin.isTTY) process.exitCode = 1;
+ }
+ } catch (error) {
+ console.error(`${error.line ? `${error.line}:${error.column || 1} ` : ''}${error.message}`);
+ if (!process.stdin.isTTY) process.exitCode = 1;
+ }
+ if (process.stdin.isTTY) terminal.prompt();
+ }
+}
+
+main().catch((error) => { console.error(error.message); process.exitCode = 1; });
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/examples/direct.pure
new file mode 100644
index 0000000..b29721c
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/examples/direct.pure
@@ -0,0 +1,47 @@
+# 도형을 잡은 위치를 유지해 이동합니다. 취소하면 잡기 전 위치로 돌아갑니다.
+state x = 80
+state y = 160
+state grab_x = 0
+state grab_y = 0
+state start_x = 80
+state start_y = 160
+state dragging = 0
+
+input drag = pointer(tile)
+input left = ArrowLeft
+input right = ArrowRight
+
+box left_area(20, 100, 280, 220, "#233a31")
+box right_area(340, 100, 280, 220, "#343b25")
+box tile(x, y, 56, 44, "#c9f08b")
+text horizontal(30, 45, x, "x: ")
+text vertical(180, 45, y, "y: ")
+text region(350, 70, x >= 320, "오른쪽 영역: ")
+text active(30, 75, dragging, "잡고 있음: ")
+
+on drag.down {
+ start_x = x
+ start_y = y
+ grab_x = event_x - x
+ grab_y = event_y - y
+ dragging = 1
+}
+on drag.move {
+ x = clamp(event_x - grab_x, 20, 564)
+ y = clamp(event_y - grab_y, 100, 276)
+}
+on drag.up {
+ # 놓기는 마지막으로 표시한 위치를 확정합니다.
+ dragging = 0
+}
+on drag.cancel {
+ x = start_x
+ y = start_y
+ dragging = 0
+}
+on left.down {
+ if dragging == 0 { x = max(20, x - 10) }
+}
+on right.down {
+ if dragging == 0 { x = min(564, x + 10) }
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/remote.mjs
new file mode 100644
index 0000000..6ee6a59
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/remote.mjs
@@ -0,0 +1,89 @@
+import http from 'node:http';
+import { MAX_PROJECT_BYTES, parseProject } from './web/project.mjs';
+
+const maxRevision = 9223372036854775807n;
+function revisionText(value, allowZero = false) {
+ if (typeof value !== 'string' || !/^(0|[1-9]\d*)$/.test(value) ||
+ value.length > 19 || BigInt(value) > maxRevision || (!allowZero && value === '0'))
+ throw new Error('revision은 범위 안의 정수 문자열이어야 합니다.');
+ return value;
+}
+
+export function remoteTarget(base, key) {
+ const url = new URL(base);
+ if (url.protocol !== 'http:' || !['127.0.0.1', 'localhost'].includes(url.hostname) ||
+ url.username || url.password || url.pathname !== '/' || url.search || url.hash)
+ throw new Error('현재 연결 호스트는 로컬 http://127.0.0.1:PORT 형식을 사용합니다.');
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(key)) throw new Error('작업물 이름은 영문·숫자·_·- 1~64자입니다.');
+ url.hostname = '127.0.0.1';
+ url.pathname = `/objects/${key}`;
+ return url;
+}
+
+function request(url, method, body, headers = {}) {
+ return new Promise((resolve, reject) => {
+ let done = false;
+ const finish = (error, value) => {
+ if (done) return;
+ done = true;
+ clearTimeout(deadline);
+ if (error) reject(error); else resolve(value);
+ };
+ const req = http.request(url, { method, agent: false, headers: { ...headers,
+ 'Content-Length': body?.byteLength ?? 0, Connection: 'close' } }, (res) => {
+ const chunks = [];
+ let size = 0;
+ res.on('data', (chunk) => {
+ size += chunk.length;
+ if (size > MAX_PROJECT_BYTES) {
+ const error = new Error('서버 작업물이 허용 크기를 넘었습니다.');
+ finish(error);
+ res.destroy(error);
+ req.destroy(error);
+ } else chunks.push(chunk);
+ });
+ res.on('error', (error) => finish(error));
+ res.on('aborted', () => finish(new Error('응답이 완료되기 전에 연결이 끊겼습니다.')));
+ res.on('end', () => {
+ if (res.statusCode !== 200) {
+ const error = new Error(`원격 작업 실패: ${res.statusCode} ${res.headers['x-pure-error'] ?? ''}`.trim());
+ error.status = res.statusCode;
+ error.etag = res.headers.etag;
+ finish(error);
+ return;
+ }
+ try {
+ const match = /^"([1-9]\d*)"$/.exec(res.headers.etag ?? '');
+ if (!match) throw new Error('서버의 revision 응답이 올바르지 않습니다.');
+ const revision = revisionText(match[1]);
+ finish(null, { revision, bytes: Buffer.concat(chunks, size) });
+ } catch (error) { finish(error); }
+ });
+ });
+ const deadline = setTimeout(() => {
+ const error = new Error('원격 작업 시간 초과: 저장 완료 여부는 다시 확인해야 합니다.');
+ finish(error);
+ req.destroy(error);
+ }, 12000);
+ req.on('error', (error) => finish(error));
+ req.end(body);
+ });
+}
+
+export async function pushProject(target, expected, project) {
+ revisionText(expected, true);
+ const text = JSON.stringify(project);
+ parseProject(text);
+ const body = Buffer.from(text, 'utf8');
+ const headers = expected === '0' ? { 'If-None-Match': '*' } : { 'If-Match': `"${expected}"` };
+ const result = await request(target, 'PUT', body, headers);
+ return result.revision;
+}
+
+export async function pullProject(target) {
+ const result = await request(target, 'GET');
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(result.bytes);
+ const project = parseProject(text);
+ if (project.version !== 2) throw new Error('현재 배치를 포함한 version 2 작업물이 필요합니다.');
+ return { project, revision: result.revision };
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/compiler.mjs
new file mode 100644
index 0000000..6aec335
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/compiler.mjs
@@ -0,0 +1,496 @@
+// Pure's experimental scalar interaction language. No host code is evaluated.
+const LIMITS = Object.freeze({ sourceBytes: 32768, states: 32, inputs: 8,
+ views: 24, handlers: 64, instructions: 512, depth: 64, stack: 64 });
+const RESERVED = new Set(['frame', 'dt', 'event_x', 'event_y', 'event_value', 'min', 'max', 'clamp']);
+const BINARY = new Map([
+ ['||', [1, 15]], ['&&', [2, 14]], ['==', [3, 12]], ['!=', [3, 13]],
+ ['<', [4, 8]], ['<=', [4, 9]], ['>', [4, 10]], ['>=', [4, 11]],
+ ['+', [5, 3]], ['-', [5, 4]], ['*', [6, 5]], ['/', [6, 6]],
+]);
+const FUNCTIONS = { min: [2, 17], max: [2, 18], clamp: [3, 19] };
+const VIEW_FIELDS = { box: ['x', 'y', 'width', 'height'],
+ circle: ['x', 'y', 'radius'], text: ['x', 'y', 'value'] };
+
+function fail(token, message) {
+ const error = new Error(`${message} (${token.line}:${token.column})`);
+ error.line = token.line;
+ error.column = token.column;
+ throw error;
+}
+
+function lex(source) {
+ const tokens = [];
+ let offset = 0, line = 1, column = 1;
+ const advance = () => {
+ const char = source[offset++];
+ if (char === '\n') { line++; column = 1; } else column++;
+ return char;
+ };
+ while (offset < source.length) {
+ const char = source[offset];
+ if (char === ' ' || char === '\t' || char === '\r') { advance(); continue; }
+ if (char === '#') { while (offset < source.length && source[offset] !== '\n') advance(); continue; }
+ const token = { line, column };
+ if (char === '\n') { advance(); tokens.push({ ...token, kind: 'nl' }); continue; }
+ if (char === '"') {
+ advance();
+ let value = '', closed = false;
+ while (offset < source.length) {
+ const next = advance();
+ if (next === '"') { closed = true; break; }
+ if (next === '\n' || next === '\r') fail(token, 'Strings must fit on one line');
+ if (next === '\\') {
+ const escaped = advance();
+ const escapes = { n: '\n', r: '\r', t: '\t', '"': '"', '\\': '\\' };
+ if (!Object.hasOwn(escapes, escaped)) fail(token, 'Unsupported string escape');
+ value += escapes[escaped];
+ } else value += next;
+ }
+ if (!closed) fail(token, 'Unterminated string');
+ tokens.push({ ...token, kind: 'string', value }); continue;
+ }
+ const remaining = source.slice(offset);
+ const number = remaining.match(/^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/);
+ if (number) {
+ const value = Number(number[0]);
+ if (!Number.isFinite(value)) fail(token, 'Number must be finite');
+ for (let i = 0; i < number[0].length; i++) advance();
+ tokens.push({ ...token, kind: 'number', value }); continue;
+ }
+ const name = remaining.match(/^[A-Za-z_][A-Za-z_0-9]*/);
+ if (name) {
+ for (let i = 0; i < name[0].length; i++) advance();
+ tokens.push({ ...token, kind: 'name', value: name[0] }); continue;
+ }
+ const pair = source.slice(offset, offset + 2);
+ if (['<=', '>=', '==', '!=', '&&', '||'].includes(pair)) {
+ advance(); advance(); tokens.push({ ...token, kind: pair }); continue;
+ }
+ if ('=+-*/!<>(){},.;:'.includes(char)) {
+ advance(); tokens.push({ ...token, kind: char }); continue;
+ }
+ fail(token, `Unexpected character ${JSON.stringify(char)}`);
+ }
+ tokens.push({ kind: 'eof', line, column });
+ return tokens;
+}
+
+class Parser {
+ constructor(tokens) { this.tokens = tokens; this.position = 0; this.depth = 0; }
+ peek() { return this.tokens[this.position]; }
+ take() { return this.tokens[this.position++]; }
+ is(kind, value) { return this.peek().kind === kind && (value === undefined || this.peek().value === value); }
+ expect(kind, description = kind) {
+ if (!this.is(kind)) fail(this.peek(), `Expected ${description}`);
+ return this.take();
+ }
+ newlines() { while (this.is('nl')) this.take(); }
+ separators() { while (this.is('nl') || this.is(';')) this.take(); }
+ end() {
+ if (!['nl', ';', '}', 'eof'].includes(this.peek().kind)) fail(this.peek(), 'Expected newline or semicolon');
+ this.separators();
+ }
+ reference(token) {
+ if (!this.is('.')) return token.value;
+ this.take();
+ return `${token.value}.${this.expect('name', 'field name').value}`;
+ }
+ initial() {
+ let sign = 1;
+ if (this.is('-') || this.is('+')) sign = this.take().kind === '-' ? -1 : 1;
+ return sign * this.expect('number', 'numeric state initializer').value;
+ }
+ node(token, properties, children = []) {
+ const depth = 1 + Math.max(0, ...children.map(child => child.depth));
+ if (depth > LIMITS.depth) fail(token, 'Expression nesting exceeds 64');
+ return { token, depth, ...properties };
+ }
+ expression(minimum = 0, multiline = false) {
+ if (++this.depth > LIMITS.depth) fail(this.peek(), 'Expression nesting exceeds 64');
+ if (multiline) this.newlines();
+ const token = this.take();
+ let left;
+ if (token.kind === 'number') left = this.node(token, { kind: 'constant', value: token.value });
+ else if (token.kind === '-' || token.kind === '!') {
+ const child = this.expression(7, multiline);
+ left = this.node(token, { kind: 'unary', op: token.kind === '-' ? 7 : 16, child }, [child]);
+ } else if (token.kind === '(') {
+ left = this.expression(0, true); this.newlines(); this.expect(')');
+ } else if (token.kind === 'name') {
+ if (Object.hasOwn(FUNCTIONS, token.value) && this.is('(')) {
+ this.take();
+ const args = [];
+ this.newlines();
+ if (!this.is(')')) {
+ for (;;) {
+ args.push(this.expression(0, true)); this.newlines();
+ if (!this.is(',')) break;
+ this.take();
+ }
+ }
+ this.expect(')');
+ if (args.length !== FUNCTIONS[token.value][0]) fail(token, `${token.value} expects ${FUNCTIONS[token.value][0]} arguments`);
+ left = this.node(token, { kind: 'call', op: FUNCTIONS[token.value][1], args }, args);
+ } else left = this.node(token, { kind: 'reference', name: this.reference(token) });
+ } else fail(token, 'Expected a number, state, or expression');
+ for (;;) {
+ if (multiline) this.newlines();
+ const entry = BINARY.get(this.peek().kind);
+ if (!entry || entry[0] < minimum) break;
+ const operator = this.take();
+ const right = this.expression(entry[0] + 1, multiline);
+ left = this.node(operator, { kind: 'binary', op: entry[1], left, right }, [left, right]);
+ }
+ this.depth--;
+ return left;
+ }
+ block(depth = 0) {
+ if (depth >= LIMITS.depth) fail(this.peek(), 'Handler nesting exceeds 64');
+ this.newlines(); this.expect('{'); this.separators();
+ const statements = [];
+ while (!this.is('}')) {
+ const token = this.expect('name', 'assignment, if, or closing brace');
+ if (token.value === 'if' && !this.is('=')) {
+ const condition = this.expression();
+ const body = this.block(depth + 1);
+ statements.push({ kind: 'if', token, condition, body });
+ } else {
+ const name = this.reference(token);
+ this.expect('=');
+ statements.push({ kind: 'assign', token, name, expression: this.expression() });
+ }
+ this.end();
+ }
+ this.take();
+ return statements;
+ }
+ parse() {
+ const declarations = [];
+ this.separators();
+ while (!this.is('eof')) {
+ const token = this.expect('name', 'declaration');
+ const kind = token.value;
+ if (kind === 'state') {
+ const name = this.expect('name', 'state name'); this.expect('=');
+ declarations.push({ kind, token, name, initial: this.initial() });
+ } else if (kind === 'record') {
+ const name = this.expect('name', 'record type'); this.newlines(); this.expect('{'); this.separators();
+ const fields = [];
+ while (!this.is('}')) {
+ const field = this.expect('name', 'field name'); this.expect('=');
+ fields.push({name:field, initial:this.initial()}); this.end();
+ }
+ this.take(); declarations.push({kind,token,name,fields});
+ } else if (kind === 'item') {
+ const name = this.expect('name', 'item name'); this.expect('=');
+ const type = this.expect('name', 'record type'); this.expect('('); this.newlines();
+ const fields = [];
+ while (!this.is(')')) {
+ const field = this.expect('name', 'field name'); this.expect('=');
+ fields.push({name:field, initial:this.initial()}); this.newlines();
+ if (!this.is(',')) break;
+ this.take(); this.newlines();
+ }
+ this.expect(')'); declarations.push({kind,token,name,type,fields});
+ } else if (kind === 'rule') {
+ const name = this.expect('name', 'rule name'); this.expect('(');
+ const parameter = this.expect('name', 'record parameter'); this.expect(':');
+ const type = this.expect('name', 'record type'); this.expect(')');
+ declarations.push({kind,token,name,parameter,type,body:this.block()});
+ } else if (kind === 'input') {
+ const name = this.expect('name', 'input name'); this.expect('=');
+ const key = this.expect('name', 'keyboard code or pointer(view)');
+ if (key.value === 'value') {
+ declarations.push({kind,token,name,valueInput:true}); this.end(); continue;
+ }
+ if (key.value === 'pointer') {
+ if (this.is('{')) {
+ this.take(); this.separators(); const routes = [];
+ while (!this.is('}')) {
+ const target = this.expect('name', 'view ID'); this.expect(':');
+ const item = this.expect('name', 'item name'); routes.push({target,item}); this.end();
+ }
+ this.take(); declarations.push({kind,token,name,routes}); this.end(); continue;
+ }
+ this.expect('(');
+ const target = this.expect('name', 'view ID'); this.expect(')');
+ declarations.push({ kind, token, name, target });
+ this.end(); continue;
+ }
+ if (!/^(?:Space|Enter|Arrow(?:Left|Right|Up|Down)|Key[A-Z]|Digit[0-9])$/.test(key.value)) fail(key, 'Unsupported keyboard code');
+ declarations.push({ kind, token, name, key });
+ } else if (Object.hasOwn(VIEW_FIELDS, kind)) {
+ const name = this.expect('name', 'view ID'); this.expect('(');
+ const expressions = [];
+ for (let i = 0; i < VIEW_FIELDS[kind].length; i++) {
+ if (i) { this.newlines(); this.expect(','); }
+ expressions.push(this.expression(0, true));
+ }
+ this.newlines();
+ let color, prefix = '';
+ if (kind !== 'text') {
+ this.expect(','); this.newlines();
+ const value = this.expect('string', 'literal hex color');
+ if (!/^#[0-9a-fA-F]{6}$/.test(value.value)) fail(value, 'Color must use six hexadecimal digits, such as "#356548"');
+ color = value.value;
+ } else if (this.is(',')) { this.take(); this.newlines(); prefix = this.expect('string', 'literal text prefix').value; }
+ this.newlines(); this.expect(')');
+ declarations.push({ kind: 'view', type: kind, token, name, expressions, color, prefix });
+ } else if (kind === 'on') {
+ const event = this.expect('name', 'input name or frame');
+ let edge;
+ if (event.value !== 'frame') {
+ this.expect('.'); edge = this.expect('name', 'down, up, move or cancel');
+ if (!['down', 'up', 'move', 'cancel', 'change'].includes(edge.value)) fail(edge, 'Expected down or up, move, cancel or change');
+ }
+ if (this.is('name', 'use')) {
+ this.take(); const rule = this.expect('name', 'rule name'); this.expect('(');
+ const item = this.expect('name', 'item name or input.target');
+ const argument = this.reference(item); this.expect(')');
+ declarations.push({kind:'handler',token,event,edge,rule,item,argument});
+ } else declarations.push({ kind: 'handler', token, event, edge, body: this.block() });
+ } else fail(token, `Unknown declaration ${kind}`);
+ this.end();
+ }
+ return declarations;
+ }
+}
+
+export function compile(source) {
+ if (typeof source !== 'string') fail({ line: 1, column: 1 }, 'Source must be a string');
+ const sourceBytes = new TextEncoder().encode(source).length;
+ if (sourceBytes > LIMITS.sourceBytes) fail({ line: 1, column: 1 }, 'Source exceeds 32768 UTF-8 bytes');
+ const declarations = new Parser(lex(source)).parse();
+ const program = { states: [], inputs: [], instructions: [], handlers: [], views: [], pointerTargets: [], metrics: {} };
+ const states = new Map(), inputs = new Map(), views = new Set(), keys = new Set(), targets = new Set();
+ const records = new Map(), items = new Map(), rules = new Map();
+ const scalarNames = new Set(declarations.filter(d => d.kind === 'state').map(d => d.name.value));
+ for (const declaration of declarations) {
+ if (!['record','rule'].includes(declaration.kind)) continue;
+ const {name,fields,kind} = declaration, symbols = kind === 'record' ? records : rules;
+ if (RESERVED.has(name.value)) fail(name, `Reserved name ${name.value}`);
+ if (symbols.has(name.value)) fail(name, `Duplicate ${kind} ${name.value}`);
+ if (kind === 'record') {
+ if (!fields.length || fields.length > 8) fail(name, 'A record needs 1..8 fields');
+ const used = new Set();
+ for (const field of fields) {
+ if (used.has(field.name.value)) fail(field.name, `Duplicate field ${field.name.value}`);
+ if (RESERVED.has(field.name.value)) fail(field.name, `Reserved name ${field.name.value}`);
+ used.add(field.name.value);
+ }
+ }
+ symbols.set(name.value, declaration);
+ }
+ let handlerCount = 0;
+ for (const declaration of declarations) {
+ const { kind, name, token } = declaration;
+ if (name && RESERVED.has(name.value)) fail(name, `Reserved name ${name.value}`);
+ if (kind === 'record' || kind === 'rule') continue;
+ if (kind === 'item') {
+ if (items.has(name.value) || scalarNames.has(name.value)) fail(name, `Duplicate state/item ${name.value}`);
+ const record = records.get(declaration.type.value);
+ if (!record) fail(declaration.type, `Unknown record ${declaration.type.value}`);
+ const overrides = new Map();
+ for (const field of declaration.fields) {
+ if (!record.fields.some(f => f.name.value === field.name.value)) fail(field.name, `Unknown field ${field.name.value}`);
+ if (overrides.has(field.name.value)) fail(field.name, `Duplicate field ${field.name.value}`);
+ overrides.set(field.name.value, field.initial);
+ }
+ const base = states.size;
+ for (const field of record.fields) {
+ if (states.size >= LIMITS.states) fail(name, 'At most 32 states are allowed, including item fields');
+ const qualified = `${name.value}.${field.name.value}`, index = states.size;
+ states.set(qualified,index);
+ program.states.push({name:qualified,initial:overrides.has(field.name.value) ? overrides.get(field.name.value) : field.initial,line:token.line});
+ }
+ items.set(name.value,{type:declaration.type.value,base,count:record.fields.length});
+ } else if (kind === 'state' || kind === 'input') {
+ const symbols = kind === 'state' ? states : inputs;
+ if (symbols.has(name.value)) fail(name, `Duplicate ${kind} ${name.value}`);
+ if (symbols.size >= LIMITS[`${kind}s`]) fail(name, `At most ${LIMITS[`${kind}s`]} ${kind}s are allowed`);
+ symbols.set(name.value, symbols.size);
+ if (kind === 'state') program.states.push({ name: name.value, initial: declaration.initial, line: token.line });
+ else if (declaration.valueInput) {
+ program.inputs.push({name:name.value,kind:'value',line:token.line});
+ } else if (declaration.routes) {
+ if (!declaration.routes.length) fail(name, 'A pointer group needs at least one target');
+ program.inputs.push({name:name.value, routes:[], line:token.line});
+ } else if (declaration.target) {
+ const target = declaration.target;
+ if (targets.has(target.value)) fail(target, `Duplicate pointer target ${target.value}`);
+ targets.add(target.value);
+ program.inputs.push({ name: name.value, target: target.value, line: token.line });
+ } else {
+ if (keys.has(declaration.key.value)) fail(declaration.key, `Duplicate keyboard code ${declaration.key.value}`);
+ keys.add(declaration.key.value);
+ program.inputs.push({ name: name.value, key: declaration.key.value, line: token.line });
+ }
+ } else if (kind === 'view') {
+ if (views.has(name.value)) fail(name, `Duplicate view ID ${name.value}`);
+ if (views.size >= LIMITS.views) fail(name, 'At most 24 views are allowed');
+ views.add(name.value);
+ } else if (++handlerCount > LIMITS.handlers) fail(token, 'At most 64 handlers are allowed');
+ }
+ for (const declaration of declarations) {
+ if (declaration.target && !views.has(declaration.target.value))
+ fail(declaration.target, `Unknown pointer target ${declaration.target.value}`);
+ }
+ const routedItems = new Set();
+ for (const declaration of declarations) {
+ if (!declaration.routes) continue;
+ const input = inputs.get(declaration.name.value), resolved = program.inputs[input];
+ let type;
+ for (const {target,item:token} of declaration.routes) {
+ if (!views.has(target.value)) fail(target, `Unknown pointer target ${target.value}`);
+ if (targets.has(target.value)) fail(target, `Duplicate pointer target ${target.value}`);
+ const item = items.get(token.value);
+ if (!item) fail(token, `Unknown item ${token.value}`);
+ if (type && type !== item.type) fail(token, 'Pointer group targets must share one record type');
+ if (routedItems.has(token.value)) fail(token, `Item ${token.value} already has a pointer route`);
+ targets.add(target.value); routedItems.add(token.value); type = item.type;
+ const route = program.pointerTargets.length;
+ if (route >= 24) fail(target, 'At most 24 pointer targets are allowed');
+ program.pointerTargets.push({input,base:item.base,count:item.count});
+ resolved.routes.push({target:target.value, item:token.value, route});
+ }
+ resolved.type = type;
+ }
+ for (const rule of rules.values()) {
+ if (!records.has(rule.type.value)) fail(rule.type, `Unknown record ${rule.type.value}`);
+ if (RESERVED.has(rule.parameter.value)) fail(rule.parameter, `Reserved name ${rule.parameter.value}`);
+ }
+ let pointHandler = false, valueHandler = false;
+ let parameters = null;
+ let parameterPrefix = null;
+ let stack = 0, maxStack = 0;
+ const emit = (op, token, arg = 0, value = 0, delta = 0) => {
+ if (program.instructions.length >= LIMITS.instructions) fail(token, 'At most 512 instructions are allowed');
+ stack += delta;
+ if (stack < 0 || stack > LIMITS.stack) fail(token, 'Expression stack exceeds 64');
+ maxStack = Math.max(maxStack, stack);
+ program.instructions.push({ op, arg, value, line: token.line });
+ };
+ const expression = (node, binding = false) => {
+ const { token } = node;
+ if (node.kind === 'constant') emit(0, token, 0, node.value, 1);
+ else if (node.kind === 'reference') {
+ if (node.name === 'event_value') {
+ if (binding || !valueHandler) fail(token, 'event_value is only available in value change handlers');
+ emit(26, token, 0, 0, 1);
+ } else if (node.name === 'event_x' || node.name === 'event_y') {
+ if (binding || !pointHandler) fail(token, 'event_x and event_y are only available in pointer handlers');
+ emit(node.name === 'event_x' ? 22 : 23, token, 0, 0, 1);
+ } else if (node.name === 'dt') {
+ if (binding) fail(token, 'dt is only available in handlers');
+ emit(2, token, 0, 0, 1);
+ } else {
+ if (parameterPrefix && node.name.startsWith(parameterPrefix) && !parameters.has(node.name))
+ fail(token, `Unknown parameter field ${node.name}`);
+ if (parameters?.has(node.name)) {
+ emit(24, token, parameters.get(node.name), 0, 1);
+ return;
+ }
+ if (!states.has(node.name)) fail(token, `Unknown state ${node.name}`);
+ emit(1, token, states.get(node.name), 0, 1);
+ }
+ } else if (node.kind === 'unary') { expression(node.child, binding); emit(node.op, token); }
+ else if (node.kind === 'binary') {
+ expression(node.left, binding); expression(node.right, binding); emit(node.op, token, 0, 0, -1);
+ } else {
+ for (const arg of node.args) expression(arg, binding);
+ emit(node.op, token, 0, 0, 1 - node.args.length);
+ }
+ };
+ const statements = body => {
+ for (const statement of body) {
+ if (statement.kind === 'assign') {
+ if (parameterPrefix && statement.name.startsWith(parameterPrefix) && !parameters.has(statement.name))
+ fail(statement.token, `Unknown parameter field ${statement.name}`);
+ const parameter = parameters?.get(statement.name);
+ if (parameter === undefined && !states.has(statement.name)) fail(statement.token, `Unknown state ${statement.name}`);
+ expression(statement.expression);
+ emit(parameter === undefined ? 20 : 25, statement.token, parameter === undefined ? states.get(statement.name) : parameter, 0, -1);
+ } else {
+ expression(statement.condition);
+ const branch = program.instructions.length;
+ emit(21, statement.token, 0, 0, -1);
+ statements(statement.body);
+ program.instructions[branch].arg = program.instructions.length;
+ }
+ }
+ };
+ const bodies = new Map();
+ function ruleBody(rule) {
+ if (bodies.has(rule.name.value)) return bodies.get(rule.name.value);
+ parameterPrefix = `${rule.parameter.value}.`;
+ parameters = new Map(records.get(rule.type.value).fields.map((field,index) => [`${rule.parameter.value}.${field.name.value}`,index]));
+ // A shared body may read coordinates; every binding is checked below.
+ pointHandler = true; valueHandler = true; stack = 0;
+ const start = program.instructions.length;
+ statements(rule.body);
+ const length = program.instructions.length - start;
+ const point = program.instructions.slice(start).some(op => op.op === 22 || op.op === 23);
+ parameters = null; parameterPrefix = null;
+ const value = program.instructions.slice(start).some(op => op.op === 26);
+ const body = {start,length,point,value}; bodies.set(rule.name.value,body); return body;
+ }
+ for (const declaration of declarations) {
+ if (declaration.kind === 'handler') {
+ let event = 0;
+ pointHandler = false; valueHandler = false;
+ if (declaration.event.value !== 'frame') {
+ if (!inputs.has(declaration.event.value)) fail(declaration.event, `Unknown input ${declaration.event.value}`);
+ const index = inputs.get(declaration.event.value), edge = declaration.edge.value;
+ pointHandler = Boolean(program.inputs[index].target || program.inputs[index].routes);
+ valueHandler = program.inputs[index].kind === 'value';
+ if (valueHandler !== (edge === 'change')) fail(declaration.edge, 'Value inputs require change; change requires a value input');
+ if (!pointHandler && (edge === 'move' || edge === 'cancel')) fail(declaration.edge, 'move and cancel require a pointer input');
+ event = edge === 'change' ? 33 + index : edge === 'move' ? 17 + index : edge === 'cancel' ? 25 + index : 1 + index * 2 + (edge === 'up' ? 1 : 0);
+ }
+ if (declaration.rule) {
+ const rule = rules.get(declaration.rule.value);
+ const routed = declaration.argument.includes('.');
+ const input = program.inputs[inputs.get(declaration.event.value)];
+ if (routed && (!input?.routes || declaration.argument !== `${declaration.event.value}.target`))
+ fail(declaration.item, 'Use the current pointer group target');
+ const item = routed ? {type:input.type, count:records.get(input.type).fields.length} : items.get(declaration.item.value);
+ if (!rule) fail(declaration.rule, `Unknown rule ${declaration.rule.value}`);
+ if (!item) fail(declaration.item, `Unknown item ${declaration.item.value}`);
+ if (item.type !== rule.type.value) fail(declaration.item, `Rule ${rule.name.value} requires ${rule.type.value}`);
+ const pointer = pointHandler, value = valueHandler;
+ const body = ruleBody(rule);
+ if (body.value && !value) fail(declaration.rule, 'Value-reading rules require a value input');
+ if (body.point && !pointer) fail(declaration.rule, 'Coordinate-reading rules require a pointer input');
+ program.handlers.push({event,start:body.start,length:body.length,line:declaration.token.line,parameterBase:routed ? 255 : item.base,parameterCount:item.count});
+ } else {
+ const start = program.instructions.length;
+ stack = 0; statements(declaration.body);
+ program.handlers.push({ event, start, length: program.instructions.length - start, line: declaration.token.line });
+ }
+ } else if (declaration.kind === 'view') {
+ const view = { id: declaration.name.value, type: declaration.type, line: declaration.token.line, bindings: {} };
+ if (view.type === 'text') view.prefix = declaration.prefix;
+ else view.color = declaration.color;
+ declaration.expressions.forEach((node, index) => {
+ const start = program.instructions.length;
+ stack = 0; expression(node, true);
+ view.bindings[VIEW_FIELDS[view.type][index]] = { start, length: program.instructions.length - start };
+ });
+ program.views.push(view);
+ }
+ }
+ // Unused definitions still receive name/type checking, but reserve no VM body.
+ for (const rule of rules.values()) {
+ if (bodies.has(rule.name.value)) continue;
+ const instructions = program.instructions, previousMax = maxStack;
+ program.instructions = [];
+ ruleBody(rule);
+ program.instructions = instructions; maxStack = previousMax;
+ }
+ program.metrics = { sourceBytes, sourceLines: source === '' ? 0 : source.split('\n').length,
+ states: states.size, inputs: inputs.size, views: views.size,
+ handlers: program.handlers.length, instructions: program.instructions.length, maxStack };
+ return program;
+}
+
+export { LIMITS };
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/core.wasm
new file mode 100644
index 0000000..79c50a8
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/core.wasm
Binary files differ
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/project.mjs
new file mode 100644
index 0000000..861e0fc
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/project.mjs
@@ -0,0 +1,47 @@
+import { compile } from './compiler.mjs';
+
+// Two escaped source strings plus up to 32 generated item.field names. A long
+// schema field can appear in every item without repeating in the source text.
+export const MAX_PROJECT_BYTES = (2 * 6 + 32) * 32768 + 8192;
+const encoder = new TextEncoder();
+function sourceText(value, label) {
+ if (typeof value !== 'string' || encoder.encode(value).length > 32768)
+ throw new Error(`${label}: UTF-8 32 KiB 이하의 코드가 필요합니다.`);
+ return value;
+}
+
+// No runtime mutation. Validate every saved value before opening core staging.
+export function validateDocument(document) {
+ if (!document || typeof document !== 'object' || Array.isArray(document) ||
+ document.format !== 'pure-workbench' || document.version !== 2)
+ throw new Error('배치 파일 형식이 맞지 않습니다. pure-workbench version 2가 필요합니다.');
+ const source = sourceText(document.source, '적용된 코드');
+ const draft = sourceText(Object.hasOwn(document, 'draft') ? document.draft : source, '편집 중인 코드');
+ const program = compile(source);
+ if (!Array.isArray(document.states) || document.states.length !== program.states.length)
+ throw new Error('저장된 상태 목록이 적용된 코드와 일치하지 않습니다.');
+ const values = new Map();
+ for (const entry of document.states) {
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry) || typeof entry.name !== 'string' || values.has(entry.name))
+ throw new Error('상태 이름이 잘못됐거나 중복됐습니다.');
+ const value = entry.value === '-0' ? -0 : entry.value;
+ if (typeof value !== 'number' || !Number.isFinite(value)) throw new Error('상태 값은 유한한 숫자여야 합니다.');
+ values.set(entry.name, value);
+ }
+ for (const state of program.states)
+ if (!values.has(state.name)) throw new Error(`저장된 상태가 없습니다: ${state.name}`);
+ return { source, draft, program, values: program.states.map(state => values.get(state.name)) };
+}
+
+export function parseProject(text) {
+ if (typeof text !== 'string' || encoder.encode(text).length > MAX_PROJECT_BYTES)
+ throw new Error('작업 파일이 너무 큽니다.');
+ let project;
+ try { project = JSON.parse(text); } catch { throw new Error('올바른 JSON 파일이 아닙니다.'); }
+ if (!project || typeof project !== 'object' || Array.isArray(project) || project.format !== 'pure-workbench')
+ throw new Error('Pure 작업물 형식이 아닙니다.');
+ if (project.version === 1) sourceText(project.source, '코드');
+ else if (project.version === 2) validateDocument(project);
+ else throw new Error('지원하지 않는 작업 파일 버전입니다.');
+ return project;
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/runtime.mjs
new file mode 100644
index 0000000..d8d6618
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/runtime.mjs
@@ -0,0 +1,350 @@
+import { compile } from './compiler.mjs';
+import { validateDocument } from './project.mjs';
+
+const messages = [
+ '', '실행기의 용량 한도를 넘었습니다.', '실행 정의가 유효하지 않습니다.',
+ '계산 스택의 범위를 벗어났습니다.', '0으로 나눌 수 없습니다.',
+ '계산 결과가 유한한 수의 범위를 벗어났습니다.', '조건 분기 범위가 유효하지 않습니다.',
+ '존재하지 않는 상태를 참조했습니다.', '시간 간격이 유효하지 않습니다.',
+ '정의되지 않은 입력 사건입니다.',
+];
+const sourceLimit = 32768;
+
+function problem(message, line = null, column = null) {
+ return Object.assign(new Error(message), { line, column });
+}
+
+function freezeProgram(value) {
+ if (value && typeof value === 'object') {
+ for (const child of Object.values(value)) freezeProgram(child);
+ Object.freeze(value);
+ }
+ return value;
+}
+
+export async function createRuntime(wasmBytes, evaluationOptions = {}) {
+ if (wasmBytes === undefined) {
+ const response = await fetch(new URL('./core.wasm', import.meta.url), { cache: 'no-store' });
+ if (!response.ok) throw new Error(`실행기를 불러오지 못했습니다 (HTTP ${response.status}).`);
+ wasmBytes = await response.arrayBuffer();
+ }
+ const module = await WebAssembly.compile(wasmBytes);
+ if (WebAssembly.Module.imports(module).length) throw new Error('이 실행기는 호스트 함수 없이 동작해야 합니다.');
+ const { exports: core } = await WebAssembly.instantiate(module, {});
+ if (typeof core.eval_bounded !== 'function' || typeof core.abort_program !== 'function' || typeof core.dispatch_point !== 'function' || typeof core.put_restored_value !== 'function' || typeof core.commit_restored_program !== 'function' || typeof core.bind_handler !== 'function' || typeof core.put_pointer_target !== 'function' || typeof core.bind_target_handler !== 'function' || typeof core.dispatch_target !== 'function' || typeof core.dispatch_value !== 'function')
+ throw new Error('실행기 버전이 맞지 않습니다. 새로고침하거나 core.wasm을 다시 빌드하세요.');
+ core.reset_all();
+ let program = null;
+ let appliedSource = '';
+ let lastEvent = null;
+ // Keep a failed event visible through its release/focus-loss cleanup.
+ // The active program can clear this diagnostic by apply or explicit reset.
+ let lastFailure = null;
+ const held = new Map();
+ const points = new Map();
+ let presenting = false;
+ let bindings = [];
+ let pointRoutes = new Map();
+ // One bounded publication buffer, never exposed to callers. No state mirror.
+ let displayValues = null, displayValid = null;
+ function available() {
+ if (presenting) throw new Error('출력 전달 중에는 실행기를 다시 호출할 수 없습니다.');
+ }
+ let evaluationPolicy = { cacheEntries: 0, historyEntries: 8, workLimit: 2048 };
+
+ function checked(code, line = null) {
+ if (code !== 0) throw problem(messages[code] || `실행기 오류 ${code}`, line);
+ }
+ function inputIndex(name) {
+ const index = program?.inputs.findIndex((input) => input.name === name) ?? -1;
+ if (index < 0) throw new Error(`정의되지 않은 입력: ${name}`);
+ return index;
+ }
+ function dispatch(event, name, phase, source, point = null, value = undefined) {
+ const status = value !== undefined ? core.dispatch_value(event, value) : point ? (point.route === undefined ? core.dispatch_point(event, point.x, point.y)
+ : core.dispatch_target(event, point.route, point.x, point.y)) : core.dispatch(event, event === 0 ? 1 / 60 : 0);
+ const pc = core.info(2) >>> 0;
+ lastEvent = { name, phase, source, status,
+ line: program?.instructions[pc]?.line ?? null, ops: core.info(3) >>> 0 };
+ if (point) { lastEvent.x = point.x; lastEvent.y = point.y; if (point.target) lastEvent.target = point.target; }
+ if (value !== undefined) lastEvent.value = value;
+ if (status) lastFailure = { ...lastEvent };
+ return status;
+ }
+
+ function evaluateDisplay() {
+ core.begin_evaluation(evaluationPolicy.workLimit);
+ if (!bindings.length) return null;
+ displayValues ??= new Float64Array(96);
+ displayValid ??= new Uint8Array(24);
+ let error = null, slot = 0;
+ for (let i = 0; i < bindings.length; i++) {
+ const view = program.views[i];
+ displayValid[i] = 1;
+ for (const [name, binding] of bindings[i]) {
+ const value = core.eval_bounded(binding.start, binding.length);
+ if (core.evaluation_info(0) === 10) {
+ displayValid.fill(0);
+ return { code: 101, line: view.line,
+ message: '표시 계산의 작업량 한도를 넘었습니다. 한도를 늘리거나 표시식을 줄이세요.' };
+ }
+ if (!Number.isFinite(value) || ((name === 'width' || name === 'height' || name === 'radius') && value < 0)) {
+ error ??= { line: view.line, message: `${view.id}.${name}: 표시할 수 없는 값입니다. 0 나눗셈이나 음수 크기를 확인하세요.` };
+ displayValid[i] = 0;
+ }
+ displayValues[slot++] = value;
+ }
+ }
+ return error;
+ }
+
+ function status(viewError) {
+ const executionError = lastFailure?.status || 0;
+ return {
+ tick: core.info(0) >>> 0, revision: core.info(4) >>> 0,
+ displayError: viewError ? { code: viewError.code || 100, ...viewError } : null,
+ lastError: executionError || (viewError ? viewError.code || 100 : 0),
+ errorMessage: executionError ? messages[executionError] || `실행 오류 ${executionError}` : viewError?.message || '',
+ errorLine: executionError ? lastFailure.line : viewError?.line ?? null,
+ lastOps: core.info(3) >>> 0, successfulEvents: core.info(9) >>> 0,
+ held: [...held.keys()], lastEvent: lastEvent ? { ...lastEvent } : null,
+ lastFailure: lastFailure ? { ...lastFailure } : null,
+ memory: {
+ linearBytes: core.memory.buffer.byteLength,
+ reservedEngineBytes: core.layout(0), programReservedBytes: core.layout(1),
+ instructionBytes: core.layout(2), handlerBytes: core.layout(3),
+ activeIRBytes: core.layout(4), activeStateBytes: core.layout(5),
+ stagingReservedBytes: core.layout(6), evalStackBytes: core.layout(7),
+ pointerTargetBytes: core.layout(8), pointerTargets: core.layout(9),
+ },
+ };
+ }
+
+ function load(next, source, restored = null) {
+ const oldIds = new Map(restored ? [] : program?.states.map((state, index) => [state.name, index]) ?? []);
+ checked(core.begin_program(next.states.length, next.inputs.length));
+ try {
+ for (let index = 0; index < next.states.length; index++) {
+ const state = next.states[index];
+ checked(core.put_state(index, state.initial, oldIds.get(state.name) ?? -1), state.line);
+ if (restored) checked(core.put_restored_value(index, restored[index]), state.line);
+ }
+ for (const op of next.instructions) checked(core.put_instruction(op.op, op.arg, op.value), op.line);
+ for (const target of next.pointerTargets) checked(core.put_pointer_target(target.input, target.base, target.count));
+ for (const [index, handler] of next.handlers.entries()) {
+ checked(core.put_handler(handler.event, handler.start, handler.length), handler.line);
+ if (handler.parameterCount)
+ checked(handler.parameterBase === 255 ? core.bind_target_handler(index, handler.parameterCount)
+ : core.bind_handler(index, handler.parameterBase, handler.parameterCount), handler.line);
+ }
+ checked(restored ? core.commit_restored_program() : core.commit_program());
+ } catch (error) {
+ core.abort_program();
+ throw error;
+ }
+ pointRoutes = new Map(next.inputs.flatMap(input => (input.routes ?? []).map(route => [route.target, route])));
+ program = next;
+ bindings = next.views.map(view => Object.entries(view.bindings));
+ appliedSource = source;
+ lastEvent = null;
+ lastFailure = null;
+ }
+
+ const api = {
+ get currentProgram() { return program; },
+ configureEvaluation({ cacheEntries = evaluationPolicy.cacheEntries,
+ historyEntries = evaluationPolicy.historyEntries, workLimit = evaluationPolicy.workLimit } = {}) {
+ available();
+ for (const [name, value, maximum] of [['cacheEntries', cacheEntries, 32],
+ ['historyEntries', historyEntries, 8], ['workLimit', workLimit, 0xffffffff]]) {
+ if (!Number.isInteger(value) || value < 0 || value > maximum)
+ throw new Error(`${name}: 0~${maximum} 사이의 정수여야 합니다.`);
+ }
+ checked(core.configure_evaluation(cacheEntries, historyEntries));
+ evaluationPolicy = { cacheEntries, historyEntries, workLimit };
+ return { ...evaluationPolicy };
+ },
+ forgetEvaluation(part = 'all') {
+ available();
+ const flags = part === 'all' ? 3 : part === 'values' ? 1 : part === 'history' ? 2 : undefined;
+ if (flags === undefined) throw new Error('삭제할 기록은 values, history, all 중 하나여야 합니다.');
+ core.forget_evaluation(flags);
+ },
+ evaluationStats() {
+ return { ...evaluationPolicy, work: core.evaluation_info(1),
+ instructions: core.evaluation_info(2), cacheHits: core.evaluation_info(3),
+ reservedBytes: core.evaluation_info(6), dedicatedBytes: core.evaluation_info(7),
+ sharedBytes: core.evaluation_info(8), reuseAvailable: Boolean(core.evaluation_info(9)),
+ workspaceTagBytes: core.evaluation_info(10) };
+ },
+ apply(source) {
+ available();
+ if (typeof source !== 'string' || new TextEncoder().encode(source).length > sourceLimit) {
+ throw problem('정의는 UTF-8 기준 32,768바이트 이하의 텍스트여야 합니다.');
+ }
+ if (held.size) throw problem('누르고 있는 입력을 놓은 뒤 적용하세요. 현재 작업은 그대로 유지합니다.');
+ const next = freezeProgram(compile(source));
+ load(next, source);
+ return next;
+ },
+ restoreDocument(document) {
+ available();
+ if (held.size) throw problem('누르고 있는 입력을 놓은 뒤 복원하세요.');
+ const next = validateDocument(document);
+ load(freezeProgram(next.program), next.source, next.values);
+ return { source: next.source, draft: next.draft };
+ },
+ exportDocument(draft = appliedSource) {
+ available();
+ if (!program) throw problem('먼저 코드를 적용하세요.');
+ if (held.size) throw problem('조작을 끝낸 뒤 배치를 저장하세요.');
+ if (lastFailure) throw problem('실행 오류를 해결한 뒤 배치를 저장하세요.');
+ if (typeof draft !== 'string' || new TextEncoder().encode(draft).length > sourceLimit)
+ throw problem('편집 중인 코드는 UTF-8 32 KiB 이하여야 합니다.');
+ return { format: 'pure-workbench', version: 2, source: appliedSource, draft,
+ states: program.states.map((state, i) => {
+ const value = core.get_state(i);
+ return { name: state.name, value: Object.is(value, -0) ? '-0' : value };
+ }) };
+ },
+ reset() {
+ available();
+ held.clear();
+ points.clear();
+ core.reset_state();
+ lastEvent = null;
+ lastFailure = null;
+ },
+ frame(count = 1) {
+ available();
+ if (!Number.isInteger(count) || count < 0 || count > 600) throw new Error('프레임 수는 0~600 사이의 정수여야 합니다.');
+ if (!program) return 0;
+ for (let i = 0; i < count; i++) {
+ const code = dispatch(0, 'frame', 'frame', 'clock');
+ if (code !== 0) return code;
+ }
+ return 0;
+ },
+ down(name, source = 'external') {
+ available();
+ const index = inputIndex(name);
+ if (program.inputs[index].kind === 'value') throw new Error('숫자 입력은 change(name, value)를 사용하세요.');
+ if (program.inputs[index].target || program.inputs[index].routes) throw new Error('좌표 입력은 point(name, phase, x, y)를 사용하세요.');
+ const sources = held.get(name) || new Set();
+ if (sources.has(source)) return 0;
+ const wasDown = sources.size > 0;
+ sources.add(source);
+ held.set(name, sources);
+ return wasDown ? 0 : dispatch(1 + index * 2, name, 'down', source);
+ },
+ up(name, source = 'external') {
+ available();
+ const index = inputIndex(name);
+ if (program.inputs[index].kind === 'value') throw new Error('숫자 입력은 change(name, value)를 사용하세요.');
+ if (program.inputs[index].target || program.inputs[index].routes) throw new Error('좌표 입력은 point(name, phase, x, y)를 사용하세요.');
+ const sources = held.get(name);
+ if (!sources?.delete(source)) return 0;
+ if (sources.size) return 0;
+ held.delete(name);
+ return dispatch(2 + index * 2, name, 'up', source);
+ },
+ change(name, value, source = 'external') {
+ available();
+ const index = inputIndex(name);
+ if (program.inputs[index].kind !== 'value') throw new Error('숫자 값에 연결된 입력이 아닙니다.');
+ if (!Number.isFinite(value)) throw new Error('입력 값은 유한한 숫자여야 합니다.');
+ return dispatch(33 + index, name, 'change', source, null, value);
+ },
+ point(name, phase, x, y, source = 'external', target = undefined) {
+ available();
+ const index = inputIndex(name);
+ const input = program.inputs[index];
+ if (!input.target && !input.routes) throw new Error('포인터에 연결된 입력이 아닙니다.');
+ const route = input.routes ? pointRoutes.get(target) : undefined;
+ if (input.routes && (!route || program.pointerTargets[route.route].input !== index)) throw new Error('포인터 그룹에 연결된 도형 이름이 필요합니다.');
+ if (!input.routes && target !== undefined) throw new Error('단일 포인터 입력에는 대상 인자를 지정하지 않습니다.');
+ const owner = route ? `${name}:${target}` : name;
+ if (!['down', 'move', 'up', 'cancel'].includes(phase)) throw new Error('포인터 단계는 down, move, up, cancel 중 하나여야 합니다.');
+ if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error('포인터 좌표는 유한한 숫자여야 합니다.');
+ if (typeof source !== 'string' || source.length === 0) throw new Error('포인터 출처 이름이 필요합니다.');
+ let point = points.get(owner);
+ if (phase === 'down') {
+ if (point?.source === source) return 0;
+ if (point) throw new Error('이 입력은 다른 포인터가 사용 중입니다.');
+ point = {name, source, x, y, target, route:route?.route};
+ points.set(owner, point);
+ held.set(owner, new Set([source]));
+ } else {
+ if (!point || point.source !== source) return 0;
+ point.x = x; point.y = y;
+ if (phase === 'up' || phase === 'cancel') { points.delete(owner); held.delete(owner); }
+ }
+ const event = phase === 'move' ? 17 + index : phase === 'cancel' ? 25 + index : 1 + index * 2 + (phase === 'up' ? 1 : 0);
+ return dispatch(event, name, phase, source, point);
+ },
+ releaseAll() {
+ available();
+ let firstError = 0;
+ for (const point of [...points.values()]) {
+ const status = api.point(point.name, 'cancel', point.x, point.y, point.source, point.target);
+ if (!firstError) firstError = status;
+ }
+ for (const [name, sources] of [...held]) {
+ for (const source of [...sources]) {
+ const status = api.up(name, source);
+ if (!firstError) firstError = status;
+ }
+ }
+ return firstError;
+ },
+ exportProject() {
+ return { format: 'pure-workbench', version: 1, source: appliedSource };
+ },
+ // Synchronous trusted output adapter. Callbacks run only after the whole
+ // display has been evaluated. Runtime mutation/reentrant reads are refused.
+ // Exceptions from an adapter propagate; the next call republishes all values.
+ present(target) {
+ available();
+ presenting = true;
+ try {
+ const { begin, state, view, value, end } = target;
+ for (const callback of [begin, state, view, value, end])
+ if (typeof callback !== 'function') throw new TypeError('출력 어댑터 함수가 필요합니다.');
+ const error = evaluateDisplay();
+ begin.call(target, program);
+ for (let i = 0; i < (program?.states.length ?? 0); i++) state.call(target, i, core.get_state(i));
+ let slot = 0, visible = 0;
+ for (let i = 0; i < bindings.length; i++) {
+ const valid = Boolean(displayValid[i]);
+ view.call(target, i, valid);
+ for (const [name] of bindings[i]) {
+ if (valid) value.call(target, i, name, displayValues[slot]);
+ slot++;
+ }
+ if (valid) visible++;
+ }
+ end.call(target, visible);
+ return status(error);
+ } finally { presenting = false; }
+ },
+ snapshot() {
+ available();
+ const error = evaluateDisplay();
+ const states = Object.fromEntries((program?.states ?? []).map((state, index) => [state.name, core.get_state(index)]));
+ const views = [];
+ let slot = 0;
+ for (let i = 0; i < bindings.length; i++) {
+ const view = program.views[i];
+ const rendered = displayValid[i] ? { id: view.id, type: view.type, color: view.color, prefix: view.prefix } : null;
+ for (const [name] of bindings[i]) {
+ if (rendered) rendered[name] = displayValues[slot];
+ slot++;
+ }
+ if (rendered) views.push(rendered);
+ }
+ return { states, views, ...status(error) };
+ },
+ };
+ api.configureEvaluation(evaluationOptions);
+ return Object.freeze(api);
+}
diff --git a/prototypes/connections/results/2026-09-23-prepared-connections/verification.json b/prototypes/connections/results/2026-09-23-prepared-connections/verification.json
new file mode 100644
index 0000000..2c4036d
--- /dev/null
+++ b/prototypes/connections/results/2026-09-23-prepared-connections/verification.json
@@ -0,0 +1,51 @@
+{
+ "date": "2026-09-23",
+ "scope": "Authored storage/response connections, prepared range semantics, lifetime correctness; no performance comparison",
+ "compiler": "cc (Debian 14.2.0-19) 14.2.0",
+ "os_page_size_bytes": 16384,
+ "cwd": "prototypes/connections",
+ "native_command": "make check EVIDENCE=results/2026-09-23-prepared-connections/native.json",
+ "sanitized_command": "make check BUILD=build/sanitized CFLAGS='-std=c17 -O1 -g -Wall -Wextra -Wpedantic -Werror -fsanitize=address,undefined -fno-omit-frame-pointer' EVIDENCE=results/2026-09-23-prepared-connections/sanitized.json",
+ "core_checks": {
+ "test-flow": {
+ "native": "passed",
+ "asan_ubsan": "passed"
+ },
+ "test-program": {
+ "native": "passed",
+ "asan_ubsan": "passed",
+ "boundary_semantic_comparisons": 3584
+ },
+ "leak_detection_disabled": false
+ },
+ "unit_binary_sha256": {
+ "build/test-flow": "63830f166dc326d95e91a30b24bba1f750de05c50b60cf454616b35eef1634dc",
+ "build/test-program": "cb8e9de15301090fda719144bd0fbbbeb9e6e91a14b2bae8e434df2ac510b527",
+ "build/sanitized/test-flow": "1b608d5ad31b21180e8a4bce0c0ceee79db281c93ef2e7adf0261ca0b5f96df2",
+ "build/sanitized/test-program": "4396abe70699580d8c27d74aeb50713a9452e6f4060289b97e2d48d5c478666d"
+ },
+ "integration_scenarios_each": 22,
+ "original_storage_scenarios": 16,
+ "authored_connection_scenarios": 6,
+ "source_snapshots": "sources/",
+ "source_hashes_equal": true,
+ "source_snapshots_match_records": true,
+ "final_binaries_match_records": true,
+ "prepared_connections_equal": true,
+ "native_dynamic_dependencies_ldd": [
+ "\tlinux-vdso.so.1 (0x00007ffee8528000)",
+ "\tlibc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x00007ffee82e0000)",
+ "\t/lib/ld-linux-aarch64.so.1 (0x00007ffee84f0000)"
+ ],
+ "limitations": [
+ "Fixed byte-range authoring profile, not the general application language or graph runtime",
+ "One registered storage effect per handler; constant ranges, no dynamic branches or feedback loops",
+ "The native connection authoring parser is separate from the existing UI/scalar parser",
+ "runtime_view_steps is a structural description, not a measured instruction count",
+ "Logical payload bounds exclude mmap page rounding, page cache, disk retention, sockets and client allocations",
+ "Selecting input bytes for a reply retains and charges the complete original receive allocation",
+ "Synchronous storage calls delay other connections and timeout enforcement",
+ "Linux AArch64 only; macOS and real power loss have not been tested",
+ "Unpublished files left by crashes have no automatic recovery/collection policy"
+ ]
+}