diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/results | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/results')
534 files changed, 40253 insertions, 0 deletions
diff --git a/prototypes/connections/results/2026-09-23-byte-storage/README.md b/prototypes/connections/results/2026-09-23-byte-storage/README.md new file mode 100644 index 0000000..f28229b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/README.md @@ -0,0 +1,31 @@ +# 019 검증 자료 + +> 작성 기능 도입 후 검증은 [020](../2026-09-23-prepared-connections/README.md)에 있다. +> 이 디렉터리의 소스·결과는 도입 전 기록으로 보존한다. + +SQL 의존성을 제거한 C17/POSIX 저장 경로와 실제 루프백 TCP·기존 Workbench CLI의 +기능·수명 검사다. 시간·처리량·전체 RSS 비교는 수행하지 않았다. + +- [native.json](native.json): 일반 빌드의 16개 통합 시나리오, 종료 시 자원 수, 환경·소스·실행 파일 해시. +- [sanitized.json](sanitized.json): ASan·UBSan 빌드의 같은 시나리오. 누수 검사를 끄지 않았다. +- [verification.json](verification.json): 명령, 단위 검사 결과, 링크 의존성, 소스 사본·실행 파일 일치 확인. +- `sources/`: 결과에 기록된 소스·Wasm 사본. 이후 작업 트리가 바뀌면 이 파일들을 별도 작업 트리에 사용한다. + +단위 검사는 필요한 범위만 읽기, 갱신 중 이전 참조, 공개 전후 오류, 공개 전 자식 프로세스 +종료 등을 포함한다. 처음 검사한 범위 fixture는 4 KiB를 가정했으나 현재 환경은 16 KiB +페이지였다. 최종 검사는 실제 OS 페이지 크기를 사용해 페이지 경계를 넘고, 서로 다른 +페이지의 잘못된 참조도 구분할 수 있는 데이터로 수정했다. 초기 실행 결과는 `initial/`에 +보존했다. 그때와 최종 소스의 유일한 차이는 `initial/test-flow.c`이며, 기록의 해시와 맞는다. +성능 비교 자료로 사용하지 않는다. + +전원 장애나 실제 디스크 고장을 재현한 것은 아니다. fsync 전후의 실패 주입과 프로세스 +종료를 검사했다. 본문 예산은 논리적인 보유 바이트 수이며 매핑·페이지 캐시·디스크·소켓· +클라이언트 메모리를 합친 상한이 아니다. 전송 시점에 따라 수신 접두부 복사량과 쓰기 경쟁의 +승자가 바뀔 수 있으므로 빌드 간 진단 수치의 차이를 최적화 결과로 해석하지 않는다. + +재현은 [구현 README](../../README.md)의 `make check` 명령을 따른다. +`EVIDENCE=/새/경로.json`을 추가하면 새 파일로 기록하며 기존 자료는 덮어쓰지 않는다. +이 환경에서는 소켓·자식 프로세스·LeakSanitizer의 샌드박스 제한 때문에 승인된 환경에서 +실행했다. 외부 서비스는 사용하지 않았다. macOS 실행은 아직 확인하지 않았다. + +이전 SQLite 경로의 자료는 [018](../2026-09-23-native-connections/README.md)에 별도로 있다. diff --git a/prototypes/connections/results/2026-09-23-byte-storage/initial/native.json b/prototypes/connections/results/2026-09-23-byte-storage/initial/native.json new file mode 100644 index 0000000..450a8ac --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/initial/native.json @@ -0,0 +1,116 @@ +{ + "kind": "native-byte-path-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" + ], + "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": 39176 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 41, + "commits": 11, + "gets": 14, + "conflicts": 5, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115417, + "server_structure_bytes": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "2f8c664c61f0ea0a26574530c165a7fdff1058902b9938e73d146927b1fd44eb", + "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": "9b88dfd664bf85e3fd1e971bf5d1b5bd408f6c2a069ffd02d41c05973d8cd2dd", + "prototypes/connections/store.h": "8675d39a02558b8f877477b3715e15816d12f4b4864eda19a63635941503c774", + "prototypes/connections/server.c": "e85b6d7f91e14eed6b22a4615654c33e9cc3163020ffa80fbd201f8f055eb3c2", + "prototypes/connections/check.py": "30363c718294540fbc653223fdf2a07dbcafbfa8bf1470aa5704a33ce0ea2b01", + "prototypes/connections/test-flow.c": "e209f1d76e076d5f1a680908bbfbfb0b07efc79ec99b308e544fe3902ef26d05", + "prototypes/connections/Makefile": "82995e6c3eda516c2fae02d680dff90371fb595edc2c5c9d4b3e5fd05137d36b", + "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-byte-storage/initial/sanitized.json b/prototypes/connections/results/2026-09-23-byte-storage/initial/sanitized.json new file mode 100644 index 0000000..b1d57b3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/initial/sanitized.json @@ -0,0 +1,116 @@ +{ + "kind": "native-byte-path-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" + ], + "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": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "bd016cc93b1bd2a9c01d7f84bcc212e049b28e41528d52a90bdb70fb13100cb1", + "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": "9b88dfd664bf85e3fd1e971bf5d1b5bd408f6c2a069ffd02d41c05973d8cd2dd", + "prototypes/connections/store.h": "8675d39a02558b8f877477b3715e15816d12f4b4864eda19a63635941503c774", + "prototypes/connections/server.c": "e85b6d7f91e14eed6b22a4615654c33e9cc3163020ffa80fbd201f8f055eb3c2", + "prototypes/connections/check.py": "30363c718294540fbc653223fdf2a07dbcafbfa8bf1470aa5704a33ce0ea2b01", + "prototypes/connections/test-flow.c": "e209f1d76e076d5f1a680908bbfbfb0b07efc79ec99b308e544fe3902ef26d05", + "prototypes/connections/Makefile": "82995e6c3eda516c2fae02d680dff90371fb595edc2c5c9d4b3e5fd05137d36b", + "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-byte-storage/initial/test-flow.c b/prototypes/connections/results/2026-09-23-byte-storage/initial/test-flow.c new file mode 100644 index 0000000..23798b4 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/initial/test-flow.c @@ -0,0 +1,178 @@ +#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. */ + unsigned char bytes[12288]; + for (size_t i = 0; i < sizeof(bytes); ++i) bytes[i] = (unsigned char)i; + CHECK(pure_store_put(path, "range", 0, (PureSpan){bytes, sizeof(bytes)}, &revision) == PURE_OK); + CHECK(pure_store_get_range(path, "range", 4079, 4201, 4201, &first) == PURE_OK); + CHECK(first.body.size == 4201 && !memcmp(first.body.data, bytes + 4079, 4201)); + pure_store_release(&first); + 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-byte-storage/native.json b/prototypes/connections/results/2026-09-23-byte-storage/native.json new file mode 100644 index 0000000..52bea4c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/native.json @@ -0,0 +1,116 @@ +{ + "kind": "native-byte-path-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" + ], + "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": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "2f8c664c61f0ea0a26574530c165a7fdff1058902b9938e73d146927b1fd44eb", + "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": "9b88dfd664bf85e3fd1e971bf5d1b5bd408f6c2a069ffd02d41c05973d8cd2dd", + "prototypes/connections/store.h": "8675d39a02558b8f877477b3715e15816d12f4b4864eda19a63635941503c774", + "prototypes/connections/server.c": "e85b6d7f91e14eed6b22a4615654c33e9cc3163020ffa80fbd201f8f055eb3c2", + "prototypes/connections/check.py": "30363c718294540fbc653223fdf2a07dbcafbfa8bf1470aa5704a33ce0ea2b01", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/Makefile": "82995e6c3eda516c2fae02d680dff90371fb595edc2c5c9d4b3e5fd05137d36b", + "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-byte-storage/sanitized.json b/prototypes/connections/results/2026-09-23-byte-storage/sanitized.json new file mode 100644 index 0000000..e2db165 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sanitized.json @@ -0,0 +1,116 @@ +{ + "kind": "native-byte-path-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" + ], + "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": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + }, + { + "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": 39176 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "bd016cc93b1bd2a9c01d7f84bcc212e049b28e41528d52a90bdb70fb13100cb1", + "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": "9b88dfd664bf85e3fd1e971bf5d1b5bd408f6c2a069ffd02d41c05973d8cd2dd", + "prototypes/connections/store.h": "8675d39a02558b8f877477b3715e15816d12f4b4864eda19a63635941503c774", + "prototypes/connections/server.c": "e85b6d7f91e14eed6b22a4615654c33e9cc3163020ffa80fbd201f8f055eb3c2", + "prototypes/connections/check.py": "30363c718294540fbc653223fdf2a07dbcafbfa8bf1470aa5704a33ce0ea2b01", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/Makefile": "82995e6c3eda516c2fae02d680dff90371fb595edc2c5c9d4b3e5fd05137d36b", + "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-byte-storage/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/Makefile new file mode 100644 index 0000000..5f5005b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/Makefile @@ -0,0 +1,20 @@ +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 store.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) server.c store.c flow.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 $@ + +check: $(BUILD)/test-flow $(BUILD)/pure-connections + $(BUILD)/test-flow + python3 check.py --server $(BUILD)/pure-connections $(if $(EVIDENCE),--output "$(EVIDENCE)") diff --git a/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/check.py new file mode 100644 index 0000000..1d7b403 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/check.py @@ -0,0 +1,317 @@ +#!/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): + self.process = subprocess.Popen([str(binary), '--store', str(storage), '--port', '0', + '--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) + return {'kind': 'native-byte-path-correctness-not-performance', 'checks': checks, + 'server_runs': records, 'storage': server.ready['storage']} + + +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', 'server.c', 'check.py', 'test-flow.c', 'Makefile')), + *(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-byte-storage/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/flow.c new file mode 100644 index 0000000..1070c70 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/flow.h new file mode 100644 index 0000000..6e27c58 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/server.c new file mode 100644 index 0000000..cea5af8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/server.c @@ -0,0 +1,347 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.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; + 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; + if (c->put) { + result = pure_store_put(c->server->path, c->key, c->expected, + (PureSpan){c->body, c->body_size}, &revision); + /* The synchronous write no longer accesses this input on any result. */ + release_payload(c); + if (result == PURE_OK) ++c->server->commits; + } else { + result = pure_store_get(c->server->path, c->key, + 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; + ++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, c->stored.body); 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; + for (int i = 1; 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], "--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 (!server.path) goto usage; + 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 --store DIRECTORY [--port 8767] [--budget BYTES] [--timeout-ms 10000] [--send-buffer BYTES]\n", stderr); + return 2; +} diff --git a/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/store.c new file mode 100644 index 0000000..d0111d0 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/store.c @@ -0,0 +1,225 @@ +#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}; +} + +static PureResult view_range(const char *path, const char *key, size_t offset, + size_t length, bool whole, 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 (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 view_range(path, key, 0, 0, true, 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 view_range(path, key, offset, length, false, 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-byte-storage/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/store.h new file mode 100644 index 0000000..d9bcc1c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/store.h @@ -0,0 +1,34 @@ +#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); +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-byte-storage/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/connections/test-flow.c new file mode 100644 index 0000000..5ff028d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/cli.mjs new file mode 100644 index 0000000..b67f4d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/examples/direct.pure new file mode 100644 index 0000000..b29721c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/remote.mjs new file mode 100644 index 0000000..6ee6a59 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/compiler.mjs new file mode 100644 index 0000000..6aec335 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/core.wasm Binary files differnew file mode 100644 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/core.wasm diff --git a/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/project.mjs new file mode 100644 index 0000000..861e0fc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-byte-storage/sources/prototypes/workbench/web/runtime.mjs new file mode 100644 index 0000000..d8d6618 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/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-byte-storage/verification.json b/prototypes/connections/results/2026-09-23-byte-storage/verification.json new file mode 100644 index 0000000..418c75a --- /dev/null +++ b/prototypes/connections/results/2026-09-23-byte-storage/verification.json @@ -0,0 +1,52 @@ +{ + "date": "2026-09-23", + "scope": "SQL-free storage correctness, lifetimes and admission bounds; not a 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-byte-storage/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-byte-storage/sanitized.json", + "core_checks": { + "native": "passed", + "asan_ubsan": "passed", + "leak_detection_disabled": false, + "coverage": [ + "partial transfer and single release", + "range access including actual page boundaries and nonzero mapping offsets", + "immutable read across replacement", + "budget, conflicts, empty data, revision exhaustion", + "invalid file rejection", + "failure before publication preserves prior version", + "failure after publication reports uncertain durability", + "child exit before publication preserves prior version and releases writer lock" + ], + "native_binary_sha256": "a3189c0db63960bdf7b464d3813403065d7222366c00895e8eaad66450fd2f27", + "sanitized_binary_sha256": "acf03ba3881be9d534fc2419abed964f27d4dbd23173a34ab3efdeb831b7547f" + }, + "integration_scenarios_each": 16, + "source_hashes_equal": true, + "source_snapshots": "sources/", + "source_snapshots_match_records": true, + "final_binaries_match_records": true, + "native_dynamic_dependencies_ldd": [ + "\tlinux-vdso.so.1 (0x00007ffed5098000)", + "\tlibc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x00007ffed4e50000)", + "\t/lib/ld-linux-aarch64.so.1 (0x00007ffed5060000)" + ], + "initial_run": { + "directory": "initial/", + "difference": "Only test-flow.c changed: page-boundary fixture now uses actual OS page size and distinct per-page contents", + "original_test_sha256_verified": true + }, + "limitations": [ + "Linux AArch64 only; macOS not executed", + "No actual power-loss or disk-fault testing", + "Logical payload budget excludes page rounding, page cache, disk retention, OS sockets and client allocations", + "Unpublished files left by crashes are ignored but not automatically collected", + "Synchronous filesystem operations can delay other connections and timeout enforcement", + "Single-threaded control API with one cooperating process writer lock per directory", + "Whole-object replacement, no multi-object transaction or payload checksum", + "Stored files must not be mutated in place by external writers", + "Pure-authored external effects and automatic path optimization remain unimplemented" + ] +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/README.md b/prototypes/connections/results/2026-09-23-document-edits/README.md new file mode 100644 index 0000000..ee6ed76 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/README.md @@ -0,0 +1,27 @@ +# 문서 부분 변경과 micro 비용 경계 + +2026-09-23, Linux AArch64 / GCC 14.2.0. 정확성·구조 비용 검증이며 성능 순위 자료가 아니다. + +- `native.json`, `sanitized.json`: 각 36개 통합 시나리오, 실행 코어와 splice 단위 검사, 바이너리·소스 해시. +- `core.json`: 실행 전용 아카이브의 섹션·심볼·구조체 비용과 검사 상한. +- `boundary-checks.json`: 코어만 선택한 빌드의 파일, 비용 증가 시 검사 실패, 전체 CLI/서버 ELF 비용. +- `verification.json`: 두 빌드의 소스와 바이너리 확인, 서버 종료 시 회수 확인. +- `sources/`: 해시가 일치하는 소스·기존 클라이언트 37개. 검증에 사용한 Workbench Wasm도 포함한다. + +실행 명령 (`prototypes/connections` 기준): + +```sh +make all +make check EVIDENCE=results/2026-09-23-document-edits/native.json +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-document-edits/sanitized.json +make check-core CORE_EVIDENCE=results/2026-09-23-document-edits/core.json +``` + +기존 결과를 덮어쓰지 않는다. 재현 시 출력 인자를 생략하거나 새 경로를 사용한다. +동결 소스의 `sources/prototypes/connections`에서도 같은 빌드·검사가 가능하다. +C17 컴파일러·make·Python·Node·로컬 TCP/자식 프로세스 실행이 필요하며, 크기 검사는 +GNU 호환 ar/nm/size를 사용한다. ASan 계측물에는 release 크기 상한을 적용하지 않는다. + +1 MiB 문서 편집의 11 B는 입력 크레딧이다. 파일을 재작성하는 4 KiB 호스트 스택 버퍼, +커널·파일·allocator·파서·서버 비용을 포함한 전체 메모리라고 해석하지 않는다. +[구현, 수치 범위와 한계](../../../../docs/research/023-document-edits.md). diff --git a/prototypes/connections/results/2026-09-23-document-edits/boundary-checks.json b/prototypes/connections/results/2026-09-23-document-edits/boundary-checks.json new file mode 100644 index 0000000..c24fa21 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/boundary-checks.json @@ -0,0 +1,34 @@ +{ + "core_only_build_files": [ + "evaluate.o", + "libpure-execution.a", + "runtime.o" + ], + "reject_smaller_budget": [ + "domain_bytes: 200 > 199" + ], + "reject_added_dependency_and_static_state": [ + "unexpected archive members", + "bss: 4096 > 0", + "unexpected dependencies: ['malloc']" + ], + "selected_binaries": { + "pure-edit": { + "debug_file_bytes": 177384, + "stripped_file_bytes": 67904, + "sections": " text\t data\t bss\t dec\t hex\tfilename\n 35807\t 944\t 16\t 36767\t 8f9f\t/home/eunhhu/work/pure/prototypes/connections/build/pure-edit\n", + "direct_dynamic_libraries": [ + "0x0000000000000001 (NEEDED) Shared library: [libc.so.6]" + ] + }, + "pure-connections": { + "debug_file_bytes": 199080, + "stripped_file_bytes": 68000, + "sections": " text\t data\t bss\t dec\t hex\tfilename\n 42761\t 1040\t 16\t 43817\t ab29\t/home/eunhhu/work/pure/prototypes/connections/build/pure-connections\n", + "direct_dynamic_libraries": [ + "0x0000000000000001 (NEEDED) Shared library: [libc.so.6]" + ] + } + }, + "scope": "Selected files and direct ELF dependencies, not complete dependency memory, RSS, stack or latency." +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/core.json b/prototypes/connections/results/2026-09-23-document-edits/core.json new file mode 100644 index 0000000..8c0caf7 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5932, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5012\t 0\t 0\t 5012\t 1394\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "d56c50e38c6c0949cfad069d34568da2ac45b1d60097cf9e7c4bf804f44c8128", + "layout_binary_sha256": "47cf1803007fc1ec85f119bd46195ad39d9f01ef0146bf5cc45e1af31eb18098", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/native.json b/prototypes/connections/results/2026-09-23-document-edits/native.json new file mode 100644 index 0000000..c9bb1d4 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/native.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "07c328c98769d02d544aea2fc5389048a7620c00d9b3ea49b9c7ba45e33984b1", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "0e4ec2c78334af2056af376a6ba9aef7416a34c6d4fbd9082f4abb640e6de28c", + "editor_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "binary_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f", + "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": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "5d08639b5a71572553bda47cbd35562805d601aaa81f20731cd9154768ec025e", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "e1735316769b4ce4d5904b9587ccfa3ebeafa26517c53dba4491191d58c97b8e", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "b5e7ed077be1333f3a948844608785dbab98bdc9c4ae6fe9c6a4805655fc9c63", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-document-edits/sanitized.json b/prototypes/connections/results/2026-09-23-document-edits/sanitized.json new file mode 100644 index 0000000..0e2dbe4 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sanitized.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "776be59b5e35de39587fd5a8f703160572b3a6c098a22af635e597208bba1950", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "ec0db33c9937034148d934c658c98aa98a2fdb279a92f96a2d7374e2d292468e", + "editor_sha256": "23ee47873d7924163ace86cad44946e71fd21a6122930f35eff1c36f5e418bdc", + "binary_sha256": "f145f0f30c944e400ef8d1594bd0387f42f42569290f28c7a1272a4ee8849d4c", + "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": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "5d08639b5a71572553bda47cbd35562805d601aaa81f20731cd9154768ec025e", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "e1735316769b4ce4d5904b9587ccfa3ebeafa26517c53dba4491191d58c97b8e", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "b5e7ed077be1333f3a948844608785dbab98bdc9c4ae6fe9c6a4805655fc9c63", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-document-edits/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/Makefile new file mode 100644 index 0000000..d1b46e2 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/Makefile @@ -0,0 +1,59 @@ +CC ?= cc +CFLAGS ?= -O2 -g +CFLAGS += -std=c17 -Wall -Wextra -Wpedantic -Werror +BUILD ?= build + +.PHONY: all core editor server check check-core +CORE = runtime.c evaluate.c +HOST = store.c store-port.c +HEADERS = runtime.h program.h store.h store-port.h flow.h result.h +all: core editor server +core: $(BUILD)/libpure-execution.a +editor: $(BUILD)/pure-edit +server: $(BUILD)/pure-connections + +$(BUILD): + mkdir -p $@ + +$(BUILD)/pure-connections: server.c $(HOST) flow.c program.c $(CORE) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) server.c $(HOST) flow.c program.c $(CORE) $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/pure-edit: edit.c $(HOST) program.c $(CORE) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) edit.c $(HOST) program.c $(CORE) $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/runtime.o: runtime.c $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -c runtime.c -o $@ + +$(BUILD)/evaluate.o: evaluate.c program.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -c evaluate.c -o $@ + +$(BUILD)/libpure-execution.a: $(BUILD)/runtime.o $(BUILD)/evaluate.o + $(AR) rcs $@ $^ + +$(BUILD)/test-flow: test-flow.c store.c flow.c store.h flow.h result.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 evaluate.c program.h store.h flow.h result.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-program.c program.c evaluate.c $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-fields: test-fields.c program.c evaluate.c program.h store.h flow.h result.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-fields.c program.c evaluate.c $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-runtime: test-runtime.c program.c $(CORE) $(HOST) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-runtime.c program.c $(CORE) $(HOST) $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-splice: test-splice.c program.c $(CORE) $(HOST) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -DPURE_STORE_TEST test-splice.c program.c $(CORE) $(HOST) $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/core-layout: core-layout.c $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) core-layout.c $(LDFLAGS) $(LDLIBS) -o $@ + +# Run on the release build, separately from sanitizer-instrumented objects. +check-core: core $(BUILD)/core-layout + python3 check_core.py --archive $(BUILD)/libpure-execution.a --layout $(BUILD)/core-layout $(if $(CORE_EVIDENCE),--output "$(CORE_EVIDENCE)") + +check: $(BUILD)/test-flow $(BUILD)/test-program $(BUILD)/test-fields $(BUILD)/test-runtime $(BUILD)/test-splice $(BUILD)/pure-edit $(BUILD)/pure-connections + $(BUILD)/test-flow + $(BUILD)/test-program + $(BUILD)/test-fields + python3 check.py --server $(BUILD)/pure-connections --runtime-test $(BUILD)/test-runtime --splice-test $(BUILD)/test-splice --editor $(BUILD)/pure-edit $(if $(EVIDENCE),--output "$(EVIDENCE)") diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check.py new file mode 100644 index 0000000..4538995 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check.py @@ -0,0 +1,551 @@ +#!/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 sys +import tempfile +import time +from check_edits import check_edits + +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, editor=None): + 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) + fields = field_connections(binary, directory, storage, records, checks) + edits = check_edits(editor, binary, ROOT, directory, serving, rpc, header, records, checks) if editor else None + return {'kind': 'prepared-connection-correctness-not-performance', 'checks': checks, + 'prepared_connections': prepared, + 'field_connections': fields, + 'document_edits': edits, + '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'] == { + 'to_end': 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 field_connections(binary, directory, storage, records, checks): + program = ROOT / 'examples/selection.pure' + plans = {'selection': json.loads(subprocess.check_output( + [str(binary), '--check-program', str(program)], text=True))} + assert plans['selection']['runtime_view_steps'] == 1 + assert plans['selection']['steps'][0] == { + 'source_step': 0, 'source': {'offset': 8, 'length': 0, 'to_end': True}, + 'offset': {'value': 0, 'from_u32le': True}, 'length': {'value': 4, 'from_u32le': True}} + document = '처음🙂 Pure는 필드를 연결한다.\n끝' + document_file = directory / 'document.txt' + document_file.write_bytes(document.encode('utf-8')) + with serving(binary, storage, program=program) as selected: + command = [sys.executable, str(ROOT / 'examples/select-text.py'), str(document_file), + '--key', 'text_selection', '--start', '2', '--end', '12', '--url', f'http://127.0.0.1:{selected.port}'] + result = subprocess.run(command, capture_output=True, text=True, timeout=15) + assert result.returncode == 0, (result.stdout, result.stderr) + expected = document[2:12].encode('utf-8') + assert json.loads(result.stdout) == {'revision': '1', 'selected': document[2:12], 'stored_bytes': len(expected)} + assert rpc(selected, 'GET', 'text_selection')[2] == expected + checks.append('text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes') + + # Validation precedes the write: even a stale header cannot expose or + # commit partial input, and prior content/revision survive all failures. + invalid = [b'\0' * n for n in range(8)] + [ + struct.pack('<II', 0xffffffff, 0) + b'abc', + struct.pack('<II', 0, 0xffffffff) + b'abc', + struct.pack('<II', 2, 2) + b'abc', + struct.pack('<II', 4, 0) + b'abc'] + for payload in invalid: + assert rpc(selected, 'PUT', 'text_selection', payload, 1)[0] == 413 + status, headers, body = rpc(selected, 'GET', 'text_selection') + assert status == 200 and headers['ETag'] == '"1"' and body == expected + for key, payload in [('selection_empty', struct.pack('<II', 0, 0)), + ('selection_end', struct.pack('<II', 3, 0) + b'abc')]: + assert rpc(selected, 'PUT', key, payload, 0)[0] == 200 + assert rpc(selected, 'GET', key)[2] == b'' + assert rpc(selected, 'PUT', 'text_selection', struct.pack('<II', 0, 3) + b'abc', 0)[0] == 412 + checks.append('truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid') + records.append(selected.stats) + + declaration = 'layout E { tag: bytes[3]; count: u32le; start: u32le; document: bytes; } ' + 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); } ' + reordered = directory / 'reordered.pure' + reordered.write_text(declaration + get + 'on put { let e = view(E, request.body); let alias = e; ' + 'let part = range(alias.document, alias.start, alias.count); let again = part; ' + 'let s = store.write(request.key, request.revision, again); reply(s, part); }') + plans['reordered'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(reordered)], text=True)) + assert plans['reordered']['runtime_view_steps'] == 1 + with serving(binary, storage, program=reordered) as moved: + status, headers, body = rpc(moved, 'PUT', 'reordered', b'tag' + struct.pack('<II', 4, 2) + b'abcdefgh', 0) + assert status == 200 and body == b'cdef' and headers['ETag'] == '"1"' + assert rpc(moved, 'GET', 'reordered')[2] == b'cdef' + records.append(moved.stats) + checks.append('changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps') + + unused = directory / 'unused-dynamic.pure' + unused.write_text(declaration + get + 'on put { let e = view(E, request.body); ' + 'let unused = range(e.document, e.start, e.count); ' + 'let s = store.write(request.key, request.revision, e.document); reply(s); }') + with serving(binary, storage, program=unused) as guarded: + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 4, 2) + b'abc', 0)[0] == 413 + assert rpc(guarded, 'GET', 'unused_dynamic')[0] == 404 + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 1, 1) + b'abc', 0)[0] == 200 + assert rpc(guarded, 'GET', 'unused_dynamic')[2] == b'abc' + records.append(guarded.stats) + checks.append('unused dynamic field selection retains its validation before the write effect') + + field_get = directory / 'field-get.pure' + field_get.write_text(declaration + 'on get { let o = store.read(request.key); ' + 'let e = view(E, o.body); reply(o, e.document); } ' + put) + plans['field_get'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(field_get)], text=True)) + tail = '다른 길이의 문서'.encode('utf-8') + with serving(binary, storage, program=field_get) as writer: + assert rpc(writer, 'PUT', 'tail', b'tag' + struct.pack('<II', 0, 0) + tail, 0)[0] == 200 + assert rpc(writer, 'PUT', 'bad_layout', b'short', 0)[0] == 200 + assert rpc(writer, 'PUT', 'empty_tail', b'tag' + struct.pack('<II', 0, 0), 0)[0] == 200 + records.append(writer.stats) + with serving(binary, storage, budget=len(tail), program=field_get) as reader: + assert rpc(reader, 'GET', 'tail')[2] == tail + assert rpc(reader, 'GET', 'empty_tail')[2] == b'' + assert rpc(reader, 'GET', 'bad_layout')[0] == 413 + assert reader.stats['peak_retained_payload_bytes'] == len(tail) + records.append(reader.stats) + checks.append('named variable tail reads use the same file version and charge only the selected suffix') + + document_bytes = bytes(range(256)) * 4096 + payload = struct.pack('<II', 16, len(document_bytes) - 32) + document_bytes + with serving(binary, storage, budget=len(payload), program=program) as slow: + with socket.create_connection(('127.0.0.1', slow.port), timeout=8) as sock: + sock.sendall(header('PUT', 'dynamic_slow', len(payload), b'If-None-Match: *\r\n') + payload) + response = http.client.HTTPResponse(sock) + response.begin() + assert response.status == 200 and response.read(1) == document_bytes[16:17] + assert rpc(slow, 'GET', 'binary')[0] == 503 + assert response.read() == document_bytes[17:-16] + response.close() + assert rpc(slow, 'GET', 'binary')[2] == b'new' + assert rpc(slow, 'GET', 'dynamic_slow')[2] == document_bytes[16:-16] + assert slow.stats['peak_retained_payload_bytes'] == len(payload) + records.append(slow.stats) + checks.append('data-dependent response keeps and charges the original input through partial transmission') + return plans + + +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) + parser.add_argument('--runtime-test', type=Path) + parser.add_argument('--splice-test', type=Path) + parser.add_argument('--editor', 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), args.editor.resolve() if args.editor else None) + if args.runtime_test: + runtime_test = args.runtime_test.resolve() + tested = subprocess.run([str(runtime_test)], cwd=ROOT, capture_output=True, text=True, timeout=30) + assert tested.returncode == 0, (tested.stdout, tested.stderr) + result['execution_core'] = json.loads(tested.stdout) + result['execution_core_test_sha256'] = hashlib.sha256(runtime_test.read_bytes()).hexdigest() + if args.splice_test: + splice_test = args.splice_test.resolve() + tested = subprocess.run([str(splice_test)], cwd=ROOT, capture_output=True, text=True, timeout=30) + assert tested.returncode == 0, (tested.stdout, tested.stderr) + result['document_edit_units'] = json.loads(tested.stdout) + result['document_edit_test_sha256'] = hashlib.sha256(splice_test.read_bytes()).hexdigest() + if args.editor: + result['editor_sha256'] = hashlib.sha256(args.editor.resolve().read_bytes()).hexdigest() + 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', + 'runtime.c', 'runtime.h', 'result.h', 'evaluate.c', 'store-port.c', 'store-port.h', 'test-runtime.c', + 'server.c', 'check.py', 'check_edits.py', 'edit.c', 'test-splice.c', + 'test-flow.c', 'test-program.c', 'test-fields.c', 'Makefile', + 'core-layout.c', 'check_core.py', 'core-budget.json', 'examples/editor.pure', + 'examples/storage.pure', 'examples/excerpt.pure', 'examples/selection.pure', 'examples/select-text.py')), + *(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-document-edits/sources/prototypes/connections/check_core.py b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check_core.py new file mode 100644 index 0000000..321896e --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check_core.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Fail when the selected execution core grows past its reviewed cost boundary. + +This is a release-build structural check using GNU-compatible ar/nm/size. Run it +separately from correctness/sanitizer tests; instrumented objects have other costs. +""" +import argparse +import hashlib +import json +from pathlib import Path +import platform +import subprocess + +ROOT = Path(__file__).resolve().parent + + +def run(*args): + return subprocess.check_output([str(arg) for arg in args], text=True) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--archive', type=Path, required=True) + parser.add_argument('--layout', type=Path, required=True) + parser.add_argument('--budget', type=Path, default=ROOT / 'core-budget.json') + parser.add_argument('--output', type=Path) + args = parser.parse_args() + if args.output and args.output.exists(): + parser.error('evidence file already exists') + budget = json.loads(args.budget.read_text()) + archive = args.archive.resolve() + members = run('ar', 't', archive).splitlines() + layout = json.loads(run(args.layout.resolve())) + size_output = run('size', archive) + rows = [line.split() for line in size_output.splitlines() if line.split() and line.split()[0].isdigit()] + if len(rows) != len(members): + parser.error('unsupported size output; no footprint claim was made') + sizes = {name: sum(int(row[i]) for row in rows) for i, name in enumerate(('text', 'data', 'bss'))} + defined = {line.split()[-1] for line in run('nm', '-g', '--defined-only', archive).splitlines() + if len(line.split()) >= 3} + undefined = {line.split()[-1] for line in run('nm', '-u', archive).splitlines() + if len(line.split()) == 2 and line.split()[0] == 'U'} + external = sorted(undefined - defined) + failures = [] + if sorted(members) != sorted(budget['archive_members']): + failures.append('unexpected archive members') + for name, value in sizes.items(): + if value > budget[f'max_{name}_bytes']: + failures.append(f'{name}: {value} > {budget[f"max_{name}_bytes"]}') + for name, maximum in budget['layout_max_bytes'].items(): + if layout[name] > maximum: + failures.append(f'{name}: {layout[name]} > {maximum}') + extra = set(external) - set(budget['external_symbols_allowed']) + if extra: + failures.append(f'unexpected dependencies: {sorted(extra)}') + result = {'passed': not failures, 'failures': failures, 'budget': budget, + 'layout': layout, 'sections': sizes, 'members': members, + 'external_symbols': external, 'size_output': size_output, + 'archive_sha256': hashlib.sha256(archive.read_bytes()).hexdigest(), + 'layout_binary_sha256': hashlib.sha256(args.layout.resolve().read_bytes()).hexdigest(), + 'budget_sha256': hashlib.sha256(args.budget.read_bytes()).hexdigest(), + 'platform': platform.platform(), 'size_tool': run('size', '--version').splitlines()[0]} + output = json.dumps(result, 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 failures: + raise SystemExit(1) + + +if __name__ == '__main__': + main() diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check_edits.py b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check_edits.py new file mode 100644 index 0000000..1a6dc53 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/check_edits.py @@ -0,0 +1,108 @@ +"""Real native automation and HTTP clients editing the same authored document.""" +from concurrent.futures import ThreadPoolExecutor +import json +import socket +import struct +import subprocess + + +def check_edits(editor, binary, root, directory, serving, rpc, header, records, checks): + storage = directory / 'edited-objects' + program = root / 'examples/editor.pure' + file = directory / 'insertion.bin' + + def local(*args, body=None, code=0, source=program, budget=None): + if body is not None: + file.write_bytes(body) + args = (*args, str(file)) + command = [str(editor), '--program', str(source), '--store', str(storage)] + if budget is not None: + command += ['--budget', str(budget)] + result = subprocess.run(command + [str(arg) for arg in args], capture_output=True, timeout=20) + assert result.returncode == code, (command, args, result.stdout, result.stderr) + return result + + initial = 'Pure에서 안녕 🌏\n'.encode() + created = local('edit', 'note', 0, 0, 0, body=initial) + assert json.loads(created.stdout)['revision'] == '1' + start = initial.index('안녕'.encode()) + inserted = '반가워'.encode() + expected = initial[:start] + inserted + initial[start + len('안녕'.encode()):] + local('edit', 'note', 1, start, len('안녕'.encode()), body=inserted) + shown = local('show', 'note') + assert shown.stdout == expected and json.loads(shown.stderr)['revision'] == '2' + checks.append('native authored byte edit and fresh native process restore UTF-8 content') + stale = local('edit', 'note', 1, 0, 0, body=b'bad', code=3) + assert json.loads(stale.stderr) == {'error': 'conflict', 'revision': '2'} + local('edit', 'note', 2, 0xffffffff, 0, body=b'bad', code=2) + assert local('show', 'note').stdout == expected + checks.append('native stale revision and invalid byte position preserve the committed document') + + reordered = directory / 'edit-reordered.pure' + reordered.write_text('layout E { tag: bytes[1]; remove: u32le; start: u32le; body: bytes; } ' + 'on get { let o = store.read(request.key); reply(o, o.body); } ' + 'on put { let e = view(E, request.body); ' + 'let s = store.splice(request.key, request.revision, e.start, e.remove, e.body); reply(s, e.body); }') + local('edit', 'note', 2, len(expected), 0, body=b'!', source=reordered) + expected += b'!' + assert local('show', 'note', source=reordered).stdout == expected + checks.append('native convenience editor derives unaligned reordered field positions from the authored program') + + custom = directory / 'edit-custom.pure' + custom.write_text('on get { let o = store.read(request.key); reply(o, o.body); } ' + 'on put { let s = store.splice(request.key, request.revision, 0, 0, request.body); reply(s); }') + local('edit', 'custom', 0, 0, 0, body=b'ignored', source=custom, code=2) + applied = local('apply', 'custom', 0, body=b'custom input', source=custom) + assert json.loads(applied.stdout) == {'revision': '1', 'reply_bytes': 0} + assert local('show', 'custom', source=custom).stdout == b'custom input' + checks.append('raw native apply runs another authored input contract without changing the executor') + + with serving(binary, storage, program=program) as server: + assert rpc(server, 'GET', 'note')[2] == expected + status, headers, body = rpc(server, 'PUT', 'note', struct.pack('<II', len(expected), 0) + b'?', 3) + assert status == 200 and headers['ETag'] == '"4"' and body == b'?' + expected += b'?' + assert local('show', 'note').stdout == expected + local('edit', 'note', 4, 0, 4, body=b'PURE') + expected = b'PURE' + expected[4:] + assert rpc(server, 'PUT', 'note', struct.pack('<II', 0, 0) + b'stale', 4)[0] == 412 + assert rpc(server, 'GET', 'note')[2] == expected + checks.append('native CLI and external HTTP share revisions and the same authored storage effect in both directions') + with socket.create_connection(('127.0.0.1', server.port), timeout=8) as sock: + sock.sendall(header('PUT', 'note', 12, b'If-Match: "5"\r\n') + b'partial') + sock.shutdown(socket.SHUT_WR) + assert sock.recv(1) == b'' + assert rpc(server, 'PUT', 'note', b'short', 5)[0] == 413 + assert rpc(server, 'PUT', 'note', struct.pack('<II', 0xffffffff, 0), 5)[0] == 413 + assert rpc(server, 'GET', 'note')[2] == expected + checks.append('partial or invalid edit input never modifies the stored document') + with serving(binary, storage, program=program) as other: + with ThreadPoolExecutor(max_workers=2) as pool: + tasks = [pool.submit(rpc, target, 'PUT', 'note', struct.pack('<II', 0, 0) + value, 5) + for target, value in ((server, b'A'), (other, b'B'))] + 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', 'note') + assert status == 200 and headers['ETag'] == '"6"' + assert body == (b'A' if statuses[0] == 200 else b'B') + expected + records.append(other.stats) + checks.append('two processes cannot both splice the same expected document revision') + records.append(server.stats) + + # This tests retained input size for a real large edit, not throughput. + large = bytes(range(256)) * 4096 + local('edit', 'large', 0, 0, 0, body=large) + local('edit', 'large', 1, 524287, 3, body=b'XYZ', budget=11) + expected_large = large[:524287] + b'XYZ' + large[524290:] + assert local('show', 'large').stdout == expected_large + with serving(binary, storage, program=program, budget=11) as bounded: + status, headers, body = rpc(bounded, 'PUT', 'large', struct.pack('<II', 524287, 3) + b'abc', 2) + assert status == 200 and headers['ETag'] == '"3"' and body == b'abc' + assert rpc(bounded, 'PUT', 'large', struct.pack('<II', 0, 0) + b'abcd', 3)[0] == 503 + assert bounded.stats['peak_retained_payload_bytes'] == 11 + records.append(bounded.stats) + assert local('show', 'large').stdout == large[:524287] + b'abc' + large[524290:] + checks.append('1 MiB document changes with 11 input credits; larger input is rejected before an effect') + return {'large_document_bytes': len(large), 'small_edit_input_bytes': 11, + 'storage_rewrites_full_extent': True, 'storage_scratch_bytes': 4096, + 'kind': 'correctness-and-admission-not-total-memory-or-performance'} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-budget.json b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-budget.json new file mode 100644 index 0000000..82d830b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-budget.json @@ -0,0 +1,15 @@ +{ + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": ["runtime.o", "evaluate.o"], + "external_symbols_allowed": ["memcpy", "memmove", "memset", "strlen"], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-layout.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-layout.c new file mode 100644 index 0000000..6687be7 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/core-layout.c @@ -0,0 +1,11 @@ +#include "runtime.h" +#include <stdio.h> + +/* Host-side measurement tool; never linked into libpure-execution.a. */ +int main(void) { + printf("{\"domain_bytes\":%zu,\"work_slot_bytes\":%zu,\"ticket_bytes\":%zu," + "\"prepared_program_bytes\":%zu,\"pointer_bytes\":%zu}\n", + sizeof(PureRuntime), sizeof(PureWork), sizeof(PureTicket), + sizeof(PureProgram), sizeof(void *)); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/edit.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/edit.c new file mode 100644 index 0000000..4daad14 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/edit.c @@ -0,0 +1,153 @@ +#define _POSIX_C_SOURCE 200809L +#include "runtime.h" +#include "store-port.h" +#include "store.h" +#include <inttypes.h> +#include <stdio.h> +#include <string.h> + +/* Native automation entry for the same prepared document program as the + * network adapter. No socket or body serialization in the local execution. */ +static bool number(const char *text, uint64_t limit, uint64_t *out) { + if (!*text) return false; + uint64_t value = 0; + for (; *text; ++text) { + if (*text < '0' || *text > '9') return false; + unsigned digit = (unsigned)(*text - '0'); + if (digit > limit || value > (limit - digit) / 10) return false; + value = value * 10 + digit; + } + *out = value; + return true; +} + +static const char *message(PureResult status) { + switch (status) { + case PURE_OK: return "ok"; + case PURE_NOT_FOUND: return "not_found"; + case PURE_CONFLICT: return "conflict"; + case PURE_BUSY: return "busy"; + case PURE_BUDGET: return "payload_budget"; + case PURE_LIMIT: return "limit"; + case PURE_UNCERTAIN: return "durability_unknown"; + case PURE_DENIED: return "denied"; + case PURE_ALLOCATION: return "allocation"; + case PURE_INVALID: return "invalid"; + default: return "failed"; + } +} + +static void put32(unsigned char *to, uint32_t value) { + for (unsigned i = 0; i < 4; ++i) to[i] = (unsigned char)(value >> (8 * i)); +} + +/* The convenience command fills the program's two numeric splice arguments + * and its trailing insertion bytes. A more general input uses apply. */ +static bool edit_layout(const PureProgram *p, size_t *prefix) { + if (!p->splice || !p->write.to_end || p->write_step || p->input_steps || + !p->splice_offset.from_u32le || !p->splice_remove.from_u32le) return false; + size_t start = p->splice_offset.value, remove = p->splice_remove.value; + *prefix = p->write.offset; + if (*prefix > PURE_OBJECT_LIMIT || p->input_required > *prefix || start > *prefix || + remove > *prefix || *prefix - start < 4 || *prefix - remove < 4) return false; + return start < remove ? remove - start >= 4 : start - remove >= 4; +} + +int main(int argc, char **argv) { + const char *program_path = NULL, *store_path = NULL; + size_t budget = 4u * 1024u * 1024u; + int at = 1; + for (; at + 1 < argc && !strncmp(argv[at], "--", 2); at += 2) { + uint64_t value; + if (!strcmp(argv[at], "--program")) program_path = argv[at + 1]; + else if (!strcmp(argv[at], "--store")) store_path = argv[at + 1]; + else if (!strcmp(argv[at], "--budget") && number(argv[at + 1], SIZE_MAX, &value)) budget = (size_t)value; + else goto usage; + } + if (!program_path || !store_path || at >= argc) goto usage; + bool show = !strcmp(argv[at], "show"), edit = !strcmp(argv[at], "edit"); + if ((!show && !edit && strcmp(argv[at], "apply")) || + argc - at != (show ? 2 : edit ? 6 : 4)) goto usage; + uint64_t expected = 0, offset = 0, remove = 0; + if (!show && !number(argv[at + 2], INT64_MAX, &expected)) goto usage; + if (edit && (!number(argv[at + 3], UINT32_MAX, &offset) || !number(argv[at + 4], UINT32_MAX, &remove))) goto usage; + PureProgram program; + char error[160]; + if (!pure_program_load(program_path, &program, error, sizeof(error))) { + fprintf(stderr, "Program: %s\n", error); return 2; + } + size_t prefix = 0; + if (edit && !edit_layout(&program, &prefix)) { + fputs("edit requires two distinct u32le splice fields and trailing insertion bytes; use apply for a custom input\n", stderr); + return 2; + } + FILE *file = NULL; + size_t length = 0; + if (!show) { + file = fopen(argv[at + (edit ? 5 : 3)], "rb"); + if (!file) { perror("input"); return 2; } + long bytes; + if (fseek(file, 0, SEEK_END) || (bytes = ftell(file)) < 0 || + (uintmax_t)bytes > PURE_OBJECT_LIMIT - prefix || fseek(file, 0, SEEK_SET)) { + fputs("input must be a seekable file within the request size limit\n", stderr); + fclose(file); return 2; + } + length = (size_t)bytes; + } + if (pure_store_init(store_path) != PURE_OK) { + if (file) fclose(file); + fputs("cannot initialize store\n", stderr); return 2; + } + PureRuntime runtime; + PureWork work[1]; + if (!pure_runtime_init(&runtime, work, 1, &program, pure_store_host(store_path), budget)) { + if (file) fclose(file); + return 2; + } + PureTicket ticket; + PureResult admitted = pure_runtime_accept(&runtime, show ? PURE_INPUT_READ : PURE_INPUT_WRITE, + argv[at + 1], (int64_t)expected, prefix + length, &ticket); + if (admitted != PURE_OK) { + if (file) fclose(file); + fprintf(stderr, "%s\n", message(admitted)); return 2; + } + if (!show) { + unsigned char *input = pure_runtime_ingress(ticket, NULL); + if (prefix) memset(input, 0, prefix); + if (edit) { + put32(input + program.splice_offset.value, (uint32_t)offset); + put32(input + program.splice_remove.value, (uint32_t)remove); + } + bool valid = !length || fread(input + prefix, 1, length, file) == length; + if (fgetc(file) != EOF || ferror(file)) valid = false; + if (fclose(file)) valid = false; + if (!valid) { + pure_runtime_release(ticket); + fputs("input changed or could not be read; nothing was applied\n", stderr); return 2; + } + if (!pure_runtime_ready(ticket)) { pure_runtime_release(ticket); return 2; } + } + PureOutcome outcome; + while (!pure_runtime_result(ticket, &outcome)) { + if (!pure_runtime_step(&runtime)) { pure_runtime_release(ticket); return 2; } + } + int status = 0; + if (outcome.status != PURE_OK) { + fprintf(stderr, "{\"error\":\"%s\",\"revision\":\"%" PRId64 "\"}\n", message(outcome.status), outcome.revision); + status = outcome.status == PURE_CONFLICT ? 3 : 2; + } else if (show) { + if (outcome.body.size && fwrite(outcome.body.data, 1, outcome.body.size, stdout) != outcome.body.size) status = 2; + fprintf(stderr, "{\"revision\":\"%" PRId64 "\",\"bytes\":%zu}\n", outcome.revision, outcome.body.size); + } else { + if (printf("{\"revision\":\"%" PRId64 "\",\"reply_bytes\":%zu}\n", outcome.revision, outcome.body.size) < 0) status = 2; + } + if (fflush(stdout)) status = 2; + pure_runtime_release(ticket); + return status; +usage: + fputs("pure-edit --program FILE --store DIRECTORY [--budget BYTES] show KEY\n" + "pure-edit --program FILE --store DIRECTORY [--budget BYTES] edit KEY REVISION BYTE_OFFSET REMOVE_BYTES INSERT_FILE\n" + "pure-edit --program FILE --store DIRECTORY [--budget BYTES] apply KEY REVISION INPUT_FILE\n" + "edit uses the prepared program's field locations; apply accepts its raw input bytes.\n", stderr); + return 2; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/evaluate.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/evaluate.c new file mode 100644 index 0000000..88ffae7 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/evaluate.c @@ -0,0 +1,53 @@ +#include "program.h" +#include <stdint.h> + +bool pure_region_span(PureRegion region, PureSpan source, PureSpan *out) { + *out = (PureSpan){0}; + if (region.offset > source.size) return false; + size_t length = region.to_end ? source.size - region.offset : region.length; + if (length > source.size - region.offset) return false; + *out = (PureSpan){length ? source.data + region.offset : NULL, length}; + return true; +} + +static bool index_read(PureIndex index, PureSpan source, size_t *out) { + if (!index.from_u32le) { *out = index.value; return true; } + if (index.value > source.size || 4 > source.size - index.value) return false; + const unsigned char *bytes = source.data + index.value; + *out = (size_t)bytes[0] | (size_t)bytes[1] << 8 | (size_t)bytes[2] << 16 | (size_t)bytes[3] << 24; + return true; +} + +bool pure_program_splice(const PureProgram *p, PureSpan source, size_t *offset, size_t *remove) { + return p->splice && index_read(p->splice_offset, source, offset) && + index_read(p->splice_remove, source, remove); +} + +static bool resolve(PureRegion region, size_t step, PureSpan source, + const PureSpan *values, size_t count, PureSpan *out) { + if (!step) return pure_region_span(region, source, out); + if (step > count) return false; + *out = values[step - 1]; + return true; +} + +bool pure_program_input(const PureProgram *p, PureSpan source, PureSpan *write, PureSpan *reply) { + *write = (PureSpan){0}; + *reply = (PureSpan){0}; + if (source.size < p->input_required || p->input_steps > PURE_DYNAMIC_LIMIT) return false; + PureSpan values[PURE_DYNAMIC_LIMIT]; + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + PureSpan parent; + size_t offset, length; + if (!resolve(step->source, step->source_step, source, values, i, &parent) || + !index_read(step->offset, source, &offset) || !index_read(step->length, source, &length) || + !pure_region_span((PureRegion){.offset = offset, .length = length}, parent, &values[i])) return false; + } + PureSpan stored, returned = {0}; + if (!resolve(p->write, p->write_step, source, values, p->input_steps, &stored) || + (p->reply_input && !resolve(p->reply, p->reply_step, source, values, p->input_steps, &returned))) return false; + *write = stored; + *reply = returned; + return true; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/editor.pure b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/editor.pure new file mode 100644 index 0000000..4de78bc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/editor.pure @@ -0,0 +1,19 @@ +// A revision-checked byte edit. The input carries only the replacement. +// UTF-8 cursor/grapheme positions are translated to byte ranges by the UI. +layout Edit { + start: u32le; + remove: u32le; + insert: bytes; +} + +on get { + let document = store.read(request.key); + reply(document, document.body); +} + +on put { + let edit = view(Edit, request.body); + let saved = store.splice(request.key, request.revision, + edit.start, edit.remove, edit.insert); + reply(saved, edit.insert); +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/excerpt.pure b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/excerpt.pure new file mode 100644 index 0000000..622f9bb --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/connections/examples/select-text.py b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/select-text.py new file mode 100644 index 0000000..ffaf80b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/select-text.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Save a UTF-8 document selection through selection.pure (character indices).""" +import argparse +import http.client +import json +from pathlib import Path +import re +import struct +from urllib.parse import urlsplit + +LIMIT = (2 * 6 + 32) * 32768 + 8192 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('file', type=Path) + parser.add_argument('--key', required=True) + parser.add_argument('--start', type=int, required=True, help='first Unicode code point, starting at 0') + parser.add_argument('--end', type=int, required=True, help='exclusive Unicode code point boundary') + parser.add_argument('--revision', type=int, default=0, help='0 creates; otherwise expected stored revision') + parser.add_argument('--url', default='http://127.0.0.1:8767') + args = parser.parse_args() + url = urlsplit(args.url) + try: + port = url.port or 80 + except ValueError as error: + parser.error(str(error)) + if (url.scheme != 'http' or url.hostname not in ['localhost', '127.0.0.1'] or + url.username is not None or url.password is not None or url.path not in ['', '/'] or + url.query or url.fragment): + parser.error('use a local HTTP server origin') + if not re.fullmatch(r'[A-Za-z0-9_-]{1,64}', args.key) or not 0 <= args.revision <= 2**63 - 1: + parser.error('invalid object key or revision') + try: + with args.file.open('rb') as source: + document = source.read(LIMIT - 8 + 1) + if len(document) > LIMIT - 8: + parser.error('document exceeds the prototype request limit') + text = document.decode('utf-8') + except (OSError, UnicodeError) as error: + parser.error(str(error)) + if not 0 <= args.start <= args.end <= len(text): + parser.error('selection is outside the document') + start = len(text[:args.start].encode('utf-8')) + selected = text[args.start:args.end].encode('utf-8') + # The boundary layout is explicit; storage receives only selected bytes. + payload = struct.pack('<II', start, len(selected)) + document + headers = {'Content-Type': 'application/octet-stream'} + headers['If-None-Match' if args.revision == 0 else 'If-Match'] = ( + '*' if args.revision == 0 else f'"{args.revision}"') + connection = http.client.HTTPConnection(url.hostname, port, timeout=12) + try: + connection.request('PUT', '/objects/' + args.key, payload, headers) + response = connection.getresponse() + body = response.read(LIMIT + 1) + if response.status != 200: + parser.error(f'storage response {response.status}: {response.getheader("X-Pure-Error")}') + if body != selected: + parser.error('response differs from the selection; check the installed program (write may have occurred)') + revision = response.getheader('ETag', '') + if not re.fullmatch(r'"[1-9][0-9]*"', revision) or int(revision[1:-1]) > 2**63 - 1: + parser.error('response has no valid stored revision (write may have occurred)') + except (OSError, http.client.HTTPException) as error: + parser.error(f'{error}; storage outcome is unknown if the request was sent') + finally: + connection.close() + print(json.dumps({'revision': revision[1:-1], 'selected': selected.decode('utf-8'), + 'stored_bytes': len(selected)}, ensure_ascii=False)) + + +if __name__ == '__main__': + main() diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/selection.pure b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/selection.pure new file mode 100644 index 0000000..7d9d4ec --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/selection.pure @@ -0,0 +1,19 @@ +// This is an input boundary layout, not a required storage or RAM model. +// The client sends two little-endian u32 values followed by document bytes. +layout Selection { + start: u32le; + count: u32le; + document: bytes; +} + +on get { + let saved = store.read(request.key); + reply(saved, saved.body); +} + +on put { + let input = view(Selection, request.body); + let selected = range(input.document, input.start, input.count); + let saved = store.write(request.key, request.revision, selected); + reply(saved, selected); +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/storage.pure b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/examples/storage.pure new file mode 100644 index 0000000..ff54ea3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/flow.c new file mode 100644 index 0000000..1070c70 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/flow.h new file mode 100644 index 0000000..6e27c58 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/connections/program.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/program.c new file mode 100644 index 0000000..8680782 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/program.c @@ -0,0 +1,366 @@ +#include "program.h" +#include "store.h" +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +enum { NAME = 256, NUMBER, SYMBOLS = 32, LAYOUTS = 4, FIELDS = 16 }; +typedef enum { BYTE_VALUE, RESULT_VALUE, LAYOUT_VALUE, INDEX_VALUE } ValueKind; +typedef struct { + char name[32]; + ValueKind kind; + PureRegion region; + size_t step, layout; + PureIndex index; +} Symbol; +typedef struct { + char name[32]; + size_t offset, length; + bool number, tail; +} Field; +typedef struct { + char name[32]; + Field fields[FIELDS]; + size_t count, prefix; + bool tail; +} Layout; +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, layout_count; + Layout layouts[LAYOUTS]; + 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 require_bytes(Parser *p, const Symbol *value) { + return value->kind == BYTE_VALUE || reject(p, "expected bytes; select a byte field"); +} + +static void require_length(Parser *p, size_t required) { + size_t *minimum = p->put ? &p->program.input_required : &p->program.read_required; + if (required > *minimum) *minimum = required; +} + +static Layout *find_layout(Parser *p, const char *name) { + for (size_t i = 0; i < p->layout_count; ++i) + if (!strcmp(name, p->layouts[i].name)) return &p->layouts[i]; + return NULL; +} + +static bool layout_definition(Parser *p) { + if (p->layout_count == LAYOUTS) return reject(p, "program exceeds 4 layouts"); + if (!next(p)) return false; + if (p->token != NAME || find_layout(p, p->name)) return reject(p, "expected a unique layout name"); + Layout *layout = &p->layouts[p->layout_count]; + strcpy(layout->name, p->name); + if (!next(p) || !token(p, '{')) return false; + while (p->token != '}') { + if (layout->count == FIELDS) return reject(p, "layout exceeds 16 fields"); + if (layout->tail) return reject(p, "variable bytes must be the last field"); + if (p->token != NAME) return reject(p, "expected a field name"); + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(layout->fields[i].name, p->name)) return reject(p, "duplicate field"); + Field *f = &layout->fields[layout->count]; + strcpy(f->name, p->name); + f->offset = layout->prefix; + if (!next(p) || !token(p, ':')) return false; + if (is_word(p, "u32le")) { + f->number = true; + f->length = 4; + if (!next(p)) return false; + } else if (is_word(p, "bytes")) { + if (!next(p)) return false; + if (p->token == '[') { + if (!next(p)) return false; + if (p->token != NUMBER) return reject(p, "fixed bytes requires a constant size"); + f->length = p->number; + if (!next(p) || !token(p, ']')) return false; + } else { f->tail = true; layout->tail = true; } + } else return reject(p, "supported field types: u32le, bytes[N], bytes"); + if (f->length > PURE_OBJECT_LIMIT - layout->prefix) return reject(p, "layout exceeds object bound"); + layout->prefix += f->length; + ++layout->count; + if (!token(p, ';')) return false; + } + if (!layout->count) return reject(p, "empty layout"); + ++p->layout_count; + return token(p, '}'); +} + +static bool reference(Parser *p, Symbol *out) { + if (is_word(p, "request")) { + if (!p->put) return reject(p, "request.body is only available in on put"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return field(p, "request", "body"); + } + Symbol *symbol = p->token == NAME ? find(p, p->name) : NULL; + if (!symbol) return reject(p, "unknown value"); + *out = *symbol; + if (!next(p)) return false; + if (p->token != '.') return true; + if (!next(p)) return false; + if (symbol->kind == RESULT_VALUE) { + if (p->put) return reject(p, "write result has no body; use the input value"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return word(p, "body"); + } + if (symbol->kind != LAYOUT_VALUE || p->token != NAME) return reject(p, "expected a layout field"); + Layout *layout = &p->layouts[symbol->layout]; + Field *f = NULL; + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(p->name, layout->fields[i].name)) f = &layout->fields[i]; + if (!f) return reject(p, "unknown layout field"); + size_t offset = symbol->region.offset + f->offset; + *out = (Symbol){.kind = f->number ? INDEX_VALUE : BYTE_VALUE}; + if (f->number) out->index = (PureIndex){.value = offset, .from_u32le = true}; + else out->region = (PureRegion){.offset = offset, + .length = f->tail ? (symbol->region.to_end ? 0 : symbol->region.length - f->offset) : f->length, + .to_end = f->tail && symbol->region.to_end}; + return next(p); +} + +static bool byte_reference(Parser *p, Symbol *out) { + return reference(p, out) && require_bytes(p, out); +} + +static bool index_value(Parser *p, PureIndex *out) { + if (p->token == NUMBER) { + *out = (PureIndex){.value = p->number}; + return next(p); + } + Symbol value; + if (!reference(p, &value)) return false; + if (value.kind != INDEX_VALUE) return reject(p, "range requires an integer or u32le field"); + *out = value.index; + return true; +} + +static bool layout_view(Parser *p, Symbol *out) { + if (!next(p) || !token(p, '(')) return false; + Layout *layout = p->token == NAME ? find_layout(p, p->name) : NULL; + if (!layout) return reject(p, "unknown layout"); + if (!next(p) || !token(p, ',')) return false; + Symbol source; + if (!byte_reference(p, &source) || !token(p, ')')) return false; + if (source.step) return reject(p, "layout view requires a statically located source"); + size_t bound = source.region.to_end ? PURE_OBJECT_LIMIT - source.region.offset : source.region.length; + if (layout->prefix > bound) return reject(p, "layout exceeds source bound"); + require_length(p, source.region.offset + layout->prefix); + *out = (Symbol){.kind = LAYOUT_VALUE, .region = source.region, .layout = (size_t)(layout - p->layouts)}; + if (!layout->tail) { out->region.length = layout->prefix; out->region.to_end = false; } + return true; +} + +static bool view(Parser *p, Symbol *out) { + if (is_word(p, "view")) return layout_view(p, out); + if (!is_word(p, "range")) return reference(p, out); + Symbol parent; + PureIndex offset, length; + if (!next(p) || !token(p, '(') || !byte_reference(p, &parent) || !token(p, ',') || + !index_value(p, &offset) || !token(p, ',') || !index_value(p, &length) || !token(p, ')')) return false; + bool dynamic = parent.step || offset.from_u32le || length.from_u32le; + *out = (Symbol){.kind = BYTE_VALUE}; + if (dynamic) { + if (!p->put) return reject(p, "data-dependent ranges currently require PUT input"); + if (p->program.input_steps == PURE_DYNAMIC_LIMIT) return reject(p, "input exceeds 8 dynamic ranges"); + if ((!offset.from_u32le && offset.value > PURE_OBJECT_LIMIT) || + (!length.from_u32le && length.value > PURE_OBJECT_LIMIT)) return reject(p, "range exceeds object bound"); + PureRangeStep *step = &p->program.steps[p->program.input_steps++]; + *step = (PureRangeStep){.source = parent.region, .source_step = parent.step, + .offset = offset, .length = length}; + out->step = p->program.input_steps; + } else { + size_t bound = parent.region.to_end ? PURE_OBJECT_LIMIT - parent.region.offset : parent.region.length; + if (offset.value > bound || length.value > bound - offset.value) return reject(p, "range exceeds its source bound"); + out->region = (PureRegion){.offset = parent.region.offset + offset.value, .length = length.value}; + require_length(p, out->region.offset + out->region.length); + } + ++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, '.')) return false; + bool splice = p->put && is_word(p, "splice"); + if (!word(p, p->put ? (splice ? "splice" : "write") : "read") || + !token(p, '(') || !field(p, "request", "key")) return false; + if (p->put) { + Symbol value; + if (!token(p, ',') || !field(p, "request", "revision") || !token(p, ',')) return false; + if (splice) { + if (!index_value(p, &p->program.splice_offset) || !token(p, ',') || + !index_value(p, &p->program.splice_remove) || !token(p, ',')) return false; + if ((!p->program.splice_offset.from_u32le && p->program.splice_offset.value > PURE_OBJECT_LIMIT) || + (!p->program.splice_remove.from_u32le && p->program.splice_remove.value > PURE_OBJECT_LIMIT)) + return reject(p, "splice exceeds object bound"); + p->program.splice = true; + } + if (!byte_reference(p, &value)) return false; + p->program.write = value.region; + p->program.write_step = value.step; + } + if (!token(p, ')')) return false; + out->kind = RESULT_VALUE; + 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 the storage effect; 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") || is_word(p, "view")) return reject(p, "expected a non-reserved definition name"); + if (find(p, p->name)) return reject(p, "duplicate definition"); + char name[32]; + strcpy(name, p->name); + Symbol symbol = {0}; + if (!next(p) || !token(p, '=')) return false; + if (is_word(p, "store")) { + if (!effect(p, &symbol)) return false; + } else { + if (!view(p, &symbol)) return false; + ++p->program.view_definitions; + } + if (!token(p, ';')) return false; + strcpy(symbol.name, name); + 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->kind != RESULT_VALUE) return reject(p, "reply requires this handler's storage result"); + if (!next(p)) return false; + Symbol body = {0}; + bool has_body = p->token == ','; + if (has_body && (!next(p) || !byte_reference(p, &body))) return false; + if (!token(p, ')') || !token(p, ';') || !token(p, '}')) return false; + if (p->put) { + p->program.reply = body.region; + p->program.reply_step = body.step; + p->program.reply_input = has_body; + } else p->program.read = body.region; + 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; + while (is_word(&p, "layout")) if (!layout_definition(&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); +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/program.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/program.h new file mode 100644 index 0000000..c102eb1 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/program.h @@ -0,0 +1,44 @@ +#ifndef PURE_PROGRAM_H +#define PURE_PROGRAM_H +#include "flow.h" + +#define PURE_PROGRAM_LIMIT 16384u +#define PURE_DYNAMIC_LIMIT 8u +typedef struct { + size_t offset, length; + bool to_end; +} PureRegion; + +typedef struct { size_t value; bool from_u32le; } PureIndex; +typedef struct { + PureRegion source; + size_t source_step; /* 0: source region; otherwise a preceding step + 1. */ + PureIndex offset, length; +} PureRangeStep; + +/* 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; + size_t write_step, reply_step, input_steps; + PureRangeStep steps[PURE_DYNAMIC_LIMIT]; + bool reply_input; + bool splice; + PureIndex splice_offset, splice_remove; +} 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); +/* Validates all input views, including unused dynamic views, before any effect. + * Returns borrowed selections; their lifetime remains that of source. */ +bool pure_program_input(const PureProgram *, PureSpan source, PureSpan *write, PureSpan *reply); +/* Byte positions in the stored value. Bounds against the current version are + * checked by the conditional storage effect under the writer lock. */ +bool pure_program_splice(const PureProgram *, PureSpan source, size_t *offset, size_t *remove); +#endif diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/result.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/result.h new file mode 100644 index 0000000..8abb0f2 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/result.h @@ -0,0 +1,8 @@ +#ifndef PURE_RESULT_H +#define PURE_RESULT_H +typedef enum { + PURE_OK, PURE_NOT_FOUND, PURE_CONFLICT, PURE_BUSY, PURE_BUDGET, + PURE_LIMIT, PURE_STORE_ERROR, PURE_UNCERTAIN, + PURE_INVALID, PURE_DENIED, PURE_CLOSED, PURE_CANCELLED, PURE_ALLOCATION +} PureResult; +#endif diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.c new file mode 100644 index 0000000..aa4ed05 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.c @@ -0,0 +1,273 @@ +#include "runtime.h" +#include <string.h> + +#define NONE SIZE_MAX +enum { FREE, RECEIVING, READY, RUNNING, DONE }; + +static PureWork *lookup(PureTicket ticket, bool require_held) { + PureRuntime *r = ticket.runtime; + if (!r || ticket.slot >= r->capacity || !ticket.generation) return NULL; + PureWork *w = &r->work[ticket.slot]; + if (w->state == FREE || w->generation != ticket.generation || + (require_held && !w->held)) return NULL; + return w; +} + +static bool valid_key(const char *key) { + if (!key || !*key) return false; + size_t n = 0; + for (; n <= 64 && key[n]; ++n) { + unsigned char c = (unsigned char)key[n]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '_')) return false; + } + return n <= 64; +} + +static void dispose(PureResource *resource) { + PureResource old = *resource; + *resource = (PureResource){0}; + if (old.release) old.release(old.allocation, old.allocation_size); +} + +static void payload_release(PureRuntime *r, PureWork *w); + +/* Iteratively retire a chain of forwarded values. An arbitrarily long chain + * must not turn completion into recursive stack growth. */ +static void retire(PureRuntime *r, PureWork *w) { + while (w && !w->held && !w->pins) { + PureTicket parent = w->parent; + bool owned = w->charged || w->resource.release || parent.runtime; + dispose(&w->resource); + r->retained -= w->charged; + if (owned) ++r->releases; + size_t slot = (size_t)(w - r->work); + *w = (PureWork){.next = r->free_head}; + r->free_head = slot; + --r->active; + if (!parent.runtime) break; + r = parent.runtime; + w = lookup(parent, false); + /* Pins can only be created and removed by the owner-thread core. */ + if (!w || !w->pins) break; + --w->pins; + } +} + +static void payload_release(PureRuntime *r, PureWork *w) { + PureTicket parent = w->parent; + bool owned = w->charged || w->resource.release || parent.runtime; + dispose(&w->resource); + r->retained -= w->charged; + w->charged = 0; + w->parent = (PureTicket){0}; + if (owned) ++r->releases; + if (parent.runtime) { + PureWork *source = lookup(parent, false); + if (source && source->pins) { --source->pins; retire(parent.runtime, source); } + } +} + +bool pure_runtime_init(PureRuntime *r, PureWork *work, size_t capacity, + const PureProgram *program, PureHost host, size_t budget) { + if (!r || !work || !capacity || !program) return false; + *r = (PureRuntime){.program = program, .host = host, .work = work, + .capacity = capacity, .free_head = 0, .ready_head = NONE, .ready_tail = NONE, + .budget = budget, .accepting = true}; + for (size_t i = 0; i < capacity; ++i) + work[i] = (PureWork){.next = i + 1 < capacity ? i + 1 : NONE}; + return true; +} + +bool pure_runtime_restart(PureRuntime *r, const PureProgram *p, PureHost host, size_t budget) { + if (!r || !p || r->running || r->active) return false; + r->program = p; + r->host = host; + r->budget = budget; + r->accepting = true; + return true; +} + +void pure_runtime_stop(PureRuntime *r) { if (r && !r->running) r->accepting = false; } + +static PureResult admission(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t bytes) { + if (!r || r->running || !valid_key(key) || + (input != PURE_INPUT_READ && input != PURE_INPUT_WRITE) || + (input == PURE_INPUT_READ ? bytes != 0 : expected < 0)) return PURE_INVALID; + if (!r->accepting) return PURE_CLOSED; + if (input == PURE_INPUT_READ ? !r->host.read : + (r->program->splice ? !r->host.splice : !r->host.write)) return PURE_DENIED; + if (r->free_head == NONE) return PURE_BUSY; + if (r->serial == UINT64_MAX) return PURE_LIMIT; + if (bytes > r->budget - r->retained) { ++r->budget_rejections; return PURE_BUDGET; } + return PURE_OK; +} + +static PureTicket occupy(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t bytes, PureResource resource) { + size_t slot = r->free_head; + PureWork *w = &r->work[slot]; + r->free_head = w->next; + *w = (PureWork){.generation = ++r->serial, .next = NONE, .charged = bytes, + .state = RECEIVING, .held = true, .input = input, .expected = expected, + .resource = resource}; + memcpy(w->key, key, strlen(key) + 1); + ++r->active; + ++r->accepted; + r->retained += bytes; + if (r->active > r->peak_active) r->peak_active = r->active; + if (r->retained > r->peak_retained) r->peak_retained = r->retained; + return (PureTicket){r, slot, w->generation}; +} + +bool pure_runtime_ready(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running || w->state != RECEIVING) return false; + PureRuntime *r = ticket.runtime; + w->state = READY; + if (r->ready_tail == NONE) r->ready_head = ticket.slot; + else r->work[r->ready_tail].next = ticket.slot; + r->ready_tail = ticket.slot; + return true; +} + +PureResult pure_runtime_accept(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t size, PureTicket *out) { + if (!out) return PURE_INVALID; + PureResult status = admission(r, input, key, expected, size); + if (status != PURE_OK) return status; + PureResource resource = {0}; + if (size) { + if (!r->host.allocate) return PURE_ALLOCATION; + r->running = true; + bool ok = r->host.allocate(r->host.context, size, &resource); + r->running = false; + if (!ok || !resource.body.data || resource.body.size != size || !resource.release) { + dispose(&resource); + return PURE_ALLOCATION; + } + } + *out = occupy(r, input, key, expected, size, resource); + if (input == PURE_INPUT_READ) pure_runtime_ready(*out); + return PURE_OK; +} + +unsigned char *pure_runtime_ingress(PureTicket ticket, size_t *size) { + PureWork *w = lookup(ticket, true); + if (size) *size = 0; + if (!w || ticket.runtime->running || w->state != RECEIVING || + w->input != PURE_INPUT_WRITE || w->parent.runtime) return NULL; + if (size) *size = w->resource.body.size; + return (unsigned char *)w->resource.body.data; +} + +bool pure_runtime_step(PureRuntime *r) { + if (!r || r->running || r->ready_head == NONE) return false; + size_t slot = r->ready_head; + PureWork *w = &r->work[slot]; + r->ready_head = w->next; + if (r->ready_head == NONE) r->ready_tail = NONE; + w->next = NONE; + w->state = RUNNING; + r->running = true; + PureOutcome outcome = {0}; + if (w->input == PURE_INPUT_WRITE) { + PureSpan value; + size_t offset = 0, remove = 0; + if (!pure_program_input(r->program, w->resource.body, &value, &outcome.body)) + outcome.status = PURE_LIMIT; + else if (r->program->splice) { + if (!pure_program_splice(r->program, w->resource.body, &offset, &remove)) + outcome.status = PURE_LIMIT; + else outcome.status = r->host.splice(r->host.context, w->key, w->expected, + offset, remove, value, &outcome.revision); + } + else outcome.status = r->host.write(r->host.context, w->key, w->expected, + value, &outcome.revision); + if (outcome.status == PURE_OK) ++r->commits; + else if (outcome.status == PURE_CONFLICT) ++r->conflicts; + } else { + outcome.status = r->host.read(r->host.context, w->key, r->program->read, + r->program->read_required, r->budget - r->retained, &w->resource, &outcome.revision); + if (outcome.status == PURE_OK) { + if (w->resource.body.size > r->budget - r->retained) outcome.status = PURE_BUDGET; + else if (w->resource.body.size && (!w->resource.body.data || !w->resource.release)) + outcome.status = PURE_STORE_ERROR; + else { + w->charged = w->resource.body.size; + r->retained += w->charged; + if (r->retained > r->peak_retained) r->peak_retained = r->retained; + outcome.body = w->resource.body; + ++r->gets; + } + } + if (outcome.status == PURE_BUDGET) ++r->budget_rejections; + } + if (outcome.status != PURE_OK || !outcome.body.size) { + outcome.body = (PureSpan){0}; + payload_release(r, w); + } + r->borrowed_output_bytes += outcome.body.size; + w->outcome = outcome; + w->state = DONE; + r->running = false; + return true; +} + +bool pure_runtime_result(PureTicket ticket, PureOutcome *out) { + PureWork *w = lookup(ticket, true); + if (!out || !w || ticket.runtime->running || w->state != DONE) return false; + *out = w->outcome; + return true; +} + +PureResult pure_runtime_forward(PureTicket source, PureRuntime *r, + const char *key, int64_t expected, PureTicket *out) { + PureWork *parent = lookup(source, true); + if (!out || !parent || source.runtime->running || parent->state != DONE || + parent->outcome.status != PURE_OK) return PURE_INVALID; + PureResult status = admission(r, PURE_INPUT_WRITE, key, expected, parent->outcome.body.size); + if (status != PURE_OK) return status; + if (parent->pins == SIZE_MAX) return PURE_LIMIT; + PureTicket ticket = occupy(r, PURE_INPUT_WRITE, key, expected, parent->outcome.body.size, + (PureResource){.body = parent->outcome.body}); + ++parent->pins; + r->work[ticket.slot].parent = source; + pure_runtime_ready(ticket); + *out = ticket; + return PURE_OK; +} + +static void unlink_ready(PureRuntime *r, size_t slot) { + size_t previous = NONE; + for (size_t at = r->ready_head; at != NONE; at = r->work[at].next) { + if (at == slot) { + if (previous == NONE) r->ready_head = r->work[at].next; + else r->work[previous].next = r->work[at].next; + if (r->ready_tail == at) r->ready_tail = previous; + r->work[at].next = NONE; + return; + } + previous = at; + } +} + +bool pure_runtime_cancel(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running || (w->state != RECEIVING && w->state != READY)) return false; + if (w->state == READY) unlink_ready(ticket.runtime, ticket.slot); + payload_release(ticket.runtime, w); + w->outcome = (PureOutcome){.status = PURE_CANCELLED}; + w->state = DONE; + return true; +} + +bool pure_runtime_release(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running) return false; + if (w->state == READY) unlink_ready(ticket.runtime, ticket.slot); + w->held = false; + retire(ticket.runtime, w); + return true; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.h new file mode 100644 index 0000000..1bff082 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/runtime.h @@ -0,0 +1,88 @@ +#ifndef PURE_RUNTIME_H +#define PURE_RUNTIME_H +#include "program.h" +#include "result.h" +#include <stdint.h> + +/* First execution profile: authored selection -> read/conditional write -> + * reply. No socket, file descriptor, allocator, parser or OS type in the core. + * All control calls and connected domains share one owner thread. Host effects + * are synchronous in this profile and must not reenter the runtime. */ +typedef struct PureRuntime PureRuntime; +typedef struct PureWork PureWork; +typedef struct { PureRuntime *runtime; size_t slot; uint64_t generation; } PureTicket; +typedef enum { PURE_INPUT_READ, PURE_INPUT_WRITE } PureInput; + +/* The host owns this resource until it successfully returns it to the core. + * release must end all access and free/unmap allocation exactly once. */ +typedef struct { + PureSpan body; + void *allocation; + size_t allocation_size; + void (*release)(void *allocation, size_t size); +} PureResource; +typedef struct { + void *context; + bool (*allocate)(void *, size_t, PureResource *); + PureResult (*read)(void *, const char *, PureRegion, size_t minimum, + size_t available, PureResource *, int64_t *revision); + PureResult (*write)(void *, const char *, int64_t expected, + PureSpan, int64_t *revision); + PureResult (*splice)(void *, const char *, int64_t expected, size_t offset, + size_t remove, PureSpan insert, int64_t *revision); +} PureHost; +typedef struct { PureResult status; int64_t revision; PureSpan body; } PureOutcome; + +/* Caller-provided work slots: no background thread or per-domain heap. These + * records are implementation storage, not an ABI or a wire representation. */ +struct PureWork { + uint64_t generation; + size_t next, charged, pins; + unsigned state; + bool held; + PureInput input; + int64_t expected; + char key[65]; + PureResource resource; + PureTicket parent; + PureOutcome outcome; +}; +struct PureRuntime { + const PureProgram *program; + PureHost host; + PureWork *work; + size_t capacity, free_head, ready_head, ready_tail; + size_t budget, retained, peak_retained, active, peak_active; + uint64_t serial, accepted, commits, gets, conflicts, budget_rejections; + uint64_t releases, borrowed_output_bytes; + bool accepting, running; +}; + +/* Initialize fresh storage once. Use restart, not init, to reuse a domain; + * the monotonic ticket sequence then survives restart. program is immutable + * and remains alive until all work has drained. */ +bool pure_runtime_init(PureRuntime *, PureWork *, size_t capacity, + const PureProgram *, PureHost, size_t budget); +bool pure_runtime_restart(PureRuntime *, const PureProgram *, PureHost, size_t budget); +void pure_runtime_stop(PureRuntime *); /* stop admission, allow queued work to drain */ + +/* READ queues immediately. WRITE reserves its slot, budget and host allocation + * before returning. Fill ingress then publish once with ready. A partial input + * released without ready never executes. On error, out is unchanged. */ +PureResult pure_runtime_accept(PureRuntime *, PureInput, const char *key, + int64_t expected, size_t input_size, PureTicket *out); +unsigned char *pure_runtime_ingress(PureTicket, size_t *size); +bool pure_runtime_ready(PureTicket); +bool pure_runtime_step(PureRuntime *); /* execute at most one ready job */ +bool pure_runtime_result(PureTicket, PureOutcome *out); + +/* Pin a completed reply and queue it as another domain's WRITE input, with no + * allocation, serialization or payload copy. Source may be released as soon + * as this succeeds; the core retains its backing until the consumer ends. + * Each domain charges its own retained input bytes as admission credits. + * Credits across domains are NOT distinct physical memory and must not be summed. */ +PureResult pure_runtime_forward(PureTicket source, PureRuntime *destination, + const char *key, int64_t expected, PureTicket *out); +bool pure_runtime_cancel(PureTicket); /* only before execution; keeps a completion */ +bool pure_runtime_release(PureTicket); /* invalidates caller's ticket, preserves pins */ +#endif diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/server.c new file mode 100644 index 0000000..50480c0 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/server.c @@ -0,0 +1,372 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.h" +#include "program.h" +#include "store-port.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; + unsigned char *body; + bool put; + int64_t expected; + PureTicket ticket; + PureTransfer transfer; + Server *server; +} Connection; +struct Server { + const char *path; + PureProgram program; + PureRuntime runtime; + PureWork work[SLOTS]; + size_t budget, active, peak_active; + uint64_t accepted, expired, prefix_copy_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; + if (c->ticket.runtime) { + bool released = pure_runtime_release(c->ticket); + assert(released); + (void)released; + } + c->ticket = (PureTicket){0}; + c->body = NULL; +} + +static void close_connection(Connection *c) { + if (c->fd < 0) return; + if (c->transfer.active) pure_transfer_abort(&c->transfer); + else release_payload(c); + close(c->fd); + --c->server->active; + *c = (Connection){.fd = -1}; +} + +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. */ + release_payload(c); + respond(c, status, reason, error, 0, (PureSpan){0}); +} + +static void answer(Connection *c, PureOutcome result) { + switch (result.status) { + case PURE_OK: respond(c, 200, "OK", "none", result.revision, result.body); break; + case PURE_NOT_FOUND: fail(c, 404, "Not Found", "not_found"); break; + case PURE_CONFLICT: + respond(c, 412, "Precondition Failed", "conflict", result.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_ALLOCATION: fail(c, 503, "Service Unavailable", "allocation"); break; + case PURE_CLOSED: fail(c, 503, "Service Unavailable", "stopped"); break; + case PURE_DENIED: fail(c, 403, "Forbidden", "denied"); 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; + } +} + +static void run_request(Connection *c) { + if (c->put && !pure_runtime_ready(c->ticket)) abort(); + PureOutcome result; + while (!pure_runtime_result(c->ticket, &result)) + if (!pure_runtime_step(&c->server->runtime)) abort(); + c->body = NULL; /* Core now owns input until all consumers release it. */ + answer(c, result); +} + +/* 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; } + PureResult admitted = pure_runtime_accept(&c->server->runtime, + c->put ? PURE_INPUT_WRITE : PURE_INPUT_READ, c->key, c->expected, c->body_size, &c->ticket); + if (admitted != PURE_OK) { answer(c, (PureOutcome){.status = admitted}); return; } + if (!c->put) { run_request(c); return; } + c->body = pure_runtime_ingress(c->ticket, NULL); + 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\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"write\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"reply_input\":%s,\"view_definitions\":%zu,\"range_definitions\":%zu," + "\"runtime_view_steps\":%zu,\"write_step\":%zu,\"reply_step\":%zu,\"prepared_program_bytes\":%zu,\"steps\":[", + p->read.to_end ? "true" : "false", p->read.offset, p->read.length, p->read_required, + p->write.to_end ? "true" : "false", p->write.offset, p->write.length, p->input_required, + p->reply_input ? "true" : "false", p->view_definitions, p->range_definitions, + p->input_steps, p->write_step, p->reply_step, sizeof(*p)); + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + printf("%s{\"source_step\":%zu,\"source\":{\"offset\":%zu,\"length\":%zu,\"to_end\":%s}," + "\"offset\":{\"value\":%zu,\"from_u32le\":%s},\"length\":{\"value\":%zu,\"from_u32le\":%s}}", + i ? "," : "", step->source_step, step->source.offset, step->source.length, + step->source.to_end ? "true" : "false", step->offset.value, step->offset.from_u32le ? "true" : "false", + step->length.value, step->length.from_u32le ? "true" : "false"); + } + printf("],\"write_effect\":\"%s\",\"splice_offset\":{\"value\":%zu,\"from_u32le\":%s}," + "\"splice_remove\":{\"value\":%zu,\"from_u32le\":%s}}\n", + p->splice ? "splice" : "write", p->splice_offset.value, + p->splice_offset.from_u32le ? "true" : "false", p->splice_remove.value, + p->splice_remove.from_u32le ? "true" : "false"); + return 0; + } + if (pure_store_init(server.path) != PURE_OK) { fputs("Cannot initialize byte store directory\n", stderr); return 1; } + if (!pure_runtime_init(&server.runtime, server.work, SLOTS, &server.program, + pure_store_host(server.path), server.budget)) 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); + pure_runtime_stop(&server.runtime); + 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.runtime.retained, server.runtime.peak_retained, server.budget, server.peak_active, + server.accepted, server.runtime.commits, server.runtime.gets, server.runtime.conflicts, server.runtime.budget_rejections, + server.expired, server.runtime.releases, server.prefix_copy_bytes, server.runtime.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-document-edits/sources/prototypes/connections/store-port.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store-port.c new file mode 100644 index 0000000..9860308 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store-port.c @@ -0,0 +1,44 @@ +#include "store-port.h" +#include "store.h" +#include <stdlib.h> +#include <sys/mman.h> + +static void free_memory(void *data, size_t size) { (void)size; free(data); } +static void unmap_memory(void *data, size_t size) { if (size) munmap(data, size); } + +static bool allocate(void *context, size_t size, PureResource *out) { + (void)context; + void *data = malloc(size); + if (!data) return false; + *out = (PureResource){.body = {data, size}, .allocation = data, + .allocation_size = size, .release = free_memory}; + return true; +} + +static PureResult read_value(void *context, const char *key, PureRegion region, + size_t minimum, size_t available, PureResource *out, int64_t *revision) { + PureStoredView view = {0}; + PureResult result = pure_store_select(context, key, region.offset, region.length, + region.to_end, minimum, available, &view); + if (result == PURE_OK) { + *out = (PureResource){.body = view.body, .allocation = view.mapping, + .allocation_size = view.mapped_size, .release = view.mapped_size ? unmap_memory : NULL}; + *revision = view.revision; + } + return result; +} + +static PureResult write_value(void *context, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + return pure_store_put(context, key, expected, body, revision); +} + +static PureResult splice_value(void *context, const char *key, int64_t expected, + size_t offset, size_t remove, PureSpan insert, int64_t *revision) { + return pure_store_splice(context, key, expected, offset, remove, insert, revision); +} + +PureHost pure_store_host(const char *path) { + return (PureHost){.context = (void *)path, .allocate = allocate, + .read = read_value, .write = write_value, .splice = splice_value}; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store-port.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store-port.h new file mode 100644 index 0000000..4c0d49d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store-port.h @@ -0,0 +1,6 @@ +#ifndef PURE_STORE_PORT_H +#define PURE_STORE_PORT_H +#include "runtime.h" +/* The path remains alive for the domain's complete lifetime, including pins. */ +PureHost pure_store_host(const char *path); +#endif diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.c new file mode 100644 index 0000000..4c8bdc4 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.c @@ -0,0 +1,258 @@ +#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 (offset > size) { result = PURE_LIMIT; goto finish; } + if (whole) length = size - offset; + 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); +} + +static bool copy_extent(int source, int destination, size_t offset, size_t length) { + unsigned char scratch[4096]; + while (length) { + size_t wanted = length < sizeof(scratch) ? length : sizeof(scratch); + ssize_t n = pread(source, scratch, wanted, (off_t)(HEADER_BYTES + offset)); + if (n < 0 && errno == EINTR) continue; + if (n <= 0 || !write_bytes(destination, scratch, (size_t)n)) return false; + offset += (size_t)n; + length -= (size_t)n; + } + return true; +} + +static PureResult mutate(const char *path, const char *key, int64_t expected, + PureSpan body, bool splice, size_t offset, size_t remove, 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, source = -1; + size_t old_size = 0, result_size = body.size; + 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; + } + source = openat(directory, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC); + if (source >= 0) { + if (!read_header(source, revision, &old_size)) goto finish; + } else if (errno != ENOENT) goto finish; + if (*revision != expected) { result = PURE_CONFLICT; goto finish; } + if (*revision == INT64_MAX) { result = PURE_LIMIT; goto finish; } + if (splice) { + if (offset > old_size || remove > old_size - offset || + body.size > PURE_OBJECT_LIMIT - (old_size - remove)) { result = PURE_LIMIT; goto finish; } + result_size = old_size - remove + body.size; + } + 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, result_size); + if (!write_bytes(fd, header, sizeof(header)) || fault(PURE_AFTER_HEADER)) goto finish; + if (splice && !copy_extent(source, fd, 0, offset)) goto finish; + if (!write_bytes(fd, body.data, body.size)) goto finish; + if (splice && !copy_extent(source, fd, offset + remove, old_size - offset - remove)) goto finish; + if (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 (source >= 0) close(source); + if (*temporary) unlinkat(directory, temporary, 0); + if (lock >= 0) close(lock); + close(directory); + return result; +} + +PureResult pure_store_put(const char *path, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + return mutate(path, key, expected, body, false, 0, 0, revision); +} + +PureResult pure_store_splice(const char *path, const char *key, int64_t expected, + size_t offset, size_t remove, PureSpan insert, int64_t *revision) { + return mutate(path, key, expected, insert, true, offset, remove, revision); +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.h new file mode 100644 index 0000000..99a3ef5 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/store.h @@ -0,0 +1,44 @@ +#ifndef PURE_STORE_H +#define PURE_STORE_H +#include "flow.h" +#include "result.h" +#include <stdint.h> + +#define PURE_OBJECT_LIMIT ((2u * 6u + 32u) * 32768u + 8192u) +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. + * whole selects the suffix starting at offset; otherwise length is exact. */ +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); +/* Atomic byte replacement: retain [0,offset), insert bytes, retain suffix after + * remove bytes. Check the expected revision and bounds under the same writer + * lock. expected=0 permits creating an empty base (offset=remove=0). + * Streams retained bytes through bounded scratch space; never assembles a + * whole new object in application memory. Still rewrites the immutable file. */ +PureResult pure_store_splice(const char *path, const char *key, int64_t expected, + size_t offset, size_t remove, PureSpan insert, 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-document-edits/sources/prototypes/connections/test-fields.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-fields.c new file mode 100644 index 0000000..63dfc3e --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-fields.c @@ -0,0 +1,153 @@ +#include "program.h" +#include <stdint.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 const char layout[] = "layout Envelope { marker: bytes[1]; start: u32le; count: u32le; text: bytes; }"; +static PureProgram parse(const char *source) { + PureProgram p; + char error[160]; + if (!pure_program_parse(source, strlen(source), &p, error, sizeof(error))) { + fprintf(stderr, "%s\n", error); exit(1); + } + return p; +} +static void invalid(const char *source) { + PureProgram p; + memset(&p, 0x5a, sizeof(p)); + unsigned char before[sizeof(p)]; + memcpy(before, &p, sizeof(p)); + char error[160]; + CHECK(!pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(*error && !memcmp(before, &p, sizeof(p))); +} +static void encode(unsigned char *at, uint32_t value) { + for (unsigned i = 0; i < 4; ++i) at[i] = (unsigned char)(value >> (8 * i)); +} +int main(void) { + char source[8000]; + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let alias = input; let start = alias.start; let text = alias.text; " + "let selected = range(text, start, alias.count); let result = selected; " + "let saved = store.write(request.key, request.revision, result); reply(saved, selected); }", layout, get); + PureProgram p = parse(source); + CHECK(p.input_steps == 1 && p.input_required == 9 && p.write_step == 1 && p.reply_step == 1); + CHECK(p.steps[0].source.offset == 9 && p.steps[0].source.to_end); + CHECK(p.steps[0].offset.value == 1 && p.steps[0].offset.from_u32le); + CHECK(p.steps[0].length.value == 5 && p.steps[0].length.from_u32le); + unsigned char bytes[609]; + for (size_t i = 0; i < sizeof(bytes); ++i) bytes[i] = (unsigned char)(i * 13); + PureSpan stored, returned; + for (size_t n = 0; n < 9; ++n) { + CHECK(!pure_program_input(&p, (PureSpan){bytes, n}, &stored, &returned)); + CHECK(!stored.size && !returned.size); + } + const uint32_t offsets[] = {0, 1, 255, 257, 599, 600, 601, UINT32_MAX}; + const uint32_t counts[] = {0, 1, 2, 260, 600, UINT32_MAX}; + size_t comparisons = 0; + for (size_t i = 0; i < sizeof(offsets) / sizeof(*offsets); ++i) + for (size_t j = 0; j < sizeof(counts) / sizeof(*counts); ++j) { + uint32_t start = offsets[i], count = counts[j]; + encode(bytes + 1, start); + encode(bytes + 5, count); + bool expected = start <= 600 && count <= 600 - start; + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned) == expected); + if (expected) { + CHECK(stored.size == count && returned.size == count && stored.data == returned.data); + CHECK(!count || stored.data == bytes + 9 + start); + } else CHECK(!stored.size && !returned.size); + ++comparisons; + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let first = range(input.text, input.start, input.count); let second = range(first, 1, 2); " + "let saved = store.write(request.key, request.revision, second); reply(saved, second); }", layout, get); + p = parse(source); + CHECK(p.input_steps == 2 && p.steps[1].source_step == 1); + encode(bytes + 1, 3); encode(bytes + 5, 4); + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + CHECK(stored.size == 2 && stored.data == bytes + 13); + encode(bytes + 5, 2); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let unused = range(input.text, input.start, input.count); " + "let saved = store.write(request.key, request.revision, input.text); reply(saved); }", layout, get); + p = parse(source); + encode(bytes + 1, UINT32_MAX); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); " + "let input = view(Envelope, o.body); reply(o, input.text); } %s", layout, put); + p = parse(source); + CHECK(p.read.to_end && p.read.offset == 9 && p.read_required == 9 && !p.input_steps); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let selection = range(input.text, 2, 3); let saved = store.write(request.key, request.revision, selection); reply(saved); }", layout, get); + p = parse(source); + CHECK(!p.input_steps && p.write.offset == 11 && p.write.length == 3 && p.input_required == 14); + snprintf(source, sizeof(source), "layout Empty { body: bytes; } %s on put { let input = view(Empty, request.body); " + "let saved = store.write(request.key, request.revision, input.body); reply(saved, input.body); }", get); + p = parse(source); + CHECK(pure_program_input(&p, (PureSpan){0}, &stored, &returned) && !stored.size && !returned.size); + + const char *bad_layout[] = { + "layout E {}", "layout E { x: bytes; y: u32le; }", "layout E { x: u32le; x: bytes; }", + "layout E { x: bytes[1449985]; }", "layout E { x: bytes[1449984]; y: u32le; }", + "layout E { x: bytes[184467440737095516160]; }", "layout E { x: u64le; }", + "layout E { x: u32le; } layout E { y: bytes; }" + }; + for (size_t i = 0; i < sizeof(bad_layout) / sizeof(*bad_layout); ++i) { + snprintf(source, sizeof(source), "%s %s %s", bad_layout[i], get, put); invalid(source); + } + const char *bad_input[] = { + "let input = view(Missing, request.body);", + "let input = view(Envelope, request.body); let x = input.missing;", + "let input = view(Envelope, request.body); let x = range(input.text, input.text, 1);", + "let short = range(request.body, 0, 8); let input = view(Envelope, short);", + "let input = view(Envelope, request.body); let x = range(input.text, input.start, 20); let y = view(Envelope, x);" + }; + for (size_t i = 0; i < sizeof(bad_input) / sizeof(*bad_input); ++i) { + snprintf(source, sizeof(source), "%s %s on put { %s let saved = store.write(request.key, request.revision, request.body); reply(saved); }", layout, get, bad_input[i]); + invalid(source); + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let saved = store.write(request.key, request.revision, input.start); reply(saved); }", layout, get); + invalid(source); + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); let input = view(Envelope, o.body); " + "let x = range(input.text, input.start, input.count); reply(o, x); } %s", layout, put); + invalid(source); + for (size_t count = 8; count <= 9; ++count) { + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); ", layout, get); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let v%zu = range(input.text, input.start, input.count); ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let saved = store.write(request.key, request.revision, input.text); reply(saved); }"); + if (count == 8) CHECK(parse(source).input_steps == 8); else invalid(source); + } + for (size_t count = 4; count <= 5; ++count) { + source[0] = 0; + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "layout L%zu { body: bytes; } ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "%s %s", get, put); + if (count == 4) (void)parse(source); else invalid(source); + } + for (size_t count = 16; count <= 17; ++count) { + strcpy(source, "layout L { "); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "f%zu: u32le; ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "} %s %s", get, put); + if (count == 16) (void)parse(source); else invalid(source); + } + printf("{\"fields\":\"named-layouts-unaligned-u32le-tail-dynamic-ranges-aliases-failures-limits\"," + "\"data_boundary_cases\":%zu}\n", comparisons); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-flow.c new file mode 100644 index 0000000..5ff028d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/connections/test-program.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-program.c new file mode 100644 index 0000000..6a4a19d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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.to_end && plan.write.to_end && !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.to_end); + 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.to_end && !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-document-edits/sources/prototypes/connections/test-runtime.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-runtime.c new file mode 100644 index 0000000..0ff6a8b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-runtime.c @@ -0,0 +1,285 @@ +#define _POSIX_C_SOURCE 200809L +#include "runtime.h" +#include "store-port.h" +#include "store.h" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static const PureProgram identity = { + .read = {.to_end = true}, .write = {.to_end = true}, + .reply = {.to_end = true}, .reply_input = true +}; +typedef struct { + size_t allocations, writes, frees, live; + bool allocation_failure, oversized_read, reenter; + PureRuntime *runtime; + unsigned char received[256]; + size_t received_size; +} Host; +typedef struct { Host *host; unsigned char bytes[]; } Allocation; + +static void free_input(void *pointer, size_t size) { + Allocation *a = pointer; + assert(size > 0 && a->host->live > 0); + ++a->host->frees; + --a->host->live; + free(a); +} + +static bool allocate(void *context, size_t size, PureResource *out) { + Host *host = context; + if (host->allocation_failure) return false; + Allocation *a = malloc(sizeof(*a) + size); + assert(a); + a->host = host; + ++host->allocations; + ++host->live; + *out = (PureResource){.body = {a->bytes, size}, .allocation = a, + .allocation_size = size, .release = free_input}; + return true; +} + +static PureResult write_value(void *context, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + Host *host = context; + (void)key; + if (host->reenter) { + PureTicket ignored; + assert(pure_runtime_accept(host->runtime, PURE_INPUT_WRITE, "reenter", 0, 0, &ignored) == PURE_INVALID); + assert(!pure_runtime_step(host->runtime)); + } + if (expected == 99) { *revision = 1; return PURE_CONFLICT; } + ++host->writes; + assert(body.size <= sizeof(host->received)); + if (body.size) memcpy(host->received, body.data, body.size); + host->received_size = body.size; + *revision = 1; + return PURE_OK; +} + +static PureResult read_value(void *context, const char *key, PureRegion range, + size_t minimum, size_t available, PureResource *out, int64_t *revision) { + Host *host = context; + (void)key; (void)range; (void)minimum; + if (!host->oversized_read) return PURE_NOT_FOUND; + assert(available < 1000); + assert(allocate(host, available + 1, out)); + *revision = 1; + return PURE_OK; /* Deliberately violates the available-credit contract. */ +} + +static PureHost port(Host *host) { + return (PureHost){.context = host, .allocate = allocate, .read = read_value, .write = write_value}; +} + +static PureTicket submit(PureRuntime *r, const char *key, const void *bytes, size_t length) { + PureTicket ticket; + assert(pure_runtime_accept(r, PURE_INPUT_WRITE, key, 0, length, &ticket) == PURE_OK); + size_t capacity; + unsigned char *input = pure_runtime_ingress(ticket, &capacity); + assert(capacity == length); + if (length) memcpy(input, bytes, length); + assert(pure_runtime_ready(ticket)); + assert(!pure_runtime_ready(ticket)); + return ticket; +} + +static PureOutcome completed(PureRuntime *r, PureTicket ticket) { + PureOutcome result; + while (!pure_runtime_result(ticket, &result)) assert(pure_runtime_step(r)); + return result; +} + +static void admission_and_lifecycle(void) { + Host host = {0}; + PureRuntime r; + PureWork work[3]; + assert(pure_runtime_init(&r, work, 3, &identity, port(&host), 5)); + host.runtime = &r; + PureTicket a, untouched = {0}, out = untouched; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "../bad", 0, 1, &out) == PURE_INVALID); + assert(!out.runtime && !host.allocations); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "a", 0, 4, &a) == PURE_OK); + assert(r.retained == 4 && !pure_runtime_step(&r) && !host.writes); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "b", 0, 2, &out) == PURE_BUDGET); + assert(!out.runtime && host.allocations == 1); + assert(pure_runtime_cancel(a)); + PureOutcome cancelled; + assert(pure_runtime_result(a, &cancelled) && cancelled.status == PURE_CANCELLED); + assert(r.retained == 0 && host.frees == 1 && !pure_runtime_cancel(a)); + assert(pure_runtime_release(a) && !pure_runtime_release(a)); + assert(!pure_runtime_result(a, &cancelled)); + + PureTicket first = submit(&r, "first", "1", 1); + PureTicket middle = submit(&r, "middle", "2", 1); + PureTicket last = submit(&r, "last", "3", 1); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "full", 0, 0, &out) == PURE_BUSY); + assert(pure_runtime_cancel(middle)); + assert(pure_runtime_release(middle)); + pure_runtime_stop(&r); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "closed", 0, 0, &out) == PURE_CLOSED); + assert(!pure_runtime_restart(&r, &identity, port(&host), 5)); + host.reenter = true; + assert(completed(&r, first).status == PURE_OK && host.received[0] == '1'); + assert(completed(&r, last).status == PURE_OK && host.received[0] == '3'); + assert(!pure_runtime_step(&r)); + assert(pure_runtime_release(first) && pure_runtime_release(last)); + assert(r.active == 0 && r.retained == 0 && host.live == 0); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + PureTicket next = submit(&r, "next", "4", 1); + assert(next.generation > last.generation && !pure_runtime_ready(a)); + assert(!pure_runtime_release(last)); + assert(pure_runtime_release(next)); /* queued input: no effect */ + assert(host.writes == 2 && !pure_runtime_step(&r)); + + host.allocation_failure = true; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "no_memory", 0, 1, &out) == PURE_ALLOCATION); + assert(r.active == 0 && r.retained == 0); + host.allocation_failure = false; + PureHost denied = port(&host); + denied.write = NULL; + assert(pure_runtime_restart(&r, &identity, denied, 5)); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "denied", 0, 1, &out) == PURE_DENIED); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + host.oversized_read = true; + assert(pure_runtime_accept(&r, PURE_INPUT_READ, "bad_host", 0, 0, &out) == PURE_OK); + assert(completed(&r, out).status == PURE_BUDGET && host.live == 0 && r.retained == 0); + assert(pure_runtime_release(out)); + r.serial = UINT64_MAX; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "exhausted", 0, 0, &out) == PURE_LIMIT); +} + +static void forwarding(void) { + Host a = {0}, b = {0}, c = {0}; + PureRuntime source, left, right; + PureWork source_work[1], left_work[1], right_work[1]; + assert(pure_runtime_init(&source, source_work, 1, &identity, port(&a), 8)); + assert(pure_runtime_init(&left, left_work, 1, &identity, port(&b), 2)); + assert(pure_runtime_init(&right, right_work, 1, &identity, port(&c), 8)); + PureTicket original = submit(&source, "original", "value", 5), x = {0}, y; + PureOutcome result = completed(&source, original); + assert(result.status == PURE_OK); + const unsigned char *backing = result.body.data; + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_BUDGET); + assert(!x.runtime && source.work[0].pins == 0); + assert(pure_runtime_restart(&left, &identity, port(&b), 8)); + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_OK); + assert(pure_runtime_forward(original, &right, "right", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(!pure_runtime_result(original, &result)); + pure_runtime_stop(&source); + assert(!pure_runtime_restart(&source, &identity, port(&a), 8)); + assert(a.live == 1 && !b.allocations && !c.allocations); + assert(pure_runtime_cancel(x) && pure_runtime_release(x)); + assert(a.live == 1 && !b.writes); + result = completed(&right, y); + assert(result.body.data == backing && result.body.size == 5 && !memcmp(result.body.data, "value", 5)); + assert(c.writes == 1 && !c.allocations && source.retained == 5); + assert(!pure_runtime_cancel(y)); /* effects cannot be rolled back */ + assert(pure_runtime_release(y)); + assert(a.frees == 1 && !source.active && !source.retained && !right.active && !right.retained); + assert(pure_runtime_restart(&source, &identity, port(&a), 8)); + + /* A completed consumer does not retain its input when the program has no + * input-backed reply. The producing domain can then be reclaimed early. */ + PureProgram no_reply = identity; + no_reply.reply_input = false; + assert(pure_runtime_restart(&right, &no_reply, port(&c), 8)); + original = submit(&source, "again", "last", 4); + assert(completed(&source, original).status == PURE_OK); + assert(pure_runtime_forward(original, &right, "no_reply", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(completed(&right, y).body.size == 0 && a.live == 0); + assert(source.active == 0 && right.active == 1 && right.retained == 0); + assert(pure_runtime_release(y)); +} + +static void long_chain(void) { + enum { COUNT = 1024 }; + PureWork *work = calloc(COUNT, sizeof(*work)); + assert(work); + Host host = {0}; + PureRuntime r; + assert(pure_runtime_init(&r, work, COUNT, &identity, port(&host), COUNT)); + PureTicket ticket = submit(&r, "chain", "x", 1); + for (size_t i = 1; i < COUNT; ++i) { + assert(completed(&r, ticket).status == PURE_OK); + PureTicket next; + assert(pure_runtime_forward(ticket, &r, "chain", 0, &next) == PURE_OK); + assert(pure_runtime_release(ticket)); + ticket = next; + } + assert(completed(&r, ticket).status == PURE_OK); + assert(host.allocations == 1 && r.active == COUNT); + assert(pure_runtime_release(ticket)); + assert(r.retained == 0 && r.active == 0 && host.frees == 1); + free(work); +} + +static void actual_storage(void) { + char directory[] = "/tmp/pure-runtime-check-XXXXXX"; + assert(mkdtemp(directory)); + char source_path[128], target_path[128], name[160]; + snprintf(source_path, sizeof(source_path), "%s/source", directory); + snprintf(target_path, sizeof(target_path), "%s/target", directory); + assert(pure_store_init(source_path) == PURE_OK && pure_store_init(target_path) == PURE_OK); + PureProgram selection; + char error[160]; + assert(pure_program_load("examples/selection.pure", &selection, error, sizeof(error))); + PureRuntime source, target; + PureWork a[2], b[2]; + assert(pure_runtime_init(&source, a, 2, &selection, pure_store_host(source_path), 128)); + assert(pure_runtime_init(&target, b, 2, &identity, pure_store_host(target_path), 128)); + const unsigned char bytes[] = {1,0,0,0, 3,0,0,0, 'a','b','c','d','e'}; + PureTicket ticket = submit(&source, "excerpt", bytes, sizeof(bytes)), consumer; + PureOutcome result = completed(&source, ticket); + assert(result.status == PURE_OK && result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_forward(ticket, &target, "archive", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + assert(source.active == 1 && source.retained == sizeof(bytes)); + assert(completed(&target, consumer).status == PURE_OK); + assert(pure_runtime_release(consumer) && source.active == 0); + PureStoredView stored = {0}; + assert(pure_store_get(target_path, "archive", 128, &stored) == PURE_OK); + assert(stored.body.size == 3 && !memcmp(stored.body.data, "bcd", 3)); + pure_store_release(&stored); + + assert(pure_runtime_accept(&source, PURE_INPUT_READ, "excerpt", 0, 0, &ticket) == PURE_OK); + assert(completed(&source, ticket).status == PURE_OK); + assert(pure_runtime_forward(ticket, &target, "snapshot", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + int64_t revision; + assert(pure_store_put(source_path, "excerpt", 1, (PureSpan){(const unsigned char *)"new", 3}, &revision) == PURE_OK); + result = completed(&target, consumer); + assert(result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_release(consumer)); + + unsigned char invalid[] = {255,255,255,255, 1,0,0,0, 'x'}; + ticket = submit(&source, "invalid", invalid, sizeof(invalid)); + assert(completed(&source, ticket).status == PURE_LIMIT); + assert(pure_runtime_release(ticket)); + assert(pure_store_get(source_path, "invalid", 128, &stored) == PURE_NOT_FOUND); + assert(source.active == 0 && target.active == 0 && source.retained == 0 && target.retained == 0); + const char *files[] = {"source/obj-excerpt", "source/.writer.lock", "target/obj-archive", + "target/obj-snapshot", "target/.writer.lock"}; + for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) { + snprintf(name, sizeof(name), "%s/%s", directory, files[i]); + assert(unlink(name) == 0); + } + assert(rmdir(source_path) == 0 && rmdir(target_path) == 0 && rmdir(directory) == 0); +} + +int main(void) { + admission_and_lifecycle(); + forwarding(); + long_chain(); + actual_storage(); + printf("{\"runtime_tests\":\"passed\",\"domain_bytes\":%zu,\"work_slot_bytes\":%zu," + "\"ticket_bytes\":%zu,\"prepared_program_bytes\":%zu,\"forwarding_chain\":1024," + "\"native_storage_pipeline\":true}\n", sizeof(PureRuntime), sizeof(PureWork), + sizeof(PureTicket), sizeof(PureProgram)); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-splice.c b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-splice.c new file mode 100644 index 0000000..5b8232b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/connections/test-splice.c @@ -0,0 +1,172 @@ +#define _POSIX_C_SOURCE 200809L +#include "runtime.h" +#include "store-port.h" +#include "store.h" +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0) +static int failed_stage = -1; +int pure_store_test_fault(PureStoreStage stage) { return (int)stage == failed_stage; } + +static void expect(const char *path, const char *key, int64_t revision, PureSpan bytes) { + PureStoredView view; + CHECK(pure_store_get(path, key, PURE_OBJECT_LIMIT, &view) == PURE_OK); + CHECK(view.revision == revision && view.body.size == bytes.size); + CHECK(!bytes.size || !memcmp(view.body.data, bytes.data, bytes.size)); + pure_store_release(&view); +} + +static void parser(void) { + const char *get = "on get { let o = store.read(request.key); reply(o, o.body); }"; + const char *layout = "layout E { marker: bytes[1]; remove: u32le; start: u32le; insert: bytes; }"; + char source[2048], error[160]; + PureProgram p; + snprintf(source, sizeof(source), "%s %s on put { let e = view(E, request.body); " + "let s = store.splice(request.key, request.revision, e.start, e.remove, e.insert); reply(s, e.insert); }", layout, get); + CHECK(pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(p.splice && p.input_required == 9 && p.splice_offset.value == 5 && p.splice_remove.value == 1); + unsigned char input[] = {0, 2,0,0,0, 3,0,0,0, 0,255}; + PureSpan write, reply; + size_t offset, remove; + for (size_t n = 0; n < 9; ++n) + CHECK(!pure_program_input(&p, (PureSpan){input, n}, &write, &reply)); + CHECK(pure_program_input(&p, (PureSpan){input, sizeof(input)}, &write, &reply)); + CHECK(write.size == 2 && write.data == input + 9 && reply.data == write.data); + CHECK(pure_program_splice(&p, (PureSpan){input, sizeof(input)}, &offset, &remove)); + CHECK(offset == 3 && remove == 2); + + snprintf(source, sizeof(source), "%s on put { let s = store.splice(request.key, request.revision, 0, 0, request.body); reply(s); }", get); + CHECK(pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(pure_program_splice(&p, (PureSpan){0}, &offset, &remove) && offset == 0 && remove == 0); + const char *invalid[] = { + "let s = store.splice(request.key, request.revision, e.insert, 0, e.insert); reply(s);", + "let s = store.splice(request.key, request.revision, 0, 1449985, e.insert); reply(s);", + "let s = store.splice(request.key, request.revision, 0, 0, e.start); reply(s);", + "let s = store.splice(request.key, request.revision, 0, 0, e.insert); let t = store.write(request.key, request.revision, e.insert); reply(t);" + }; + for (size_t i = 0; i < sizeof(invalid) / sizeof(*invalid); ++i) { + snprintf(source, sizeof(source), "%s %s on put { let e = view(E, request.body); %s }", layout, get, invalid[i]); + unsigned char before[sizeof(p)]; + memcpy(before, &p, sizeof(p)); + CHECK(!pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(*error && !memcmp(before, &p, sizeof(p))); + } +} + +static void storage(const char *path) { + /* Reference whole-value assembly exists only in this test oracle. */ + unsigned char oracle[65536], before[65536], insert[9000]; + for (size_t i = 0; i < sizeof(insert); ++i) insert[i] = (unsigned char)(i * 19u); + size_t length = 49157; + for (size_t i = 0; i < length; ++i) oracle[i] = (unsigned char)((i * 17u) ^ (i >> 8)); + int64_t revision; + CHECK(pure_store_splice(path, "work", 0, 0, 0, (PureSpan){oracle, length}, &revision) == PURE_OK); + CHECK(revision == 1); + /* Prefix/suffix copies cross both scratch and mapping page boundaries. */ + const size_t edits[][3] = {{16381, 7, 9000}, {0, 0, 5}, {50000, 1, 0}, {4095, 4097, 3}, {0, 0, 0}}; + for (size_t i = 0; i < sizeof(edits) / sizeof(*edits); ++i) { + size_t offset = edits[i][0], remove = edits[i][1], count = edits[i][2], previous = length; + memcpy(before, oracle, length); + PureStoredView old; + CHECK(pure_store_get(path, "work", PURE_OBJECT_LIMIT, &old) == PURE_OK); + int64_t expected = revision; + CHECK(pure_store_splice(path, "work", expected, offset, remove, (PureSpan){insert, count}, &revision) == PURE_OK); + CHECK(revision == expected + 1); + memmove(oracle + offset + count, oracle + offset + remove, length - offset - remove); + memcpy(oracle + offset, insert, count); + length = length - remove + count; + expect(path, "work", revision, (PureSpan){oracle, length}); + CHECK(old.body.size == previous && !memcmp(old.body.data, before, previous)); + pure_store_release(&old); + } + int64_t latest = revision; + CHECK(pure_store_splice(path, "work", latest - 1, 0, 0, (PureSpan){insert, 1}, &revision) == PURE_CONFLICT); + CHECK(revision == latest); + CHECK(pure_store_splice(path, "work", latest, SIZE_MAX, 0, (PureSpan){0}, &revision) == PURE_LIMIT); + CHECK(pure_store_splice(path, "work", latest, 0, SIZE_MAX, (PureSpan){0}, &revision) == PURE_LIMIT); + CHECK(pure_store_splice(path, "work", latest, length + 1, 0, (PureSpan){0}, &revision) == PURE_LIMIT); + /* Rejected before reading the intentionally undersized insertion pointer. */ + CHECK(pure_store_splice(path, "work", latest, 0, 0, (PureSpan){insert, PURE_OBJECT_LIMIT}, &revision) == PURE_LIMIT); + expect(path, "work", latest, (PureSpan){oracle, length}); + CHECK(pure_store_splice(path, "missing", 0, 1, 0, (PureSpan){0}, &revision) == PURE_LIMIT); + PureStoredView view; + CHECK(pure_store_get(path, "missing", 0, &view) == PURE_NOT_FOUND); + for (failed_stage = PURE_AFTER_HEADER; failed_stage <= PURE_BEFORE_RENAME; ++failed_stage) { + CHECK(pure_store_splice(path, "work", latest, 1, 3, (PureSpan){insert, 5}, &revision) == PURE_STORE_ERROR); + expect(path, "work", latest, (PureSpan){oracle, length}); + } + failed_stage = PURE_BEFORE_DIR_SYNC; + CHECK(pure_store_splice(path, "work", latest, length, 0, (PureSpan){insert, 5}, &revision) == PURE_UNCERTAIN); + CHECK(revision == latest + 1); + failed_stage = -1; + memcpy(oracle + length, insert, 5); length += 5; + expect(path, "work", revision, (PureSpan){oracle, length}); + CHECK(pure_store_splice(path, "work", revision, 0, length, (PureSpan){0}, &revision) == PURE_OK); + expect(path, "work", revision, (PureSpan){0}); + char file[256]; + snprintf(file, sizeof(file), "%s/obj-work", path); + int fd = open(file, O_WRONLY); + const unsigned char maximum[] = {255,255,255,255,255,255,255,127}; + CHECK(fd >= 0 && pwrite(fd, maximum, 8, 8) == 8 && close(fd) == 0); + CHECK(pure_store_splice(path, "work", INT64_MAX, 0, 0, (PureSpan){0}, &revision) == PURE_LIMIT); +} + +static void runtime(const char *path) { + PureProgram program; + char error[160]; + CHECK(pure_program_load("examples/editor.pure", &program, error, sizeof(error))); + PureRuntime runtime; + PureWork slots[1]; + PureHost denied = pure_store_host(path); + denied.splice = NULL; /* ordinary writes do not grant permission to splice */ + CHECK(pure_runtime_init(&runtime, slots, 1, &program, denied, 32)); + PureTicket ticket = {0}; + CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_DENIED); + CHECK(!ticket.runtime && runtime.active == 0 && runtime.retained == 0); + CHECK(pure_runtime_restart(&runtime, &program, pure_store_host(path), 32)); + CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_OK); + CHECK(pure_runtime_cancel(ticket) && pure_runtime_release(ticket)); + CHECK(!pure_runtime_step(&runtime)); + PureStoredView view; + CHECK(pure_store_get(path, "native", 32, &view) == PURE_NOT_FOUND); + CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 7, &ticket) == PURE_OK); + memset(pure_runtime_ingress(ticket, NULL), 0, 7); + CHECK(pure_runtime_ready(ticket) && pure_runtime_step(&runtime)); + PureOutcome result; + CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_LIMIT); + CHECK(pure_runtime_release(ticket) && runtime.commits == 0); + CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_OK); + unsigned char *input = pure_runtime_ingress(ticket, NULL); + memset(input, 0, 8); input[8] = 'x'; + CHECK(pure_runtime_ready(ticket) && pure_runtime_step(&runtime)); + CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_OK && result.revision == 1); + CHECK(result.body.size == 1 && result.body.data == input + 8 && runtime.retained == 9); + expect(path, "native", 1, (PureSpan){(const unsigned char *)"x", 1}); + CHECK(pure_runtime_release(ticket) && runtime.retained == 0 && runtime.active == 0); +} + +int main(void) { + parser(); + char path[] = "/tmp/pure-splice-check-XXXXXX"; + CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK); + storage(path); + runtime(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(strncmp(entry->d_name, ".pending-", 9)); + CHECK(unlinkat(dirfd(entries), entry->d_name, 0) == 0); + } + CHECK(closedir(entries) == 0 && rmdir(path) == 0); + puts("{\"splice_tests\":\"passed\",\"checks\":[\"authored-fields-and-atomic-parse-failure\"," + "\"binary-edit-oracle-and-old-snapshot\",\"bounds-conflict-size-overflow\"," + "\"publication-failures-and-uncertain-durability\",\"cancel-admission-and-borrowed-reply\"]}"); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/cli.mjs new file mode 100644 index 0000000..b67f4d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/examples/direct.pure new file mode 100644 index 0000000..b29721c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/remote.mjs new file mode 100644 index 0000000..6ee6a59 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/compiler.mjs new file mode 100644 index 0000000..6aec335 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/core.wasm Binary files differnew file mode 100644 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/core.wasm diff --git a/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/project.mjs new file mode 100644 index 0000000..861e0fc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-document-edits/sources/prototypes/workbench/web/runtime.mjs new file mode 100644 index 0000000..d8d6618 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/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-document-edits/verification.json b/prototypes/connections/results/2026-09-23-document-edits/verification.json new file mode 100644 index 0000000..598aed9 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-document-edits/verification.json @@ -0,0 +1,32 @@ +{ + "sources_verified_and_frozen": 37, + "native_and_sanitized_source_hashes_match": true, + "recorded_binaries_verified": 8, + "integration_scenarios_per_build": 36, + "server_runs_per_build": 19, + "all_server_runs_drained": true, + "compiler": "cc (Debian 14.2.0-19) 14.2.0", + "core_sections": { + "text": 5932, + "data": 0, + "bss": 0 + }, + "structure_sizes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "core_external_symbols": [ + "memcpy", + "strlen" + ], + "server_structure_bytes": [41528], + "limitations": [ + "synchronous full-file rewrite with 4096-byte host scratch", + "11-byte credit test does not measure total memory", + "limited storage authoring profile, no native GUI or secure peer RPC", + "GNU footprint tooling validated on Linux AArch64; macOS unverified" + ] +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/README.md b/prototypes/connections/results/2026-09-23-execution-core/README.md new file mode 100644 index 0000000..fd92adb --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/README.md @@ -0,0 +1,28 @@ +# 실행 코어 검증 기록 + +2026-09-23, Linux AArch64, GCC 14.2.0. 첫 공개 체크포인트 진행 중의 증거다. + +- `native.json`: 일반 빌드의 실제 TCP 28개 시나리오, 실행 코어의 수명·예산·세대·내부 전달·실제 저장 검사. +- `sanitized.json`: 같은 경로를 ASan·UBSan 및 기본 누수 검사로 통과한 결과. +- `verification.json`: 두 결과의 소스 일치, 바이너리 해시, 종료 시 자원 회수, 코어 외부 심볼과 크기 기록. +- `sources/`: 결과에 기록된 30개 소스·클라이언트 파일의 해시 검증 후 복사본. + +당시 실행 명령은 `prototypes/connections`에서 다음과 같다. + +```sh +make all +make check EVIDENCE=results/2026-09-23-execution-core/native.json +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-execution-core/sanitized.json +size build/runtime.o build/evaluate.o build/pure-connections +nm -u build/libpure-execution.a +``` + +기존 증거 파일은 덮어쓰지 않는다. 재현할 때는 `EVIDENCE`를 생략하거나 새 경로를 사용한다. +동결된 소스는 `sources/prototypes/connections`에서 `make all`과 `make check`로 빌드·검사할 수 있다. +검사에는 C17 컴파일러·make·Python·Node와 로컬 소켓/자식 프로세스 실행이 필요하다. + +영역 192 B, 작업 슬롯 224 B, 티켓 24 B는 구조체 크기다. 코어 오브젝트의 `size text` 합 +5,672 B는 전체 RSS가 아니다. 준비 프로그램·작업 슬롯·실제 본문·호스트 자원 등은 별도이며, +서버 구조체는 기능 추가로 이전보다 1,664 B 증가했다. 처리량·지연의 우위를 측정한 자료가 아니다. +동기 효과·단일 소유 스레드·get/put 작성 프로필이라는 현재 한계는 +[022 보고서](../../../../docs/research/022-execution-core.md)에 기록했다. diff --git a/prototypes/connections/results/2026-09-23-execution-core/native.json b/prototypes/connections/results/2026-09-23-execution-core/native.json new file mode 100644 index 0000000..173e833 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/native.json @@ -0,0 +1,505 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ] + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ] + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "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": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41488 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 11679, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 8, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41488 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 192, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 648, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "f0700e597f634c2304ccb4cbc375d8b491964ed8be6e2aa573a58df9ee00c5fe", + "binary_sha256": "09ecc629b9bbddf09f5b6090c9b67eec7d2e70c6065624c60b902b5b3f8ae802", + "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": "b7e7d6806cf9785df1d01f2418d1e493eaf2d4b9cf43abc90082c60bd61dcd0b", + "prototypes/connections/store.h": "d2d7f8bd9845ce13095520f4401b9c48b1b4e8b6da10f8c4c63ab495c2950ef5", + "prototypes/connections/program.c": "b72b0aef7fc04ef044e3c0d177222aea688765526316fe2d0de988573ce8003b", + "prototypes/connections/program.h": "2c3c955967883a9a8ea4566a006eccd44f3a173ac0682c2f272d09700f209639", + "prototypes/connections/runtime.c": "d1163fdc6e5a1948ec0109fc1d04312a0b71edebf8f3762f38ac674ccd686af3", + "prototypes/connections/runtime.h": "03a69db466a217fff195183af174acba1949772c16523aab6f425e84600798e5", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "d3fbbd4ddba2b7fe29fc11ba46d3fc471945f724afff52bb5d7699f79cb80b5d", + "prototypes/connections/store-port.c": "5c7c3c1dcd2ae90a39ecc93b5bccf7f54fcafe1d0e10b2ad7b1eb0c89c1382bf", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "d1e2dc14185a8b4301cc6bfa001ee2cdfa674acad505b92c51f06311abf36be9", + "prototypes/connections/server.c": "21dc7edf1acb58f347586a92bab02f2d7edf6a7369c9b8212acab8b87f58aa74", + "prototypes/connections/check.py": "6bf6ccb8acd14a01f635733d3a9a42439a6f9dc37a9d43b5e3681301a9ab2235", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "1c4083e549bf2bb83e0127c910607e60cefd6f3af9e1fae355caadb2506ded09", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-execution-core/sanitized.json b/prototypes/connections/results/2026-09-23-execution-core/sanitized.json new file mode 100644 index 0000000..67c195e --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sanitized.json @@ -0,0 +1,505 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ] + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ] + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "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": 41488 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 14232, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "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": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41488 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41488 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 192, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 648, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "eb526e3dff8a87f1f53446efa25026a9720058bf414be08ce8cc57a9787c6bb8", + "binary_sha256": "49fb5aec3b1ec71cea66ab708e507583cc2403203845e8737a8c0fb2a050a899", + "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": "b7e7d6806cf9785df1d01f2418d1e493eaf2d4b9cf43abc90082c60bd61dcd0b", + "prototypes/connections/store.h": "d2d7f8bd9845ce13095520f4401b9c48b1b4e8b6da10f8c4c63ab495c2950ef5", + "prototypes/connections/program.c": "b72b0aef7fc04ef044e3c0d177222aea688765526316fe2d0de988573ce8003b", + "prototypes/connections/program.h": "2c3c955967883a9a8ea4566a006eccd44f3a173ac0682c2f272d09700f209639", + "prototypes/connections/runtime.c": "d1163fdc6e5a1948ec0109fc1d04312a0b71edebf8f3762f38ac674ccd686af3", + "prototypes/connections/runtime.h": "03a69db466a217fff195183af174acba1949772c16523aab6f425e84600798e5", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "d3fbbd4ddba2b7fe29fc11ba46d3fc471945f724afff52bb5d7699f79cb80b5d", + "prototypes/connections/store-port.c": "5c7c3c1dcd2ae90a39ecc93b5bccf7f54fcafe1d0e10b2ad7b1eb0c89c1382bf", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "d1e2dc14185a8b4301cc6bfa001ee2cdfa674acad505b92c51f06311abf36be9", + "prototypes/connections/server.c": "21dc7edf1acb58f347586a92bab02f2d7edf6a7369c9b8212acab8b87f58aa74", + "prototypes/connections/check.py": "6bf6ccb8acd14a01f635733d3a9a42439a6f9dc37a9d43b5e3681301a9ab2235", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "1c4083e549bf2bb83e0127c910607e60cefd6f3af9e1fae355caadb2506ded09", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-execution-core/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/Makefile new file mode 100644 index 0000000..ea15285 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/Makefile @@ -0,0 +1,43 @@ +CC ?= cc +CFLAGS ?= -O2 -g +CFLAGS += -std=c17 -Wall -Wextra -Wpedantic -Werror +BUILD ?= build + +.PHONY: all check +CORE = runtime.c evaluate.c +HOST = store.c store-port.c +HEADERS = runtime.h program.h store.h store-port.h flow.h result.h +all: $(BUILD)/pure-connections $(BUILD)/libpure-execution.a + +$(BUILD): + mkdir -p $@ + +$(BUILD)/pure-connections: server.c $(HOST) flow.c program.c $(CORE) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) server.c $(HOST) flow.c program.c $(CORE) $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/runtime.o: runtime.c $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -c runtime.c -o $@ + +$(BUILD)/evaluate.o: evaluate.c program.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) -c evaluate.c -o $@ + +$(BUILD)/libpure-execution.a: $(BUILD)/runtime.o $(BUILD)/evaluate.o + $(AR) rcs $@ $^ + +$(BUILD)/test-flow: test-flow.c store.c flow.c store.h flow.h result.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 evaluate.c program.h store.h flow.h result.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-program.c program.c evaluate.c $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-fields: test-fields.c program.c evaluate.c program.h store.h flow.h result.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-fields.c program.c evaluate.c $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-runtime: test-runtime.c program.c $(CORE) $(HOST) $(HEADERS) | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-runtime.c program.c $(CORE) $(HOST) $(LDFLAGS) $(LDLIBS) -o $@ + +check: $(BUILD)/test-flow $(BUILD)/test-program $(BUILD)/test-fields $(BUILD)/test-runtime $(BUILD)/pure-connections + $(BUILD)/test-flow + $(BUILD)/test-program + $(BUILD)/test-fields + python3 check.py --server $(BUILD)/pure-connections --runtime-test $(BUILD)/test-runtime $(if $(EVIDENCE),--output "$(EVIDENCE)") diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/check.py new file mode 100644 index 0000000..a512748 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/check.py @@ -0,0 +1,536 @@ +#!/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 sys +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) + fields = field_connections(binary, directory, storage, records, checks) + return {'kind': 'prepared-connection-correctness-not-performance', 'checks': checks, + 'prepared_connections': prepared, + 'field_connections': fields, + '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'] == { + 'to_end': 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 field_connections(binary, directory, storage, records, checks): + program = ROOT / 'examples/selection.pure' + plans = {'selection': json.loads(subprocess.check_output( + [str(binary), '--check-program', str(program)], text=True))} + assert plans['selection']['runtime_view_steps'] == 1 + assert plans['selection']['steps'][0] == { + 'source_step': 0, 'source': {'offset': 8, 'length': 0, 'to_end': True}, + 'offset': {'value': 0, 'from_u32le': True}, 'length': {'value': 4, 'from_u32le': True}} + document = '처음🙂 Pure는 필드를 연결한다.\n끝' + document_file = directory / 'document.txt' + document_file.write_bytes(document.encode('utf-8')) + with serving(binary, storage, program=program) as selected: + command = [sys.executable, str(ROOT / 'examples/select-text.py'), str(document_file), + '--key', 'text_selection', '--start', '2', '--end', '12', '--url', f'http://127.0.0.1:{selected.port}'] + result = subprocess.run(command, capture_output=True, text=True, timeout=15) + assert result.returncode == 0, (result.stdout, result.stderr) + expected = document[2:12].encode('utf-8') + assert json.loads(result.stdout) == {'revision': '1', 'selected': document[2:12], 'stored_bytes': len(expected)} + assert rpc(selected, 'GET', 'text_selection')[2] == expected + checks.append('text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes') + + # Validation precedes the write: even a stale header cannot expose or + # commit partial input, and prior content/revision survive all failures. + invalid = [b'\0' * n for n in range(8)] + [ + struct.pack('<II', 0xffffffff, 0) + b'abc', + struct.pack('<II', 0, 0xffffffff) + b'abc', + struct.pack('<II', 2, 2) + b'abc', + struct.pack('<II', 4, 0) + b'abc'] + for payload in invalid: + assert rpc(selected, 'PUT', 'text_selection', payload, 1)[0] == 413 + status, headers, body = rpc(selected, 'GET', 'text_selection') + assert status == 200 and headers['ETag'] == '"1"' and body == expected + for key, payload in [('selection_empty', struct.pack('<II', 0, 0)), + ('selection_end', struct.pack('<II', 3, 0) + b'abc')]: + assert rpc(selected, 'PUT', key, payload, 0)[0] == 200 + assert rpc(selected, 'GET', key)[2] == b'' + assert rpc(selected, 'PUT', 'text_selection', struct.pack('<II', 0, 3) + b'abc', 0)[0] == 412 + checks.append('truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid') + records.append(selected.stats) + + declaration = 'layout E { tag: bytes[3]; count: u32le; start: u32le; document: bytes; } ' + 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); } ' + reordered = directory / 'reordered.pure' + reordered.write_text(declaration + get + 'on put { let e = view(E, request.body); let alias = e; ' + 'let part = range(alias.document, alias.start, alias.count); let again = part; ' + 'let s = store.write(request.key, request.revision, again); reply(s, part); }') + plans['reordered'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(reordered)], text=True)) + assert plans['reordered']['runtime_view_steps'] == 1 + with serving(binary, storage, program=reordered) as moved: + status, headers, body = rpc(moved, 'PUT', 'reordered', b'tag' + struct.pack('<II', 4, 2) + b'abcdefgh', 0) + assert status == 200 and body == b'cdef' and headers['ETag'] == '"1"' + assert rpc(moved, 'GET', 'reordered')[2] == b'cdef' + records.append(moved.stats) + checks.append('changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps') + + unused = directory / 'unused-dynamic.pure' + unused.write_text(declaration + get + 'on put { let e = view(E, request.body); ' + 'let unused = range(e.document, e.start, e.count); ' + 'let s = store.write(request.key, request.revision, e.document); reply(s); }') + with serving(binary, storage, program=unused) as guarded: + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 4, 2) + b'abc', 0)[0] == 413 + assert rpc(guarded, 'GET', 'unused_dynamic')[0] == 404 + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 1, 1) + b'abc', 0)[0] == 200 + assert rpc(guarded, 'GET', 'unused_dynamic')[2] == b'abc' + records.append(guarded.stats) + checks.append('unused dynamic field selection retains its validation before the write effect') + + field_get = directory / 'field-get.pure' + field_get.write_text(declaration + 'on get { let o = store.read(request.key); ' + 'let e = view(E, o.body); reply(o, e.document); } ' + put) + plans['field_get'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(field_get)], text=True)) + tail = '다른 길이의 문서'.encode('utf-8') + with serving(binary, storage, program=field_get) as writer: + assert rpc(writer, 'PUT', 'tail', b'tag' + struct.pack('<II', 0, 0) + tail, 0)[0] == 200 + assert rpc(writer, 'PUT', 'bad_layout', b'short', 0)[0] == 200 + assert rpc(writer, 'PUT', 'empty_tail', b'tag' + struct.pack('<II', 0, 0), 0)[0] == 200 + records.append(writer.stats) + with serving(binary, storage, budget=len(tail), program=field_get) as reader: + assert rpc(reader, 'GET', 'tail')[2] == tail + assert rpc(reader, 'GET', 'empty_tail')[2] == b'' + assert rpc(reader, 'GET', 'bad_layout')[0] == 413 + assert reader.stats['peak_retained_payload_bytes'] == len(tail) + records.append(reader.stats) + checks.append('named variable tail reads use the same file version and charge only the selected suffix') + + document_bytes = bytes(range(256)) * 4096 + payload = struct.pack('<II', 16, len(document_bytes) - 32) + document_bytes + with serving(binary, storage, budget=len(payload), program=program) as slow: + with socket.create_connection(('127.0.0.1', slow.port), timeout=8) as sock: + sock.sendall(header('PUT', 'dynamic_slow', len(payload), b'If-None-Match: *\r\n') + payload) + response = http.client.HTTPResponse(sock) + response.begin() + assert response.status == 200 and response.read(1) == document_bytes[16:17] + assert rpc(slow, 'GET', 'binary')[0] == 503 + assert response.read() == document_bytes[17:-16] + response.close() + assert rpc(slow, 'GET', 'binary')[2] == b'new' + assert rpc(slow, 'GET', 'dynamic_slow')[2] == document_bytes[16:-16] + assert slow.stats['peak_retained_payload_bytes'] == len(payload) + records.append(slow.stats) + checks.append('data-dependent response keeps and charges the original input through partial transmission') + return plans + + +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) + parser.add_argument('--runtime-test', 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)) + if args.runtime_test: + runtime_test = args.runtime_test.resolve() + tested = subprocess.run([str(runtime_test)], cwd=ROOT, capture_output=True, text=True, timeout=30) + assert tested.returncode == 0, (tested.stdout, tested.stderr) + result['execution_core'] = json.loads(tested.stdout) + result['execution_core_test_sha256'] = hashlib.sha256(runtime_test.read_bytes()).hexdigest() + 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', + 'runtime.c', 'runtime.h', 'result.h', 'evaluate.c', 'store-port.c', 'store-port.h', 'test-runtime.c', + 'server.c', 'check.py', 'test-flow.c', 'test-program.c', 'test-fields.c', 'Makefile', + 'examples/storage.pure', 'examples/excerpt.pure', 'examples/selection.pure', 'examples/select-text.py')), + *(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-execution-core/sources/prototypes/connections/evaluate.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/evaluate.c new file mode 100644 index 0000000..3b1652f --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/evaluate.c @@ -0,0 +1,48 @@ +#include "program.h" +#include <stdint.h> + +bool pure_region_span(PureRegion region, PureSpan source, PureSpan *out) { + *out = (PureSpan){0}; + if (region.offset > source.size) return false; + size_t length = region.to_end ? source.size - region.offset : region.length; + if (length > source.size - region.offset) return false; + *out = (PureSpan){length ? source.data + region.offset : NULL, length}; + return true; +} + +static bool index_read(PureIndex index, PureSpan source, size_t *out) { + if (!index.from_u32le) { *out = index.value; return true; } + if (index.value > source.size || 4 > source.size - index.value) return false; + const unsigned char *bytes = source.data + index.value; + *out = (size_t)bytes[0] | (size_t)bytes[1] << 8 | (size_t)bytes[2] << 16 | (size_t)bytes[3] << 24; + return true; +} + +static bool resolve(PureRegion region, size_t step, PureSpan source, + const PureSpan *values, size_t count, PureSpan *out) { + if (!step) return pure_region_span(region, source, out); + if (step > count) return false; + *out = values[step - 1]; + return true; +} + +bool pure_program_input(const PureProgram *p, PureSpan source, PureSpan *write, PureSpan *reply) { + *write = (PureSpan){0}; + *reply = (PureSpan){0}; + if (source.size < p->input_required || p->input_steps > PURE_DYNAMIC_LIMIT) return false; + PureSpan values[PURE_DYNAMIC_LIMIT]; + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + PureSpan parent; + size_t offset, length; + if (!resolve(step->source, step->source_step, source, values, i, &parent) || + !index_read(step->offset, source, &offset) || !index_read(step->length, source, &length) || + !pure_region_span((PureRegion){.offset = offset, .length = length}, parent, &values[i])) return false; + } + PureSpan stored, returned = {0}; + if (!resolve(p->write, p->write_step, source, values, p->input_steps, &stored) || + (p->reply_input && !resolve(p->reply, p->reply_step, source, values, p->input_steps, &returned))) return false; + *write = stored; + *reply = returned; + return true; +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/excerpt.pure b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/excerpt.pure new file mode 100644 index 0000000..622f9bb --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/connections/examples/select-text.py b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/select-text.py new file mode 100644 index 0000000..ffaf80b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/select-text.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Save a UTF-8 document selection through selection.pure (character indices).""" +import argparse +import http.client +import json +from pathlib import Path +import re +import struct +from urllib.parse import urlsplit + +LIMIT = (2 * 6 + 32) * 32768 + 8192 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('file', type=Path) + parser.add_argument('--key', required=True) + parser.add_argument('--start', type=int, required=True, help='first Unicode code point, starting at 0') + parser.add_argument('--end', type=int, required=True, help='exclusive Unicode code point boundary') + parser.add_argument('--revision', type=int, default=0, help='0 creates; otherwise expected stored revision') + parser.add_argument('--url', default='http://127.0.0.1:8767') + args = parser.parse_args() + url = urlsplit(args.url) + try: + port = url.port or 80 + except ValueError as error: + parser.error(str(error)) + if (url.scheme != 'http' or url.hostname not in ['localhost', '127.0.0.1'] or + url.username is not None or url.password is not None or url.path not in ['', '/'] or + url.query or url.fragment): + parser.error('use a local HTTP server origin') + if not re.fullmatch(r'[A-Za-z0-9_-]{1,64}', args.key) or not 0 <= args.revision <= 2**63 - 1: + parser.error('invalid object key or revision') + try: + with args.file.open('rb') as source: + document = source.read(LIMIT - 8 + 1) + if len(document) > LIMIT - 8: + parser.error('document exceeds the prototype request limit') + text = document.decode('utf-8') + except (OSError, UnicodeError) as error: + parser.error(str(error)) + if not 0 <= args.start <= args.end <= len(text): + parser.error('selection is outside the document') + start = len(text[:args.start].encode('utf-8')) + selected = text[args.start:args.end].encode('utf-8') + # The boundary layout is explicit; storage receives only selected bytes. + payload = struct.pack('<II', start, len(selected)) + document + headers = {'Content-Type': 'application/octet-stream'} + headers['If-None-Match' if args.revision == 0 else 'If-Match'] = ( + '*' if args.revision == 0 else f'"{args.revision}"') + connection = http.client.HTTPConnection(url.hostname, port, timeout=12) + try: + connection.request('PUT', '/objects/' + args.key, payload, headers) + response = connection.getresponse() + body = response.read(LIMIT + 1) + if response.status != 200: + parser.error(f'storage response {response.status}: {response.getheader("X-Pure-Error")}') + if body != selected: + parser.error('response differs from the selection; check the installed program (write may have occurred)') + revision = response.getheader('ETag', '') + if not re.fullmatch(r'"[1-9][0-9]*"', revision) or int(revision[1:-1]) > 2**63 - 1: + parser.error('response has no valid stored revision (write may have occurred)') + except (OSError, http.client.HTTPException) as error: + parser.error(f'{error}; storage outcome is unknown if the request was sent') + finally: + connection.close() + print(json.dumps({'revision': revision[1:-1], 'selected': selected.decode('utf-8'), + 'stored_bytes': len(selected)}, ensure_ascii=False)) + + +if __name__ == '__main__': + main() diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/selection.pure b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/selection.pure new file mode 100644 index 0000000..7d9d4ec --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/selection.pure @@ -0,0 +1,19 @@ +// This is an input boundary layout, not a required storage or RAM model. +// The client sends two little-endian u32 values followed by document bytes. +layout Selection { + start: u32le; + count: u32le; + document: bytes; +} + +on get { + let saved = store.read(request.key); + reply(saved, saved.body); +} + +on put { + let input = view(Selection, request.body); + let selected = range(input.document, input.start, input.count); + let saved = store.write(request.key, request.revision, selected); + reply(saved, selected); +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/storage.pure b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/examples/storage.pure new file mode 100644 index 0000000..ff54ea3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/flow.c new file mode 100644 index 0000000..1070c70 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/flow.h new file mode 100644 index 0000000..6e27c58 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/connections/program.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/program.c new file mode 100644 index 0000000..e394d11 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/program.c @@ -0,0 +1,357 @@ +#include "program.h" +#include "store.h" +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +enum { NAME = 256, NUMBER, SYMBOLS = 32, LAYOUTS = 4, FIELDS = 16 }; +typedef enum { BYTE_VALUE, RESULT_VALUE, LAYOUT_VALUE, INDEX_VALUE } ValueKind; +typedef struct { + char name[32]; + ValueKind kind; + PureRegion region; + size_t step, layout; + PureIndex index; +} Symbol; +typedef struct { + char name[32]; + size_t offset, length; + bool number, tail; +} Field; +typedef struct { + char name[32]; + Field fields[FIELDS]; + size_t count, prefix; + bool tail; +} Layout; +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, layout_count; + Layout layouts[LAYOUTS]; + 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 require_bytes(Parser *p, const Symbol *value) { + return value->kind == BYTE_VALUE || reject(p, "expected bytes; select a byte field"); +} + +static void require_length(Parser *p, size_t required) { + size_t *minimum = p->put ? &p->program.input_required : &p->program.read_required; + if (required > *minimum) *minimum = required; +} + +static Layout *find_layout(Parser *p, const char *name) { + for (size_t i = 0; i < p->layout_count; ++i) + if (!strcmp(name, p->layouts[i].name)) return &p->layouts[i]; + return NULL; +} + +static bool layout_definition(Parser *p) { + if (p->layout_count == LAYOUTS) return reject(p, "program exceeds 4 layouts"); + if (!next(p)) return false; + if (p->token != NAME || find_layout(p, p->name)) return reject(p, "expected a unique layout name"); + Layout *layout = &p->layouts[p->layout_count]; + strcpy(layout->name, p->name); + if (!next(p) || !token(p, '{')) return false; + while (p->token != '}') { + if (layout->count == FIELDS) return reject(p, "layout exceeds 16 fields"); + if (layout->tail) return reject(p, "variable bytes must be the last field"); + if (p->token != NAME) return reject(p, "expected a field name"); + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(layout->fields[i].name, p->name)) return reject(p, "duplicate field"); + Field *f = &layout->fields[layout->count]; + strcpy(f->name, p->name); + f->offset = layout->prefix; + if (!next(p) || !token(p, ':')) return false; + if (is_word(p, "u32le")) { + f->number = true; + f->length = 4; + if (!next(p)) return false; + } else if (is_word(p, "bytes")) { + if (!next(p)) return false; + if (p->token == '[') { + if (!next(p)) return false; + if (p->token != NUMBER) return reject(p, "fixed bytes requires a constant size"); + f->length = p->number; + if (!next(p) || !token(p, ']')) return false; + } else { f->tail = true; layout->tail = true; } + } else return reject(p, "supported field types: u32le, bytes[N], bytes"); + if (f->length > PURE_OBJECT_LIMIT - layout->prefix) return reject(p, "layout exceeds object bound"); + layout->prefix += f->length; + ++layout->count; + if (!token(p, ';')) return false; + } + if (!layout->count) return reject(p, "empty layout"); + ++p->layout_count; + return token(p, '}'); +} + +static bool reference(Parser *p, Symbol *out) { + if (is_word(p, "request")) { + if (!p->put) return reject(p, "request.body is only available in on put"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return field(p, "request", "body"); + } + Symbol *symbol = p->token == NAME ? find(p, p->name) : NULL; + if (!symbol) return reject(p, "unknown value"); + *out = *symbol; + if (!next(p)) return false; + if (p->token != '.') return true; + if (!next(p)) return false; + if (symbol->kind == RESULT_VALUE) { + if (p->put) return reject(p, "write result has no body; use the input value"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return word(p, "body"); + } + if (symbol->kind != LAYOUT_VALUE || p->token != NAME) return reject(p, "expected a layout field"); + Layout *layout = &p->layouts[symbol->layout]; + Field *f = NULL; + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(p->name, layout->fields[i].name)) f = &layout->fields[i]; + if (!f) return reject(p, "unknown layout field"); + size_t offset = symbol->region.offset + f->offset; + *out = (Symbol){.kind = f->number ? INDEX_VALUE : BYTE_VALUE}; + if (f->number) out->index = (PureIndex){.value = offset, .from_u32le = true}; + else out->region = (PureRegion){.offset = offset, + .length = f->tail ? (symbol->region.to_end ? 0 : symbol->region.length - f->offset) : f->length, + .to_end = f->tail && symbol->region.to_end}; + return next(p); +} + +static bool byte_reference(Parser *p, Symbol *out) { + return reference(p, out) && require_bytes(p, out); +} + +static bool index_value(Parser *p, PureIndex *out) { + if (p->token == NUMBER) { + *out = (PureIndex){.value = p->number}; + return next(p); + } + Symbol value; + if (!reference(p, &value)) return false; + if (value.kind != INDEX_VALUE) return reject(p, "range requires an integer or u32le field"); + *out = value.index; + return true; +} + +static bool layout_view(Parser *p, Symbol *out) { + if (!next(p) || !token(p, '(')) return false; + Layout *layout = p->token == NAME ? find_layout(p, p->name) : NULL; + if (!layout) return reject(p, "unknown layout"); + if (!next(p) || !token(p, ',')) return false; + Symbol source; + if (!byte_reference(p, &source) || !token(p, ')')) return false; + if (source.step) return reject(p, "layout view requires a statically located source"); + size_t bound = source.region.to_end ? PURE_OBJECT_LIMIT - source.region.offset : source.region.length; + if (layout->prefix > bound) return reject(p, "layout exceeds source bound"); + require_length(p, source.region.offset + layout->prefix); + *out = (Symbol){.kind = LAYOUT_VALUE, .region = source.region, .layout = (size_t)(layout - p->layouts)}; + if (!layout->tail) { out->region.length = layout->prefix; out->region.to_end = false; } + return true; +} + +static bool view(Parser *p, Symbol *out) { + if (is_word(p, "view")) return layout_view(p, out); + if (!is_word(p, "range")) return reference(p, out); + Symbol parent; + PureIndex offset, length; + if (!next(p) || !token(p, '(') || !byte_reference(p, &parent) || !token(p, ',') || + !index_value(p, &offset) || !token(p, ',') || !index_value(p, &length) || !token(p, ')')) return false; + bool dynamic = parent.step || offset.from_u32le || length.from_u32le; + *out = (Symbol){.kind = BYTE_VALUE}; + if (dynamic) { + if (!p->put) return reject(p, "data-dependent ranges currently require PUT input"); + if (p->program.input_steps == PURE_DYNAMIC_LIMIT) return reject(p, "input exceeds 8 dynamic ranges"); + if ((!offset.from_u32le && offset.value > PURE_OBJECT_LIMIT) || + (!length.from_u32le && length.value > PURE_OBJECT_LIMIT)) return reject(p, "range exceeds object bound"); + PureRangeStep *step = &p->program.steps[p->program.input_steps++]; + *step = (PureRangeStep){.source = parent.region, .source_step = parent.step, + .offset = offset, .length = length}; + out->step = p->program.input_steps; + } else { + size_t bound = parent.region.to_end ? PURE_OBJECT_LIMIT - parent.region.offset : parent.region.length; + if (offset.value > bound || length.value > bound - offset.value) return reject(p, "range exceeds its source bound"); + out->region = (PureRegion){.offset = parent.region.offset + offset.value, .length = length.value}; + require_length(p, out->region.offset + out->region.length); + } + ++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) { + Symbol value; + if (!token(p, ',') || !field(p, "request", "revision") || !token(p, ',') || + !byte_reference(p, &value)) return false; + p->program.write = value.region; + p->program.write_step = value.step; + } + if (!token(p, ')')) return false; + out->kind = RESULT_VALUE; + 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") || is_word(p, "view")) return reject(p, "expected a non-reserved definition name"); + if (find(p, p->name)) return reject(p, "duplicate definition"); + char name[32]; + strcpy(name, p->name); + Symbol symbol = {0}; + if (!next(p) || !token(p, '=')) return false; + if (is_word(p, "store")) { + if (!effect(p, &symbol)) return false; + } else { + if (!view(p, &symbol)) return false; + ++p->program.view_definitions; + } + if (!token(p, ';')) return false; + strcpy(symbol.name, name); + 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->kind != RESULT_VALUE) return reject(p, "reply requires this handler's storage result"); + if (!next(p)) return false; + Symbol body = {0}; + bool has_body = p->token == ','; + if (has_body && (!next(p) || !byte_reference(p, &body))) return false; + if (!token(p, ')') || !token(p, ';') || !token(p, '}')) return false; + if (p->put) { + p->program.reply = body.region; + p->program.reply_step = body.step; + p->program.reply_input = has_body; + } else p->program.read = body.region; + 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; + while (is_word(&p, "layout")) if (!layout_definition(&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); +} + diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/program.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/program.h new file mode 100644 index 0000000..f2b5b2f --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/program.h @@ -0,0 +1,39 @@ +#ifndef PURE_PROGRAM_H +#define PURE_PROGRAM_H +#include "flow.h" + +#define PURE_PROGRAM_LIMIT 16384u +#define PURE_DYNAMIC_LIMIT 8u +typedef struct { + size_t offset, length; + bool to_end; +} PureRegion; + +typedef struct { size_t value; bool from_u32le; } PureIndex; +typedef struct { + PureRegion source; + size_t source_step; /* 0: source region; otherwise a preceding step + 1. */ + PureIndex offset, length; +} PureRangeStep; + +/* 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; + size_t write_step, reply_step, input_steps; + PureRangeStep steps[PURE_DYNAMIC_LIMIT]; + 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); +/* Validates all input views, including unused dynamic views, before any effect. + * Returns borrowed selections; their lifetime remains that of source. */ +bool pure_program_input(const PureProgram *, PureSpan source, PureSpan *write, PureSpan *reply); +#endif diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/result.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/result.h new file mode 100644 index 0000000..8abb0f2 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/result.h @@ -0,0 +1,8 @@ +#ifndef PURE_RESULT_H +#define PURE_RESULT_H +typedef enum { + PURE_OK, PURE_NOT_FOUND, PURE_CONFLICT, PURE_BUSY, PURE_BUDGET, + PURE_LIMIT, PURE_STORE_ERROR, PURE_UNCERTAIN, + PURE_INVALID, PURE_DENIED, PURE_CLOSED, PURE_CANCELLED, PURE_ALLOCATION +} PureResult; +#endif diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.c new file mode 100644 index 0000000..3b7e930 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.c @@ -0,0 +1,265 @@ +#include "runtime.h" +#include <string.h> + +#define NONE SIZE_MAX +enum { FREE, RECEIVING, READY, RUNNING, DONE }; + +static PureWork *lookup(PureTicket ticket, bool require_held) { + PureRuntime *r = ticket.runtime; + if (!r || ticket.slot >= r->capacity || !ticket.generation) return NULL; + PureWork *w = &r->work[ticket.slot]; + if (w->state == FREE || w->generation != ticket.generation || + (require_held && !w->held)) return NULL; + return w; +} + +static bool valid_key(const char *key) { + if (!key || !*key) return false; + size_t n = 0; + for (; n <= 64 && key[n]; ++n) { + unsigned char c = (unsigned char)key[n]; + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-' || c == '_')) return false; + } + return n <= 64; +} + +static void dispose(PureResource *resource) { + PureResource old = *resource; + *resource = (PureResource){0}; + if (old.release) old.release(old.allocation, old.allocation_size); +} + +static void payload_release(PureRuntime *r, PureWork *w); + +/* Iteratively retire a chain of forwarded values. An arbitrarily long chain + * must not turn completion into recursive stack growth. */ +static void retire(PureRuntime *r, PureWork *w) { + while (w && !w->held && !w->pins) { + PureTicket parent = w->parent; + bool owned = w->charged || w->resource.release || parent.runtime; + dispose(&w->resource); + r->retained -= w->charged; + if (owned) ++r->releases; + size_t slot = (size_t)(w - r->work); + *w = (PureWork){.next = r->free_head}; + r->free_head = slot; + --r->active; + if (!parent.runtime) break; + r = parent.runtime; + w = lookup(parent, false); + /* Pins can only be created and removed by the owner-thread core. */ + if (!w || !w->pins) break; + --w->pins; + } +} + +static void payload_release(PureRuntime *r, PureWork *w) { + PureTicket parent = w->parent; + bool owned = w->charged || w->resource.release || parent.runtime; + dispose(&w->resource); + r->retained -= w->charged; + w->charged = 0; + w->parent = (PureTicket){0}; + if (owned) ++r->releases; + if (parent.runtime) { + PureWork *source = lookup(parent, false); + if (source && source->pins) { --source->pins; retire(parent.runtime, source); } + } +} + +bool pure_runtime_init(PureRuntime *r, PureWork *work, size_t capacity, + const PureProgram *program, PureHost host, size_t budget) { + if (!r || !work || !capacity || !program) return false; + *r = (PureRuntime){.program = program, .host = host, .work = work, + .capacity = capacity, .free_head = 0, .ready_head = NONE, .ready_tail = NONE, + .budget = budget, .accepting = true}; + for (size_t i = 0; i < capacity; ++i) + work[i] = (PureWork){.next = i + 1 < capacity ? i + 1 : NONE}; + return true; +} + +bool pure_runtime_restart(PureRuntime *r, const PureProgram *p, PureHost host, size_t budget) { + if (!r || !p || r->running || r->active) return false; + r->program = p; + r->host = host; + r->budget = budget; + r->accepting = true; + return true; +} + +void pure_runtime_stop(PureRuntime *r) { if (r && !r->running) r->accepting = false; } + +static PureResult admission(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t bytes) { + if (!r || r->running || !valid_key(key) || + (input != PURE_INPUT_READ && input != PURE_INPUT_WRITE) || + (input == PURE_INPUT_READ ? bytes != 0 : expected < 0)) return PURE_INVALID; + if (!r->accepting) return PURE_CLOSED; + if (input == PURE_INPUT_READ ? !r->host.read : !r->host.write) return PURE_DENIED; + if (r->free_head == NONE) return PURE_BUSY; + if (r->serial == UINT64_MAX) return PURE_LIMIT; + if (bytes > r->budget - r->retained) { ++r->budget_rejections; return PURE_BUDGET; } + return PURE_OK; +} + +static PureTicket occupy(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t bytes, PureResource resource) { + size_t slot = r->free_head; + PureWork *w = &r->work[slot]; + r->free_head = w->next; + *w = (PureWork){.generation = ++r->serial, .next = NONE, .charged = bytes, + .state = RECEIVING, .held = true, .input = input, .expected = expected, + .resource = resource}; + memcpy(w->key, key, strlen(key) + 1); + ++r->active; + ++r->accepted; + r->retained += bytes; + if (r->active > r->peak_active) r->peak_active = r->active; + if (r->retained > r->peak_retained) r->peak_retained = r->retained; + return (PureTicket){r, slot, w->generation}; +} + +bool pure_runtime_ready(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running || w->state != RECEIVING) return false; + PureRuntime *r = ticket.runtime; + w->state = READY; + if (r->ready_tail == NONE) r->ready_head = ticket.slot; + else r->work[r->ready_tail].next = ticket.slot; + r->ready_tail = ticket.slot; + return true; +} + +PureResult pure_runtime_accept(PureRuntime *r, PureInput input, const char *key, + int64_t expected, size_t size, PureTicket *out) { + if (!out) return PURE_INVALID; + PureResult status = admission(r, input, key, expected, size); + if (status != PURE_OK) return status; + PureResource resource = {0}; + if (size) { + if (!r->host.allocate) return PURE_ALLOCATION; + r->running = true; + bool ok = r->host.allocate(r->host.context, size, &resource); + r->running = false; + if (!ok || !resource.body.data || resource.body.size != size || !resource.release) { + dispose(&resource); + return PURE_ALLOCATION; + } + } + *out = occupy(r, input, key, expected, size, resource); + if (input == PURE_INPUT_READ) pure_runtime_ready(*out); + return PURE_OK; +} + +unsigned char *pure_runtime_ingress(PureTicket ticket, size_t *size) { + PureWork *w = lookup(ticket, true); + if (size) *size = 0; + if (!w || ticket.runtime->running || w->state != RECEIVING || + w->input != PURE_INPUT_WRITE || w->parent.runtime) return NULL; + if (size) *size = w->resource.body.size; + return (unsigned char *)w->resource.body.data; +} + +bool pure_runtime_step(PureRuntime *r) { + if (!r || r->running || r->ready_head == NONE) return false; + size_t slot = r->ready_head; + PureWork *w = &r->work[slot]; + r->ready_head = w->next; + if (r->ready_head == NONE) r->ready_tail = NONE; + w->next = NONE; + w->state = RUNNING; + r->running = true; + PureOutcome outcome = {0}; + if (w->input == PURE_INPUT_WRITE) { + PureSpan value; + if (!pure_program_input(r->program, w->resource.body, &value, &outcome.body)) + outcome.status = PURE_LIMIT; + else outcome.status = r->host.write(r->host.context, w->key, w->expected, + value, &outcome.revision); + if (outcome.status == PURE_OK) ++r->commits; + else if (outcome.status == PURE_CONFLICT) ++r->conflicts; + } else { + outcome.status = r->host.read(r->host.context, w->key, r->program->read, + r->program->read_required, r->budget - r->retained, &w->resource, &outcome.revision); + if (outcome.status == PURE_OK) { + if (w->resource.body.size > r->budget - r->retained) outcome.status = PURE_BUDGET; + else if (w->resource.body.size && (!w->resource.body.data || !w->resource.release)) + outcome.status = PURE_STORE_ERROR; + else { + w->charged = w->resource.body.size; + r->retained += w->charged; + if (r->retained > r->peak_retained) r->peak_retained = r->retained; + outcome.body = w->resource.body; + ++r->gets; + } + } + if (outcome.status == PURE_BUDGET) ++r->budget_rejections; + } + if (outcome.status != PURE_OK || !outcome.body.size) { + outcome.body = (PureSpan){0}; + payload_release(r, w); + } + r->borrowed_output_bytes += outcome.body.size; + w->outcome = outcome; + w->state = DONE; + r->running = false; + return true; +} + +bool pure_runtime_result(PureTicket ticket, PureOutcome *out) { + PureWork *w = lookup(ticket, true); + if (!out || !w || ticket.runtime->running || w->state != DONE) return false; + *out = w->outcome; + return true; +} + +PureResult pure_runtime_forward(PureTicket source, PureRuntime *r, + const char *key, int64_t expected, PureTicket *out) { + PureWork *parent = lookup(source, true); + if (!out || !parent || source.runtime->running || parent->state != DONE || + parent->outcome.status != PURE_OK) return PURE_INVALID; + PureResult status = admission(r, PURE_INPUT_WRITE, key, expected, parent->outcome.body.size); + if (status != PURE_OK) return status; + if (parent->pins == SIZE_MAX) return PURE_LIMIT; + PureTicket ticket = occupy(r, PURE_INPUT_WRITE, key, expected, parent->outcome.body.size, + (PureResource){.body = parent->outcome.body}); + ++parent->pins; + r->work[ticket.slot].parent = source; + pure_runtime_ready(ticket); + *out = ticket; + return PURE_OK; +} + +static void unlink_ready(PureRuntime *r, size_t slot) { + size_t previous = NONE; + for (size_t at = r->ready_head; at != NONE; at = r->work[at].next) { + if (at == slot) { + if (previous == NONE) r->ready_head = r->work[at].next; + else r->work[previous].next = r->work[at].next; + if (r->ready_tail == at) r->ready_tail = previous; + r->work[at].next = NONE; + return; + } + previous = at; + } +} + +bool pure_runtime_cancel(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running || (w->state != RECEIVING && w->state != READY)) return false; + if (w->state == READY) unlink_ready(ticket.runtime, ticket.slot); + payload_release(ticket.runtime, w); + w->outcome = (PureOutcome){.status = PURE_CANCELLED}; + w->state = DONE; + return true; +} + +bool pure_runtime_release(PureTicket ticket) { + PureWork *w = lookup(ticket, true); + if (!w || ticket.runtime->running) return false; + if (w->state == READY) unlink_ready(ticket.runtime, ticket.slot); + w->held = false; + retire(ticket.runtime, w); + return true; +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.h new file mode 100644 index 0000000..4462e96 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/runtime.h @@ -0,0 +1,86 @@ +#ifndef PURE_RUNTIME_H +#define PURE_RUNTIME_H +#include "program.h" +#include "result.h" +#include <stdint.h> + +/* First execution profile: authored selection -> read/conditional write -> + * reply. No socket, file descriptor, allocator, parser or OS type in the core. + * All control calls and connected domains share one owner thread. Host effects + * are synchronous in this profile and must not reenter the runtime. */ +typedef struct PureRuntime PureRuntime; +typedef struct PureWork PureWork; +typedef struct { PureRuntime *runtime; size_t slot; uint64_t generation; } PureTicket; +typedef enum { PURE_INPUT_READ, PURE_INPUT_WRITE } PureInput; + +/* The host owns this resource until it successfully returns it to the core. + * release must end all access and free/unmap allocation exactly once. */ +typedef struct { + PureSpan body; + void *allocation; + size_t allocation_size; + void (*release)(void *allocation, size_t size); +} PureResource; +typedef struct { + void *context; + bool (*allocate)(void *, size_t, PureResource *); + PureResult (*read)(void *, const char *, PureRegion, size_t minimum, + size_t available, PureResource *, int64_t *revision); + PureResult (*write)(void *, const char *, int64_t expected, + PureSpan, int64_t *revision); +} PureHost; +typedef struct { PureResult status; int64_t revision; PureSpan body; } PureOutcome; + +/* Caller-provided work slots: no background thread or per-domain heap. These + * records are implementation storage, not an ABI or a wire representation. */ +struct PureWork { + uint64_t generation; + size_t next, charged, pins; + unsigned state; + bool held; + PureInput input; + int64_t expected; + char key[65]; + PureResource resource; + PureTicket parent; + PureOutcome outcome; +}; +struct PureRuntime { + const PureProgram *program; + PureHost host; + PureWork *work; + size_t capacity, free_head, ready_head, ready_tail; + size_t budget, retained, peak_retained, active, peak_active; + uint64_t serial, accepted, commits, gets, conflicts, budget_rejections; + uint64_t releases, borrowed_output_bytes; + bool accepting, running; +}; + +/* Initialize fresh storage once. Use restart, not init, to reuse a domain; + * the monotonic ticket sequence then survives restart. program is immutable + * and remains alive until all work has drained. */ +bool pure_runtime_init(PureRuntime *, PureWork *, size_t capacity, + const PureProgram *, PureHost, size_t budget); +bool pure_runtime_restart(PureRuntime *, const PureProgram *, PureHost, size_t budget); +void pure_runtime_stop(PureRuntime *); /* stop admission, allow queued work to drain */ + +/* READ queues immediately. WRITE reserves its slot, budget and host allocation + * before returning. Fill ingress then publish once with ready. A partial input + * released without ready never executes. On error, out is unchanged. */ +PureResult pure_runtime_accept(PureRuntime *, PureInput, const char *key, + int64_t expected, size_t input_size, PureTicket *out); +unsigned char *pure_runtime_ingress(PureTicket, size_t *size); +bool pure_runtime_ready(PureTicket); +bool pure_runtime_step(PureRuntime *); /* execute at most one ready job */ +bool pure_runtime_result(PureTicket, PureOutcome *out); + +/* Pin a completed reply and queue it as another domain's WRITE input, with no + * allocation, serialization or payload copy. Source may be released as soon + * as this succeeds; the core retains its backing until the consumer ends. + * Each domain charges its own retained input bytes as admission credits. + * Credits across domains are NOT distinct physical memory and must not be summed. */ +PureResult pure_runtime_forward(PureTicket source, PureRuntime *destination, + const char *key, int64_t expected, PureTicket *out); +bool pure_runtime_cancel(PureTicket); /* only before execution; keeps a completion */ +bool pure_runtime_release(PureTicket); /* invalidates caller's ticket, preserves pins */ +#endif diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/server.c new file mode 100644 index 0000000..a65b634 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/server.c @@ -0,0 +1,368 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.h" +#include "program.h" +#include "store-port.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; + unsigned char *body; + bool put; + int64_t expected; + PureTicket ticket; + PureTransfer transfer; + Server *server; +} Connection; +struct Server { + const char *path; + PureProgram program; + PureRuntime runtime; + PureWork work[SLOTS]; + size_t budget, active, peak_active; + uint64_t accepted, expired, prefix_copy_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; + if (c->ticket.runtime) { + bool released = pure_runtime_release(c->ticket); + assert(released); + (void)released; + } + c->ticket = (PureTicket){0}; + c->body = NULL; +} + +static void close_connection(Connection *c) { + if (c->fd < 0) return; + if (c->transfer.active) pure_transfer_abort(&c->transfer); + else release_payload(c); + close(c->fd); + --c->server->active; + *c = (Connection){.fd = -1}; +} + +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. */ + release_payload(c); + respond(c, status, reason, error, 0, (PureSpan){0}); +} + +static void answer(Connection *c, PureOutcome result) { + switch (result.status) { + case PURE_OK: respond(c, 200, "OK", "none", result.revision, result.body); break; + case PURE_NOT_FOUND: fail(c, 404, "Not Found", "not_found"); break; + case PURE_CONFLICT: + respond(c, 412, "Precondition Failed", "conflict", result.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_ALLOCATION: fail(c, 503, "Service Unavailable", "allocation"); break; + case PURE_CLOSED: fail(c, 503, "Service Unavailable", "stopped"); break; + case PURE_DENIED: fail(c, 403, "Forbidden", "denied"); 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; + } +} + +static void run_request(Connection *c) { + if (c->put && !pure_runtime_ready(c->ticket)) abort(); + PureOutcome result; + while (!pure_runtime_result(c->ticket, &result)) + if (!pure_runtime_step(&c->server->runtime)) abort(); + c->body = NULL; /* Core now owns input until all consumers release it. */ + answer(c, result); +} + +/* 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; } + PureResult admitted = pure_runtime_accept(&c->server->runtime, + c->put ? PURE_INPUT_WRITE : PURE_INPUT_READ, c->key, c->expected, c->body_size, &c->ticket); + if (admitted != PURE_OK) { answer(c, (PureOutcome){.status = admitted}); return; } + if (!c->put) { run_request(c); return; } + c->body = pure_runtime_ingress(c->ticket, NULL); + 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\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"write\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"reply_input\":%s,\"view_definitions\":%zu,\"range_definitions\":%zu," + "\"runtime_view_steps\":%zu,\"write_step\":%zu,\"reply_step\":%zu,\"prepared_program_bytes\":%zu,\"steps\":[", + p->read.to_end ? "true" : "false", p->read.offset, p->read.length, p->read_required, + p->write.to_end ? "true" : "false", p->write.offset, p->write.length, p->input_required, + p->reply_input ? "true" : "false", p->view_definitions, p->range_definitions, + p->input_steps, p->write_step, p->reply_step, sizeof(*p)); + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + printf("%s{\"source_step\":%zu,\"source\":{\"offset\":%zu,\"length\":%zu,\"to_end\":%s}," + "\"offset\":{\"value\":%zu,\"from_u32le\":%s},\"length\":{\"value\":%zu,\"from_u32le\":%s}}", + i ? "," : "", step->source_step, step->source.offset, step->source.length, + step->source.to_end ? "true" : "false", step->offset.value, step->offset.from_u32le ? "true" : "false", + step->length.value, step->length.from_u32le ? "true" : "false"); + } + puts("]}"); + return 0; + } + if (pure_store_init(server.path) != PURE_OK) { fputs("Cannot initialize byte store directory\n", stderr); return 1; } + if (!pure_runtime_init(&server.runtime, server.work, SLOTS, &server.program, + pure_store_host(server.path), server.budget)) 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); + pure_runtime_stop(&server.runtime); + 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.runtime.retained, server.runtime.peak_retained, server.budget, server.peak_active, + server.accepted, server.runtime.commits, server.runtime.gets, server.runtime.conflicts, server.runtime.budget_rejections, + server.expired, server.runtime.releases, server.prefix_copy_bytes, server.runtime.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-execution-core/sources/prototypes/connections/store-port.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store-port.c new file mode 100644 index 0000000..06ff114 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store-port.c @@ -0,0 +1,39 @@ +#include "store-port.h" +#include "store.h" +#include <stdlib.h> +#include <sys/mman.h> + +static void free_memory(void *data, size_t size) { (void)size; free(data); } +static void unmap_memory(void *data, size_t size) { if (size) munmap(data, size); } + +static bool allocate(void *context, size_t size, PureResource *out) { + (void)context; + void *data = malloc(size); + if (!data) return false; + *out = (PureResource){.body = {data, size}, .allocation = data, + .allocation_size = size, .release = free_memory}; + return true; +} + +static PureResult read_value(void *context, const char *key, PureRegion region, + size_t minimum, size_t available, PureResource *out, int64_t *revision) { + PureStoredView view = {0}; + PureResult result = pure_store_select(context, key, region.offset, region.length, + region.to_end, minimum, available, &view); + if (result == PURE_OK) { + *out = (PureResource){.body = view.body, .allocation = view.mapping, + .allocation_size = view.mapped_size, .release = view.mapped_size ? unmap_memory : NULL}; + *revision = view.revision; + } + return result; +} + +static PureResult write_value(void *context, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + return pure_store_put(context, key, expected, body, revision); +} + +PureHost pure_store_host(const char *path) { + return (PureHost){.context = (void *)path, .allocate = allocate, + .read = read_value, .write = write_value}; +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store-port.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store-port.h new file mode 100644 index 0000000..4c0d49d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store-port.h @@ -0,0 +1,6 @@ +#ifndef PURE_STORE_PORT_H +#define PURE_STORE_PORT_H +#include "runtime.h" +/* The path remains alive for the domain's complete lifetime, including pins. */ +PureHost pure_store_host(const char *path); +#endif diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store.c new file mode 100644 index 0000000..1c13a80 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store.c @@ -0,0 +1,228 @@ +#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 (offset > size) { result = PURE_LIMIT; goto finish; } + if (whole) length = size - offset; + 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-execution-core/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store.h new file mode 100644 index 0000000..1c0e054 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/store.h @@ -0,0 +1,37 @@ +#ifndef PURE_STORE_H +#define PURE_STORE_H +#include "flow.h" +#include "result.h" +#include <stdint.h> + +#define PURE_OBJECT_LIMIT ((2u * 6u + 32u) * 32768u + 8192u) +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. + * whole selects the suffix starting at offset; otherwise length is exact. */ +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-execution-core/sources/prototypes/connections/test-fields.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-fields.c new file mode 100644 index 0000000..63dfc3e --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-fields.c @@ -0,0 +1,153 @@ +#include "program.h" +#include <stdint.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 const char layout[] = "layout Envelope { marker: bytes[1]; start: u32le; count: u32le; text: bytes; }"; +static PureProgram parse(const char *source) { + PureProgram p; + char error[160]; + if (!pure_program_parse(source, strlen(source), &p, error, sizeof(error))) { + fprintf(stderr, "%s\n", error); exit(1); + } + return p; +} +static void invalid(const char *source) { + PureProgram p; + memset(&p, 0x5a, sizeof(p)); + unsigned char before[sizeof(p)]; + memcpy(before, &p, sizeof(p)); + char error[160]; + CHECK(!pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(*error && !memcmp(before, &p, sizeof(p))); +} +static void encode(unsigned char *at, uint32_t value) { + for (unsigned i = 0; i < 4; ++i) at[i] = (unsigned char)(value >> (8 * i)); +} +int main(void) { + char source[8000]; + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let alias = input; let start = alias.start; let text = alias.text; " + "let selected = range(text, start, alias.count); let result = selected; " + "let saved = store.write(request.key, request.revision, result); reply(saved, selected); }", layout, get); + PureProgram p = parse(source); + CHECK(p.input_steps == 1 && p.input_required == 9 && p.write_step == 1 && p.reply_step == 1); + CHECK(p.steps[0].source.offset == 9 && p.steps[0].source.to_end); + CHECK(p.steps[0].offset.value == 1 && p.steps[0].offset.from_u32le); + CHECK(p.steps[0].length.value == 5 && p.steps[0].length.from_u32le); + unsigned char bytes[609]; + for (size_t i = 0; i < sizeof(bytes); ++i) bytes[i] = (unsigned char)(i * 13); + PureSpan stored, returned; + for (size_t n = 0; n < 9; ++n) { + CHECK(!pure_program_input(&p, (PureSpan){bytes, n}, &stored, &returned)); + CHECK(!stored.size && !returned.size); + } + const uint32_t offsets[] = {0, 1, 255, 257, 599, 600, 601, UINT32_MAX}; + const uint32_t counts[] = {0, 1, 2, 260, 600, UINT32_MAX}; + size_t comparisons = 0; + for (size_t i = 0; i < sizeof(offsets) / sizeof(*offsets); ++i) + for (size_t j = 0; j < sizeof(counts) / sizeof(*counts); ++j) { + uint32_t start = offsets[i], count = counts[j]; + encode(bytes + 1, start); + encode(bytes + 5, count); + bool expected = start <= 600 && count <= 600 - start; + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned) == expected); + if (expected) { + CHECK(stored.size == count && returned.size == count && stored.data == returned.data); + CHECK(!count || stored.data == bytes + 9 + start); + } else CHECK(!stored.size && !returned.size); + ++comparisons; + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let first = range(input.text, input.start, input.count); let second = range(first, 1, 2); " + "let saved = store.write(request.key, request.revision, second); reply(saved, second); }", layout, get); + p = parse(source); + CHECK(p.input_steps == 2 && p.steps[1].source_step == 1); + encode(bytes + 1, 3); encode(bytes + 5, 4); + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + CHECK(stored.size == 2 && stored.data == bytes + 13); + encode(bytes + 5, 2); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let unused = range(input.text, input.start, input.count); " + "let saved = store.write(request.key, request.revision, input.text); reply(saved); }", layout, get); + p = parse(source); + encode(bytes + 1, UINT32_MAX); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); " + "let input = view(Envelope, o.body); reply(o, input.text); } %s", layout, put); + p = parse(source); + CHECK(p.read.to_end && p.read.offset == 9 && p.read_required == 9 && !p.input_steps); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let selection = range(input.text, 2, 3); let saved = store.write(request.key, request.revision, selection); reply(saved); }", layout, get); + p = parse(source); + CHECK(!p.input_steps && p.write.offset == 11 && p.write.length == 3 && p.input_required == 14); + snprintf(source, sizeof(source), "layout Empty { body: bytes; } %s on put { let input = view(Empty, request.body); " + "let saved = store.write(request.key, request.revision, input.body); reply(saved, input.body); }", get); + p = parse(source); + CHECK(pure_program_input(&p, (PureSpan){0}, &stored, &returned) && !stored.size && !returned.size); + + const char *bad_layout[] = { + "layout E {}", "layout E { x: bytes; y: u32le; }", "layout E { x: u32le; x: bytes; }", + "layout E { x: bytes[1449985]; }", "layout E { x: bytes[1449984]; y: u32le; }", + "layout E { x: bytes[184467440737095516160]; }", "layout E { x: u64le; }", + "layout E { x: u32le; } layout E { y: bytes; }" + }; + for (size_t i = 0; i < sizeof(bad_layout) / sizeof(*bad_layout); ++i) { + snprintf(source, sizeof(source), "%s %s %s", bad_layout[i], get, put); invalid(source); + } + const char *bad_input[] = { + "let input = view(Missing, request.body);", + "let input = view(Envelope, request.body); let x = input.missing;", + "let input = view(Envelope, request.body); let x = range(input.text, input.text, 1);", + "let short = range(request.body, 0, 8); let input = view(Envelope, short);", + "let input = view(Envelope, request.body); let x = range(input.text, input.start, 20); let y = view(Envelope, x);" + }; + for (size_t i = 0; i < sizeof(bad_input) / sizeof(*bad_input); ++i) { + snprintf(source, sizeof(source), "%s %s on put { %s let saved = store.write(request.key, request.revision, request.body); reply(saved); }", layout, get, bad_input[i]); + invalid(source); + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let saved = store.write(request.key, request.revision, input.start); reply(saved); }", layout, get); + invalid(source); + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); let input = view(Envelope, o.body); " + "let x = range(input.text, input.start, input.count); reply(o, x); } %s", layout, put); + invalid(source); + for (size_t count = 8; count <= 9; ++count) { + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); ", layout, get); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let v%zu = range(input.text, input.start, input.count); ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let saved = store.write(request.key, request.revision, input.text); reply(saved); }"); + if (count == 8) CHECK(parse(source).input_steps == 8); else invalid(source); + } + for (size_t count = 4; count <= 5; ++count) { + source[0] = 0; + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "layout L%zu { body: bytes; } ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "%s %s", get, put); + if (count == 4) (void)parse(source); else invalid(source); + } + for (size_t count = 16; count <= 17; ++count) { + strcpy(source, "layout L { "); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "f%zu: u32le; ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "} %s %s", get, put); + if (count == 16) (void)parse(source); else invalid(source); + } + printf("{\"fields\":\"named-layouts-unaligned-u32le-tail-dynamic-ranges-aliases-failures-limits\"," + "\"data_boundary_cases\":%zu}\n", comparisons); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-flow.c new file mode 100644 index 0000000..5ff028d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/connections/test-program.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-program.c new file mode 100644 index 0000000..6a4a19d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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.to_end && plan.write.to_end && !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.to_end); + 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.to_end && !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-execution-core/sources/prototypes/connections/test-runtime.c b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-runtime.c new file mode 100644 index 0000000..7ce2290 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/connections/test-runtime.c @@ -0,0 +1,285 @@ +#define _POSIX_C_SOURCE 200809L +#include "runtime.h" +#include "store-port.h" +#include "store.h" +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static const PureProgram identity = { + .read = {.to_end = true}, .write = {.to_end = true}, + .reply = {.to_end = true}, .reply_input = true +}; +typedef struct { + size_t allocations, writes, frees, live; + bool allocation_failure, oversized_read, reenter; + PureRuntime *runtime; + unsigned char received[256]; + size_t received_size; +} Host; +typedef struct { Host *host; unsigned char bytes[]; } Allocation; + +static void free_input(void *pointer, size_t size) { + Allocation *a = pointer; + assert(size > 0 && a->host->live > 0); + ++a->host->frees; + --a->host->live; + free(a); +} + +static bool allocate(void *context, size_t size, PureResource *out) { + Host *host = context; + if (host->allocation_failure) return false; + Allocation *a = malloc(sizeof(*a) + size); + assert(a); + a->host = host; + ++host->allocations; + ++host->live; + *out = (PureResource){.body = {a->bytes, size}, .allocation = a, + .allocation_size = size, .release = free_input}; + return true; +} + +static PureResult write_value(void *context, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + Host *host = context; + (void)key; + if (host->reenter) { + PureTicket ignored; + assert(pure_runtime_accept(host->runtime, PURE_INPUT_WRITE, "reenter", 0, 0, &ignored) == PURE_INVALID); + assert(!pure_runtime_step(host->runtime)); + } + if (expected == 99) { *revision = 1; return PURE_CONFLICT; } + ++host->writes; + assert(body.size <= sizeof(host->received)); + if (body.size) memcpy(host->received, body.data, body.size); + host->received_size = body.size; + *revision = 1; + return PURE_OK; +} + +static PureResult read_value(void *context, const char *key, PureRegion range, + size_t minimum, size_t available, PureResource *out, int64_t *revision) { + Host *host = context; + (void)key; (void)range; (void)minimum; + if (!host->oversized_read) return PURE_NOT_FOUND; + assert(available < 1000); + assert(allocate(host, available + 1, out)); + *revision = 1; + return PURE_OK; /* Deliberately violates the available-credit contract. */ +} + +static PureHost port(Host *host) { + return (PureHost){host, allocate, read_value, write_value}; +} + +static PureTicket submit(PureRuntime *r, const char *key, const void *bytes, size_t length) { + PureTicket ticket; + assert(pure_runtime_accept(r, PURE_INPUT_WRITE, key, 0, length, &ticket) == PURE_OK); + size_t capacity; + unsigned char *input = pure_runtime_ingress(ticket, &capacity); + assert(capacity == length); + if (length) memcpy(input, bytes, length); + assert(pure_runtime_ready(ticket)); + assert(!pure_runtime_ready(ticket)); + return ticket; +} + +static PureOutcome completed(PureRuntime *r, PureTicket ticket) { + PureOutcome result; + while (!pure_runtime_result(ticket, &result)) assert(pure_runtime_step(r)); + return result; +} + +static void admission_and_lifecycle(void) { + Host host = {0}; + PureRuntime r; + PureWork work[3]; + assert(pure_runtime_init(&r, work, 3, &identity, port(&host), 5)); + host.runtime = &r; + PureTicket a, untouched = {0}, out = untouched; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "../bad", 0, 1, &out) == PURE_INVALID); + assert(!out.runtime && !host.allocations); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "a", 0, 4, &a) == PURE_OK); + assert(r.retained == 4 && !pure_runtime_step(&r) && !host.writes); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "b", 0, 2, &out) == PURE_BUDGET); + assert(!out.runtime && host.allocations == 1); + assert(pure_runtime_cancel(a)); + PureOutcome cancelled; + assert(pure_runtime_result(a, &cancelled) && cancelled.status == PURE_CANCELLED); + assert(r.retained == 0 && host.frees == 1 && !pure_runtime_cancel(a)); + assert(pure_runtime_release(a) && !pure_runtime_release(a)); + assert(!pure_runtime_result(a, &cancelled)); + + PureTicket first = submit(&r, "first", "1", 1); + PureTicket middle = submit(&r, "middle", "2", 1); + PureTicket last = submit(&r, "last", "3", 1); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "full", 0, 0, &out) == PURE_BUSY); + assert(pure_runtime_cancel(middle)); + assert(pure_runtime_release(middle)); + pure_runtime_stop(&r); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "closed", 0, 0, &out) == PURE_CLOSED); + assert(!pure_runtime_restart(&r, &identity, port(&host), 5)); + host.reenter = true; + assert(completed(&r, first).status == PURE_OK && host.received[0] == '1'); + assert(completed(&r, last).status == PURE_OK && host.received[0] == '3'); + assert(!pure_runtime_step(&r)); + assert(pure_runtime_release(first) && pure_runtime_release(last)); + assert(r.active == 0 && r.retained == 0 && host.live == 0); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + PureTicket next = submit(&r, "next", "4", 1); + assert(next.generation > last.generation && !pure_runtime_ready(a)); + assert(!pure_runtime_release(last)); + assert(pure_runtime_release(next)); /* queued input: no effect */ + assert(host.writes == 2 && !pure_runtime_step(&r)); + + host.allocation_failure = true; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "no_memory", 0, 1, &out) == PURE_ALLOCATION); + assert(r.active == 0 && r.retained == 0); + host.allocation_failure = false; + PureHost denied = port(&host); + denied.write = NULL; + assert(pure_runtime_restart(&r, &identity, denied, 5)); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "denied", 0, 1, &out) == PURE_DENIED); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + host.oversized_read = true; + assert(pure_runtime_accept(&r, PURE_INPUT_READ, "bad_host", 0, 0, &out) == PURE_OK); + assert(completed(&r, out).status == PURE_BUDGET && host.live == 0 && r.retained == 0); + assert(pure_runtime_release(out)); + r.serial = UINT64_MAX; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "exhausted", 0, 0, &out) == PURE_LIMIT); +} + +static void forwarding(void) { + Host a = {0}, b = {0}, c = {0}; + PureRuntime source, left, right; + PureWork source_work[1], left_work[1], right_work[1]; + assert(pure_runtime_init(&source, source_work, 1, &identity, port(&a), 8)); + assert(pure_runtime_init(&left, left_work, 1, &identity, port(&b), 2)); + assert(pure_runtime_init(&right, right_work, 1, &identity, port(&c), 8)); + PureTicket original = submit(&source, "original", "value", 5), x = {0}, y; + PureOutcome result = completed(&source, original); + assert(result.status == PURE_OK); + const unsigned char *backing = result.body.data; + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_BUDGET); + assert(!x.runtime && source.work[0].pins == 0); + assert(pure_runtime_restart(&left, &identity, port(&b), 8)); + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_OK); + assert(pure_runtime_forward(original, &right, "right", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(!pure_runtime_result(original, &result)); + pure_runtime_stop(&source); + assert(!pure_runtime_restart(&source, &identity, port(&a), 8)); + assert(a.live == 1 && !b.allocations && !c.allocations); + assert(pure_runtime_cancel(x) && pure_runtime_release(x)); + assert(a.live == 1 && !b.writes); + result = completed(&right, y); + assert(result.body.data == backing && result.body.size == 5 && !memcmp(result.body.data, "value", 5)); + assert(c.writes == 1 && !c.allocations && source.retained == 5); + assert(!pure_runtime_cancel(y)); /* effects cannot be rolled back */ + assert(pure_runtime_release(y)); + assert(a.frees == 1 && !source.active && !source.retained && !right.active && !right.retained); + assert(pure_runtime_restart(&source, &identity, port(&a), 8)); + + /* A completed consumer does not retain its input when the program has no + * input-backed reply. The producing domain can then be reclaimed early. */ + PureProgram no_reply = identity; + no_reply.reply_input = false; + assert(pure_runtime_restart(&right, &no_reply, port(&c), 8)); + original = submit(&source, "again", "last", 4); + assert(completed(&source, original).status == PURE_OK); + assert(pure_runtime_forward(original, &right, "no_reply", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(completed(&right, y).body.size == 0 && a.live == 0); + assert(source.active == 0 && right.active == 1 && right.retained == 0); + assert(pure_runtime_release(y)); +} + +static void long_chain(void) { + enum { COUNT = 1024 }; + PureWork *work = calloc(COUNT, sizeof(*work)); + assert(work); + Host host = {0}; + PureRuntime r; + assert(pure_runtime_init(&r, work, COUNT, &identity, port(&host), COUNT)); + PureTicket ticket = submit(&r, "chain", "x", 1); + for (size_t i = 1; i < COUNT; ++i) { + assert(completed(&r, ticket).status == PURE_OK); + PureTicket next; + assert(pure_runtime_forward(ticket, &r, "chain", 0, &next) == PURE_OK); + assert(pure_runtime_release(ticket)); + ticket = next; + } + assert(completed(&r, ticket).status == PURE_OK); + assert(host.allocations == 1 && r.active == COUNT); + assert(pure_runtime_release(ticket)); + assert(r.retained == 0 && r.active == 0 && host.frees == 1); + free(work); +} + +static void actual_storage(void) { + char directory[] = "/tmp/pure-runtime-check-XXXXXX"; + assert(mkdtemp(directory)); + char source_path[128], target_path[128], name[160]; + snprintf(source_path, sizeof(source_path), "%s/source", directory); + snprintf(target_path, sizeof(target_path), "%s/target", directory); + assert(pure_store_init(source_path) == PURE_OK && pure_store_init(target_path) == PURE_OK); + PureProgram selection; + char error[160]; + assert(pure_program_load("examples/selection.pure", &selection, error, sizeof(error))); + PureRuntime source, target; + PureWork a[2], b[2]; + assert(pure_runtime_init(&source, a, 2, &selection, pure_store_host(source_path), 128)); + assert(pure_runtime_init(&target, b, 2, &identity, pure_store_host(target_path), 128)); + const unsigned char bytes[] = {1,0,0,0, 3,0,0,0, 'a','b','c','d','e'}; + PureTicket ticket = submit(&source, "excerpt", bytes, sizeof(bytes)), consumer; + PureOutcome result = completed(&source, ticket); + assert(result.status == PURE_OK && result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_forward(ticket, &target, "archive", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + assert(source.active == 1 && source.retained == sizeof(bytes)); + assert(completed(&target, consumer).status == PURE_OK); + assert(pure_runtime_release(consumer) && source.active == 0); + PureStoredView stored = {0}; + assert(pure_store_get(target_path, "archive", 128, &stored) == PURE_OK); + assert(stored.body.size == 3 && !memcmp(stored.body.data, "bcd", 3)); + pure_store_release(&stored); + + assert(pure_runtime_accept(&source, PURE_INPUT_READ, "excerpt", 0, 0, &ticket) == PURE_OK); + assert(completed(&source, ticket).status == PURE_OK); + assert(pure_runtime_forward(ticket, &target, "snapshot", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + int64_t revision; + assert(pure_store_put(source_path, "excerpt", 1, (PureSpan){(const unsigned char *)"new", 3}, &revision) == PURE_OK); + result = completed(&target, consumer); + assert(result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_release(consumer)); + + unsigned char invalid[] = {255,255,255,255, 1,0,0,0, 'x'}; + ticket = submit(&source, "invalid", invalid, sizeof(invalid)); + assert(completed(&source, ticket).status == PURE_LIMIT); + assert(pure_runtime_release(ticket)); + assert(pure_store_get(source_path, "invalid", 128, &stored) == PURE_NOT_FOUND); + assert(source.active == 0 && target.active == 0 && source.retained == 0 && target.retained == 0); + const char *files[] = {"source/obj-excerpt", "source/.writer.lock", "target/obj-archive", + "target/obj-snapshot", "target/.writer.lock"}; + for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); ++i) { + snprintf(name, sizeof(name), "%s/%s", directory, files[i]); + assert(unlink(name) == 0); + } + assert(rmdir(source_path) == 0 && rmdir(target_path) == 0 && rmdir(directory) == 0); +} + +int main(void) { + admission_and_lifecycle(); + forwarding(); + long_chain(); + actual_storage(); + printf("{\"runtime_tests\":\"passed\",\"domain_bytes\":%zu,\"work_slot_bytes\":%zu," + "\"ticket_bytes\":%zu,\"prepared_program_bytes\":%zu,\"forwarding_chain\":1024," + "\"native_storage_pipeline\":true}\n", sizeof(PureRuntime), sizeof(PureWork), + sizeof(PureTicket), sizeof(PureProgram)); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/cli.mjs new file mode 100644 index 0000000..b67f4d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/examples/direct.pure new file mode 100644 index 0000000..b29721c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/remote.mjs new file mode 100644 index 0000000..6ee6a59 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/compiler.mjs new file mode 100644 index 0000000..6aec335 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/core.wasm Binary files differnew file mode 100755 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/core.wasm diff --git a/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/project.mjs new file mode 100644 index 0000000..861e0fc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-execution-core/sources/prototypes/workbench/web/runtime.mjs new file mode 100644 index 0000000..d8d6618 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/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-execution-core/verification.json b/prototypes/connections/results/2026-09-23-execution-core/verification.json new file mode 100644 index 0000000..ad430f8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-execution-core/verification.json @@ -0,0 +1,30 @@ +{ + "sources_verified_and_frozen": 30, + "native_and_sanitized_binaries_match": true, + "tcp_scenarios_per_build": 28, + "all_http_runs_drained": true, + "core_archive_external_symbols": [ + "memcpy", + "strlen" + ], + "core_archive_sha256": "59eccbea380378a6650c59a99eacd92132184e5b9aa64feada7a176cf03ccfcc", + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 4892\t 0\t 0\t 4892\t 131c\t/home/eunhhu/work/pure/prototypes/connections/build/runtime.o\n 780\t 0\t 0\t 780\t 30c\t/home/eunhhu/work/pure/prototypes/connections/build/evaluate.o\n 41144\t 1048\t 16\t 42208\t a4e0\t/home/eunhhu/work/pure/prototypes/connections/build/pure-connections\n", + "compiler": "cc (Debian 14.2.0-19) 14.2.0", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "structure_sizes": { + "runtime_tests": "passed", + "domain_bytes": 192, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 648, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "limits": [ + "synchronous storage effects", + "one owner thread", + "storage authoring profile only", + "no TLS or native UI yet", + "sizes are not process RSS or performance comparisons" + ] +} diff --git a/prototypes/connections/results/2026-09-23-field-connections/README.md b/prototypes/connections/results/2026-09-23-field-connections/README.md new file mode 100644 index 0000000..d516556 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/README.md @@ -0,0 +1,31 @@ +# 021 검증 자료 + +이름 기반 필드와 실제 입력 값으로 계산하는 구간을 네이티브 저장·응답에 연결한 기록이다. +시간·처리량·전체 RSS 비교는 하지 않았다. + +- [native.json](native.json): 일반 빌드의 28개 통합 시나리오, 준비 결과, 자원 회수, 소스·실행 파일 해시. +- [sanitized.json](sanitized.json): ASan·UBSan 빌드의 같은 시나리오. 누수 검사를 끄지 않았다. +- [verification.json](verification.json): 명령, 세 단위 검사 결과, 소스 사본·실행 파일 일치 확인. +- `sources/`: 기록된 소스·예제·클라이언트·Wasm 사본. 이후 작업 트리가 바뀌면 별도 트리에 배치해 재현한다. + +기존 22개 검사에 6개 필드 시나리오를 추가했다. 실제 예제 클라이언트가 한글·이모지 문서의 +문자 위치를 바이트 위치로 바꿔 전송하고, 네이티브 서버가 필드 값에 따라 선택·저장·응답한다. +잘린 헤더·잘못된 범위·정렬되지 않은 숫자·바뀐 필드 순서·빈 선택·사용하지 않는 계산의 오류를 +확인한다. 별칭은 추가 동적 단계를 만들지 않으며, 느린 응답 동안 원본 수신 할당을 유지·계상한다. + +`field_connections`에는 선언에서 준비한 실제 접근 위치·데이터 의존 단계가 있다. `steps`의 +source_step 0은 입력의 정적 구간이고 양수는 앞 단계의 결과다. offset/length의 from_u32le가 +참이면 value는 입력에서 숫자를 읽을 위치이며, 거짓이면 상수다. write_step/reply_step이 양수인 +결과는 정적 offset/length 대신 해당 단계에서 정한다. 진단의 이전 whole 표기는 to_end로 바뀌었다. + +`runtime_view_steps`는 준비된 단계 수로 실행 시간 계측값이 아니다. `prepared_program_bytes` +648 B는 고정 구조체 하나의 크기이며 사용하지 않는 동적 단계 자리도 포함한다. 기존 112 B보다 +536 B 늘어난 기능 비용이다. 논리적 본문 예산에는 OS·페이지·클라이언트 메모리가 포함되지 않는다. + +`test-flow`는 기존 저장·전송 의미를, `test-program`은 상수 구간 준비 의미를, +`test-fields`는 필드 타입·배치·숫자·동적 범위·한도를 검사한다. 일반/검사 빌드 모두 통과했다. + +[구현 README](../../README.md)의 `make check`로 재현한다. `EVIDENCE=/새/경로.json`으로 +새 원자료를 남기며 기존 파일을 덮어쓰지 않는다. 이 환경에서는 소켓·자식 프로세스· +LeakSanitizer의 샌드박스 제한 때문에 승인된 환경에서 로컬 검사를 실행했다. +외부 서비스·macOS·실제 전원 장애·대규모 서비스는 검증하지 않았다. diff --git a/prototypes/connections/results/2026-09-23-field-connections/native.json b/prototypes/connections/results/2026-09-23-field-connections/native.json new file mode 100644 index 0000000..2d8ff24 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/native.json @@ -0,0 +1,488 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ] + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ] + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 18, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 39824 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "d478daf835c30152d05aeba6d2c07dd598166129f18cc5f7cb3b893966b60a18", + "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": "b7e7d6806cf9785df1d01f2418d1e493eaf2d4b9cf43abc90082c60bd61dcd0b", + "prototypes/connections/store.h": "03e23e349a19ffc3491d1bb9e4200b834cedf84a3f026613470a37a727ded11e", + "prototypes/connections/program.c": "54df0c5cd4579f64b2f32b965c520e2a4f36794cd3dece944d062454dc01df63", + "prototypes/connections/program.h": "2c3c955967883a9a8ea4566a006eccd44f3a173ac0682c2f272d09700f209639", + "prototypes/connections/server.c": "b905fa656fa626111ba765d6379aa03bd32fee97d19d48461cd752e75918ea31", + "prototypes/connections/check.py": "bf890ee92009fd57afcb6708ad325af360bf43a7c667c79c5dc90631889f5a3f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "8c0353ae6643351ba69505de4092aa40e34e5318f27d670de944cd875c4d51cc", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-field-connections/sanitized.json b/prototypes/connections/results/2026-09-23-field-connections/sanitized.json new file mode 100644 index 0000000..1fbb30f --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sanitized.json @@ -0,0 +1,488 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ] + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 648, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ] + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 648, + "steps": [] + } + }, + "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": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "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": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 18, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 39824 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 39824 + } + ], + "storage": "immutable-byte-extents-v1", + "binary_sha256": "be8a599f33d340e04f9269de9cc9b66d817aea567ca9a26a6a26258c2011ed91", + "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": "b7e7d6806cf9785df1d01f2418d1e493eaf2d4b9cf43abc90082c60bd61dcd0b", + "prototypes/connections/store.h": "03e23e349a19ffc3491d1bb9e4200b834cedf84a3f026613470a37a727ded11e", + "prototypes/connections/program.c": "54df0c5cd4579f64b2f32b965c520e2a4f36794cd3dece944d062454dc01df63", + "prototypes/connections/program.h": "2c3c955967883a9a8ea4566a006eccd44f3a173ac0682c2f272d09700f209639", + "prototypes/connections/server.c": "b905fa656fa626111ba765d6379aa03bd32fee97d19d48461cd752e75918ea31", + "prototypes/connections/check.py": "bf890ee92009fd57afcb6708ad325af360bf43a7c667c79c5dc90631889f5a3f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "8c0353ae6643351ba69505de4092aa40e34e5318f27d670de944cd875c4d51cc", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "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-field-connections/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/Makefile new file mode 100644 index 0000000..110acab --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/Makefile @@ -0,0 +1,28 @@ +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 $@ + +$(BUILD)/test-fields: test-fields.c program.c program.h store.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-fields.c program.c $(LDFLAGS) $(LDLIBS) -o $@ + +check: $(BUILD)/test-flow $(BUILD)/test-program $(BUILD)/test-fields $(BUILD)/pure-connections + $(BUILD)/test-flow + $(BUILD)/test-program + $(BUILD)/test-fields + python3 check.py --server $(BUILD)/pure-connections $(if $(EVIDENCE),--output "$(EVIDENCE)") diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/check.py new file mode 100644 index 0000000..5c6a463 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/check.py @@ -0,0 +1,528 @@ +#!/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 sys +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) + fields = field_connections(binary, directory, storage, records, checks) + return {'kind': 'prepared-connection-correctness-not-performance', 'checks': checks, + 'prepared_connections': prepared, + 'field_connections': fields, + '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'] == { + 'to_end': 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 field_connections(binary, directory, storage, records, checks): + program = ROOT / 'examples/selection.pure' + plans = {'selection': json.loads(subprocess.check_output( + [str(binary), '--check-program', str(program)], text=True))} + assert plans['selection']['runtime_view_steps'] == 1 + assert plans['selection']['steps'][0] == { + 'source_step': 0, 'source': {'offset': 8, 'length': 0, 'to_end': True}, + 'offset': {'value': 0, 'from_u32le': True}, 'length': {'value': 4, 'from_u32le': True}} + document = '처음🙂 Pure는 필드를 연결한다.\n끝' + document_file = directory / 'document.txt' + document_file.write_bytes(document.encode('utf-8')) + with serving(binary, storage, program=program) as selected: + command = [sys.executable, str(ROOT / 'examples/select-text.py'), str(document_file), + '--key', 'text_selection', '--start', '2', '--end', '12', '--url', f'http://127.0.0.1:{selected.port}'] + result = subprocess.run(command, capture_output=True, text=True, timeout=15) + assert result.returncode == 0, (result.stdout, result.stderr) + expected = document[2:12].encode('utf-8') + assert json.loads(result.stdout) == {'revision': '1', 'selected': document[2:12], 'stored_bytes': len(expected)} + assert rpc(selected, 'GET', 'text_selection')[2] == expected + checks.append('text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes') + + # Validation precedes the write: even a stale header cannot expose or + # commit partial input, and prior content/revision survive all failures. + invalid = [b'\0' * n for n in range(8)] + [ + struct.pack('<II', 0xffffffff, 0) + b'abc', + struct.pack('<II', 0, 0xffffffff) + b'abc', + struct.pack('<II', 2, 2) + b'abc', + struct.pack('<II', 4, 0) + b'abc'] + for payload in invalid: + assert rpc(selected, 'PUT', 'text_selection', payload, 1)[0] == 413 + status, headers, body = rpc(selected, 'GET', 'text_selection') + assert status == 200 and headers['ETag'] == '"1"' and body == expected + for key, payload in [('selection_empty', struct.pack('<II', 0, 0)), + ('selection_end', struct.pack('<II', 3, 0) + b'abc')]: + assert rpc(selected, 'PUT', key, payload, 0)[0] == 200 + assert rpc(selected, 'GET', key)[2] == b'' + assert rpc(selected, 'PUT', 'text_selection', struct.pack('<II', 0, 3) + b'abc', 0)[0] == 412 + checks.append('truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid') + records.append(selected.stats) + + declaration = 'layout E { tag: bytes[3]; count: u32le; start: u32le; document: bytes; } ' + 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); } ' + reordered = directory / 'reordered.pure' + reordered.write_text(declaration + get + 'on put { let e = view(E, request.body); let alias = e; ' + 'let part = range(alias.document, alias.start, alias.count); let again = part; ' + 'let s = store.write(request.key, request.revision, again); reply(s, part); }') + plans['reordered'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(reordered)], text=True)) + assert plans['reordered']['runtime_view_steps'] == 1 + with serving(binary, storage, program=reordered) as moved: + status, headers, body = rpc(moved, 'PUT', 'reordered', b'tag' + struct.pack('<II', 4, 2) + b'abcdefgh', 0) + assert status == 200 and body == b'cdef' and headers['ETag'] == '"1"' + assert rpc(moved, 'GET', 'reordered')[2] == b'cdef' + records.append(moved.stats) + checks.append('changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps') + + unused = directory / 'unused-dynamic.pure' + unused.write_text(declaration + get + 'on put { let e = view(E, request.body); ' + 'let unused = range(e.document, e.start, e.count); ' + 'let s = store.write(request.key, request.revision, e.document); reply(s); }') + with serving(binary, storage, program=unused) as guarded: + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 4, 2) + b'abc', 0)[0] == 413 + assert rpc(guarded, 'GET', 'unused_dynamic')[0] == 404 + assert rpc(guarded, 'PUT', 'unused_dynamic', b'tag' + struct.pack('<II', 1, 1) + b'abc', 0)[0] == 200 + assert rpc(guarded, 'GET', 'unused_dynamic')[2] == b'abc' + records.append(guarded.stats) + checks.append('unused dynamic field selection retains its validation before the write effect') + + field_get = directory / 'field-get.pure' + field_get.write_text(declaration + 'on get { let o = store.read(request.key); ' + 'let e = view(E, o.body); reply(o, e.document); } ' + put) + plans['field_get'] = json.loads(subprocess.check_output([str(binary), '--check-program', str(field_get)], text=True)) + tail = '다른 길이의 문서'.encode('utf-8') + with serving(binary, storage, program=field_get) as writer: + assert rpc(writer, 'PUT', 'tail', b'tag' + struct.pack('<II', 0, 0) + tail, 0)[0] == 200 + assert rpc(writer, 'PUT', 'bad_layout', b'short', 0)[0] == 200 + assert rpc(writer, 'PUT', 'empty_tail', b'tag' + struct.pack('<II', 0, 0), 0)[0] == 200 + records.append(writer.stats) + with serving(binary, storage, budget=len(tail), program=field_get) as reader: + assert rpc(reader, 'GET', 'tail')[2] == tail + assert rpc(reader, 'GET', 'empty_tail')[2] == b'' + assert rpc(reader, 'GET', 'bad_layout')[0] == 413 + assert reader.stats['peak_retained_payload_bytes'] == len(tail) + records.append(reader.stats) + checks.append('named variable tail reads use the same file version and charge only the selected suffix') + + document_bytes = bytes(range(256)) * 4096 + payload = struct.pack('<II', 16, len(document_bytes) - 32) + document_bytes + with serving(binary, storage, budget=len(payload), program=program) as slow: + with socket.create_connection(('127.0.0.1', slow.port), timeout=8) as sock: + sock.sendall(header('PUT', 'dynamic_slow', len(payload), b'If-None-Match: *\r\n') + payload) + response = http.client.HTTPResponse(sock) + response.begin() + assert response.status == 200 and response.read(1) == document_bytes[16:17] + assert rpc(slow, 'GET', 'binary')[0] == 503 + assert response.read() == document_bytes[17:-16] + response.close() + assert rpc(slow, 'GET', 'binary')[2] == b'new' + assert rpc(slow, 'GET', 'dynamic_slow')[2] == document_bytes[16:-16] + assert slow.stats['peak_retained_payload_bytes'] == len(payload) + records.append(slow.stats) + checks.append('data-dependent response keeps and charges the original input through partial transmission') + return plans + + +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', 'test-fields.c', 'Makefile', + 'examples/storage.pure', 'examples/excerpt.pure', 'examples/selection.pure', 'examples/select-text.py')), + *(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-field-connections/sources/prototypes/connections/examples/excerpt.pure b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/excerpt.pure new file mode 100644 index 0000000..622f9bb --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/connections/examples/select-text.py b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/select-text.py new file mode 100644 index 0000000..ffaf80b --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/select-text.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Save a UTF-8 document selection through selection.pure (character indices).""" +import argparse +import http.client +import json +from pathlib import Path +import re +import struct +from urllib.parse import urlsplit + +LIMIT = (2 * 6 + 32) * 32768 + 8192 + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('file', type=Path) + parser.add_argument('--key', required=True) + parser.add_argument('--start', type=int, required=True, help='first Unicode code point, starting at 0') + parser.add_argument('--end', type=int, required=True, help='exclusive Unicode code point boundary') + parser.add_argument('--revision', type=int, default=0, help='0 creates; otherwise expected stored revision') + parser.add_argument('--url', default='http://127.0.0.1:8767') + args = parser.parse_args() + url = urlsplit(args.url) + try: + port = url.port or 80 + except ValueError as error: + parser.error(str(error)) + if (url.scheme != 'http' or url.hostname not in ['localhost', '127.0.0.1'] or + url.username is not None or url.password is not None or url.path not in ['', '/'] or + url.query or url.fragment): + parser.error('use a local HTTP server origin') + if not re.fullmatch(r'[A-Za-z0-9_-]{1,64}', args.key) or not 0 <= args.revision <= 2**63 - 1: + parser.error('invalid object key or revision') + try: + with args.file.open('rb') as source: + document = source.read(LIMIT - 8 + 1) + if len(document) > LIMIT - 8: + parser.error('document exceeds the prototype request limit') + text = document.decode('utf-8') + except (OSError, UnicodeError) as error: + parser.error(str(error)) + if not 0 <= args.start <= args.end <= len(text): + parser.error('selection is outside the document') + start = len(text[:args.start].encode('utf-8')) + selected = text[args.start:args.end].encode('utf-8') + # The boundary layout is explicit; storage receives only selected bytes. + payload = struct.pack('<II', start, len(selected)) + document + headers = {'Content-Type': 'application/octet-stream'} + headers['If-None-Match' if args.revision == 0 else 'If-Match'] = ( + '*' if args.revision == 0 else f'"{args.revision}"') + connection = http.client.HTTPConnection(url.hostname, port, timeout=12) + try: + connection.request('PUT', '/objects/' + args.key, payload, headers) + response = connection.getresponse() + body = response.read(LIMIT + 1) + if response.status != 200: + parser.error(f'storage response {response.status}: {response.getheader("X-Pure-Error")}') + if body != selected: + parser.error('response differs from the selection; check the installed program (write may have occurred)') + revision = response.getheader('ETag', '') + if not re.fullmatch(r'"[1-9][0-9]*"', revision) or int(revision[1:-1]) > 2**63 - 1: + parser.error('response has no valid stored revision (write may have occurred)') + except (OSError, http.client.HTTPException) as error: + parser.error(f'{error}; storage outcome is unknown if the request was sent') + finally: + connection.close() + print(json.dumps({'revision': revision[1:-1], 'selected': selected.decode('utf-8'), + 'stored_bytes': len(selected)}, ensure_ascii=False)) + + +if __name__ == '__main__': + main() diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/selection.pure b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/selection.pure new file mode 100644 index 0000000..7d9d4ec --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/selection.pure @@ -0,0 +1,19 @@ +// This is an input boundary layout, not a required storage or RAM model. +// The client sends two little-endian u32 values followed by document bytes. +layout Selection { + start: u32le; + count: u32le; + document: bytes; +} + +on get { + let saved = store.read(request.key); + reply(saved, saved.body); +} + +on put { + let input = view(Selection, request.body); + let selected = range(input.document, input.start, input.count); + let saved = store.write(request.key, request.revision, selected); + reply(saved, selected); +} diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/storage.pure b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/examples/storage.pure new file mode 100644 index 0000000..ff54ea3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/flow.c new file mode 100644 index 0000000..1070c70 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/flow.h new file mode 100644 index 0000000..6e27c58 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/connections/program.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/program.c new file mode 100644 index 0000000..a01bb7c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/program.c @@ -0,0 +1,402 @@ +#include "program.h" +#include "store.h" +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +enum { NAME = 256, NUMBER, SYMBOLS = 32, LAYOUTS = 4, FIELDS = 16 }; +typedef enum { BYTE_VALUE, RESULT_VALUE, LAYOUT_VALUE, INDEX_VALUE } ValueKind; +typedef struct { + char name[32]; + ValueKind kind; + PureRegion region; + size_t step, layout; + PureIndex index; +} Symbol; +typedef struct { + char name[32]; + size_t offset, length; + bool number, tail; +} Field; +typedef struct { + char name[32]; + Field fields[FIELDS]; + size_t count, prefix; + bool tail; +} Layout; +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, layout_count; + Layout layouts[LAYOUTS]; + 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 require_bytes(Parser *p, const Symbol *value) { + return value->kind == BYTE_VALUE || reject(p, "expected bytes; select a byte field"); +} + +static void require_length(Parser *p, size_t required) { + size_t *minimum = p->put ? &p->program.input_required : &p->program.read_required; + if (required > *minimum) *minimum = required; +} + +static Layout *find_layout(Parser *p, const char *name) { + for (size_t i = 0; i < p->layout_count; ++i) + if (!strcmp(name, p->layouts[i].name)) return &p->layouts[i]; + return NULL; +} + +static bool layout_definition(Parser *p) { + if (p->layout_count == LAYOUTS) return reject(p, "program exceeds 4 layouts"); + if (!next(p)) return false; + if (p->token != NAME || find_layout(p, p->name)) return reject(p, "expected a unique layout name"); + Layout *layout = &p->layouts[p->layout_count]; + strcpy(layout->name, p->name); + if (!next(p) || !token(p, '{')) return false; + while (p->token != '}') { + if (layout->count == FIELDS) return reject(p, "layout exceeds 16 fields"); + if (layout->tail) return reject(p, "variable bytes must be the last field"); + if (p->token != NAME) return reject(p, "expected a field name"); + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(layout->fields[i].name, p->name)) return reject(p, "duplicate field"); + Field *f = &layout->fields[layout->count]; + strcpy(f->name, p->name); + f->offset = layout->prefix; + if (!next(p) || !token(p, ':')) return false; + if (is_word(p, "u32le")) { + f->number = true; + f->length = 4; + if (!next(p)) return false; + } else if (is_word(p, "bytes")) { + if (!next(p)) return false; + if (p->token == '[') { + if (!next(p)) return false; + if (p->token != NUMBER) return reject(p, "fixed bytes requires a constant size"); + f->length = p->number; + if (!next(p) || !token(p, ']')) return false; + } else { f->tail = true; layout->tail = true; } + } else return reject(p, "supported field types: u32le, bytes[N], bytes"); + if (f->length > PURE_OBJECT_LIMIT - layout->prefix) return reject(p, "layout exceeds object bound"); + layout->prefix += f->length; + ++layout->count; + if (!token(p, ';')) return false; + } + if (!layout->count) return reject(p, "empty layout"); + ++p->layout_count; + return token(p, '}'); +} + +static bool reference(Parser *p, Symbol *out) { + if (is_word(p, "request")) { + if (!p->put) return reject(p, "request.body is only available in on put"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return field(p, "request", "body"); + } + Symbol *symbol = p->token == NAME ? find(p, p->name) : NULL; + if (!symbol) return reject(p, "unknown value"); + *out = *symbol; + if (!next(p)) return false; + if (p->token != '.') return true; + if (!next(p)) return false; + if (symbol->kind == RESULT_VALUE) { + if (p->put) return reject(p, "write result has no body; use the input value"); + *out = (Symbol){.kind = BYTE_VALUE, .region = {.to_end = true}}; + return word(p, "body"); + } + if (symbol->kind != LAYOUT_VALUE || p->token != NAME) return reject(p, "expected a layout field"); + Layout *layout = &p->layouts[symbol->layout]; + Field *f = NULL; + for (size_t i = 0; i < layout->count; ++i) + if (!strcmp(p->name, layout->fields[i].name)) f = &layout->fields[i]; + if (!f) return reject(p, "unknown layout field"); + size_t offset = symbol->region.offset + f->offset; + *out = (Symbol){.kind = f->number ? INDEX_VALUE : BYTE_VALUE}; + if (f->number) out->index = (PureIndex){.value = offset, .from_u32le = true}; + else out->region = (PureRegion){.offset = offset, + .length = f->tail ? (symbol->region.to_end ? 0 : symbol->region.length - f->offset) : f->length, + .to_end = f->tail && symbol->region.to_end}; + return next(p); +} + +static bool byte_reference(Parser *p, Symbol *out) { + return reference(p, out) && require_bytes(p, out); +} + +static bool index_value(Parser *p, PureIndex *out) { + if (p->token == NUMBER) { + *out = (PureIndex){.value = p->number}; + return next(p); + } + Symbol value; + if (!reference(p, &value)) return false; + if (value.kind != INDEX_VALUE) return reject(p, "range requires an integer or u32le field"); + *out = value.index; + return true; +} + +static bool layout_view(Parser *p, Symbol *out) { + if (!next(p) || !token(p, '(')) return false; + Layout *layout = p->token == NAME ? find_layout(p, p->name) : NULL; + if (!layout) return reject(p, "unknown layout"); + if (!next(p) || !token(p, ',')) return false; + Symbol source; + if (!byte_reference(p, &source) || !token(p, ')')) return false; + if (source.step) return reject(p, "layout view requires a statically located source"); + size_t bound = source.region.to_end ? PURE_OBJECT_LIMIT - source.region.offset : source.region.length; + if (layout->prefix > bound) return reject(p, "layout exceeds source bound"); + require_length(p, source.region.offset + layout->prefix); + *out = (Symbol){.kind = LAYOUT_VALUE, .region = source.region, .layout = (size_t)(layout - p->layouts)}; + if (!layout->tail) { out->region.length = layout->prefix; out->region.to_end = false; } + return true; +} + +static bool view(Parser *p, Symbol *out) { + if (is_word(p, "view")) return layout_view(p, out); + if (!is_word(p, "range")) return reference(p, out); + Symbol parent; + PureIndex offset, length; + if (!next(p) || !token(p, '(') || !byte_reference(p, &parent) || !token(p, ',') || + !index_value(p, &offset) || !token(p, ',') || !index_value(p, &length) || !token(p, ')')) return false; + bool dynamic = parent.step || offset.from_u32le || length.from_u32le; + *out = (Symbol){.kind = BYTE_VALUE}; + if (dynamic) { + if (!p->put) return reject(p, "data-dependent ranges currently require PUT input"); + if (p->program.input_steps == PURE_DYNAMIC_LIMIT) return reject(p, "input exceeds 8 dynamic ranges"); + if ((!offset.from_u32le && offset.value > PURE_OBJECT_LIMIT) || + (!length.from_u32le && length.value > PURE_OBJECT_LIMIT)) return reject(p, "range exceeds object bound"); + PureRangeStep *step = &p->program.steps[p->program.input_steps++]; + *step = (PureRangeStep){.source = parent.region, .source_step = parent.step, + .offset = offset, .length = length}; + out->step = p->program.input_steps; + } else { + size_t bound = parent.region.to_end ? PURE_OBJECT_LIMIT - parent.region.offset : parent.region.length; + if (offset.value > bound || length.value > bound - offset.value) return reject(p, "range exceeds its source bound"); + out->region = (PureRegion){.offset = parent.region.offset + offset.value, .length = length.value}; + require_length(p, out->region.offset + out->region.length); + } + ++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) { + Symbol value; + if (!token(p, ',') || !field(p, "request", "revision") || !token(p, ',') || + !byte_reference(p, &value)) return false; + p->program.write = value.region; + p->program.write_step = value.step; + } + if (!token(p, ')')) return false; + out->kind = RESULT_VALUE; + 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") || is_word(p, "view")) return reject(p, "expected a non-reserved definition name"); + if (find(p, p->name)) return reject(p, "duplicate definition"); + char name[32]; + strcpy(name, p->name); + Symbol symbol = {0}; + if (!next(p) || !token(p, '=')) return false; + if (is_word(p, "store")) { + if (!effect(p, &symbol)) return false; + } else { + if (!view(p, &symbol)) return false; + ++p->program.view_definitions; + } + if (!token(p, ';')) return false; + strcpy(symbol.name, name); + 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->kind != RESULT_VALUE) return reject(p, "reply requires this handler's storage result"); + if (!next(p)) return false; + Symbol body = {0}; + bool has_body = p->token == ','; + if (has_body && (!next(p) || !byte_reference(p, &body))) return false; + if (!token(p, ')') || !token(p, ';') || !token(p, '}')) return false; + if (p->put) { + p->program.reply = body.region; + p->program.reply_step = body.step; + p->program.reply_input = has_body; + } else p->program.read = body.region; + 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; + while (is_word(&p, "layout")) if (!layout_definition(&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.offset > source.size) return false; + size_t length = region.to_end ? source.size - region.offset : region.length; + if (length > source.size - region.offset) return false; + *out = (PureSpan){length ? source.data + region.offset : NULL, length}; + return true; +} + +static bool index_read(PureIndex index, PureSpan source, size_t *out) { + if (!index.from_u32le) { *out = index.value; return true; } + if (index.value > source.size || 4 > source.size - index.value) return false; + const unsigned char *bytes = source.data + index.value; + *out = (size_t)bytes[0] | (size_t)bytes[1] << 8 | (size_t)bytes[2] << 16 | (size_t)bytes[3] << 24; + return true; +} + +static bool resolve(PureRegion region, size_t step, PureSpan source, + const PureSpan *values, size_t count, PureSpan *out) { + if (!step) return pure_region_span(region, source, out); + if (step > count) return false; + *out = values[step - 1]; + return true; +} + +bool pure_program_input(const PureProgram *p, PureSpan source, PureSpan *write, PureSpan *reply) { + *write = (PureSpan){0}; + *reply = (PureSpan){0}; + if (source.size < p->input_required || p->input_steps > PURE_DYNAMIC_LIMIT) return false; + PureSpan values[PURE_DYNAMIC_LIMIT]; + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + PureSpan parent; + size_t offset, length; + if (!resolve(step->source, step->source_step, source, values, i, &parent) || + !index_read(step->offset, source, &offset) || !index_read(step->length, source, &length) || + !pure_region_span((PureRegion){.offset = offset, .length = length}, parent, &values[i])) return false; + } + PureSpan stored, returned = {0}; + if (!resolve(p->write, p->write_step, source, values, p->input_steps, &stored) || + (p->reply_input && !resolve(p->reply, p->reply_step, source, values, p->input_steps, &returned))) return false; + *write = stored; + *reply = returned; + return true; +} diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/program.h b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/program.h new file mode 100644 index 0000000..f2b5b2f --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/program.h @@ -0,0 +1,39 @@ +#ifndef PURE_PROGRAM_H +#define PURE_PROGRAM_H +#include "flow.h" + +#define PURE_PROGRAM_LIMIT 16384u +#define PURE_DYNAMIC_LIMIT 8u +typedef struct { + size_t offset, length; + bool to_end; +} PureRegion; + +typedef struct { size_t value; bool from_u32le; } PureIndex; +typedef struct { + PureRegion source; + size_t source_step; /* 0: source region; otherwise a preceding step + 1. */ + PureIndex offset, length; +} PureRangeStep; + +/* 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; + size_t write_step, reply_step, input_steps; + PureRangeStep steps[PURE_DYNAMIC_LIMIT]; + 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); +/* Validates all input views, including unused dynamic views, before any effect. + * Returns borrowed selections; their lifetime remains that of source. */ +bool pure_program_input(const PureProgram *, PureSpan source, PureSpan *write, PureSpan *reply); +#endif diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/server.c new file mode 100644 index 0000000..e4e5b88 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/server.c @@ -0,0 +1,393 @@ +#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 (!pure_program_input(program, source, &value, &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.to_end, 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\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"write\":{\"to_end\":%s,\"offset\":%zu,\"length\":%zu,\"minimum_source_bytes\":%zu}," + "\"reply_input\":%s,\"view_definitions\":%zu,\"range_definitions\":%zu," + "\"runtime_view_steps\":%zu,\"write_step\":%zu,\"reply_step\":%zu,\"prepared_program_bytes\":%zu,\"steps\":[", + p->read.to_end ? "true" : "false", p->read.offset, p->read.length, p->read_required, + p->write.to_end ? "true" : "false", p->write.offset, p->write.length, p->input_required, + p->reply_input ? "true" : "false", p->view_definitions, p->range_definitions, + p->input_steps, p->write_step, p->reply_step, sizeof(*p)); + for (size_t i = 0; i < p->input_steps; ++i) { + const PureRangeStep *step = &p->steps[i]; + printf("%s{\"source_step\":%zu,\"source\":{\"offset\":%zu,\"length\":%zu,\"to_end\":%s}," + "\"offset\":{\"value\":%zu,\"from_u32le\":%s},\"length\":{\"value\":%zu,\"from_u32le\":%s}}", + i ? "," : "", step->source_step, step->source.offset, step->source.length, + step->source.to_end ? "true" : "false", step->offset.value, step->offset.from_u32le ? "true" : "false", + step->length.value, step->length.from_u32le ? "true" : "false"); + } + puts("]}"); + 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-field-connections/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/store.c new file mode 100644 index 0000000..1c13a80 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/store.c @@ -0,0 +1,228 @@ +#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 (offset > size) { result = PURE_LIMIT; goto finish; } + if (whole) length = size - offset; + 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-field-connections/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/store.h new file mode 100644 index 0000000..c0b2564 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/store.h @@ -0,0 +1,40 @@ +#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. + * whole selects the suffix starting at offset; otherwise length is exact. */ +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-field-connections/sources/prototypes/connections/test-fields.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/test-fields.c new file mode 100644 index 0000000..63dfc3e --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/test-fields.c @@ -0,0 +1,153 @@ +#include "program.h" +#include <stdint.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 const char layout[] = "layout Envelope { marker: bytes[1]; start: u32le; count: u32le; text: bytes; }"; +static PureProgram parse(const char *source) { + PureProgram p; + char error[160]; + if (!pure_program_parse(source, strlen(source), &p, error, sizeof(error))) { + fprintf(stderr, "%s\n", error); exit(1); + } + return p; +} +static void invalid(const char *source) { + PureProgram p; + memset(&p, 0x5a, sizeof(p)); + unsigned char before[sizeof(p)]; + memcpy(before, &p, sizeof(p)); + char error[160]; + CHECK(!pure_program_parse(source, strlen(source), &p, error, sizeof(error))); + CHECK(*error && !memcmp(before, &p, sizeof(p))); +} +static void encode(unsigned char *at, uint32_t value) { + for (unsigned i = 0; i < 4; ++i) at[i] = (unsigned char)(value >> (8 * i)); +} +int main(void) { + char source[8000]; + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let alias = input; let start = alias.start; let text = alias.text; " + "let selected = range(text, start, alias.count); let result = selected; " + "let saved = store.write(request.key, request.revision, result); reply(saved, selected); }", layout, get); + PureProgram p = parse(source); + CHECK(p.input_steps == 1 && p.input_required == 9 && p.write_step == 1 && p.reply_step == 1); + CHECK(p.steps[0].source.offset == 9 && p.steps[0].source.to_end); + CHECK(p.steps[0].offset.value == 1 && p.steps[0].offset.from_u32le); + CHECK(p.steps[0].length.value == 5 && p.steps[0].length.from_u32le); + unsigned char bytes[609]; + for (size_t i = 0; i < sizeof(bytes); ++i) bytes[i] = (unsigned char)(i * 13); + PureSpan stored, returned; + for (size_t n = 0; n < 9; ++n) { + CHECK(!pure_program_input(&p, (PureSpan){bytes, n}, &stored, &returned)); + CHECK(!stored.size && !returned.size); + } + const uint32_t offsets[] = {0, 1, 255, 257, 599, 600, 601, UINT32_MAX}; + const uint32_t counts[] = {0, 1, 2, 260, 600, UINT32_MAX}; + size_t comparisons = 0; + for (size_t i = 0; i < sizeof(offsets) / sizeof(*offsets); ++i) + for (size_t j = 0; j < sizeof(counts) / sizeof(*counts); ++j) { + uint32_t start = offsets[i], count = counts[j]; + encode(bytes + 1, start); + encode(bytes + 5, count); + bool expected = start <= 600 && count <= 600 - start; + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned) == expected); + if (expected) { + CHECK(stored.size == count && returned.size == count && stored.data == returned.data); + CHECK(!count || stored.data == bytes + 9 + start); + } else CHECK(!stored.size && !returned.size); + ++comparisons; + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let first = range(input.text, input.start, input.count); let second = range(first, 1, 2); " + "let saved = store.write(request.key, request.revision, second); reply(saved, second); }", layout, get); + p = parse(source); + CHECK(p.input_steps == 2 && p.steps[1].source_step == 1); + encode(bytes + 1, 3); encode(bytes + 5, 4); + CHECK(pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + CHECK(stored.size == 2 && stored.data == bytes + 13); + encode(bytes + 5, 2); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let unused = range(input.text, input.start, input.count); " + "let saved = store.write(request.key, request.revision, input.text); reply(saved); }", layout, get); + p = parse(source); + encode(bytes + 1, UINT32_MAX); + CHECK(!pure_program_input(&p, (PureSpan){bytes, sizeof(bytes)}, &stored, &returned)); + + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); " + "let input = view(Envelope, o.body); reply(o, input.text); } %s", layout, put); + p = parse(source); + CHECK(p.read.to_end && p.read.offset == 9 && p.read_required == 9 && !p.input_steps); + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let selection = range(input.text, 2, 3); let saved = store.write(request.key, request.revision, selection); reply(saved); }", layout, get); + p = parse(source); + CHECK(!p.input_steps && p.write.offset == 11 && p.write.length == 3 && p.input_required == 14); + snprintf(source, sizeof(source), "layout Empty { body: bytes; } %s on put { let input = view(Empty, request.body); " + "let saved = store.write(request.key, request.revision, input.body); reply(saved, input.body); }", get); + p = parse(source); + CHECK(pure_program_input(&p, (PureSpan){0}, &stored, &returned) && !stored.size && !returned.size); + + const char *bad_layout[] = { + "layout E {}", "layout E { x: bytes; y: u32le; }", "layout E { x: u32le; x: bytes; }", + "layout E { x: bytes[1449985]; }", "layout E { x: bytes[1449984]; y: u32le; }", + "layout E { x: bytes[184467440737095516160]; }", "layout E { x: u64le; }", + "layout E { x: u32le; } layout E { y: bytes; }" + }; + for (size_t i = 0; i < sizeof(bad_layout) / sizeof(*bad_layout); ++i) { + snprintf(source, sizeof(source), "%s %s %s", bad_layout[i], get, put); invalid(source); + } + const char *bad_input[] = { + "let input = view(Missing, request.body);", + "let input = view(Envelope, request.body); let x = input.missing;", + "let input = view(Envelope, request.body); let x = range(input.text, input.text, 1);", + "let short = range(request.body, 0, 8); let input = view(Envelope, short);", + "let input = view(Envelope, request.body); let x = range(input.text, input.start, 20); let y = view(Envelope, x);" + }; + for (size_t i = 0; i < sizeof(bad_input) / sizeof(*bad_input); ++i) { + snprintf(source, sizeof(source), "%s %s on put { %s let saved = store.write(request.key, request.revision, request.body); reply(saved); }", layout, get, bad_input[i]); + invalid(source); + } + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); " + "let saved = store.write(request.key, request.revision, input.start); reply(saved); }", layout, get); + invalid(source); + snprintf(source, sizeof(source), "%s on get { let o = store.read(request.key); let input = view(Envelope, o.body); " + "let x = range(input.text, input.start, input.count); reply(o, x); } %s", layout, put); + invalid(source); + for (size_t count = 8; count <= 9; ++count) { + snprintf(source, sizeof(source), "%s %s on put { let input = view(Envelope, request.body); ", layout, get); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let v%zu = range(input.text, input.start, input.count); ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "let saved = store.write(request.key, request.revision, input.text); reply(saved); }"); + if (count == 8) CHECK(parse(source).input_steps == 8); else invalid(source); + } + for (size_t count = 4; count <= 5; ++count) { + source[0] = 0; + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "layout L%zu { body: bytes; } ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "%s %s", get, put); + if (count == 4) (void)parse(source); else invalid(source); + } + for (size_t count = 16; count <= 17; ++count) { + strcpy(source, "layout L { "); + for (size_t i = 0; i < count; ++i) { + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "f%zu: u32le; ", i); + } + size_t used = strlen(source); + snprintf(source + used, sizeof(source) - used, "} %s %s", get, put); + if (count == 16) (void)parse(source); else invalid(source); + } + printf("{\"fields\":\"named-layouts-unaligned-u32le-tail-dynamic-ranges-aliases-failures-limits\"," + "\"data_boundary_cases\":%zu}\n", comparisons); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/test-flow.c new file mode 100644 index 0000000..5ff028d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/connections/test-program.c b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/connections/test-program.c new file mode 100644 index 0000000..6a4a19d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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.to_end && plan.write.to_end && !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.to_end); + 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.to_end && !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-field-connections/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/cli.mjs new file mode 100644 index 0000000..b67f4d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/examples/direct.pure new file mode 100644 index 0000000..b29721c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/remote.mjs new file mode 100644 index 0000000..6ee6a59 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/compiler.mjs new file mode 100644 index 0000000..6aec335 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/core.wasm Binary files differnew file mode 100644 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/core.wasm diff --git a/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/project.mjs new file mode 100644 index 0000000..861e0fc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-field-connections/sources/prototypes/workbench/web/runtime.mjs new file mode 100644 index 0000000..d8d6618 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-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-field-connections/verification.json b/prototypes/connections/results/2026-09-23-field-connections/verification.json new file mode 100644 index 0000000..4befb2d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-field-connections/verification.json @@ -0,0 +1,62 @@ +{ + "date": "2026-09-23", + "scope": "Named boundary fields and data-dependent byte selection; correctness and source lifetimes, not a 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-field-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-field-connections/sanitized.json", + "core_checks": { + "test-flow": { + "native": "passed", + "asan_ubsan": "passed" + }, + "test-program": { + "native": "passed", + "asan_ubsan": "passed", + "boundary_semantic_comparisons": 3584 + }, + "test-fields": { + "native": "passed", + "asan_ubsan": "passed", + "data_boundary_cases": 48 + }, + "leak_detection_disabled": false + }, + "unit_binary_sha256": { + "build/test-flow": "81bb00a859ffc6dad647159514e80d97ff2897687f6e904610fdd3b70ea30878", + "build/test-program": "c15f0f40c50a10b57d456e064d5b64d5a4cbef29bda55495fda0b4648105a866", + "build/test-fields": "8b274b9fe8b8c45198f8d536186aeefe0aecd8564f56d903cc4a634624889e00", + "build/sanitized/test-flow": "bff35676c65fcb9cd5cf3ee2daa1da33067c6648e436ad62d72ada4609542f93", + "build/sanitized/test-program": "e6487158d71ef075771965b4d80c7c116c689174a7cd2dee4f9e5a465def4325", + "build/sanitized/test-fields": "30852cde86252c52ff244f544b847ef05683ab0b33ed4dd4a134fe5fdd49a940" + }, + "integration_scenarios_each": 28, + "previous_scenarios": 22, + "field_scenarios": 6, + "source_snapshots": "sources/", + "source_hashes_equal": true, + "source_snapshots_match_records": true, + "final_binaries_match_records": true, + "prepared_connections_equal": true, + "field_connections_equal": true, + "prepared_program_bytes": 648, + "previous_prepared_program_bytes": 112, + "native_dynamic_dependencies_ldd": [ + "\tlinux-vdso.so.1 (0x00007fff973d8000)", + "\tlibc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x00007fff97190000)", + "\t/lib/ld-linux-aarch64.so.1 (0x00007fff973a0000)" + ], + "limitations": [ + "Declared boundary layouts are not a universal storage schema or application object model", + "Only u32le, fixed bytes, and a final variable byte field; no general arithmetic or schema version negotiation", + "Dynamic ranges operate on PUT input; GET numeric-dependent ranges are rejected", + "One storage effect per handler, no read-modify-write, branches or feedback loops", + "Program always reserves room for eight dynamic range steps, including programs that use none", + "Source receive allocation remains fully retained/charged even when a small selection is returned", + "Server byte fields do not validate UTF-8; the example Python client preserves code-point boundaries", + "Logical payload bounds exclude mapping pages, page cache, disk, OS sockets and client allocations", + "Synchronous storage can delay other connections and timeout enforcement", + "Linux AArch64 only; macOS, real power loss, and large-service performance not tested" + ] +} diff --git a/prototypes/connections/results/2026-09-23-native-connections/README.md b/prototypes/connections/results/2026-09-23-native-connections/README.md new file mode 100644 index 0000000..0e4f385 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/README.md @@ -0,0 +1,26 @@ +# 018 검증 자료 + +> 이 SQLite 구현은 [019](../2026-09-23-byte-storage/README.md)의 SQL 없는 경로로 교체했다. +> 이 디렉터리의 원자료와 소스 사본은 당시 결과를 보존한다. 현재 작업 트리에서 실행하면 +> 새 구현을 검사하므로, 당시 구현의 재현에는 `sources/`의 파일들을 별도 작업 트리에 사용한다. + +이 디렉터리는 실제 루프백 TCP·SQLite·기존 Workbench CLI를 연결한 기능·수명 검사 자료다. +속도·처리량·전체 RSS 비교는 수행하지 않았다. + +- [native.json](native.json): 일반 빌드의 15개 통합 시나리오, 종료 시 자원 수, 환경과 소스·실행 파일 해시. +- [sanitized.json](sanitized.json): ASan·UBSan 빌드에서 같은 시나리오. 누수 검사를 비활성화하지 않았다. +- [verification.json](verification.json): 빌드·실행 명령, 코어·CLI 결과와 파일 일치 확인. + +서버가 만든 임시 DB에 실제 작성물 배치를 저장하고 새 CLI에서 복원했다. 객체 버전의 +충돌과 64비트 정밀도, 저장 오류, DB 잠금, 읽는 중 다른 커밋, 부분 입력·출력, 연결 종료, +한도와 기한을 검사한다. 큰 본문은 부분 전송·한도 거절을 발생시키기 위한 정확성 fixture다. +작업량을 크게 만들어 성능을 주장하는 벤치마크가 아니다. + +`retained_payload_bytes`는 수신 할당과 빌린 DB 결과의 길이 합이다. 실제 SQLite·OS·전체 +프로세스 메모리는 포함하지 않는다. `ingress_prefix_copy_bytes`는 서버가 직접 복사한 +수신 접두부만 센다. 전송 배치에 따라 값이 바뀔 수 있어 두 빌드의 시간·복사 효율 비교에 쓰지 않는다. + +재현은 [구현 README](../../README.md)의 `make check` 명령을 따른다. `EVIDENCE=/새/경로.json`을 +추가하면 새 증거 파일을 저장한다. 이미 있는 파일을 덮어쓰지 않는다. 실제 소켓·자식 프로세스· +LeakSanitizer가 제한된 환경에서는 권한이 필요하다. 이 실행에서는 샌드박스가 해당 기능을 +차단해 승인된 샌드박스 밖의 로컬 검사로 확인했다. 외부 네트워크 서비스는 사용하지 않았다. diff --git a/prototypes/connections/results/2026-09-23-native-connections/native.json b/prototypes/connections/results/2026-09-23-native-connections/native.json new file mode 100644 index 0000000..5f7e53f --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/native.json @@ -0,0 +1,98 @@ +{ + "kind": "native-path-correctness-not-performance", + "checks": [ + "fragmented request and binary bytes round trip", + "conditional create and stale revision preserve committed bytes", + "busy and injected DB failure do not acknowledge or change data", + "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" + ], + "server_runs": [ + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 38, + "commits": 11, + "gets": 13, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115411, + "server_structure_bytes": 39112 + }, + { + "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": 39112 + }, + { + "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": 39112 + } + ], + "sqlite_runtime": "3.46.1", + "binary_sha256": "ff1c2d09d940d9f9a9170e6fea894aee9926e98f04567d389a0c00d5ddf4c7f7", + "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": "b0b50b0d85900b26257aac68177951e8319fd5482a1fcff4c91722f7ba30f74c", + "prototypes/connections/store.h": "07d347ae872a3d618dda1697b01bb32abee44b3451472a1b2225963085c11cf2", + "prototypes/connections/server.c": "3ff9d9497ad2a8ece657b50af9f8e6e564a974e6bbe3542f0ebe32279883d219", + "prototypes/connections/check.py": "be75db13ab2f11d3dd0e064aaa271bf28145aef11423e8ed536c755dcf96eaac", + "prototypes/connections/test-flow.c": "17472b90ebd3067b43a0cd06fb571111206b2025a2bd552b1602c3189f17ce0b", + "prototypes/connections/Makefile": "6c05917782adf733d31d9bfd42ee8f41b05fe94d965c362577189d6849b61e3e", + "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-native-connections/sanitized.json b/prototypes/connections/results/2026-09-23-native-connections/sanitized.json new file mode 100644 index 0000000..c391070 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sanitized.json @@ -0,0 +1,98 @@ +{ + "kind": "native-path-correctness-not-performance", + "checks": [ + "fragmented request and binary bytes round trip", + "conditional create and stale revision preserve committed bytes", + "busy and injected DB failure do not acknowledge or change data", + "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" + ], + "server_runs": [ + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 38, + "commits": 11, + "gets": 13, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115411, + "server_structure_bytes": 39112 + }, + { + "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": 39112 + }, + { + "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": 39112 + } + ], + "sqlite_runtime": "3.46.1", + "binary_sha256": "2e3bb0802082637989041063c2bc34ad6c9a072abc257d7adefdc65cba315298", + "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": "b0b50b0d85900b26257aac68177951e8319fd5482a1fcff4c91722f7ba30f74c", + "prototypes/connections/store.h": "07d347ae872a3d618dda1697b01bb32abee44b3451472a1b2225963085c11cf2", + "prototypes/connections/server.c": "3ff9d9497ad2a8ece657b50af9f8e6e564a974e6bbe3542f0ebe32279883d219", + "prototypes/connections/check.py": "be75db13ab2f11d3dd0e064aaa271bf28145aef11423e8ed536c755dcf96eaac", + "prototypes/connections/test-flow.c": "17472b90ebd3067b43a0cd06fb571111206b2025a2bd552b1602c3189f17ce0b", + "prototypes/connections/Makefile": "6c05917782adf733d31d9bfd42ee8f41b05fe94d965c362577189d6849b61e3e", + "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-native-connections/sources/prototypes/connections/Makefile b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/Makefile new file mode 100644 index 0000000..c520aa5 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/Makefile @@ -0,0 +1,22 @@ +CC ?= cc +CPPFLAGS += $(shell pkg-config --cflags sqlite3) +CFLAGS ?= -O2 -g +CFLAGS += -std=c17 -Wall -Wextra -Wpedantic -Werror +LDLIBS += $(shell pkg-config --libs sqlite3) +BUILD ?= build + +.PHONY: all check +all: $(BUILD)/pure-connections + +$(BUILD): + mkdir -p $@ + +$(BUILD)/pure-connections: server.c store.c flow.c store.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) server.c store.c flow.c $(LDFLAGS) $(LDLIBS) -o $@ + +$(BUILD)/test-flow: test-flow.c store.c flow.c store.h flow.h | $(BUILD) + $(CC) $(CPPFLAGS) $(CFLAGS) test-flow.c store.c flow.c $(LDFLAGS) $(LDLIBS) -o $@ + +check: $(BUILD)/test-flow $(BUILD)/pure-connections + $(BUILD)/test-flow + python3 check.py --server $(BUILD)/pure-connections $(if $(EVIDENCE),--output "$(EVIDENCE)") diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/check.py b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/check.py new file mode 100644 index 0000000..c19fcaa --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/check.py @@ -0,0 +1,306 @@ +#!/usr/bin/env python3 +"""Exercise the real local TCP/SQLite path and the existing workbench CLI.""" +import argparse +from contextlib import contextmanager +import hashlib +import http.client +import json +import platform +from pathlib import Path +import select +import socket +import sqlite3 +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, database, budget=4 * 1024 * 1024, timeout=5000): + self.process = subprocess.Popen([str(binary), '--db', str(database), '--port', '0', + '--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): + database = directory / 'objects.db' + checks, records = [], [] + with serving(binary, database) 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') + + lock = sqlite3.connect(database, timeout=0) + lock.execute('BEGIN IMMEDIATE') + assert rpc(server, 'PUT', 'binary', b'blocked', 2)[0] == 503 + lock.rollback() + lock.execute("CREATE TRIGGER reject_write BEFORE UPDATE ON pure_objects BEGIN SELECT RAISE(ABORT,'test failure'); END") + lock.commit() + assert rpc(server, 'PUT', 'binary', b'failure', 2)[0] == 500 + assert rpc(server, 'GET', 'binary')[2] == b'new' + lock.execute('DROP TRIGGER reject_write') + lock.commit() + lock.close() + checks.append('busy and injected DB failure do not acknowledge or change data') + + 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 SQLite row 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 database. + 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 + with sqlite3.connect(database) as database_connection: + database_connection.execute('UPDATE pure_objects SET revision=? WHERE key=?', (9007199254740993, 'wide_revision')) + 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: + with sqlite3.connect(database) as database_connection: + committed = database_connection.execute('SELECT body FROM pure_objects WHERE key=?', ('unread_ack',)).fetchone() + if committed: + assert committed[0] == 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, database) 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, database, 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) + return {'kind': 'native-path-correctness-not-performance', 'checks': checks, + 'server_runs': records, 'sqlite_runtime': server.ready['sqlite']} + + +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', 'server.c', 'check.py', 'test-flow.c', 'Makefile')), + *(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-native-connections/sources/prototypes/connections/flow.c b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/flow.c new file mode 100644 index 0000000..1070c70 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/connections/flow.h b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/flow.h new file mode 100644 index 0000000..6e27c58 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/connections/server.c b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/server.c new file mode 100644 index 0000000..8f7b1ed --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/server.c @@ -0,0 +1,346 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.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; + 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.db || 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.db || 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.db || 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; + if (c->put) { + result = pure_store_put(c->server->path, c->key, c->expected, + (PureSpan){c->body, c->body_size}, &revision); + /* Binding has been finalized, including the failure path. */ + release_payload(c); + if (result == PURE_OK) ++c->server->commits; + } else { + result = pure_store_get(c->server->path, c->key, + 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; + ++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, c->stored.body); 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", "db_busy"); break; + case PURE_BUDGET: fail(c, 503, "Service Unavailable", "payload_budget"); break; + case PURE_LIMIT: fail(c, 413, "Content Too Large", "limit"); 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; + for (int i = 1; i < argc; i += 2) { + if (i + 1 == argc) goto usage; + uint64_t value; + if (!strcmp(argv[i], "--db")) server.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 (!server.path) goto usage; + if (pure_store_init(server.path) != PURE_OK) { fputs("Cannot initialize SQLite store\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,\"sqlite\":\"%s\"}\n", ntohs(address.sin_port), sqlite3_libversion()); + 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 --db PATH [--port 8767] [--budget BYTES] [--timeout-ms 10000] [--send-buffer BYTES]\n", stderr); + return 2; +} diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.c b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.c new file mode 100644 index 0000000..a805f72 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.c @@ -0,0 +1,132 @@ +#include "store.h" +#include <limits.h> +#include <string.h> + +static PureResult error_result(int rc) { + int code = rc & 255; + return code == SQLITE_BUSY || code == SQLITE_LOCKED ? PURE_BUSY : PURE_STORE_ERROR; +} + +static int execute(sqlite3 *db, const char *sql) { + return sqlite3_exec(db, sql, NULL, NULL, NULL); +} + +static int open_db(const char *path, sqlite3 **db, int flags) { + int rc = sqlite3_open_v2(path, db, flags | SQLITE_OPEN_NOMUTEX, NULL); + if (rc != SQLITE_OK) return rc; + sqlite3_busy_timeout(*db, 0); /* No hidden wait/retry on this owner thread. */ + sqlite3_limit(*db, SQLITE_LIMIT_LENGTH, (int)PURE_OBJECT_LIMIT + 4096); + return execute(*db, "PRAGMA cache_size=-64; PRAGMA synchronous=FULL;"); +} + +PureResult pure_store_init(const char *path) { + sqlite3 *db = NULL; + int rc = open_db(path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + sqlite3_stmt *mode = NULL; + if (rc == SQLITE_OK) rc = sqlite3_prepare_v2(db, "PRAGMA journal_mode=WAL", -1, &mode, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_step(mode); + if (rc == SQLITE_ROW) { + const unsigned char *name = sqlite3_column_text(mode, 0); + rc = name && strcmp((const char *)name, "wal") == 0 ? SQLITE_OK : SQLITE_ERROR; + } + } + sqlite3_finalize(mode); + if (rc == SQLITE_OK) rc = execute(db, + "CREATE TABLE IF NOT EXISTS pure_objects(" + "key TEXT PRIMARY KEY, revision INTEGER NOT NULL CHECK(revision>0)," + "body BLOB NOT NULL CHECK(typeof(body)='blob'));"); + if (db) sqlite3_close(db); + return rc == SQLITE_OK ? PURE_OK : error_result(rc); +} + +void pure_store_release(PureStoredView *view) { + sqlite3_finalize(view->statement); + if (view->db) { + execute(view->db, "ROLLBACK"); + sqlite3_close(view->db); + } + *view = (PureStoredView){0}; +} + +PureResult pure_store_get(const char *path, const char *key, size_t available, + PureStoredView *out) { + *out = (PureStoredView){0}; + int rc = open_db(path, &out->db, SQLITE_OPEN_READONLY); + if (rc == SQLITE_OK) rc = execute(out->db, "BEGIN"); + if (rc == SQLITE_OK) rc = sqlite3_prepare_v2(out->db, + "SELECT revision,length(body) FROM pure_objects WHERE key=?", -1, &out->statement, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(out->statement, 1, key, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(out->statement); + PureResult result = PURE_STORE_ERROR; + if (rc == SQLITE_DONE) { result = PURE_NOT_FOUND; goto fail; } + if (rc != SQLITE_ROW) { result = error_result(rc); goto fail; } + out->revision = sqlite3_column_int64(out->statement, 0); + sqlite3_int64 size = sqlite3_column_int64(out->statement, 1); + if (out->revision <= 0 || size < 0) goto fail; + if (size > PURE_OBJECT_LIMIT) { result = PURE_LIMIT; goto fail; } + if ((uint64_t)size > available) { result = PURE_BUDGET; goto fail; } + sqlite3_finalize(out->statement); + out->statement = NULL; + /* Keep the read transaction so admission and body observe the same version. + * The result remains borrowed until socket completion/abort finalizes it. */ + rc = sqlite3_prepare_v2(out->db, "SELECT body FROM pure_objects WHERE key=?", -1, &out->statement, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(out->statement, 1, key, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(out->statement); + if (rc != SQLITE_ROW) { result = error_result(rc); goto fail; } + if (sqlite3_column_type(out->statement, 0) != SQLITE_BLOB) goto fail; + const unsigned char *data = sqlite3_column_blob(out->statement, 0); + int bytes = sqlite3_column_bytes(out->statement, 0); + if (bytes != size || (bytes && !data)) goto fail; + out->body = (PureSpan){data, (size_t)bytes}; + return PURE_OK; +fail: + pure_store_release(out); + return result; +} + +PureResult pure_store_put(const char *path, const char *key, int64_t expected, + PureSpan body, int64_t *revision) { + *revision = 0; + if (body.size > PURE_OBJECT_LIMIT || expected < 0) return PURE_LIMIT; + sqlite3 *db = NULL; + sqlite3_stmt *statement = NULL; + PureResult result = PURE_STORE_ERROR; + int rc = open_db(path, &db, SQLITE_OPEN_READWRITE); + if (rc == SQLITE_OK) rc = execute(db, "BEGIN IMMEDIATE"); + if (rc != SQLITE_OK) { result = error_result(rc); goto finish; } + rc = sqlite3_prepare_v2(db, "SELECT revision FROM pure_objects WHERE key=?", -1, &statement, NULL); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(statement, 1, key, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(statement); + if (rc == SQLITE_ROW) *revision = sqlite3_column_int64(statement, 0); + else if (rc != SQLITE_DONE) { result = error_result(rc); goto finish; } + sqlite3_finalize(statement); + statement = NULL; + if (*revision != expected) { result = PURE_CONFLICT; goto finish; } + if (*revision == INT64_MAX) { result = PURE_LIMIT; goto finish; } + const char *sql = *revision ? + "UPDATE pure_objects SET body=?1,revision=?2 WHERE key=?3" : + "INSERT INTO pure_objects(body,revision,key) VALUES(?1,?2,?3)"; + rc = sqlite3_prepare_v2(db, sql, -1, &statement, NULL); + static const unsigned char empty = 0; + /* Do not copy at the binding boundary. Finalize before returning the input. */ + if (rc == SQLITE_OK) rc = sqlite3_bind_blob(statement, 1, body.size ? body.data : &empty, + (int)body.size, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_bind_int64(statement, 2, *revision + 1); + if (rc == SQLITE_OK) rc = sqlite3_bind_text(statement, 3, key, -1, SQLITE_STATIC); + if (rc == SQLITE_OK) rc = sqlite3_step(statement); + if (rc != SQLITE_DONE) { result = error_result(rc); goto finish; } + rc = sqlite3_finalize(statement); + statement = NULL; + if (rc == SQLITE_OK) rc = execute(db, "COMMIT"); + if (rc != SQLITE_OK) { result = error_result(rc); goto finish; } + ++*revision; + result = PURE_OK; /* Acknowledgment is constructed only after COMMIT. */ +finish: + sqlite3_finalize(statement); + if (db) { + if (!sqlite3_get_autocommit(db)) execute(db, "ROLLBACK"); + sqlite3_close(db); + } + return result; +} diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.h b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.h new file mode 100644 index 0000000..c0e9f96 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/store.h @@ -0,0 +1,25 @@ +#ifndef PURE_STORE_H +#define PURE_STORE_H +#include "flow.h" +#include <sqlite3.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 +} PureResult; +typedef struct { + sqlite3 *db; + sqlite3_stmt *statement; + 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); +void pure_store_release(PureStoredView *); +PureResult pure_store_put(const char *path, const char *key, int64_t expected, + PureSpan body, int64_t *revision); +#endif diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/test-flow.c b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/test-flow.c new file mode 100644 index 0000000..997e7df --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/connections/test-flow.c @@ -0,0 +1,110 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "line %d: %s\n", __LINE__, #expr); exit(1); } } while (0) +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.db && !first.statement); + 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.db && !first.statement); + 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); + + sqlite3 *other = NULL; + CHECK(sqlite3_open(path, &other) == SQLITE_OK); + CHECK(sqlite3_exec(other, "BEGIN IMMEDIATE", NULL, NULL, NULL) == SQLITE_OK); + CHECK(pure_store_put(path, "work", 2, (PureSpan){original, sizeof(original)}, &revision) == PURE_BUSY); + CHECK(sqlite3_exec(other, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK); + CHECK(sqlite3_exec(other, "CREATE TRIGGER deny BEFORE UPDATE ON pure_objects " + "BEGIN SELECT RAISE(ABORT,'injected failure'); END", NULL, NULL, NULL) == SQLITE_OK); + 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); + CHECK(sqlite3_exec(other, "DROP TRIGGER deny; UPDATE pure_objects SET revision=9223372036854775807", NULL, NULL, NULL) == SQLITE_OK); + CHECK(pure_store_put(path, "work", INT64_MAX, (PureSpan){original, sizeof(original)}, &revision) == PURE_LIMIT); + CHECK(sqlite3_close(other) == SQLITE_OK); + 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); +} + +int main(void) { + transfers(); + char directory[] = "/tmp/pure-connection-unit-XXXXXX"; + CHECK(mkdtemp(directory)); + char path[256], sidecar[280]; + snprintf(path, sizeof(path), "%s/store.db", directory); + storage(path); + CHECK(unlink(path) == 0); + snprintf(sidecar, sizeof(sidecar), "%s-wal", path); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s-shm", path); unlink(sidecar); + CHECK(rmdir(directory) == 0); + puts("{\"transfer\":\"partial-consumption-and-single-release\"," + "\"storage\":\"snapshot-budget-conflict-busy-rollback-overflow-empty\"}"); + return 0; +} diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/cli.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/cli.mjs new file mode 100644 index 0000000..b67f4d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/workbench/cli.test.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/cli.test.mjs new file mode 100644 index 0000000..f355d2d --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/cli.test.mjs @@ -0,0 +1,121 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { createRuntime } from './web/runtime.mjs'; + +const root = fileURLToPath(new URL('.', import.meta.url)); +function run(input, file) { + const result = spawnSync(process.execPath, [join(root, 'cli.mjs'), ...(file ? [file] : [])], { + input, encoding: 'utf8', cwd: root, + }); + if (result.error) throw result.error; + return { ...result, snapshots: result.stdout.split('\n').filter(line => line.startsWith('{')).map(line => JSON.parse(line)) }; +} + +test('terminal starts empty and produces the same charge trace as the shared runtime', async () => { + assert.deepEqual(run('show\nquit\n').snapshots[0].states, {}); + const file = join(root, 'examples/charge.pure'); + const result = run('down jump\nframe 30\nup jump\nframe 1\nshow\nquit\n', file); + assert.equal(result.status, 0, result.stderr); + const runtime = await createRuntime(await readFile(join(root, 'web/core.wasm'))); + runtime.apply(await readFile(file, 'utf8')); + runtime.down('jump', 'terminal'); runtime.frame(30); runtime.up('jump', 'terminal'); runtime.frame(); + // JSON has no undefined-valued optional fields; compare the same wire form. + assert.deepEqual(result.snapshots.at(-1), JSON.parse(JSON.stringify(runtime.snapshot()))); + assert(result.snapshots.at(-1).states.y < 240); +}); + +test('invalid terminal commands do not alter state and fail piped execution', () => { + const result = run('show\nframe 0\nframe -1\nframe 1.5\nframe 1e2\nframe 601\nshow extra\ndown unknown\nshow\nquit\n', join(root, 'examples/counter.pure')); + assert.equal(result.status, 1); + assert.deepEqual(result.snapshots[0], result.snapshots[1]); + assert.equal(result.stderr.trim().split('\n').length, 7); +}); + +test('terminal replays authored pointer movement and cancellation through the shared runtime', async () => { + const file = join(root, 'examples/direct.pure'); + const result = run('point drag down 90 170\npoint drag move 360 220\npoint drag up 410 240\npoint drag down 410 240\npoint drag move 500 260\npoint drag cancel 500 260\nshow\nquit\n', file); + assert.equal(result.status, 0, result.stderr); + const runtime = await createRuntime(await readFile(join(root, 'web/core.wasm'))); + runtime.apply(await readFile(file, 'utf8')); + for (const [phase, x, y] of [['down',90,170],['move',360,220],['up',410,240],['down',410,240],['move',500,260],['cancel',500,260]]) + runtime.point('drag', phase, x, y, 'terminal'); + assert.deepEqual(result.snapshots.at(-1), JSON.parse(JSON.stringify(runtime.snapshot()))); + assert.equal(result.snapshots.at(-1).states.x, 350); + assert.equal(result.snapshots.at(-1).states.y, 210); + assert.deepEqual(result.snapshots.at(-1).held, []); +}); + +test('file load preserves by name, rejected load preserves active work, and save never overwrites', async t => { + const directory = await mkdtemp(join(tmpdir(), 'pure-author-cli-')); + t.after(() => rm(directory, { recursive: true, force: true })); + const first = join(directory, 'first.pure'), second = join(directory, 'second with space.pure'); + const invalid = join(directory, 'invalid.pure'), saved = join(directory, 'saved.pure'); + await writeFile(first, 'state n = 7'); + await writeFile(second, 'state n = 100\nstate added = 9'); + await writeFile(invalid, 'state n = broken'); + const result = run(`load ${second}\nload ${invalid}\nshow\nsave ${saved}\nreset\nsave ${saved}\nquit\n`, first); + assert.equal(result.status, 1); + assert.deepEqual(result.snapshots[0].states, { n: 7, added: 9 }); + assert.deepEqual(result.snapshots[1], result.snapshots[0]); + assert.deepEqual(result.snapshots.at(-1).states, { n: 100, added: 9 }); + assert.equal(await readFile(saved, 'utf8'), await readFile(second, 'utf8')); + assert.match(result.stderr, /EEXIST/); +}); + +test('terminal exposes arithmetic rollback and retains failure through matching release', async t => { + const directory = await mkdtemp(join(tmpdir(), 'pure-author-error-')); + t.after(() => rm(directory, { recursive: true, force: true })); + const file = join(directory, 'error.pure'); + await writeFile(file, 'state n = 7\ninput tap = Space\non tap.down { n = 1 / 0 }'); + const result = run('down tap\nup tap\nshow\nreset\nquit\n', file); + assert.equal(result.status, 1); + for (const snapshot of result.snapshots.slice(0, 3)) { + assert.equal(snapshot.states.n, 7); assert.equal(snapshot.lastError, 4); + assert.equal(snapshot.errorLine, 3); + } + assert.equal(result.snapshots[3].lastError, 0); +}); + +test('terminal configures and discards evaluation records and recovers from a display limit', () => { + const result = run('limits 32 8 2048\nshow\ncosts\nforget all\nlimits 32 8 0\nlimits 32 8 2048\nshow\nquit\n', join(root, 'examples/counter.pure')); + assert.equal(result.status, 1); // The explicit zero-budget display failed. + assert.equal(result.stderr, ''); + const [configured, warm, costs, forgotten, limited, recovered, final] = result.snapshots; + assert.deepEqual(warm.views, configured.views); + assert(costs.evaluation.cacheHits > 0); + assert.deepEqual(forgotten.states, configured.states); + assert.deepEqual(limited.views, []); + assert.equal(limited.lastError, 101); + assert.deepEqual(recovered, final); + assert.deepEqual(recovered.views, configured.views); + assert.deepEqual(recovered.states, configured.states); +}); + +test('terminal saves and restores placement independently of reset and preserves a browser draft', async t => { + const directory = await mkdtemp(join(tmpdir(), 'pure-layout-cli-')); + t.after(() => rm(directory, {recursive:true,force:true})); + const saved=join(directory,'layout.json'), copied=join(directory,'copy.json'); + const first=run(`point drag down 90 170\npoint drag move 360 220\npoint drag up 90 170\nsave-project ${saved}\nreset\nrestore ${saved}\nshow\nquit\n`,join(root,'examples/direct.pure')); + assert.equal(first.status,0,first.stderr);assert.equal(first.snapshots.at(-1).states.x,350); + const document=JSON.parse(await readFile(saved,'utf8'));document.draft='state incomplete ='; + await writeFile(saved,JSON.stringify(document)); + const second=run(`save-project ${copied}\nreset\nshow\nquit\n`,saved); + assert.equal(second.status,0,second.stderr);assert.equal(second.snapshots.at(-1).states.x,80); + const copy=JSON.parse(await readFile(copied,'utf8'));assert.equal(copy.draft,document.draft);assert.deepEqual(copy.states,document.states); + const rejected=run(`save-project ${saved}\nquit\n`,saved); + assert.equal(rejected.status,1);assert.match(rejected.stderr,/EEXIST/); +}); + +test('invalid restoration leaves terminal code and state unchanged', async t => { + const directory=await mkdtemp(join(tmpdir(),'pure-invalid-layout-')); + t.after(()=>rm(directory,{recursive:true,force:true})); + const file=join(directory,'bad.json'); + await writeFile(file,JSON.stringify({format:'pure-workbench',version:2,source:'state x=1',states:[]})); + const result=run(`show\nrestore ${file}\nshow\nquit\n`,join(root,'examples/counter.pure')); + assert.equal(result.status,1);assert.deepEqual(result.snapshots[0],result.snapshots[1]); +}); diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/examples/direct.pure b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/examples/direct.pure new file mode 100644 index 0000000..b29721c --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/workbench/remote.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/remote.mjs new file mode 100644 index 0000000..6ee6a59 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/workbench/web/compiler.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/compiler.mjs new file mode 100644 index 0000000..6aec335 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/workbench/web/core.wasm b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/core.wasm Binary files differnew file mode 100644 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/core.wasm diff --git a/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/project.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/project.mjs new file mode 100644 index 0000000..861e0fc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/sources/prototypes/workbench/web/runtime.mjs b/prototypes/connections/results/2026-09-23-native-connections/sources/prototypes/workbench/web/runtime.mjs new file mode 100644 index 0000000..d8d6618 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-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-native-connections/verification.json b/prototypes/connections/results/2026-09-23-native-connections/verification.json new file mode 100644 index 0000000..3a8b2cc --- /dev/null +++ b/prototypes/connections/results/2026-09-23-native-connections/verification.json @@ -0,0 +1,29 @@ +{ + "scope": "Correctness, lifetimes, admission bounds; no throughput, latency, or RSS comparison", + "compiler": "cc (Debian 14.2.0-19) 14.2.0", + "native_command": "make check EVIDENCE=results/2026-09-23-native-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-native-connections/sanitized.json", + "core_checks": { + "native": "passed", + "asan_ubsan": "passed", + "leak_detection_disabled": false + }, + "integration_scenarios_each": 15, + "cli_regression": { + "command": "node --test cli.test.mjs", + "cwd": "prototypes/workbench", + "passed": 8, + "failed": 0, + "test_sha256": "7e4688824e26dc7e56396f9d4d62726de0ccb1490e1a388bb64690ae2aae52f4" + }, + "source_hashes_equal": true, + "source_snapshots": "sources/", + "final_binaries_match_records": true, + "limitations": [ + "SQLite shared library was not rebuilt with sanitizers", + "Linux AArch64 only; no macOS execution", + "Bounded payload retention excludes SQLite internals, OS socket buffers and client allocations", + "SQLite operations remain synchronous; absolute deadlines are enforced between those operations", + "Native connection path is fixed in C; Pure-authored external effects are not implemented" + ] +} 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 Binary files differnew file mode 100644 index 0000000..79c50a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-23-prepared-connections/sources/prototypes/workbench/web/core.wasm 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" + ] +} diff --git a/prototypes/connections/results/2026-09-24-async-effects/async-core.json b/prototypes/connections/results/2026-09-24-async-effects/async-core.json new file mode 100644 index 0000000..d0932f0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-effects/async-core.json @@ -0,0 +1,55 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "selected-owner-thread-async-effects", + "max_text_bytes": 8192, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "effect_bytes": 128 + }, + "archive_members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Selected owner-thread completion extension. Host I/O and worker stacks are excluded. The default execution core and fixed document packages do not link this profile." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8, + "effect_bytes": 128 + }, + "sections": { + "text": 7312, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 6392\t 0\t 0\t 6392\t 18f8\truntime-async.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n", + "archive_sha256": "afa5efe941031832232aec8a64e0727b93927995edd1d395f8feeb7fa4f9611d", + "layout_binary_sha256": "71866a57a3a2af37a8e3d4f2f97669c482b11b3a408a1d918e5de068dab9e34a", + "budget_sha256": "496e34596e24418efb46e460126b7d67492137207be988525abda1397d70c539", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-async-effects/default-core.json b/prototypes/connections/results/2026-09-24-async-effects/default-core.json new file mode 100644 index 0000000..b92c015 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-effects/default-core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5932, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5012\t 0\t 0\t 5012\t 1394\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "2de4e84fe2c104368339145ffa7ade5c31a9fd0e2a4ff00ff5edd469759b3c79", + "layout_binary_sha256": "db112a9cfd8deed80b9bfbff7628958fdb290cadab1efc7223d8d15ef4c4c389", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-async-effects/flow.json b/prototypes/connections/results/2026-09-24-async-effects/flow.json new file mode 100644 index 0000000..75447c2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-effects/flow.json @@ -0,0 +1 @@ +{"async_effects":"authored-document-worker-overlap-release-forward-budget-recovery"} diff --git a/prototypes/connections/results/2026-09-24-async-effects/sanitized.json b/prototypes/connections/results/2026-09-24-async-effects/sanitized.json new file mode 100644 index 0000000..75447c2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-effects/sanitized.json @@ -0,0 +1 @@ +{"async_effects":"authored-document-worker-overlap-release-forward-budget-recovery"} diff --git a/prototypes/connections/results/2026-09-24-async-http/full-check.json b/prototypes/connections/results/2026-09-24-async-http/full-check.json new file mode 100644 index 0000000..55e7539 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-http/full-check.json @@ -0,0 +1,614 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "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": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 43224 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 43224 + } + ], + "storage": "immutable-byte-extents-v1", + "editor_sha256": "4b9f3e1882d010784d04673a231fdf3ec47b8823527e74f049d5ee3401e6fc60", + "binary_sha256": "08a8283c573fd6512b13242ec8a97286be913db6843ccf971a0fe01dd93eb989", + "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/store.c": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "e8c84d688ba078e909c57d56c882c963ad5431afe10051bdec84486d3e155b54", + "prototypes/connections/runtime.h": "2a9d00a7f6f916041380cef3e3dc30602364b05288f62607b62b3de2be2595d1", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "acab2509cbde4431a2e0d9e18ad0e917f8f827b7eab46acb3d81afef442b3655", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "31933e9d35521d2e9ae0efb1ff21fb4c9a3ab0e3e02444556ebb6db183155b0d", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-async-http/gated.json b/prototypes/connections/results/2026-09-24-async-http/gated.json new file mode 100644 index 0000000..a4dd464 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-http/gated.json @@ -0,0 +1,38 @@ +{ + "async_http": "reactor-progress-deadline-recovery-draining-shutdown", + "deadline_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 3, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 1, + "releases": 2, + "ingress_prefix_copy_bytes": 13, + "borrowed_output_bytes": 10, + "server_structure_bytes": 43224 + }, + "shutdown_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "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": 13, + "borrowed_output_bytes": 5, + "server_structure_bytes": 43224 + }, + "gated_test_binary_sha256": "aaa9c62fbc1533444467d61ad9a5fe3443b4e37939a3a6aa23301f5e19f5f7b1" +} diff --git a/prototypes/connections/results/2026-09-24-async-http/profile.json b/prototypes/connections/results/2026-09-24-async-http/profile.json new file mode 100644 index 0000000..6599363 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-http/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-one-worker-document-http", + "scope": "Optional one-worker HTTP host on the recorded Linux AArch64/GCC 14 toolchain. Section limits exclude the 128 KiB worker stack, libc, heap, mapped files and kernel buffers. Default CLI and HTTP do not link this option. Inspect process RSS and VM separately.", + "budget_sha256": "4c851b44ad47629459b78de3bdc8ba984f45cc799c7d4933d3edbcbaa12f21e8", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-connections-async": { + "sections": { + "text": 47897, + "data": 1180, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "08a8283c573fd6512b13242ec8a97286be913db6843ccf971a0fe01dd93eb989" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-async-http/sanitized.json b/prototypes/connections/results/2026-09-24-async-http/sanitized.json new file mode 100644 index 0000000..5dc5c69 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-http/sanitized.json @@ -0,0 +1,38 @@ +{ + "async_http": "reactor-progress-deadline-recovery-draining-shutdown", + "deadline_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 3, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 1, + "releases": 2, + "ingress_prefix_copy_bytes": 13, + "borrowed_output_bytes": 10, + "server_structure_bytes": 43224 + }, + "shutdown_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "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": 13, + "borrowed_output_bytes": 5, + "server_structure_bytes": 43224 + }, + "gated_test_binary_sha256": "f2d3e9abaaa2a389baff6c5bcfdb760ede69ff7e05aea479192b4f0c94ab05cf" +} diff --git a/prototypes/connections/results/2026-09-24-async-http/service-scale-memory.json b/prototypes/connections/results/2026-09-24-async-http/service-scale-memory.json new file mode 100644 index 0000000..5507b88 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-http/service-scale-memory.json @@ -0,0 +1,100 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "584b85fce5833d6a1eb5919a6f79c0648200e4ab3f65f2e90d008ea88db3be51", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1504, + "peak_rss_kib": 1696, + "idle_vm_kib": 2448, + "peak_vm_kib": 2512, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49184, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 449903, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "f49ad1538f8ebeecc65511db052a7610c966d33992c5eb076bd36124680aebd1", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1536, + "peak_rss_kib": 1712, + "idle_vm_kib": 2464, + "peak_vm_kib": 2528, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 41001, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 481696, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + }, + "source_async": { + "binary_sha256": "08a8283c573fd6512b13242ec8a97286be913db6843ccf971a0fe01dd93eb989", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1536, + "peak_rss_kib": 1712, + "idle_vm_kib": 2656, + "peak_vm_kib": 2736, + "idle_threads": 2, + "peak_threads": 2, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 57368, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 226974, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 43224 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-async-read-write/full-check.json b/prototypes/connections/results/2026-09-24-async-read-write/full-check.json new file mode 100644 index 0000000..914e9cd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-read-write/full-check.json @@ -0,0 +1,614 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 11, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115421, + "server_structure_bytes": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "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": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 54, + "borrowed_output_bytes": 57, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 9, + "borrowed_output_bytes": 1, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 16, + "borrowed_output_bytes": 113, + "server_structure_bytes": 43320 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 43320 + } + ], + "storage": "immutable-byte-extents-v1", + "editor_sha256": "4b9f3e1882d010784d04673a231fdf3ec47b8823527e74f049d5ee3401e6fc60", + "binary_sha256": "63211eebd1952dbc9f31bc2eee76c1c661df9bce14e3932b4e15e52fdae5d763", + "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/store.c": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "e8c84d688ba078e909c57d56c882c963ad5431afe10051bdec84486d3e155b54", + "prototypes/connections/runtime.h": "2a9d00a7f6f916041380cef3e3dc30602364b05288f62607b62b3de2be2595d1", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "e8632667de6feaf4d495f87abb159775650e3e4eaf6713b9dfdc5f82807c2bf9", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "f9a0c9fa7d0f40d6d72bc72a429dd36e136e27207aa6b0c01df9505d82189b6d", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-async-read-write/gated.json b/prototypes/connections/results/2026-09-24-async-read-write/gated.json new file mode 100644 index 0000000..04d5e53 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-read-write/gated.json @@ -0,0 +1,53 @@ +{ + "async_http": "snapshot-read-write-overlap-deadline-recovery-draining-shutdown", + "before_publication": { + "status": 200, + "etag": "\"1\"", + "body": "old" + }, + "after_publication": { + "status": 200, + "etag": "\"2\"", + "body": "oldafter" + }, + "shutdown_restore": { + "revision": "2", + "body": "oldafter" + }, + "deadline_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 16, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 1, + "releases": 3, + "ingress_prefix_copy_bytes": 13, + "borrowed_output_bytes": 16, + "server_structure_bytes": 43320 + }, + "shutdown_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "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": 13, + "borrowed_output_bytes": 5, + "server_structure_bytes": 43320 + }, + "test_source_sha256": "f38c9d3d784676ce815c276fbffc10e00f767aaa803793d8db311c19ef3b9085", + "gated_test_binary_sha256": "e1764fd031a24851d3fab665df1538fa947e2bf653accaa528bb60cd6daf7105" +} diff --git a/prototypes/connections/results/2026-09-24-async-read-write/profile.json b/prototypes/connections/results/2026-09-24-async-read-write/profile.json new file mode 100644 index 0000000..dee3dc7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-read-write/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-read-write-document-http", + "scope": "Optional one-read/one-write-worker HTTP host on the recorded Linux AArch64/GCC 14 toolchain. Section limits exclude the two 128 KiB worker stacks, libc, heap, mapped files and kernel buffers. Default CLI and HTTP do not link this option. Inspect process RSS and VM separately.", + "budget_sha256": "acd82386572fc2f874ca2fee5f4eed68ee145cf990d7315a9f1aff06daf51995", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-connections-async": { + "sections": { + "text": 48401, + "data": 1180, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "63211eebd1952dbc9f31bc2eee76c1c661df9bce14e3932b4e15e52fdae5d763" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-async-read-write/sanitized.json b/prototypes/connections/results/2026-09-24-async-read-write/sanitized.json new file mode 100644 index 0000000..f867695 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-read-write/sanitized.json @@ -0,0 +1,53 @@ +{ + "async_http": "snapshot-read-write-overlap-deadline-recovery-draining-shutdown", + "before_publication": { + "status": 200, + "etag": "\"1\"", + "body": "old" + }, + "after_publication": { + "status": 200, + "etag": "\"2\"", + "body": "oldafter" + }, + "shutdown_restore": { + "revision": "2", + "body": "oldafter" + }, + "deadline_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 16, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 1, + "releases": 3, + "ingress_prefix_copy_bytes": 13, + "borrowed_output_bytes": 16, + "server_structure_bytes": 43320 + }, + "shutdown_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "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": 13, + "borrowed_output_bytes": 5, + "server_structure_bytes": 43320 + }, + "test_source_sha256": "f38c9d3d784676ce815c276fbffc10e00f767aaa803793d8db311c19ef3b9085", + "gated_test_binary_sha256": "b3b89925aabe1f6edd3a782d99c5adb1dff68bf727066fb00372a16d00d92e53" +} diff --git a/prototypes/connections/results/2026-09-24-async-read-write/service-scale.json b/prototypes/connections/results/2026-09-24-async-read-write/service-scale.json new file mode 100644 index 0000000..eb73971 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-async-read-write/service-scale.json @@ -0,0 +1,100 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "a16cc0c8790cfc6745c6d78f09f527dcd5d9e749669fe4ad9b0d817f83f6cfee", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1504, + "peak_rss_kib": 1680, + "idle_vm_kib": 2448, + "peak_vm_kib": 2528, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49201, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 899599, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "64959669514855565554abb4622dc2bee34a0792485f19bd6466eb63a68e9688", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1520, + "peak_rss_kib": 1696, + "idle_vm_kib": 2464, + "peak_vm_kib": 2528, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49201, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 465705, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + }, + "source_async": { + "binary_sha256": "63211eebd1952dbc9f31bc2eee76c1c661df9bce14e3932b4e15e52fdae5d763", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1552, + "peak_rss_kib": 1728, + "idle_vm_kib": 2848, + "peak_vm_kib": 2928, + "idle_threads": 3, + "peak_threads": 3, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49192, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 238875, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 43320 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/policy-peer.json b/prototypes/connections/results/2026-09-24-atomic-policy/policy-peer.json new file mode 100644 index 0000000..e93d7b0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/policy-peer.json @@ -0,0 +1,12 @@ +{ + "checks": [ + "authored final size is checked against the locked document version", + "a rejected growth leaves revision and bytes unchanged", + "stale revision reports conflict before the policy decision", + "source CLI and HTTP share a policy and committed document", + "source policy changes behavior after the HTTP host restarts", + "fixed CLI and HTTP retain their compiled policy after source edits", + "source and fixed terminal editors preserve unsaved drafts on policy denial", + "authenticated peer shares the policy and current stored version" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/policy.json b/prototypes/connections/results/2026-09-24-atomic-policy/policy.json new file mode 100644 index 0000000..4e85685 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/policy.json @@ -0,0 +1,11 @@ +{ + "checks": [ + "authored final size is checked against the locked document version", + "a rejected growth leaves revision and bytes unchanged", + "stale revision reports conflict before the policy decision", + "source CLI and HTTP share a policy and committed document", + "source policy changes behavior after the HTTP host restarts", + "fixed CLI and HTTP retain their compiled policy after source edits", + "source and fixed terminal editors preserve unsaved drafts on policy denial" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..6383ee8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..a9977dd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "c82d4815c55e44d1293204d3151aea16fd1c2a439e342337a7a04ef191787bb6", + "archive_bytes": 12966, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..013ccec --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..98c8fec --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "9dee9779e621d46c6099367cdca8dc65942cf7eb32480a06b79e577be8f7e1a9", + "archive_bytes": 32144, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..b82c68c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..bb760ea --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-linux-aarch64.tar.gz.check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "3056f56b80c89e9b92755b36f4ea22a579c336ab48a32a22b9fa619eef810dd0", + "archive_bytes": 22862, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..73fdfe3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..6b54198 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "308766d5f31ac289ce65f0af849add3564d90a3c42148b46bc6a1db02e64a564", + "archive_bytes": 51398, + "selected_hosts": [ + "pure-edit", + "pure-edit-tui" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "relocated source-loaded TUI and CLI share UTF-8, large and many-document paths" + ] +} diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-atomic-policy/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..0da15d4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-atomic-policy/source-package-check.json b/prototypes/connections/results/2026-09-24-atomic-policy/source-package-check.json new file mode 100644 index 0000000..71cd254 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-atomic-policy/source-package-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "e68c5336dd0d4c20b5f6eed2d3fc132cec64419498fc8028b065774770f8f47c", + "archive_bytes": 337937, + "source_files": 173, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean native TLS client and both server profiles pass authenticated document work", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/cli-check.json b/prototypes/connections/results/2026-09-24-compact-runtime/cli-check.json new file mode 100644 index 0000000..3e93243 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "c69d3934b3567338c5a9c607b2a7f5df19963f3b45d6172d5538e8444125437b", + "archive_bytes": 12885, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui-check.json b/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui-check.json new file mode 100644 index 0000000..5d70484 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "5d554fc24ce273306efe77be9e137424cac08f16c067f4691206a33030af844b", + "archive_bytes": 31971, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..1846344 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/cli.tar.gz b/prototypes/connections/results/2026-09-24-compact-runtime/cli.tar.gz Binary files differnew file mode 100644 index 0000000..b216469 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/core.json b/prototypes/connections/results/2026-09-24-compact-runtime/core.json new file mode 100644 index 0000000..63eb227 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Compact release object sections and C bookkeeping, without opt-in PURE_RUNTIME_METRICS, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5908, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 4988\t 0\t 0\t 4988\t 137c\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "bb5750a47f6f8ac4307dd633d836460d6045347880a6c53df9669e675e49929e", + "layout_binary_sha256": "04c008fb7e0b1ecc8435bce6fbc4478de6180a54da6538d8742acb9b51992fe3", + "budget_sha256": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/default-profile.json b/prototypes/connections/results/2026-09-24-compact-runtime/default-profile.json new file mode 100644 index 0000000..b12bfac --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/default-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35783, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "e1d49cb048b3e9c25183ab0a4cf30c55486f696c13d20a85d69e0ddc04e040c7" + }, + "pure-connections": { + "sections": { + "text": 43938, + "data": 1068, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "662e2e9dfa6b77d06781d36912b9c2d79427a95e9ec14bc174454fb3f54deecb" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/fixed-memory.json b/prototypes/connections/results/2026-09-24-compact-runtime/fixed-memory.json new file mode 100644 index 0000000..cddbe7b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/fixed-memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2112, + "one_mib_show_rss_kib": 1072 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "df77304f4ab30eaee3ef41f8ac1f353d8faebcba163cfd76f0b18973e6dc16f5", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/fixed-profile.json b/prototypes/connections/results/2026-09-24-compact-runtime/fixed-profile.json new file mode 100644 index 0000000..ff98463 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "df77304f4ab30eaee3ef41f8ac1f353d8faebcba163cfd76f0b18973e6dc16f5" + }, + "pure-connections-fixed": { + "sections": { + "text": 26549, + "data": 1028, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "9cc73e8f8da266083e8da0628bb30441f6fa9f73317d75e65df8b5231007c746" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/source-cli-check.json b/prototypes/connections/results/2026-09-24-compact-runtime/source-cli-check.json new file mode 100644 index 0000000..78c01d5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/source-cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "0d2785a8893f7cb4b9947c64eb278587f96720926163575c3ec51e106fafedc7", + "archive_bytes": 22454, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/source-cli.tar.gz b/prototypes/connections/results/2026-09-24-compact-runtime/source-cli.tar.gz Binary files differnew file mode 100644 index 0000000..cb6492f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/source-cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/tui-profile.json b/prototypes/connections/results/2026-09-24-compact-runtime/tui-profile.json new file mode 100644 index 0000000..986fd17 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/tui-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app, including the bounded document browser. The raised text ceiling reviews its measured 2,541-byte addition; default CLI and core ceilings are unchanged. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS, including 1,024-key browsing, is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "4d3097c9ec3c0139cbdd82161a74d610c15d31c6ee5625101410627664f925e7", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 32015, + "data": 1036, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "3118fa11fe6ee6587bb122ae30a0d3c5cf1a8cec3131bb65b11246480c9d3b9e" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-compact-runtime/tui.json b/prototypes/connections/results/2026-09-24-compact-runtime/tui.json new file mode 100644 index 0000000..639ae64 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-compact-runtime/tui.json @@ -0,0 +1,22 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1808, + "one_mib_peak_rss_kib": 3168, + "one_thousand_key_list_peak_rss_kib": 1888, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "3118fa11fe6ee6587bb122ae30a0d3c5cf1a8cec3131bb65b11246480c9d3b9e" +} diff --git a/prototypes/connections/results/2026-09-24-copy-range-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-copy-range-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..b0a8e7a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-copy-range-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-copy-range-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..aad35c2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range-source/source-check-linux-aarch64.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "ce48ef8e330cff470b78aec9c9e6c6d8877e4c8c371512f190b91b6036ddc8ba", + "archive_bytes": 324261, + "source_files": 166, + "checks": [ + "every extracted source file matches the manifest", + "clean C17 build and host-neutral behavior tests pass", + "clean fixed and source-loaded native terminal PTY scenarios pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean native TLS client and both server profiles pass authenticated document work", + "clean selected Linux copy path passes app, scale, size, RSS and traced one-MiB splice", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/copy-path.json b/prototypes/connections/results/2026-09-24-copy-range/copy-path.json new file mode 100644 index 0000000..5c714ed --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/copy-path.json @@ -0,0 +1,27 @@ +{ + "profile": "linux-immutable-one-mib-last-byte-splice", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "baseline": { + "binary_sha256": "23ebc0ce1df9c49216511bee658fd5b9e373509e296bd2c2f8e76e6d3081cded", + "result_sha256": "06cb5e32505f66804f6e8c79e3230105ca4769444e1389ee36f6f11fd61f2d3a", + "object_pread_calls": 257, + "copy_file_range_calls": 0, + "copy_file_range_bytes": 0, + "pending_write_calls": 258, + "pending_write_bytes": 1048600, + "one_mib_splice_peak_rss_kib": 1056 + }, + "candidate": { + "binary_sha256": "52c2b828b0cba3564a37e3874f60bec793d1dd7031aff77ba850cee7683977f9", + "result_sha256": "06cb5e32505f66804f6e8c79e3230105ca4769444e1389ee36f6f11fd61f2d3a", + "object_pread_calls": 1, + "copy_file_range_calls": 1, + "copy_file_range_bytes": 1048575, + "pending_write_calls": 2, + "pending_write_bytes": 25, + "one_mib_splice_peak_rss_kib": 1056 + }, + "filesystem": "ext4", + "scratch_parent": "/home/eunhhu/work/pure/prototypes/connections/build", + "scope": "Successful object-file syscalls and child peak RSS; excludes physical device writes, page cache and proof of lower latency." +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/default-core.json b/prototypes/connections/results/2026-09-24-copy-range/default-core.json new file mode 100644 index 0000000..63eb227 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Compact release object sections and C bookkeeping, without opt-in PURE_RUNTIME_METRICS, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5908, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 4988\t 0\t 0\t 4988\t 137c\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "bb5750a47f6f8ac4307dd633d836460d6045347880a6c53df9669e675e49929e", + "layout_binary_sha256": "04c008fb7e0b1ecc8435bce6fbc4478de6180a54da6538d8742acb9b51992fe3", + "budget_sha256": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/default-fixed-memory.json b/prototypes/connections/results/2026-09-24-copy-range/default-fixed-memory.json new file mode 100644 index 0000000..6c6d1cd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-fixed-memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1072 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/default-fixed-profile.json b/prototypes/connections/results/2026-09-24-copy-range/default-fixed-profile.json new file mode 100644 index 0000000..297b9b1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec" + }, + "pure-connections-fixed": { + "sections": { + "text": 26549, + "data": 1028, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "0f1c24533177357c49270c01070a7376345763393b3914276ca028e52e8589b9" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/default-functional.json b/prototypes/connections/results/2026-09-24-copy-range/default-functional.json new file mode 100644 index 0000000..7e54edf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-functional.json @@ -0,0 +1,636 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true, + "idle_slot_binding": true + }, + "execution_core_test_sha256": "1e483f9c0525862041e843bba153106d7819052a787b683440d0babee80f8975", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "35554177ec2109f6603d88dd4b4bdccc33497e9f229e486666f224db8e24de9b", + "editor_sha256": "23ebc0ce1df9c49216511bee658fd5b9e373509e296bd2c2f8e76e6d3081cded", + "binary_sha256": "6cce6c7c18d8a24059455a2f6d4aa19c98e503a5b2e7462e2c8d3940f23bd827", + "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/store.c": "0d3df2df40fa9035cd98a1052d4c9fdad298475e9df5d49cfa7d475188fda072", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "7389c1343d6253344c57d4f12bd9c8c472ed5c11d386839a1a2e3ee19f5f66ca", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f", + "prototypes/connections/runtime.h": "e217b7acd94f3572d2780be1d6b13c5d5556227bab93b4f773e7db8b0eee6856", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "8c275bf564a674f0f8232026d0d7612df48f10054af75a43af5ed0bce7653e24", + "prototypes/connections/server.c": "e8632667de6feaf4d495f87abb159775650e3e4eaf6713b9dfdc5f82807c2bf9", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "80a942fe7cc05533df346fe656c0a1f9bbbbb803d72415aa56110f089de18322", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "72d30528cb59a3a8ca8cf8cc414873fe0b26d6093cd8f06bfe9585235ba5befb", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-copy-range/default-memory.json b/prototypes/connections/results/2026-09-24-copy-range/default-memory.json new file mode 100644 index 0000000..0ed783a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "source-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "23ebc0ce1df9c49216511bee658fd5b9e373509e296bd2c2f8e76e6d3081cded", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/default-profile.json b/prototypes/connections/results/2026-09-24-copy-range/default-profile.json new file mode 100644 index 0000000..fa1c423 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/default-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35783, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "23ebc0ce1df9c49216511bee658fd5b9e373509e296bd2c2f8e76e6d3081cded" + }, + "pure-connections": { + "sections": { + "text": 43938, + "data": 1068, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "6cce6c7c18d8a24059455a2f6d4aa19c98e503a5b2e7462e2c8d3940f23bd827" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/functional.json b/prototypes/connections/results/2026-09-24-copy-range/functional.json new file mode 100644 index 0000000..e645c99 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/functional.json @@ -0,0 +1,636 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 12, + "borrowed_output_bytes": 12, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true, + "idle_slot_binding": true + }, + "execution_core_test_sha256": "74e4a101d4c44c9aa6b544d6370cee570870386fb15c7444f6f077b2d975af8c", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "893349eba46c9e555ce8452296d4f776020d14310fcd7a55a6e78e1ba4b592fe", + "editor_sha256": "52c2b828b0cba3564a37e3874f60bec793d1dd7031aff77ba850cee7683977f9", + "binary_sha256": "34b501dec69c00aec2c5a4aaad2f6aca57de1b782f96d894a00536a440633c38", + "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/store.c": "0d3df2df40fa9035cd98a1052d4c9fdad298475e9df5d49cfa7d475188fda072", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "7389c1343d6253344c57d4f12bd9c8c472ed5c11d386839a1a2e3ee19f5f66ca", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f", + "prototypes/connections/runtime.h": "e217b7acd94f3572d2780be1d6b13c5d5556227bab93b4f773e7db8b0eee6856", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "8c275bf564a674f0f8232026d0d7612df48f10054af75a43af5ed0bce7653e24", + "prototypes/connections/server.c": "e8632667de6feaf4d495f87abb159775650e3e4eaf6713b9dfdc5f82807c2bf9", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "80a942fe7cc05533df346fe656c0a1f9bbbbb803d72415aa56110f089de18322", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "d4f130cc782730bc712268bbf4f0426bdf138ca03696fa7a32d781efded4f5cb", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-copy-range/memory.json b/prototypes/connections/results/2026-09-24-copy-range/memory.json new file mode 100644 index 0000000..a1bb66d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "source-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "52c2b828b0cba3564a37e3874f60bec793d1dd7031aff77ba850cee7683977f9", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/profile.json b/prototypes/connections/results/2026-09-24-copy-range/profile.json new file mode 100644 index 0000000..9975cfa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 36052, + "data": 952, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "52c2b828b0cba3564a37e3874f60bec793d1dd7031aff77ba850cee7683977f9" + }, + "pure-connections": { + "sections": { + "text": 44207, + "data": 1076, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "34b501dec69c00aec2c5a4aaad2f6aca57de1b782f96d894a00536a440633c38" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-copy-range/service-scale.json b/prototypes/connections/results/2026-09-24-copy-range/service-scale.json new file mode 100644 index 0000000..6ff4bbb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-copy-range/service-scale.json @@ -0,0 +1,70 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "2ca0c718fb8b38bd2bb6be2a1296c5346a427ca2fa8cf12607d81f81217f085b", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1504, + "peak_rss_kib": 1664, + "idle_vm_kib": 2448, + "peak_vm_kib": 2512, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49185, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 545480, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "34b501dec69c00aec2c5a4aaad2f6aca57de1b782f96d894a00536a440633c38", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1552, + "peak_rss_kib": 1712, + "idle_vm_kib": 2464, + "peak_vm_kib": 2544, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 40992, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 326641, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-document-peer/clean-peer.log b/prototypes/connections/results/2026-09-24-document-peer/clean-peer.log new file mode 100644 index 0000000..ff472a7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/clean-peer.log @@ -0,0 +1,43 @@ +make: Entering directory '/home/eunhhu/work/pure/prototypes/connections' +python3 check_profile.py --build build/checkpoint-clean --budget peer-budget.json +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-document-peer", + "scope": "Review threshold for the explicitly selected authenticated document host on Linux AArch64/GCC 14. Includes linked application code but excludes the shared OpenSSL/libc implementations, process heap, stack, mappings and kernel buffers. Check measured whole-process RSS separately.", + "budget_sha256": "defe8efb4d544cdbf615de4d43d4ceba78fc7f1d958f245731202625052eb2ff", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-document-peer": { + "sections": { + "text": 46878, + "data": 1384, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libcrypto.so.3", + "libssl.so.3" + ], + "sha256": "e0bec4f698ce6b561a6caa8464636317409011e3101c48649cf5585c33ec9d31" + } + } +} +python3 check_document_peer.py --peer build/checkpoint-clean/pure-document-peer --editor build/checkpoint-clean/pure-edit +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7840, + "server_after_large_work_rss_kib": 9488 +} +make: Leaving directory '/home/eunhhu/work/pure/prototypes/connections' diff --git a/prototypes/connections/results/2026-09-24-document-peer/clean-release.log b/prototypes/connections/results/2026-09-24-document-peer/clean-release.log new file mode 100644 index 0000000..db3d23d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/clean-release.log @@ -0,0 +1,737 @@ +make: Entering directory '/home/eunhhu/work/pure/prototypes/connections' +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror core-layout.c -o build/checkpoint-clean/core-layout +python3 check_core.py --archive build/checkpoint-clean/libpure-execution.a --layout build/checkpoint-clean/core-layout +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5932, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5012\t 0\t 0\t 5012\t 1394\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/checkpoint-clean/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/checkpoint-clean/libpure-execution.a)\n", + "archive_sha256": "d56c50e38c6c0949cfad069d34568da2ac45b1d60097cf9e7c4bf804f44c8128", + "layout_binary_sha256": "47cf1803007fc1ec85f119bd46195ad39d9f01ef0146bf5cc45e1af31eb18098", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} +python3 check_profile.py --build build/checkpoint-clean +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "pure-connections": { + "sections": { + "text": 42761, + "data": 1040, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f" + } + } +} +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror -DPURE_STORE_TEST test-flow.c store.c flow.c -o build/checkpoint-clean/test-flow +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror test-program.c program.c evaluate.c -o build/checkpoint-clean/test-program +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror test-fields.c program.c evaluate.c -o build/checkpoint-clean/test-fields +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror test-runtime.c program.c runtime.c evaluate.c store.c store-port.c -o build/checkpoint-clean/test-runtime +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror -DPURE_STORE_TEST test-splice.c program.c runtime.c evaluate.c store.c store-port.c -o build/checkpoint-clean/test-splice +build/checkpoint-clean/test-flow +{"transfer":"partial-consumption-and-single-release","storage":"mapped-ranges-snapshot-budget-conflict-failure-uncertain-crash-overflow-empty"} +build/checkpoint-clean/test-program +{"program":"prepare-views-preserve-guards-types-effects-and-atomic-install","boundary_semantic_comparisons":3584} +build/checkpoint-clean/test-fields +{"fields":"named-layouts-unaligned-u32le-tail-dynamic-ranges-aliases-failures-limits","data_boundary_cases":48} +python3 check.py --server build/checkpoint-clean/pure-connections --runtime-test build/checkpoint-clean/test-runtime --splice-test build/checkpoint-clean/test-splice --editor build/checkpoint-clean/pure-edit +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "07c328c98769d02d544aea2fc5389048a7620c00d9b3ea49b9c7ba45e33984b1", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "0e4ec2c78334af2056af376a6ba9aef7416a34c6d4fbd9082f4abb640e6de28c", + "editor_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "binary_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f", + "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": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "5d08639b5a71572553bda47cbd35562805d601aaa81f20731cd9154768ec025e", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "e1735316769b4ce4d5904b9587ccfa3ebeafa26517c53dba4491191d58c97b8e", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "c0767afe55bdb13e1308414116047ad76d5b105c7b4162d7220c78c1d1a4076e", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "prototypes/workbench/web/runtime.mjs": "9cf12270bb39b3d6ce0384c56df8bdfabfd9459d3375cc7819290a5fe3e5db54", + "prototypes/workbench/web/compiler.mjs": "349139612d1f7da5f75ef21c65aa697963b760cb35321150fc36f67f3b40adf1", + "prototypes/workbench/web/project.mjs": "8af4cd1a60bbb217c896be20c7c6b8d33989f2166d06e736bf1479d8ecdc8b35", + "prototypes/workbench/examples/direct.pure": "57177e5197d75a6185cd3ac5df2be920ddba9a9b0fcc2dddab3560b815249f0a" + } +} +make: Leaving directory '/home/eunhhu/work/pure/prototypes/connections' diff --git a/prototypes/connections/results/2026-09-24-document-peer/dynamic.txt b/prototypes/connections/results/2026-09-24-document-peer/dynamic.txt new file mode 100644 index 0000000..ff4f898 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/dynamic.txt @@ -0,0 +1,31 @@ + +Dynamic section at offset 0xfda8 contains 28 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [libssl.so.3] + 0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.3] + 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] + 0x000000000000000c (INIT) 0x1c18 + 0x000000000000000d (FINI) 0x92f8 + 0x0000000000000019 (INIT_ARRAY) 0x1fd98 + 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x1fda0 + 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x2b0 + 0x0000000000000005 (STRTAB) 0xc48 + 0x0000000000000006 (SYMTAB) 0x2d0 + 0x000000000000000a (STRSZ) 1217 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000015 (DEBUG) 0x0 + 0x0000000000000003 (PLTGOT) 0x1ffe8 + 0x0000000000000002 (PLTRELSZ) 2256 (bytes) + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000017 (JMPREL) 0x1348 + 0x0000000000000007 (RELA) 0x1258 + 0x0000000000000008 (RELASZ) 240 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000006ffffffb (FLAGS_1) Flags: PIE + 0x000000006ffffffe (VERNEED) 0x11d8 + 0x000000006fffffff (VERNEEDNUM) 3 + 0x000000006ffffff0 (VERSYM) 0x110a + 0x000000006ffffff9 (RELACOUNT) 4 + 0x0000000000000000 (NULL) 0x0 diff --git a/prototypes/connections/results/2026-09-24-document-peer/file-sizes.txt b/prototypes/connections/results/2026-09-24-document-peer/file-sizes.txt new file mode 100644 index 0000000..c625025 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/file-sizes.txt @@ -0,0 +1,2 @@ +68312 /tmp/pure-document-peer.stripped +213080 prototypes/connections/build/pure-document-peer diff --git a/prototypes/connections/results/2026-09-24-document-peer/hashes.txt b/prototypes/connections/results/2026-09-24-document-peer/hashes.txt new file mode 100644 index 0000000..0546b43 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/hashes.txt @@ -0,0 +1,4 @@ +7d6f7cac8422a800fa4407e76c29ce21aa0931020ee2f4aa7b0ae646b88aa034 prototypes/connections/document-peer.c +df259073c5e1cd911495b48d4ccd43c8346d955a19a8773964abe58089fbebf9 prototypes/common/tls-link.c +297a5d41b129ad134f5a398069565a1de0793024e6aa483def1dc1e09ebd1443 prototypes/common/tls-link.h +e0bec4f698ce6b561a6caa8464636317409011e3101c48649cf5585c33ec9d31 prototypes/connections/build/pure-document-peer diff --git a/prototypes/connections/results/2026-09-24-document-peer/package-check.json b/prototypes/connections/results/2026-09-24-document-peer/package-check.json new file mode 100644 index 0000000..a6031fc --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/package-check.json @@ -0,0 +1,19 @@ +{ + "archive_sha256": "dc3f7339bf5aa22b11eb5ac4c8b4a547168e0e06e65c8003a545f70a31500081", + "archive_bytes": 316927, + "selected_hosts": [ + "pure-edit", + "pure-connections", + "pure-document-peer", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted C/Python TLS peers share a 1 MiB document and recover", + "extracted GTK window, CLI and HTTP adapter edit the same document" + ], + "peer_idle_rss_kib": 7840, + "peer_after_large_work_rss_kib": 9488 +} diff --git a/prototypes/connections/results/2026-09-24-document-peer/package-hashes.txt b/prototypes/connections/results/2026-09-24-document-peer/package-hashes.txt new file mode 100644 index 0000000..825f3e9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/package-hashes.txt @@ -0,0 +1,3 @@ +dc3f7339bf5aa22b11eb5ac4c8b4a547168e0e06e65c8003a545f70a31500081 dist/pure-document-linux-aarch64.tar.gz +dc3f7339bf5aa22b11eb5ac4c8b4a547168e0e06e65c8003a545f70a31500081 /tmp/pure-document-repro.tar.gz +dc3f7339bf5aa22b11eb5ac4c8b4a547168e0e06e65c8003a545f70a31500081 prototypes/connections/results/2026-09-24-document-peer/pure-document-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-document-peer/peer-budget.json b/prototypes/connections/results/2026-09-24-document-peer/peer-budget.json new file mode 100644 index 0000000..369a71c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/peer-budget.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-document-peer", + "scope": "Review threshold for the explicitly selected authenticated document host on Linux AArch64/GCC 14. Includes linked application code but excludes the shared OpenSSL/libc implementations, process heap, stack, mappings and kernel buffers. Check measured whole-process RSS separately.", + "budget_sha256": "defe8efb4d544cdbf615de4d43d4ceba78fc7f1d958f245731202625052eb2ff", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-document-peer": { + "sections": { + "text": 46878, + "data": 1384, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libcrypto.so.3", + "libssl.so.3" + ], + "sha256": "e0bec4f698ce6b561a6caa8464636317409011e3101c48649cf5585c33ec9d31" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-document-peer/peer-flow.json b/prototypes/connections/results/2026-09-24-document-peer/peer-flow.json new file mode 100644 index 0000000..32f3991 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/peer-flow.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7840, + "server_after_large_work_rss_kib": 9488 +} diff --git a/prototypes/connections/results/2026-09-24-document-peer/peer-sanitized.json b/prototypes/connections/results/2026-09-24-document-peer/peer-sanitized.json new file mode 100644 index 0000000..c1e698c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/peer-sanitized.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 22592, + "server_after_large_work_rss_kib": 31632 +} diff --git a/prototypes/connections/results/2026-09-24-document-peer/pure-document-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-document-peer/pure-document-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..006e71d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/pure-document-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-document-peer/scalar-refactor.json b/prototypes/connections/results/2026-09-24-document-peer/scalar-refactor.json new file mode 100644 index 0000000..a1cf089 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/scalar-refactor.json @@ -0,0 +1 @@ +{"alpn": "pass", "disconnect_release": "pass", "idle_deadline": "pass", "malformed_frames": "pass", "mutual_authentication": "pass", "persist_restart": "pass", "python_client": "pass", "revision_conflict": "pass", "server_connected_rss_kib": 8400, "server_hostname": "pass", "server_idle_rss_kib": 7824} diff --git a/prototypes/connections/results/2026-09-24-document-peer/size.txt b/prototypes/connections/results/2026-09-24-document-peer/size.txt new file mode 100644 index 0000000..a37699e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-document-peer/size.txt @@ -0,0 +1,2 @@ + text data bss dec hex filename + 46878 1384 16 48278 bc96 prototypes/connections/build/pure-document-peer diff --git a/prototypes/connections/results/2026-09-24-fixed-http/backpressure.json b/prototypes/connections/results/2026-09-24-fixed-http/backpressure.json new file mode 100644 index 0000000..5723e36 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/backpressure.json @@ -0,0 +1,61 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa", + "source_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/final.json b/prototypes/connections/results/2026-09-24-fixed-http/final.json new file mode 100644 index 0000000..1594bfa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/final.json @@ -0,0 +1,65 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 3983, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1520, + "source_idle_rss_kib": 1568, + "fixed_after_large_rss_kib": 1648, + "source_after_large_rss_kib": 1696, + "fixed_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa", + "source_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/fixed-profile.json b/prototypes/connections/results/2026-09-24-fixed-http/fixed-profile.json new file mode 100644 index 0000000..3b3afbf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8" + }, + "pure-connections-fixed": { + "sections": { + "text": 25228, + "data": 1000, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/package.json b/prototypes/connections/results/2026-09-24-fixed-http/package.json new file mode 100644 index 0000000..7bea960 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "dd1c29ca367271c5be88275695886e3d2b038b20565c016dc63bd761ed62c76e", + "archive_bytes": 31733, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/parity.json b/prototypes/connections/results/2026-09-24-fixed-http/parity.json new file mode 100644 index 0000000..7566d43 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/parity.json @@ -0,0 +1,43 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "fixed_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa", + "source_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/peak-memory.json b/prototypes/connections/results/2026-09-24-fixed-http/peak-memory.json new file mode 100644 index 0000000..11902b5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/peak-memory.json @@ -0,0 +1,67 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1520, + "source_idle_rss_kib": 1552, + "fixed_after_large_rss_kib": 1648, + "source_after_large_rss_kib": 1680, + "fixed_peak_rss_kib": 2176, + "source_peak_rss_kib": 2096, + "fixed_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa", + "source_sha256": "f39eaa24cad84a3ba34a5dd4b1c5bd6c23cdf18862ee3bb9bd6ba27cdba3f895" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/pure-document-fixed-http-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-fixed-http/pure-document-fixed-http-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..c53ece6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/pure-document-fixed-http-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-fixed-http/source-profile.json b/prototypes/connections/results/2026-09-24-fixed-http/source-profile.json new file mode 100644 index 0000000..50b7913 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/source-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7" + }, + "pure-connections": { + "sections": { + "text": 43009, + "data": 1040, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "f39eaa24cad84a3ba34a5dd4b1c5bd6c23cdf18862ee3bb9bd6ba27cdba3f895" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-http/verified.json b/prototypes/connections/results/2026-09-24-fixed-http/verified.json new file mode 100644 index 0000000..93a7ff3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-http/verified.json @@ -0,0 +1,68 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1520, + "source_idle_rss_kib": 1552, + "fixed_after_large_rss_kib": 1648, + "source_after_large_rss_kib": 1680, + "fixed_peak_rss_kib": 2128, + "source_peak_rss_kib": 1680, + "fixed_peak_rss_limit_kib": 4096, + "fixed_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa", + "source_sha256": "f39eaa24cad84a3ba34a5dd4b1c5bd6c23cdf18862ee3bb9bd6ba27cdba3f895" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/memory-final.json b/prototypes/connections/results/2026-09-24-fixed-program/memory-final.json new file mode 100644 index 0000000..a85054d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/memory-final.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1088 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/memory-verified.json b/prototypes/connections/results/2026-09-24-fixed-program/memory-verified.json new file mode 100644 index 0000000..7ccf6ea --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/memory-verified.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2112, + "one_mib_show_rss_kib": 1088 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/memory.json b/prototypes/connections/results/2026-09-24-fixed-program/memory.json new file mode 100644 index 0000000..a4474ff --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1088, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1104 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/package-final.json b/prototypes/connections/results/2026-09-24-fixed-program/package-final.json new file mode 100644 index 0000000..385f64a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/package-final.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "84aaf54f6471ddf5cb6fe735c2c3621baf567e186e7252f87e2930024d89ec58", + "archive_bytes": 14385, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/package.json b/prototypes/connections/results/2026-09-24-fixed-program/package.json new file mode 100644 index 0000000..6671dd5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/package.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "5514e75fa29a9e2375e6ab5069a4b22f275d447725e63cb8b5c7245a9c966d7c", + "archive_bytes": 14386, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity-final-v2.json b/prototypes/connections/results/2026-09-24-fixed-program/parity-final-v2.json new file mode 100644 index 0000000..73c6a66 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity-final-v2.json @@ -0,0 +1,12 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "nested_read_and_required_extent": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "source_sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7", + "selection_sha256": "7d3394586fc9837a11a01bdae3aeeba2356b3abab8518e84545ede2ec1a6ab1c", + "excerpt_sha256": "b83a1fe42d45574ddf5941fdac2b759754d44da5705e6f33e5a45dabf7fd7205" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity-final.json b/prototypes/connections/results/2026-09-24-fixed-program/parity-final.json new file mode 100644 index 0000000..6aa3d12 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity-final.json @@ -0,0 +1,11 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "nested_read_and_required_extent": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b", + "source_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "selection_sha256": "836f883efb9dc86218c9606292b6c37241cc98277ce3ece03597795e076427a2" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity-reconfigured.json b/prototypes/connections/results/2026-09-24-fixed-program/parity-reconfigured.json new file mode 100644 index 0000000..561a923 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity-reconfigured.json @@ -0,0 +1,13 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "nested_read_and_required_extent": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "same_directory_program_switch_rebuilds": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "source_sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7", + "selection_sha256": "7d3394586fc9837a11a01bdae3aeeba2356b3abab8518e84545ede2ec1a6ab1c", + "excerpt_sha256": "b83a1fe42d45574ddf5941fdac2b759754d44da5705e6f33e5a45dabf7fd7205" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity-release.json b/prototypes/connections/results/2026-09-24-fixed-program/parity-release.json new file mode 100644 index 0000000..994b69d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity-release.json @@ -0,0 +1,14 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "nested_read_and_required_extent": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "compiler_rejects_source_as_output": "pass", + "same_directory_program_switch_rebuilds": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "source_sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7", + "selection_sha256": "7d3394586fc9837a11a01bdae3aeeba2356b3abab8518e84545ede2ec1a6ab1c", + "excerpt_sha256": "b83a1fe42d45574ddf5941fdac2b759754d44da5705e6f33e5a45dabf7fd7205" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity-verified.json b/prototypes/connections/results/2026-09-24-fixed-program/parity-verified.json new file mode 100644 index 0000000..1350cfe --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity-verified.json @@ -0,0 +1,12 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "nested_read_and_required_extent": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b", + "source_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "selection_sha256": "836f883efb9dc86218c9606292b6c37241cc98277ce3ece03597795e076427a2", + "excerpt_sha256": "221543dd8cee08b19c862d11d7a7f9c9c55d974964fb8fc88bf63792d96e14b9" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/parity.json b/prototypes/connections/results/2026-09-24-fixed-program/parity.json new file mode 100644 index 0000000..76e1469 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/parity.json @@ -0,0 +1,10 @@ +{ + "profile": "build-time-prepared-native-document-runner", + "editor_create_edit_conflict_large_restore": "pass", + "dynamic_selection_and_invalid_range": "pass", + "invalid_compilation_preserves_previous_plan": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b", + "source_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "selection_sha256": "836f883efb9dc86218c9606292b6c37241cc98277ce3ece03597795e076427a2" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/profile-final.json b/prototypes/connections/results/2026-09-24-fixed-program/profile-final.json new file mode 100644 index 0000000..e6e530d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/profile-final.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked document runner with one build-time prepared app. The source parser and compiler are separate build tools. This 20 KiB text ceiling applies only to the recorded Linux AArch64/GCC 14 toolchain and excludes libc, heap, stack, and storage files.", + "budget_sha256": "e6f4e727172d1d75bf109f426e169c98fa635c9cc4757ffd3871035593aa00d8", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/profile.json b/prototypes/connections/results/2026-09-24-fixed-program/profile.json new file mode 100644 index 0000000..771271e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked document runner with one build-time prepared app. The source parser and compiler are separate build tools. This 20 KiB text ceiling applies only to the recorded Linux AArch64/GCC 14 toolchain and excludes libc, heap, stack, and storage files.", + "budget_sha256": "e6f4e727172d1d75bf109f426e169c98fa635c9cc4757ffd3871035593aa00d8", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "00f0316cb318cf168d4437080642fe1a6db013e7d4ad82be80e57ad9e12a0a3b" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-final-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-final-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..511d53f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-final-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..ea09bcb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/pure-document-fixed-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-fixed-program/window-regression.json b/prototypes/connections/results/2026-09-24-fixed-program/window-regression.json new file mode 100644 index 0000000..44483d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-program/window-regression.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_line_width": 80, "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47328, 48048], "large_window_rss_kib": 44112, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui-final/package.json b/prototypes/connections/results/2026-09-24-fixed-tui-final/package.json new file mode 100644 index 0000000..86651ee --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui-final/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "3c5aa21f36a410bcef5b42c4928b2b24e35a55f7b6d5e62d322b749b48512db6", + "archive_bytes": 33654, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui-final/profile.json b/prototypes/connections/results/2026-09-24-fixed-tui-final/profile.json new file mode 100644 index 0000000..52ebb29 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui-final/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "e378cb302b2a4bcd2ee674af048116f139b214b59f24244d6c825bc71a8e8a31", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 28226, + "data": 988, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "dd6fb1f48a0212b59c4b997bd72ce099ad53f0b2d0d63cdebbc5264fc9bf23f5" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-flow.json b/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-flow.json new file mode 100644 index 0000000..8e134d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-flow.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement and edit", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1744, + "one_mib_peak_rss_kib": 3168, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "dd6fb1f48a0212b59c4b997bd72ce099ad53f0b2d0d63cdebbc5264fc9bf23f5" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-sanitized.json b/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-sanitized.json new file mode 100644 index 0000000..a5ca7da --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui-final/pty-sanitized.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement and edit", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 9840, + "one_mib_peak_rss_kib": 11712, + "one_mib_peak_rss_limit_kib": 0, + "binary_sha256": "746ebdfa531accd578ed04132a6d4155a0c8cf44842f30c46cc49ef804f19615" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui-final/pure-document-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-fixed-tui-final/pure-document-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..596d28b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui-final/pure-document-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/package-cursor.json b/prototypes/connections/results/2026-09-24-fixed-tui/package-cursor.json new file mode 100644 index 0000000..ae2716d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/package-cursor.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "0e7d03741e17cc2f39db95aa28e52b8fdd02a54b68c8e978df06407cc241c2bb", + "archive_bytes": 33625, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/package.json b/prototypes/connections/results/2026-09-24-fixed-tui/package.json new file mode 100644 index 0000000..ae2716d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "0e7d03741e17cc2f39db95aa28e52b8fdd02a54b68c8e978df06407cc241c2bb", + "archive_bytes": 33625, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/profile.json b/prototypes/connections/results/2026-09-24-fixed-tui/profile.json new file mode 100644 index 0000000..acc451b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "e378cb302b2a4bcd2ee674af048116f139b214b59f24244d6c825bc71a8e8a31", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 28098, + "data": 988, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "ae3f8ff479899929cb108dd3435750aa24b8f793744a9e7794315974182ad1f7" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-flow.json b/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-flow.json new file mode 100644 index 0000000..c85112d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-flow.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement and edit", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1744, + "one_mib_peak_rss_kib": 3168, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "ae3f8ff479899929cb108dd3435750aa24b8f793744a9e7794315974182ad1f7" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-sanitized.json b/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-sanitized.json new file mode 100644 index 0000000..7852c5d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/pty-cursor-sanitized.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement and edit", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 9808, + "one_mib_peak_rss_kib": 11760, + "one_mib_peak_rss_limit_kib": 0, + "binary_sha256": "71eeef473231fe7b693022fad39fe3b9b90e89913bbc252a2b64e6d087b1ebf6" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/pty-flow.json b/prototypes/connections/results/2026-09-24-fixed-tui/pty-flow.json new file mode 100644 index 0000000..50f5cfa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/pty-flow.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "create and save through terminal", + "open another key", + "UTF-8 edit and save", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1744, + "one_mib_peak_rss_kib": 2784, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "ae3f8ff479899929cb108dd3435750aa24b8f793744a9e7794315974182ad1f7" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/pty-sanitized.json b/prototypes/connections/results/2026-09-24-fixed-tui/pty-sanitized.json new file mode 100644 index 0000000..443e9cd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/pty-sanitized.json @@ -0,0 +1,16 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "create and save through terminal", + "open another key", + "UTF-8 edit and save", + "resize redraw", + "external revision conflict preserves draft", + "one MiB document edit and fresh CLI restore", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 9776, + "one_mib_peak_rss_kib": 11712, + "one_mib_peak_rss_limit_kib": 0, + "binary_sha256": "71eeef473231fe7b693022fad39fe3b9b90e89913bbc252a2b64e6d087b1ebf6" +} diff --git a/prototypes/connections/results/2026-09-24-fixed-tui/pure-document-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-fixed-tui/pure-document-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..05a8713 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-fixed-tui/pure-document-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-flat-export-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-flat-export-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..096ffe4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-flat-export-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-flat-export-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-flat-export-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..854f2d0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-flat-export-source/source-check-linux-aarch64.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "16126434bdbef5b75210639cf67f5e27711aa7c540ba1ca6d3949dd45009df9d", + "archive_bytes": 329118, + "source_files": 168, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-flat-export/export-check.json b/prototypes/connections/results/2026-09-24-flat-export/export-check.json new file mode 100644 index 0000000..c64ad9e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-flat-export/export-check.json @@ -0,0 +1,27 @@ +{ + "objects": 5, + "logical_bytes": 1081346, + "document_digest": "efaa71ebcdaf493723b82bc98532aba3fd5beace476f8b9f61f93706bddf850b", + "paged_reader_sha256": "f29440fcc167d1202b226f0faee450cb299989c8977277e062a330587cec5e39", + "flat_reader_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "passed": true, + "extra_documents": 0, + "source_revisions": { + "empty": 1, + "small": 1, + "boundary": 1, + "large": 1, + "megabyte": 66 + }, + "readback_sha256": { + "empty": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "small": "4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015", + "boundary": "1bd4db450abc8914c2fac721cace2704ff4c16028e6d07293154dad289835694", + "large": "b10b6985790c800b653eeb8e979f9c1551293ee9c293cbca1c4741df09cc8284", + "megabyte": "3cacf45be36c19ea18974a474bd2c668f24622b9132ab99294b3778ba0124094" + }, + "flat_edit_after_export": "revision 67", + "writer_busy_during_export_and_resumed_after": true, + "partial_export_not_published": true, + "existing_target_and_symlink_rejected": true +} diff --git a/prototypes/connections/results/2026-09-24-flat-export/export-ext4.json b/prototypes/connections/results/2026-09-24-flat-export/export-ext4.json new file mode 100644 index 0000000..3fc4829 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-flat-export/export-ext4.json @@ -0,0 +1,28 @@ +{ + "objects": 5, + "logical_bytes": 1081346, + "document_digest": "efaa71ebcdaf493723b82bc98532aba3fd5beace476f8b9f61f93706bddf850b", + "paged_reader_sha256": "f29440fcc167d1202b226f0faee450cb299989c8977277e062a330587cec5e39", + "flat_reader_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "passed": true, + "extra_documents": 0, + "scratch_parent": "/home/eunhhu/work/pure/prototypes/connections/build", + "source_revisions": { + "empty": 1, + "small": 1, + "boundary": 1, + "large": 1, + "megabyte": 66 + }, + "readback_sha256": { + "empty": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "small": "4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015", + "boundary": "1bd4db450abc8914c2fac721cace2704ff4c16028e6d07293154dad289835694", + "large": "b10b6985790c800b653eeb8e979f9c1551293ee9c293cbca1c4741df09cc8284", + "megabyte": "3cacf45be36c19ea18974a474bd2c668f24622b9132ab99294b3778ba0124094" + }, + "flat_edit_after_export": "revision 67", + "writer_busy_during_export_and_resumed_after": true, + "partial_export_not_published": true, + "existing_target_and_symlink_rejected": true +} diff --git a/prototypes/connections/results/2026-09-24-flat-export/export-scale-1024.json b/prototypes/connections/results/2026-09-24-flat-export/export-scale-1024.json new file mode 100644 index 0000000..20a2d33 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-flat-export/export-scale-1024.json @@ -0,0 +1,27 @@ +{ + "objects": 1029, + "logical_bytes": 1212418, + "document_digest": "59dabf364a2b8d06e74c275d3679a15b71095544981e90ccbb0932b096c380d1", + "paged_reader_sha256": "f29440fcc167d1202b226f0faee450cb299989c8977277e062a330587cec5e39", + "flat_reader_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "passed": true, + "extra_documents": 1024, + "source_revisions": { + "empty": 1, + "small": 1, + "boundary": 1, + "large": 1, + "megabyte": 66 + }, + "readback_sha256": { + "empty": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "small": "4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015", + "boundary": "1bd4db450abc8914c2fac721cace2704ff4c16028e6d07293154dad289835694", + "large": "b10b6985790c800b653eeb8e979f9c1551293ee9c293cbca1c4741df09cc8284", + "megabyte": "3cacf45be36c19ea18974a474bd2c668f24622b9132ab99294b3778ba0124094" + }, + "flat_edit_after_export": "revision 67", + "writer_busy_during_export_and_resumed_after": true, + "partial_export_not_published": true, + "existing_target_and_symlink_rejected": true +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/app-asan.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/app-asan.json new file mode 100644 index 0000000..9bd2b7c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/app-asan.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 11, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115421, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 60, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 14, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 45, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 15, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "bf418bdfd09f865d327c42bd598c3f251138f19e7e6ae0031c4b497affc55002", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "4ae184e15f9f4120333882bd90ea15668bbdaf1cf4fa39f3eb76ba311d129061", + "editor_sha256": "4f2875d8cc8ccd170d4d959f2491c124b1ac11b1debb38ba7f318be1982c8fb6", + "binary_sha256": "ae727ef73c5118acb3a96337ffee7f990f061c597aa22d3d818b1865da06ca6c", + "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/store-paged.c": "da01d9e0e4607ac78d9b057431e1ae6d9bd9aeb9036178b388a1bf1fbe2e7b17", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "584f6671c2daf40ee3cc4233760b0004d5035bd0e0cd10aa0fe6a0b814f66937", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-hybrid-storage-v2/app-release.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/app-release.json new file mode 100644 index 0000000..ca41eb7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/app-release.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11753, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 34, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 15, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "b9642410766ffc6dad9ed3ac38ce9ef550e3a861deb21a4e53dea219687894b6", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "a7e9ec3814b9adf81254bb781a98ac5c2896f69f665e80307f5ec1d824d1089f", + "editor_sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9", + "binary_sha256": "09b8fb1695ea4ff3a326e75660e77a4e5532f908de7e6fb01a9dec6c840a0d53", + "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/store-paged.c": "da01d9e0e4607ac78d9b057431e1ae6d9bd9aeb9036178b388a1bf1fbe2e7b17", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "584f6671c2daf40ee3cc4233760b0004d5035bd0e0cd10aa0fe6a0b814f66937", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-hybrid-storage-v2/gtk-flow.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/gtk-flow.json new file mode 100644 index 0000000..233cfc4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 43632, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/memory.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/memory.json new file mode 100644 index 0000000..e2068b8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/memory.json @@ -0,0 +1,14 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-full.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-full.json new file mode 100644 index 0000000..a753696 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-full.json @@ -0,0 +1,21 @@ +{ + "archive_sha256": "40e635bdd6746e13c1073a1d809d8753ddfab94cf973be2771ea9f8ffff0fb3c", + "archive_bytes": 114025, + "selected_hosts": [ + "pure-edit", + "pure-connections", + "pure-document-peer", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "selected storage keeps small values inline and maps large values", + "editing the packaged .pure source changes the executed reply", + "extracted HTTP host updates the same document as the native CLI", + "extracted C/Python TLS peers share a 1 MiB document and recover", + "extracted GTK window and selected external host edit the same document" + ], + "peer_idle_rss_kib": 7840, + "peer_after_large_work_rss_kib": 9504 +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-micro.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-micro.json new file mode 100644 index 0000000..dccd086 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/package-micro.json @@ -0,0 +1,13 @@ +{ + "archive_sha256": "0d3a5da21ebd94cd235259ccfee9173c44afee57e57514e05acc1a43beea8a36", + "archive_bytes": 26045, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "selected storage keeps small values inline and maps large values", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/peer.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/peer.json new file mode 100644 index 0000000..99de24a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/peer.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7856, + "server_after_large_work_rss_kib": 9520 +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/profile.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/profile.json new file mode 100644 index 0000000..c0ae382 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-hybrid-document-storage", + "scope": "Opt-in inline/PUREP001 document store on Linux AArch64/GCC 14. The CLI text ceiling is 42 KiB: 2 KiB above the original selected-page gate because the measured 41064-byte dual-format build fails 40 KiB. This is an explicit research budget, not the default micro-runtime budget. Direct dependencies only; excludes libc, process RAM, filesystem cache and journal, optional TLS/GTK.", + "budget_sha256": "038a323d8492731611dfc56e799e7da751946fe0ea5dc428550ff02ffd781b29", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 41064, + "data": 1000, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9" + }, + "pure-connections": { + "sections": { + "text": 47928, + "data": 1088, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "09b8fb1695ea4ff3a326e75660e77a4e5532f908de7e6fb01a9dec6c840a0d53" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-authoring-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-authoring-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..20dec18 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-authoring-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-micro-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-micro-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..0234d2b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/pure-document-hybrid-micro-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/small-scan.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/small-scan.json new file mode 100644 index 0000000..4dda494 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/small-scan.json @@ -0,0 +1,5 @@ +{ + "small_edit_store_directory_scans": 0, + "unrelated_directory_entries": 256, + "binary_sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9" +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit-asan.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit-asan.json new file mode 100644 index 0000000..8da4fd1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit-asan.json @@ -0,0 +1 @@ +{"hybrid_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit.json new file mode 100644 index 0000000..8da4fd1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/unit.json @@ -0,0 +1 @@ +{"hybrid_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage-v2/write-cost.json b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/write-cost.json new file mode 100644 index 0000000..266c4c8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage-v2/write-cost.json @@ -0,0 +1,49 @@ +{ + "profile": "linux-aarch64-16k-page-document-replacement", + "candidate_mode": "hybrid", + "workloads": [ + "replace last byte of 1 MiB document", + "replace one-byte document" + ], + "unit": "successful user-space write syscall bytes targeting object files", + "excludes": "physical device writes, filesystem journal, metadata and directory sync cost", + "one_mib": { + "flat": { + "object_write_syscall_bytes": 1048600, + "object_write_calls": 258, + "object_files": { + "obj-large": 1048600 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "hybrid": { + "object_write_syscall_bytes": 16944, + "object_write_calls": 3, + "object_files": { + "obj-large": 560, + "dat-large-0000000000000001": 1064960 + }, + "binary_sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9" + }, + "flat_to_hybrid_ratio": 61.8862134088763 + }, + "one_byte": { + "flat": { + "object_write_syscall_bytes": 25, + "object_write_calls": 2, + "object_files": { + "obj-tiny": 25 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "hybrid": { + "object_write_syscall_bytes": 25, + "object_write_calls": 2, + "object_files": { + "obj-tiny": 25 + }, + "binary_sha256": "e024d1829ea1a2211c58c33b8ae457794cbda0dbf927cdcb1126cd4de13a16e9" + }, + "hybrid_to_flat_ratio": 1.0 + } +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/app-asan.json b/prototypes/connections/results/2026-09-24-hybrid-storage/app-asan.json new file mode 100644 index 0000000..77ff376 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/app-asan.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "03ae899315468bf32215398b094b120c528c36e6337e6da5ebb3435204c2ab82", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "f4761e52b1b65af4b7fd89f3eaa456ca69aa5c0e61e3ade24b0b04c9377af974", + "editor_sha256": "d94ddba81b79796aab53b4131d05332e02e654c35007f48d9073ce410c5ecee0", + "binary_sha256": "db7fd98350bf3a33435921035871f33e18dad0a3b28360671544a3d34a6a93ce", + "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/store-paged.c": "02c5992465d117e576199c7348daa2b628e6c6efd41d906faa3b50713ece9f53", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "584f6671c2daf40ee3cc4233760b0004d5035bd0e0cd10aa0fe6a0b814f66937", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-hybrid-storage/app-release.json b/prototypes/connections/results/2026-09-24-hybrid-storage/app-release.json new file mode 100644 index 0000000..452cadb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/app-release.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 14227, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 16, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "dd505a9c4ecf73205f33edb491ed990234cd940c6164cd9fe56c1606dc4a280f", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "9b7366c1d005a21c393425e28c18bd161fcbb86af3961180780d67f5d62ba3d8", + "editor_sha256": "5ea320034f2c36f378bed30bb12705f35723dd9f7f334c976aec7d6f5927ac57", + "binary_sha256": "8bc87de02efca32d1281976a8e8bea2e5a7242eb5ec07fc204b7762c2081021c", + "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/store-paged.c": "02c5992465d117e576199c7348daa2b628e6c6efd41d906faa3b50713ece9f53", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "584f6671c2daf40ee3cc4233760b0004d5035bd0e0cd10aa0fe6a0b814f66937", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-hybrid-storage/gtk-flow.json b/prototypes/connections/results/2026-09-24-hybrid-storage/gtk-flow.json new file mode 100644 index 0000000..4bff12d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 43760, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/memory.json b/prototypes/connections/results/2026-09-24-hybrid-storage/memory.json new file mode 100644 index 0000000..64304e0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/memory.json @@ -0,0 +1,14 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "5ea320034f2c36f378bed30bb12705f35723dd9f7f334c976aec7d6f5927ac57", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/package-full.json b/prototypes/connections/results/2026-09-24-hybrid-storage/package-full.json new file mode 100644 index 0000000..0675a20 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/package-full.json @@ -0,0 +1,21 @@ +{ + "archive_sha256": "0aa6141240a44039ec5c3e542c9c2857678620c77c81203dd984b4121b18adce", + "archive_bytes": 114009, + "selected_hosts": [ + "pure-edit", + "pure-connections", + "pure-document-peer", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "selected storage keeps small values inline and maps large values", + "editing the packaged .pure source changes the executed reply", + "extracted HTTP host updates the same document as the native CLI", + "extracted C/Python TLS peers share a 1 MiB document and recover", + "extracted GTK window and selected external host edit the same document" + ], + "peer_idle_rss_kib": 7840, + "peer_after_large_work_rss_kib": 9536 +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/package-micro.json b/prototypes/connections/results/2026-09-24-hybrid-storage/package-micro.json new file mode 100644 index 0000000..62cfd78 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/package-micro.json @@ -0,0 +1,13 @@ +{ + "archive_sha256": "c67a92e534a43aed24254995078bb72992015766da9c3e57ec7c993fa50f3654", + "archive_bytes": 26040, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "selected storage keeps small values inline and maps large values", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/peer.json b/prototypes/connections/results/2026-09-24-hybrid-storage/peer.json new file mode 100644 index 0000000..fa238e3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/peer.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7840, + "server_after_large_work_rss_kib": 9536 +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/profile.json b/prototypes/connections/results/2026-09-24-hybrid-storage/profile.json new file mode 100644 index 0000000..96ca222 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-hybrid-document-storage", + "scope": "Opt-in inline/PUREP001 document store on Linux AArch64/GCC 14. The CLI text ceiling is 42 KiB: 2 KiB above the original selected-page gate because the measured 41064-byte dual-format build fails 40 KiB. This is an explicit research budget, not the default micro-runtime budget. Direct dependencies only; excludes libc, process RAM, filesystem cache and journal, optional TLS/GTK.", + "budget_sha256": "038a323d8492731611dfc56e799e7da751946fe0ea5dc428550ff02ffd781b29", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 41064, + "data": 1000, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "5ea320034f2c36f378bed30bb12705f35723dd9f7f334c976aec7d6f5927ac57" + }, + "pure-connections": { + "sections": { + "text": 47928, + "data": 1088, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8bc87de02efca32d1281976a8e8bea2e5a7242eb5ec07fc204b7762c2081021c" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-authoring-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-authoring-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..5280fd4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-authoring-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-micro-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-micro-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..c2fb13f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/pure-document-hybrid-micro-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/unit-asan.json b/prototypes/connections/results/2026-09-24-hybrid-storage/unit-asan.json new file mode 100644 index 0000000..8da4fd1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/unit-asan.json @@ -0,0 +1 @@ +{"hybrid_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/unit.json b/prototypes/connections/results/2026-09-24-hybrid-storage/unit.json new file mode 100644 index 0000000..8da4fd1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/unit.json @@ -0,0 +1 @@ +{"hybrid_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain-inline-transitions","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-hybrid-storage/write-cost.json b/prototypes/connections/results/2026-09-24-hybrid-storage/write-cost.json new file mode 100644 index 0000000..a3dfa76 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-hybrid-storage/write-cost.json @@ -0,0 +1,49 @@ +{ + "profile": "linux-aarch64-16k-page-document-replacement", + "candidate_mode": "hybrid", + "workloads": [ + "replace last byte of 1 MiB document", + "replace one-byte document" + ], + "unit": "successful user-space write syscall bytes targeting object files", + "excludes": "physical device writes, filesystem journal, metadata and directory sync cost", + "one_mib": { + "flat": { + "object_write_syscall_bytes": 1048600, + "object_write_calls": 258, + "object_files": { + "obj-large": 1048600 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "hybrid": { + "object_write_syscall_bytes": 16944, + "object_write_calls": 3, + "object_files": { + "obj-large": 560, + "dat-large-0000000000000001": 1064960 + }, + "binary_sha256": "5ea320034f2c36f378bed30bb12705f35723dd9f7f334c976aec7d6f5927ac57" + }, + "flat_to_hybrid_ratio": 61.8862134088763 + }, + "one_byte": { + "flat": { + "object_write_syscall_bytes": 25, + "object_write_calls": 2, + "object_files": { + "obj-tiny": 25 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "hybrid": { + "object_write_syscall_bytes": 25, + "object_write_calls": 2, + "object_files": { + "obj-tiny": 25 + }, + "binary_sha256": "5ea320034f2c36f378bed30bb12705f35723dd9f7f334c976aec7d6f5927ac57" + }, + "hybrid_to_flat_ratio": 1.0 + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/archived-comparison.json b/prototypes/connections/results/2026-09-24-idle-wake/archived-comparison.json new file mode 100644 index 0000000..f90f1a9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/archived-comparison.json @@ -0,0 +1,21 @@ +{ + "profile": "idle-http-host-poll-syscalls", + "observation_seconds": 1.25, + "before": { + "poll_calls": 25, + "timeout_values_ms": [ + 50 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=PO", + "binary_sha256": "7dd0010fa6d6024afa2fe35d57a4c73f33ac632e732616ac8f8853d7b22d79b3" + }, + "after": { + "poll_calls": 1, + "timeout_values_ms": [ + -1 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)\n--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=927636, si_uid=1000} ---\n", + "binary_sha256": "3e8162f6851ba407061d1109a87b9b2b17c87a5c5eadbc75ea7cc0fb27a84501" + }, + "deadline_driven_idle_wait": "pass" +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/fixed-profile.json b/prototypes/connections/results/2026-09-24-idle-wake/fixed-profile.json new file mode 100644 index 0000000..9a57d8c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8" + }, + "pure-connections-fixed": { + "sections": { + "text": 25292, + "data": 1000, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "3e8162f6851ba407061d1109a87b9b2b17c87a5c5eadbc75ea7cc0fb27a84501" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/fixed-server-regression.json b/prototypes/connections/results/2026-09-24-idle-wake/fixed-server-regression.json new file mode 100644 index 0000000..5400394 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/fixed-server-regression.json @@ -0,0 +1,68 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1504, + "source_idle_rss_kib": 1536, + "fixed_after_large_rss_kib": 1632, + "source_after_large_rss_kib": 1664, + "fixed_peak_rss_kib": 2128, + "source_peak_rss_kib": 1664, + "fixed_peak_rss_limit_kib": 4096, + "fixed_sha256": "3e8162f6851ba407061d1109a87b9b2b17c87a5c5eadbc75ea7cc0fb27a84501", + "source_sha256": "186fd0ab0f7cd32cc46a341cc4e6ec784126193810473c6ac28a8c95a0d44bc3" +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/idle.json b/prototypes/connections/results/2026-09-24-idle-wake/idle.json new file mode 100644 index 0000000..dee7a74 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/idle.json @@ -0,0 +1,21 @@ +{ + "profile": "idle-http-host-poll-syscalls", + "observation_seconds": 1.25, + "before": { + "poll_calls": 25, + "timeout_values_ms": [ + 50 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=PO", + "binary_sha256": "02acf468f18610332d3fe4b9c093b2bbff08496000dbae2b61151d7f3a7a46fa" + }, + "after": { + "poll_calls": 1, + "timeout_values_ms": [ + -1 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)\n--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=927237, si_uid=1000} ---\n", + "binary_sha256": "3e8162f6851ba407061d1109a87b9b2b17c87a5c5eadbc75ea7cc0fb27a84501" + }, + "deadline_driven_idle_wait": "pass" +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/package.json b/prototypes/connections/results/2026-09-24-idle-wake/package.json new file mode 100644 index 0000000..61d484b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "669d28751faa4da34dd63cb52e83fa2cfb11146a9844b0f6378f9f63500a0b71", + "archive_bytes": 31773, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/pure-document-fixed-http-idle-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-idle-wake/pure-document-fixed-http-idle-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..24d9b3d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/pure-document-fixed-http-idle-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-wake/reproducible-comparison.json b/prototypes/connections/results/2026-09-24-idle-wake/reproducible-comparison.json new file mode 100644 index 0000000..4a17350 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/reproducible-comparison.json @@ -0,0 +1,21 @@ +{ + "profile": "idle-http-host-poll-syscalls", + "observation_seconds": 1.25, + "before": { + "poll_calls": 25, + "timeout_values_ms": [ + 50 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=PO", + "binary_sha256": "7dd0010fa6d6024afa2fe35d57a4c73f33ac632e732616ac8f8853d7b22d79b3" + }, + "after": { + "poll_calls": 1, + "timeout_values_ms": [ + -1 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)\n--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=928742, si_uid=1000} ---\n", + "binary_sha256": "3e8162f6851ba407061d1109a87b9b2b17c87a5c5eadbc75ea7cc0fb27a84501" + }, + "deadline_driven_idle_wait": "pass" +} diff --git a/prototypes/connections/results/2026-09-24-idle-wake/source-profile.json b/prototypes/connections/results/2026-09-24-idle-wake/source-profile.json new file mode 100644 index 0000000..05a247c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-wake/source-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7" + }, + "pure-connections": { + "sections": { + "text": 43073, + "data": 1040, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "186fd0ab0f7cd32cc46a341cc4e6ec784126193810473c6ac28a8c95a0d44bc3" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/async-core.json b/prototypes/connections/results/2026-09-24-idle-work-binding/async-core.json new file mode 100644 index 0000000..4843a89 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/async-core.json @@ -0,0 +1,55 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "selected-owner-thread-async-effects", + "max_text_bytes": 8192, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "effect_bytes": 128 + }, + "archive_members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Selected owner-thread completion extension. Host I/O and worker stacks are excluded. The default execution core and fixed document packages do not link this profile." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8, + "effect_bytes": 128 + }, + "sections": { + "text": 7456, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 6536\t 0\t 0\t 6536\t 1988\truntime-async.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n", + "archive_sha256": "897fb2f6eff5dd07efcf0789107bfcf87423fc90aae084df169601451f23d07f", + "layout_binary_sha256": "71866a57a3a2af37a8e3d4f2f97669c482b11b3a408a1d918e5de068dab9e34a", + "budget_sha256": "496e34596e24418efb46e460126b7d67492137207be988525abda1397d70c539", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/async-gated.json b/prototypes/connections/results/2026-09-24-idle-work-binding/async-gated.json new file mode 100644 index 0000000..92f9838 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/async-gated.json @@ -0,0 +1,53 @@ +{ + "async_http": "snapshot-read-write-overlap-deadline-recovery-draining-shutdown", + "before_publication": { + "status": 200, + "etag": "\"1\"", + "body": "old" + }, + "after_publication": { + "status": 200, + "etag": "\"2\"", + "body": "oldafter" + }, + "shutdown_restore": { + "revision": "2", + "body": "oldafter" + }, + "deadline_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 16, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 1, + "releases": 3, + "ingress_prefix_copy_bytes": 13, + "borrowed_output_bytes": 16, + "server_structure_bytes": 43320 + }, + "shutdown_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 13, + "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": 13, + "borrowed_output_bytes": 5, + "server_structure_bytes": 43320 + }, + "test_source_sha256": "f38c9d3d784676ce815c276fbffc10e00f767aaa803793d8db311c19ef3b9085", + "gated_test_binary_sha256": "cb4e7209c53261b63647823aaa8ee408a860d1ccde6c85bc404d77a336128eb2" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/async-server-profile.json b/prototypes/connections/results/2026-09-24-idle-work-binding/async-server-profile.json new file mode 100644 index 0000000..a3a89b8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/async-server-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-read-write-document-http", + "scope": "Optional one-read/one-write-worker HTTP host on the recorded Linux AArch64/GCC 14 toolchain. Section limits exclude the two 128 KiB worker stacks, libc, heap, mapped files and kernel buffers. Default CLI and HTTP do not link this option. Inspect process RSS and VM separately.", + "budget_sha256": "acd82386572fc2f874ca2fee5f4eed68ee145cf990d7315a9f1aff06daf51995", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-connections-async": { + "sections": { + "text": 48561, + "data": 1180, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "9485151b8902d66052bb689e821b502810b66561e0a251d3ce091c4919b7c67e" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-check.json new file mode 100644 index 0000000..1f60a56 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "927e9b0f7bc672e661da0d82abe60aaf3743f6753fbb00e6b39035a3239bae22", + "archive_bytes": 12987, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-check.json new file mode 100644 index 0000000..0f372bf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "177bb19c9790712ccb8f75fd65eb59b02c68b526b5155d3b1e83a795c845f031", + "archive_bytes": 28719, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui-check.json new file mode 100644 index 0000000..5a6506c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "081e5fe6205760e343ca237b74ff969a10d169a5f51b3bcd07a50ac8c5e0ba23", + "archive_bytes": 47419, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui.tar.gz b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui.tar.gz Binary files differnew file mode 100644 index 0000000..de378e4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http.tar.gz b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http.tar.gz Binary files differnew file mode 100644 index 0000000..904f570 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-http.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui-check.json new file mode 100644 index 0000000..e1b39e7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "565d32fcccab9dfa10c8af7e21514fb1f4187b0bebac52053db02a6b75bf741f", + "archive_bytes": 31726, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..33a17ee --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/cli.tar.gz b/prototypes/connections/results/2026-09-24-idle-work-binding/cli.tar.gz Binary files differnew file mode 100644 index 0000000..739dda5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/core.json b/prototypes/connections/results/2026-09-24-idle-work-binding/core.json new file mode 100644 index 0000000..735a4df --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 6076, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5156\t 0\t 0\t 5156\t 1424\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "a02135602c3b18129cc9b80cb808994426d0c7869754eac1370c304294ad0be4", + "layout_binary_sha256": "db112a9cfd8deed80b9bfbff7628958fdb290cadab1efc7223d8d15ef4c4c389", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/default-profile.json b/prototypes/connections/results/2026-09-24-idle-work-binding/default-profile.json new file mode 100644 index 0000000..a997add --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/default-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35967, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3" + }, + "pure-connections": { + "sections": { + "text": 43938, + "data": 1068, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "1b7555764d059bca2be05e406d42305dfc68e3fd87813bcfcfaa390945467a8d" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer-profile.json b/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer-profile.json new file mode 100644 index 0000000..18dbba5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer-profile.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-document-peer", + "scope": "Review threshold for the explicitly selected authenticated document host on Linux AArch64/GCC 14. Includes linked application code but excludes the shared OpenSSL/libc implementations, process heap, stack, mappings and kernel buffers. Check measured whole-process RSS separately.", + "budget_sha256": "defe8efb4d544cdbf615de4d43d4ceba78fc7f1d958f245731202625052eb2ff", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-document-peer": { + "sections": { + "text": 47038, + "data": 1384, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libcrypto.so.3", + "libssl.so.3" + ], + "sha256": "3ec358021988217c212f5527f8947c301f39d6c2022f82c51deff515ce16acbf" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer.json b/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer.json new file mode 100644 index 0000000..05c62cc --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/document-peer.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7968, + "server_after_large_work_rss_kib": 9712 +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-memory.json b/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-memory.json new file mode 100644 index 0000000..641bc64 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1088, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1088 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "3d2bf30330330ca377d28db0a7d8f4598365c3cfba9c2d43dc5843246fff19b5", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-profile.json b/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-profile.json new file mode 100644 index 0000000..6097cdf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19404, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "3d2bf30330330ca377d28db0a7d8f4598365c3cfba9c2d43dc5843246fff19b5" + }, + "pure-connections-fixed": { + "sections": { + "text": 26549, + "data": 1028, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "2869469f4c40674c6904729141005c7705bbc5130019d1ce091ffb135bffe60a" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/full-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/full-check.json new file mode 100644 index 0000000..0e4d482 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/full-check.json @@ -0,0 +1,636 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 14227, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true, + "idle_slot_binding": true + }, + "execution_core_test_sha256": "80aaa8831c44101fc03364d393dd99ac1f3ca3fa861e2561447adad6d965ac0a", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "43bff520b326cfe3f17fa0e0b48efc27e393f6cb7bfff3051fe0115b23e4e5fb", + "editor_sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3", + "binary_sha256": "1b7555764d059bca2be05e406d42305dfc68e3fd87813bcfcfaa390945467a8d", + "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/store.c": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "ead1331794f8e4fb851e21eb7f7d3e7c408316d7481c51996e6ee62a81665a0c", + "prototypes/connections/runtime.h": "beff8a8a10f21c017987d69682bb279701d5627227aca60029a711b9e64cca85", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "8c275bf564a674f0f8232026d0d7612df48f10054af75a43af5ed0bce7653e24", + "prototypes/connections/server.c": "e8632667de6feaf4d495f87abb159775650e3e4eaf6713b9dfdc5f82807c2bf9", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "f9a0c9fa7d0f40d6d72bc72a429dd36e136e27207aa6b0c01df9505d82189b6d", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-idle-work-binding/package-reproducibility.json b/prototypes/connections/results/2026-09-24-idle-work-binding/package-reproducibility.json new file mode 100644 index 0000000..d9bb9fe --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/package-reproducibility.json @@ -0,0 +1,31 @@ +{ + "profile": "native-package-repeat-build", + "same_archive_bytes": "pass", + "variants": { + "source-cli": { + "bytes": 22595, + "sha256": "e770e93d15524070b254a46796667e6b0077bc0fa95fbf6ecca051742ec754ca", + "runs": 2 + }, + "cli": { + "bytes": 12987, + "sha256": "927e9b0f7bc672e661da0d82abe60aaf3743f6753fbb00e6b39035a3239bae22", + "runs": 2 + }, + "cli-http": { + "bytes": 28719, + "sha256": "177bb19c9790712ccb8f75fd65eb59b02c68b526b5155d3b1e83a795c845f031", + "runs": 2 + }, + "cli-tui": { + "bytes": 31726, + "sha256": "565d32fcccab9dfa10c8af7e21514fb1f4187b0bebac52053db02a6b75bf741f", + "runs": 2 + }, + "cli-http-tui": { + "bytes": 47419, + "sha256": "081e5fe6205760e343ca237b74ff969a10d169a5f51b3bcd07a50ac8c5e0ba23", + "runs": 2 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/precompact-budget-failure.txt b/prototypes/connections/results/2026-09-24-idle-work-binding/precompact-budget-failure.txt new file mode 100644 index 0000000..28b393a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/precompact-budget-failure.txt @@ -0,0 +1,9 @@ +Before fixed packages switched from strip --strip-debug to strip --strip-unneeded: + +python3 package_native.py --fixed --with-tui --output results/2026-09-24-idle-work-binding/cli-tui.tar.gz + +package_native.py: error: cli_tui archive grew to 35847 bytes (reviewed maximum 35840); no package was published + +This is the captured result on Linux AArch64 with the updated runtime and the +earlier packaging mode. The final package archive and relocation check are +cli-tui.tar.gz and cli-tui-check.json in this directory. diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/release.log b/prototypes/connections/results/2026-09-24-idle-work-binding/release.log new file mode 100644 index 0000000..5c3a893 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/release.log @@ -0,0 +1,800 @@ +python3 check_core.py --archive build/libpure-execution.a --layout build/core-layout --output "results/2026-09-24-idle-work-binding/core.json" +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 6076, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5156\t 0\t 0\t 5156\t 1424\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "a02135602c3b18129cc9b80cb808994426d0c7869754eac1370c304294ad0be4", + "layout_binary_sha256": "db112a9cfd8deed80b9bfbff7628958fdb290cadab1efc7223d8d15ef4c4c389", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror edit.c store.c store-port.c program.c runtime.c evaluate.c -o build/pure-edit +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror server.c store.c store-port.c flow.c program.c runtime.c evaluate.c -o build/pure-connections +python3 check_profile.py --build build --output "results/2026-09-24-idle-work-binding/default-profile.json" +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35967, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3" + }, + "pure-connections": { + "sections": { + "text": 43938, + "data": 1068, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "1b7555764d059bca2be05e406d42305dfc68e3fd87813bcfcfaa390945467a8d" + } + } +} +build/pure-compile examples/editor.pure build/embedded-plan.h +python3 check_profile.py --build build --budget fixed-budget.json --output "results/2026-09-24-idle-work-binding/fixed-profile.json" +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19404, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "3d2bf30330330ca377d28db0a7d8f4598365c3cfba9c2d43dc5843246fff19b5" + }, + "pure-connections-fixed": { + "sections": { + "text": 26549, + "data": 1028, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "2869469f4c40674c6904729141005c7705bbc5130019d1ce091ffb135bffe60a" + } + } +} +python3 check_micro_memory.py --build build --fixed --output "results/2026-09-24-idle-work-binding/fixed-memory.json" +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "fixed-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, the embedded prepared program, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1088, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1088 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "3d2bf30330330ca377d28db0a7d8f4598365c3cfba9c2d43dc5843246fff19b5", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror test-runtime.c program.c runtime.c evaluate.c store.c store-port.c -o build/test-runtime +cc -O2 -g -std=c17 -Wall -Wextra -Wpedantic -Werror -DPURE_STORE_TEST test-splice.c program.c runtime.c evaluate.c store.c store-port.c -o build/test-splice +build/test-flow +{"transfer":"partial-consumption-and-single-release","storage":"mapped-ranges-snapshot-budget-conflict-failure-uncertain-crash-overflow-empty"} +build/test-program +{"program":"prepare-views-preserve-guards-types-effects-and-atomic-install","boundary_semantic_comparisons":3584} +build/test-fields +{"fields":"named-layouts-unaligned-u32le-tail-dynamic-ranges-aliases-failures-limits","data_boundary_cases":48} +python3 check.py --server build/pure-connections --runtime-test build/test-runtime --splice-test build/test-splice --editor build/pure-edit --store-source store.c --output "results/2026-09-24-idle-work-binding/full-check.json" +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 14227, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true, + "idle_slot_binding": true + }, + "execution_core_test_sha256": "80aaa8831c44101fc03364d393dd99ac1f3ca3fa861e2561447adad6d965ac0a", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "43bff520b326cfe3f17fa0e0b48efc27e393f6cb7bfff3051fe0115b23e4e5fb", + "editor_sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3", + "binary_sha256": "1b7555764d059bca2be05e406d42305dfc68e3fd87813bcfcfaa390945467a8d", + "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/store.c": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "ead1331794f8e4fb851e21eb7f7d3e7c408316d7481c51996e6ee62a81665a0c", + "prototypes/connections/runtime.h": "beff8a8a10f21c017987d69682bb279701d5627227aca60029a711b9e64cca85", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "8c275bf564a674f0f8232026d0d7612df48f10054af75a43af5ed0bce7653e24", + "prototypes/connections/server.c": "e8632667de6feaf4d495f87abb159775650e3e4eaf6713b9dfdc5f82807c2bf9", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "f39a7c411afa80812aec6e7a7250cdc833e97cffef8d0cce25d960fe435784f6", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "f9a0c9fa7d0f40d6d72bc72a429dd36e136e27207aa6b0c01df9505d82189b6d", + "prototypes/connections/core-layout.c": "e3123bdf74edb99556b05d7cd3a36b6119ec30829541b0ddba2e23e5900b089d", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "prototypes/workbench/web/runtime.mjs": "9cf12270bb39b3d6ce0384c56df8bdfabfd9459d3375cc7819290a5fe3e5db54", + "prototypes/workbench/web/compiler.mjs": "349139612d1f7da5f75ef21c65aa697963b760cb35321150fc36f67f3b40adf1", + "prototypes/workbench/web/project.mjs": "8af4cd1a60bbb217c896be20c7c6b8d33989f2166d06e736bf1479d8ecdc8b35", + "prototypes/workbench/examples/direct.pure": "57177e5197d75a6185cd3ac5df2be920ddba9a9b0fcc2dddab3560b815249f0a" + } +} +python3 check_micro_memory.py --build build --output "results/2026-09-24-idle-work-binding/source-memory.json" +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "source-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/runtime-sanitized.json b/prototypes/connections/results/2026-09-24-idle-work-binding/runtime-sanitized.json new file mode 100644 index 0000000..0c1b5b0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/runtime-sanitized.json @@ -0,0 +1 @@ +{"runtime_tests":"passed","domain_bytes":200,"work_slot_bytes":224,"ticket_bytes":24,"prepared_program_bytes":680,"forwarding_chain":1024,"native_storage_pipeline":true,"idle_slot_binding":true} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/service-scale.json b/prototypes/connections/results/2026-09-24-idle-work-binding/service-scale.json new file mode 100644 index 0000000..b0fe27b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/service-scale.json @@ -0,0 +1,100 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "2869469f4c40674c6904729141005c7705bbc5130019d1ce091ffb135bffe60a", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1504, + "peak_rss_kib": 1680, + "idle_vm_kib": 2448, + "peak_vm_kib": 2528, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49184, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 390209, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "1b7555764d059bca2be05e406d42305dfc68e3fd87813bcfcfaa390945467a8d", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1552, + "peak_rss_kib": 1728, + "idle_vm_kib": 2464, + "peak_vm_kib": 2528, + "idle_threads": 1, + "peak_threads": 1, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49184, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 442020, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + }, + "source_async": { + "binary_sha256": "9485151b8902d66052bb689e821b502810b66561e0a251d3ce091c4919b7c67e", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1600, + "peak_rss_kib": 1776, + "idle_vm_kib": 2864, + "peak_vm_kib": 2944, + "idle_threads": 3, + "peak_threads": 3, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 40993, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 270839, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 43320 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli-check.json b/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli-check.json new file mode 100644 index 0000000..729710b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "e770e93d15524070b254a46796667e6b0077bc0fa95fbf6ecca051742ec754ca", + "archive_bytes": 22595, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli.tar.gz b/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli.tar.gz Binary files differnew file mode 100644 index 0000000..4981e60 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/source-cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/source-memory.json b/prototypes/connections/results/2026-09-24-idle-work-binding/source-memory.json new file mode 100644 index 0000000..aa663a6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/source-memory.json @@ -0,0 +1,15 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "variant": "source-program", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "dfe2b9d03c87eed8cde0d598b182dfc8aa4bba85eca1b64849c77e9c4ef302d3", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/tui-profile.json b/prototypes/connections/results/2026-09-24-idle-work-binding/tui-profile.json new file mode 100644 index 0000000..ead0b8f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/tui-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app, including the bounded document browser. The raised text ceiling reviews its measured 2,541-byte addition; default CLI and core ceilings are unchanged. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS, including 1,024-key browsing, is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "4d3097c9ec3c0139cbdd82161a74d610c15d31c6ee5625101410627664f925e7", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 31463, + "data": 1036, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "297acfbeee36de36f4651e3ae5438c8a91604d97be9bc753fe2af380887b8693" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-idle-work-binding/tui.json b/prototypes/connections/results/2026-09-24-idle-work-binding/tui.json new file mode 100644 index 0000000..d482848 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-idle-work-binding/tui.json @@ -0,0 +1,22 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, resized and last key opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1808, + "one_mib_peak_rss_kib": 2832, + "one_thousand_key_list_peak_rss_kib": 1888, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "297acfbeee36de36f4651e3ae5438c8a91604d97be9bc753fe2af380887b8693" +} diff --git a/prototypes/connections/results/2026-09-24-in-process-routing/mirror-ext4.json b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-ext4.json new file mode 100644 index 0000000..0b1454e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-ext4.json @@ -0,0 +1,56 @@ +{ + "passed": true, + "source_body_sha256": "06cb5e32505f66804f6e8c79e3230105ca4769444e1389ee36f6f11fd61f2d3a", + "mirror_sha256": "3c22ef868198f805f4cd1fe7c9c55b69f32c599f5076444f9af4f1297cb874e2", + "editor_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "source_one_mib_to_two_domains": { + "source_revision": "1", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "1" + }, + { + "index": 1, + "status": "ok", + "revision": "1" + } + ] + }, + "independent_partial_conflict": { + "source_revision": "2", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "2" + }, + { + "index": 1, + "status": "conflict", + "revision": "1" + } + ] + }, + "conflict_recovered_revision": "2", + "empty_body_forwarded": true, + "target_budget_checked_before_document_write": true, + "all_admissions_before_document_writes": true, + "source_failure_before_target_creation": true, + "one_mib_two_target_peak_rss": { + "exit_code": 0, + "max_rss_kib": 2144, + "raw_max_rss": 2144, + "raw_unit": "KiB" + }, + "one_mib_eight_targets_verified": true, + "one_mib_eight_target_peak_rss": { + "exit_code": 0, + "max_rss_kib": 2144, + "raw_max_rss": 2144, + "raw_unit": "KiB" + } +} diff --git a/prototypes/connections/results/2026-09-24-in-process-routing/mirror-sanitized.json b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-sanitized.json new file mode 100644 index 0000000..12f3550 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-sanitized.json @@ -0,0 +1,46 @@ +{ + "passed": true, + "source_body_sha256": "06cb5e32505f66804f6e8c79e3230105ca4769444e1389ee36f6f11fd61f2d3a", + "mirror_sha256": "0c25b281c3b746f74a451bb3eefb2345c98f3ba01a08d5751347246f5bc48ee1", + "editor_sha256": "6d0b6b2b06ed25e0fc0db229091d19fe56de01bae5f5a25c72ee228589412541", + "source_one_mib_to_two_domains": { + "source_revision": "1", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "1" + }, + { + "index": 1, + "status": "ok", + "revision": "1" + } + ] + }, + "independent_partial_conflict": { + "source_revision": "2", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "2" + }, + { + "index": 1, + "status": "conflict", + "revision": "1" + } + ] + }, + "conflict_recovered_revision": "2", + "empty_body_forwarded": true, + "target_budget_checked_before_document_write": true, + "all_admissions_before_document_writes": true, + "source_failure_before_target_creation": true, + "one_mib_two_target_peak_rss": null, + "one_mib_eight_targets_verified": true, + "one_mib_eight_target_peak_rss": null +} diff --git a/prototypes/connections/results/2026-09-24-in-process-routing/mirror-tmp.json b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-tmp.json new file mode 100644 index 0000000..01f80dd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-in-process-routing/mirror-tmp.json @@ -0,0 +1,56 @@ +{ + "passed": true, + "source_body_sha256": "06cb5e32505f66804f6e8c79e3230105ca4769444e1389ee36f6f11fd61f2d3a", + "mirror_sha256": "3c22ef868198f805f4cd1fe7c9c55b69f32c599f5076444f9af4f1297cb874e2", + "editor_sha256": "04974e2d3c4d7f5fb22aff67e7399aa2ebcbd85eb24da0c0e41f3ef047d5ddec", + "source_one_mib_to_two_domains": { + "source_revision": "1", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "1" + }, + { + "index": 1, + "status": "ok", + "revision": "1" + } + ] + }, + "independent_partial_conflict": { + "source_revision": "2", + "bytes": 1048576, + "targets": [ + { + "index": 0, + "status": "ok", + "revision": "2" + }, + { + "index": 1, + "status": "conflict", + "revision": "1" + } + ] + }, + "conflict_recovered_revision": "2", + "empty_body_forwarded": true, + "target_budget_checked_before_document_write": true, + "all_admissions_before_document_writes": true, + "source_failure_before_target_creation": true, + "one_mib_two_target_peak_rss": { + "exit_code": 0, + "max_rss_kib": 2128, + "raw_max_rss": 2128, + "raw_unit": "KiB" + }, + "one_mib_eight_targets_verified": true, + "one_mib_eight_target_peak_rss": { + "exit_code": 0, + "max_rss_kib": 2128, + "raw_max_rss": 2128, + "raw_unit": "KiB" + } +} diff --git a/prototypes/connections/results/2026-09-24-micro-default/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-micro-default/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..20674be --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-default/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-micro-default/source-package-check.json b/prototypes/connections/results/2026-09-24-micro-default/source-package-check.json new file mode 100644 index 0000000..bfd1940 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-default/source-package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "26bdd3f8768bc5c66c282f989026ac4d9de64398ed3c31e6dc8abbc3d84491d0", + "archive_bytes": 338097, + "source_files": 173, + "checks": [ + "every extracted source file matches the manifest", + "clean default build contains only the core, parser-free CLI and its build-time compiler", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli-check.json b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli-check.json new file mode 100644 index 0000000..da74f01 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "3e5b064bbf81b13be24494a5d5a9bd55fa5ea057d2d5614965f3e5556babf9ab", + "archive_bytes": 14692, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli.tar.gz b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli.tar.gz Binary files differnew file mode 100644 index 0000000..7467329 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http-check.json b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http-check.json new file mode 100644 index 0000000..1d8281a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "d7f35c8add62551bfaf9819e4edd64b8b3f25dc084b2c519d172d4344f66b160", + "archive_bytes": 32548, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http.tar.gz b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http.tar.gz Binary files differnew file mode 100644 index 0000000..4ce4a1a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http.tar.gz diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui-check.json b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui-check.json new file mode 100644 index 0000000..e247b61 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "6dc051ebc74eaf460c97e7259d0d408b2424ae4cfd19078a4227e12a1382faef", + "archive_bytes": 51424, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui.tar.gz b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui.tar.gz Binary files differnew file mode 100644 index 0000000..529f2b6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_http_tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui-check.json b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui-check.json new file mode 100644 index 0000000..769e922 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "daf25aaf480559f4b9cfffa13ce91dcf1b84f008f0323ea59d00566dcfdc5d89", + "archive_bytes": 33761, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui.tar.gz b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui.tar.gz Binary files differnew file mode 100644 index 0000000..905c339 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-package-boundary/cli_tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-micro-profile/default-host-budget.json b/prototypes/connections/results/2026-09-24-micro-profile/default-host-budget.json new file mode 100644 index 0000000..9ea33b6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-micro-profile/default-host-budget.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "pure-connections": { + "sections": { + "text": 42761, + "data": 1040, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-mirror-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-mirror-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..7f3ea7a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-mirror-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-mirror-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-mirror-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..8fdefbb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-mirror-source/source-check-linux-aarch64.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "7da3049ad80d1f54770524ba04583b5e2aef2e59e6d252d347947d32e7327f0b", + "archive_bytes": 332792, + "source_files": 170, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-final.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-final.json new file mode 100644 index 0000000..253130f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-final.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 135936, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-repeated.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-repeated.json new file mode 100644 index 0000000..541ec3e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan-repeated.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_rss_samples_kib": [362224, 385120], "large_window_rss_kib": 118016, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan.json new file mode 100644 index 0000000..605d189 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-asan.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 142944, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-final.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-final.json new file mode 100644 index 0000000..4365549 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-final.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 43936, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-repeated.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-repeated.json new file mode 100644 index 0000000..7d36eca --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow-repeated.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_rss_samples_kib": [46672, 48960], "large_window_rss_kib": 43952, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow.json b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow.json new file mode 100644 index 0000000..4f6d78b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 43952, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/hybrid-gtk-flow.json b/prototypes/connections/results/2026-09-24-multi-document-window/hybrid-gtk-flow.json new file mode 100644 index 0000000..8d132e5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/hybrid-gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 44032, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-recheck.json b/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-recheck.json new file mode 100644 index 0000000..dcae905 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-recheck.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "b72bc0b5321fa6efcc0ca8fbc3e328bc220333b0af134b1e75b7c5b517999a72", + "archive_bytes": 48934, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted archived GTK window edits the same document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-repeated.json b/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-repeated.json new file mode 100644 index 0000000..dcae905 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/legacy-package-repeated.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "b72bc0b5321fa6efcc0ca8fbc3e328bc220333b0af134b1e75b7c5b517999a72", + "archive_bytes": 48934, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted archived GTK window edits the same document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/package-final.json b/prototypes/connections/results/2026-09-24-multi-document-window/package-final.json new file mode 100644 index 0000000..fd4343a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/package-final.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "59a5110ef5dd186c7ba9e5c3fbe74217c10c98014c2823aabf141d3f354f8dd6", + "archive_bytes": 49746, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted GTK window switches documents without losing drafts" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/package-repeated.json b/prototypes/connections/results/2026-09-24-multi-document-window/package-repeated.json new file mode 100644 index 0000000..fd4343a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/package-repeated.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "59a5110ef5dd186c7ba9e5c3fbe74217c10c98014c2823aabf141d3f354f8dd6", + "archive_bytes": 49746, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted GTK window switches documents without losing drafts" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/package.json b/prototypes/connections/results/2026-09-24-multi-document-window/package.json new file mode 100644 index 0000000..9616e80 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "40b7fef5e3202840794c91c5eb9c7e5592244dab443ce3597cc6fb9745b97746", + "archive_bytes": 49722, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted GTK window and selected external host edit the same document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-final-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-final-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..ded86dd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-final-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..1633c3d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/pure-document-window-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-fresh-buffer.json b/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-fresh-buffer.json new file mode 100644 index 0000000..0e0b7be --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-fresh-buffer.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_rss_samples_kib": [48416, 49472, 51216, 52208, 54176, 55504, 57728, 59568], "large_window_rss_kib": 43968, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-initial.json b/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-initial.json new file mode 100644 index 0000000..71658ed --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/switch-memory-initial.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_rss_samples_kib": [47328, 48048, 49888, 51744, 53840, 55824, 56864, 58752], "large_window_rss_kib": 43920, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-multi-document-window/window-profile.json b/prototypes/connections/results/2026-09-24-multi-document-window/window-profile.json new file mode 100644 index 0000000..d47571f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-multi-document-window/window-profile.json @@ -0,0 +1,25 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-native-document-authoring", + "scope": "Review ceiling for the selected GTK document-and-definition host on Linux AArch64/GCC 14. Linked code sections and direct dependencies only; the measured 44 MiB whole-process RSS and shared GTK stack remain separate costs.", + "budget_sha256": "27ee292bf3fe9a15d27942f64cff01dfccde0ef5054acbb53faf76208d89d110", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-window": { + "sections": { + "text": 49882, + "data": 1504, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libgio-2.0.so.0", + "libglib-2.0.so.0", + "libgobject-2.0.so.0", + "libgtk-4.so.1" + ], + "sha256": "4d9806ce6f82413d17bc93452d9fd3bcc2b18272d15a2edaf81f8888e73b4e02" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-memory.json b/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-memory.json new file mode 100644 index 0000000..84c7eb4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-memory.json @@ -0,0 +1,43 @@ +{ + "passed": true, + "failures": [], + "profile": "portable-measurement-no-budget", + "variant": "fixed-program", + "scope": "Child-process peak RSS for native CLI document creation, read and one-byte revision edit; includes libc and the selected program form, excludes filesystem cache, kernel buffers, TLS, GUI and other apps. No cross-platform memory ceiling is asserted.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1072, + "one_mib_splice_rss_kib": 1104 + }, + "raw_samples": { + "small_edit_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1104, + "raw_max_rss": 1104, + "raw_unit": "KiB" + }, + "one_mib_edit_rss_kib": { + "exit_code": 0, + "max_rss_kib": 2128, + "raw_max_rss": 2128, + "raw_unit": "KiB" + }, + "one_mib_show_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1072, + "raw_max_rss": 1072, + "raw_unit": "KiB" + }, + "one_mib_splice_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1104, + "raw_max_rss": 1104, + "raw_unit": "KiB" + } + }, + "budget_sha256": null, + "binary_sha256": "435e087adf62253078678b42c169426adcaddd514c78717e3be570f88dbcc248", + "binary_bytes": 135344, + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-tui.json b/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-tui.json new file mode 100644 index 0000000..05a5d34 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-checkpoint/fixed-tui.json @@ -0,0 +1,24 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": null, + "one_mib_peak_rss_kib": null, + "one_thousand_key_list_peak_rss_kib": null, + "one_mib_peak_rss_limit_kib": 0, + "portable": true, + "terminal_locale": "C.UTF-8", + "binary_sha256": "d49431543f233d7979a3cdc07ec2c9e938acf014df6257f0165f76f30a0e248c" +} diff --git a/prototypes/connections/results/2026-09-24-native-checkpoint/source-memory.json b/prototypes/connections/results/2026-09-24-native-checkpoint/source-memory.json new file mode 100644 index 0000000..3f45eb1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-checkpoint/source-memory.json @@ -0,0 +1,43 @@ +{ + "passed": true, + "failures": [], + "profile": "portable-measurement-no-budget", + "variant": "source-program", + "scope": "Child-process peak RSS for native CLI document creation, read and one-byte revision edit; includes libc and the selected program form, excludes filesystem cache, kernel buffers, TLS, GUI and other apps. No cross-platform memory ceiling is asserted.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1056, + "one_mib_splice_rss_kib": 1104 + }, + "raw_samples": { + "small_edit_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1056, + "raw_max_rss": 1056, + "raw_unit": "KiB" + }, + "one_mib_edit_rss_kib": { + "exit_code": 0, + "max_rss_kib": 2080, + "raw_max_rss": 2080, + "raw_unit": "KiB" + }, + "one_mib_show_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1056, + "raw_max_rss": 1056, + "raw_unit": "KiB" + }, + "one_mib_splice_rss_kib": { + "exit_code": 0, + "max_rss_kib": 1104, + "raw_max_rss": 1104, + "raw_unit": "KiB" + } + }, + "budget_sha256": null, + "binary_sha256": "23ebc0ce1df9c49216511bee658fd5b9e373509e296bd2c2f8e76e6d3081cded", + "binary_bytes": 178336, + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-native-checkpoint/source-tui.json b/prototypes/connections/results/2026-09-24-native-checkpoint/source-tui.json new file mode 100644 index 0000000..0e2c5ad --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-checkpoint/source-tui.json @@ -0,0 +1,27 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": null, + "one_mib_peak_rss_kib": null, + "one_thousand_key_list_peak_rss_kib": null, + "one_mib_peak_rss_limit_kib": 0, + "portable": true, + "terminal_locale": "C.UTF-8", + "binary_sha256": "8e5ac649f2ab9a4dec02d32153f70aa13d97266cca1c9a942ba8fba27d04c8cf" +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/full-package-check.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/full-package-check.json new file mode 100644 index 0000000..05264e9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/full-package-check.json @@ -0,0 +1,20 @@ +{ + "archive_sha256": "831b049f8f4c38bc219bab7cd50138da862593e1e1c768b16705816438cac95f", + "archive_bytes": 100900, + "selected_hosts": [ + "pure-edit", + "pure-connections", + "pure-document-peer", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted HTTP host updates the same document as the native CLI", + "extracted C/Python TLS peers share a 1 MiB document and recover", + "extracted GTK window and selected external host edit the same document" + ], + "peer_idle_rss_kib": 7840, + "peer_after_large_work_rss_kib": 9520 +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow-final.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow-final.json new file mode 100644 index 0000000..4bac323 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow-final.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 43792, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow.json new file mode 100644 index 0000000..3372af3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 44032, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized-final.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized-final.json new file mode 100644 index 0000000..b65d2a2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized-final.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 147696, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized.json new file mode 100644 index 0000000..fe540f2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/gtk-sanitized.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 147952, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/http-only-package-check.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/http-only-package-check.json new file mode 100644 index 0000000..06ca49e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/http-only-package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "a6c81aaf7ffef0cf1604e727f318d40bd51989ccb4b350793723ad3434d49148", + "archive_bytes": 47527, + "selected_hosts": [ + "pure-edit", + "pure-connections" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted HTTP host updates the same document as the native CLI" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-memory.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-memory.json new file mode 100644 index 0000000..2bf6a8d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-memory.json @@ -0,0 +1,14 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1056, + "one_mib_edit_rss_kib": 2080, + "one_mib_show_rss_kib": 1104 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-package-check.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-package-check.json new file mode 100644 index 0000000..12dc47f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/micro-package-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "b010450dfe11be3196e6b57071f9ac6b79e1fd79d5d5b2a314ea8780096ad7c6", + "archive_bytes": 22482, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/peer-only-package-check.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/peer-only-package-check.json new file mode 100644 index 0000000..acf8029 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/peer-only-package-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "18a171c2f1a7b75f4fe030d6a087d6c2f3471fca731e2c9d7f103940315852e5", + "archive_bytes": 49186, + "selected_hosts": [ + "pure-edit", + "pure-document-peer" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted C/Python TLS peers share a 1 MiB document and recover" + ], + "peer_idle_rss_kib": 7840, + "peer_after_large_work_rss_kib": 9520 +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-authoring-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-authoring-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..0e64e74 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-authoring-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-http-only-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-http-only-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..3c32eb4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-http-only-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-micro-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-micro-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..9b3fddc --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-micro-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-peer-only-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-peer-only-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..d2b11d3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-peer-only-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-window-only-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-window-only-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..911348b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/pure-document-window-only-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/window-authoring.png b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-authoring.png Binary files differnew file mode 100644 index 0000000..9a7945c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-authoring.png diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/window-budget.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-budget.json new file mode 100644 index 0000000..547b408 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-budget.json @@ -0,0 +1,25 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-native-document-authoring", + "scope": "Review ceiling for the selected GTK document-and-definition host on Linux AArch64/GCC 14. Linked code sections and direct dependencies only; the measured 44 MiB whole-process RSS and shared GTK stack remain separate costs.", + "budget_sha256": "27ee292bf3fe9a15d27942f64cff01dfccde0ef5054acbb53faf76208d89d110", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-window": { + "sections": { + "text": 48247, + "data": 1456, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libgio-2.0.so.0", + "libglib-2.0.so.0", + "libgobject-2.0.so.0", + "libgtk-4.so.1" + ], + "sha256": "b16b34dfb1b05bf6437b022992fa185787631734f76043149c410b0caf165895" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-native-definition-authoring/window-only-package-check.json b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-only-package-check.json new file mode 100644 index 0000000..f0fa500 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-definition-authoring/window-only-package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "b72bc0b5321fa6efcc0ca8fbc3e328bc220333b0af134b1e75b7c5b517999a72", + "archive_bytes": 48934, + "selected_hosts": [ + "pure-edit", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted GTK window and selected external host edit the same document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-document-window/build.json b/prototypes/connections/results/2026-09-24-native-document-window/build.json new file mode 100644 index 0000000..52bcb73 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/build.json @@ -0,0 +1,16 @@ +{ + "host": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "compiler": "cc (Debian 14.2.0-19) 14.2.0", + "gtk": "4.18.6", + "binary_sha256": { + "build/pure-edit": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "build/pure-connections": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f", + "build/pure-edit-window": "97b12dbfb3af430bb13547d4d0f7791ab35a265fe9e22ca6265c1b4ec5d3165f" + }, + "sources": { + "edit-window.c": "b96687857e2fd66fc25d5ba8e3d91f06a3eae060a4161c30259174c15ee13603", + "check_window.py": "208cc9bb56123c7db0596dd1c97c6da0026f3bc9f0bb9f545f2cbb1acf30b712", + "Makefile": "d7fb2dce9f4b6854207eeb8b6b6e8b2dc294c6c1faf18bf3f486344834fae6d2", + "examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a" + } +} diff --git a/prototypes/connections/results/2026-09-24-native-document-window/connection-check.json b/prototypes/connections/results/2026-09-24-native-document-window/connection-check.json new file mode 100644 index 0000000..221db03 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/connection-check.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "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": 31, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115416, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "07c328c98769d02d544aea2fc5389048a7620c00d9b3ea49b9c7ba45e33984b1", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "0e4ec2c78334af2056af376a6ba9aef7416a34c6d4fbd9082f4abb640e6de28c", + "editor_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03", + "binary_sha256": "e76d4c0a721c9ceb7142db0edbaa1f5c44bd78a2c55029658109c83e8076875f", + "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": "44702e9414c26d1ee336b2e36e6713560013c2c8d0091a8900042e2468f88015", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "5d08639b5a71572553bda47cbd35562805d601aaa81f20731cd9154768ec025e", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "e1735316769b4ce4d5904b9587ccfa3ebeafa26517c53dba4491191d58c97b8e", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "0cb23dd0cff6bd0c9dc154f6f9ec253af72a23d93bf1a3797cd45ddd6091c9d0", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-native-document-window/core-budget.json b/prototypes/connections/results/2026-09-24-native-document-window/core-budget.json new file mode 100644 index 0000000..8c0caf7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/core-budget.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Release object sections and C bookkeeping, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5932, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5012\t 0\t 0\t 5012\t 1394\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "d56c50e38c6c0949cfad069d34568da2ac45b1d60097cf9e7c4bf804f44c8128", + "layout_binary_sha256": "47cf1803007fc1ec85f119bd46195ad39d9f01ef0146bf5cc45e1af31eb18098", + "budget_sha256": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-native-document-window/dependencies.txt b/prototypes/connections/results/2026-09-24-native-document-window/dependencies.txt new file mode 100644 index 0000000..e61b79d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/dependencies.txt @@ -0,0 +1,10 @@ +pure-edit +0x0000000000000001 (NEEDED) Shared library: [libc.so.6] +pure-connections +0x0000000000000001 (NEEDED) Shared library: [libc.so.6] +pure-edit-window +0x0000000000000001 (NEEDED) Shared library: [libgtk-4.so.1] +0x0000000000000001 (NEEDED) Shared library: [libgio-2.0.so.0] +0x0000000000000001 (NEEDED) Shared library: [libgobject-2.0.so.0] +0x0000000000000001 (NEEDED) Shared library: [libglib-2.0.so.0] +0x0000000000000001 (NEEDED) Shared library: [libc.so.6] diff --git a/prototypes/connections/results/2026-09-24-native-document-window/gtk-flow.json b/prototypes/connections/results/2026-09-24-native-document-window/gtk-flow.json new file mode 100644 index 0000000..1e4aa66 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/gtk-flow.json @@ -0,0 +1 @@ +{"empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "large_window_rss_kib": 44208, "one_mib_document_one_byte_edit": "pass", "profile": "optional-native-document-window", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-document-window/gtk-sanitized.json b/prototypes/connections/results/2026-09-24-native-document-window/gtk-sanitized.json new file mode 100644 index 0000000..babeafb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/gtk-sanitized.json @@ -0,0 +1 @@ +{"empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "large_window_rss_kib": 143168, "one_mib_document_one_byte_edit": "pass", "profile": "optional-native-document-window", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-native-document-window/size.txt b/prototypes/connections/results/2026-09-24-native-document-window/size.txt new file mode 100644 index 0000000..dc609de --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/size.txt @@ -0,0 +1,6 @@ + text data bss dec hex filename + 5012 0 0 5012 1394 runtime.o (ex prototypes/connections/build/libpure-execution.a) + 920 0 0 920 398 evaluate.o (ex prototypes/connections/build/libpure-execution.a) + 35807 944 16 36767 8f9f prototypes/connections/build/pure-edit + 42761 1040 16 43817 ab29 prototypes/connections/build/pure-connections + 43308 1376 16 44700 ae9c prototypes/connections/build/pure-edit-window diff --git a/prototypes/connections/results/2026-09-24-native-document-window/stripped.txt b/prototypes/connections/results/2026-09-24-native-document-window/stripped.txt new file mode 100644 index 0000000..6eb0197 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/stripped.txt @@ -0,0 +1,3 @@ +pure-edit elf_bytes=177384 stripped_bytes=67904 +pure-connections elf_bytes=199080 stripped_bytes=68000 +pure-edit-window elf_bytes=199184 stripped_bytes=68280 diff --git a/prototypes/connections/results/2026-09-24-native-document-window/window.png b/prototypes/connections/results/2026-09-24-native-document-window/window.png Binary files differnew file mode 100644 index 0000000..809f57a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-document-window/window.png diff --git a/prototypes/connections/results/2026-09-24-native-save/fixed-cli-package-check.json b/prototypes/connections/results/2026-09-24-native-save/fixed-cli-package-check.json new file mode 100644 index 0000000..8e62057 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/fixed-cli-package-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "2f491df77c2d5c39c838f7235bcfcaadbd89b981e5b40624e624553a74604421", + "archive_bytes": 12966, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-save/fixed-package-check.json b/prototypes/connections/results/2026-09-24-native-save/fixed-package-check.json new file mode 100644 index 0000000..97dafe5 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/fixed-package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "629dca525ec849fe9ab822ca5ff931477438534235ab00f7d34f873ee18b41e7", + "archive_bytes": 32072, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-save/flat-fixed-tui.json b/prototypes/connections/results/2026-09-24-native-save/flat-fixed-tui.json new file mode 100644 index 0000000..97fb4d7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/flat-fixed-tui.json @@ -0,0 +1,24 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1808, + "one_mib_peak_rss_kib": 2832, + "one_thousand_key_list_peak_rss_kib": 1856, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "e8b224e3ce5f5d83d597418d4027ec36dd44bfe84d17f6e9224135dc457316c1" +} diff --git a/prototypes/connections/results/2026-09-24-native-save/flat-package-check.json b/prototypes/connections/results/2026-09-24-native-save/flat-package-check.json new file mode 100644 index 0000000..f3db134 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/flat-package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "0f8d1620cf36c968174687e7ca350d97d8f3101ce38bb70239b673ab104fe6f6", + "archive_bytes": 51117, + "selected_hosts": [ + "pure-edit", + "pure-edit-tui" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "relocated source-loaded TUI and CLI share UTF-8, large and many-document paths" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-save/flat-source-tui.json b/prototypes/connections/results/2026-09-24-native-save/flat-source-tui.json new file mode 100644 index 0000000..aa7d0b9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/flat-source-tui.json @@ -0,0 +1,27 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1824, + "one_mib_peak_rss_kib": 3216, + "one_thousand_key_list_peak_rss_kib": 1936, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "46bdfde2b8997213bf69c612bd63913330859fd46d77ecb9b7fca99e552ee143" +} diff --git a/prototypes/connections/results/2026-09-24-native-save/hybrid-fixed-tui.json b/prototypes/connections/results/2026-09-24-native-save/hybrid-fixed-tui.json new file mode 100644 index 0000000..5b5e97c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/hybrid-fixed-tui.json @@ -0,0 +1,24 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1840, + "one_mib_peak_rss_kib": 3184, + "one_thousand_key_list_peak_rss_kib": 1904, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "09303ec33d07c12b07834b07fb5eeb836005c23fcb3a6b6cc2683631a9d4b13c" +} diff --git a/prototypes/connections/results/2026-09-24-native-save/hybrid-source-tui.json b/prototypes/connections/results/2026-09-24-native-save/hybrid-source-tui.json new file mode 100644 index 0000000..2da02ec --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/hybrid-source-tui.json @@ -0,0 +1,27 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1824, + "one_mib_peak_rss_kib": 3200, + "one_thousand_key_list_peak_rss_kib": 1936, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "a3812efdf7d22cc98369eb878b91a1e5341d5bed18fd99b9fe4264177fd41973" +} diff --git a/prototypes/connections/results/2026-09-24-native-save/package-check.json b/prototypes/connections/results/2026-09-24-native-save/package-check.json new file mode 100644 index 0000000..e57b8ca --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/package-check.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "b3c156eb7880ee4cb91b3fe52854cb5069ef035d082b7d539fa2a79592247ec5", + "archive_bytes": 57788, + "selected_hosts": [ + "pure-edit", + "pure-edit-tui" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "selected storage keeps small values inline and maps large values", + "editing the packaged .pure source changes the executed reply", + "relocated source-loaded TUI and CLI share UTF-8, large and many-document paths" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..9f81ca0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..5114f58 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/pure-document-fixed-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-save/pure-document-flat-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-save/pure-document-flat-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..59abe7f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/pure-document-flat-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-save/pure-document-hybrid-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-native-save/pure-document-hybrid-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..313c49d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/pure-document-hybrid-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-save/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-native-save/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..9f29727 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-save/source-package-check.json b/prototypes/connections/results/2026-09-24-native-save/source-package-check.json new file mode 100644 index 0000000..4c93ea8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-save/source-package-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "b258788cc9f90d8e6eccc964c8c9f4e2c5460df3dfbb9da07268da07799691e4", + "archive_bytes": 333925, + "source_files": 171, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean selected page store runs the source-loaded TUI and relocatable CLI+TUI package", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-native-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-native-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..40d2e5e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-native-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-native-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..8fc4225 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-native-source/source-check-linux-aarch64.json @@ -0,0 +1,13 @@ +{ + "archive_sha256": "39a450a8a2e2cbfad39b95b9e91ede9899f2530cf9574ebff5d7b3748b9b0c3a", + "archive_bytes": 325436, + "source_files": 166, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..26c92da --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..b20331e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-linux-aarch64.tar.gz.check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "8a6dc8691eca1eb0a2b6daaccfd9e58c64777e5b42a4bb73c4b21b72375b880c", + "archive_bytes": 12886, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..f208ccf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..2ca530a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-fixed-cli-tui-linux-aarch64.tar.gz.check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "5cb94eed37f7c7acbaad85d20715f2cc42d84d5d0de3af4788db284c9ede3e9e", + "archive_bytes": 31965, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..b026d9a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..fcc2a79 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-linux-aarch64.tar.gz.check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "d83048e6804252857ae80b6ee843fe8c9346802b9f1f9df30188845338e227af", + "archive_bytes": 22655, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..101a1fa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json new file mode 100644 index 0000000..1f72434 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-document-source-cli-tui-linux-aarch64.tar.gz.check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "6f7a534dca12bb670cb4a4f6d28e3d9e569f0667015415c3c0473b5a0f909361", + "archive_bytes": 51005, + "selected_hosts": [ + "pure-edit", + "pure-edit-tui" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "relocated source-loaded TUI and CLI share UTF-8, large and many-document paths" + ] +} diff --git a/prototypes/connections/results/2026-09-24-opaque-core/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-opaque-core/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..ce3e1b1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-opaque-core/source-package-check.json b/prototypes/connections/results/2026-09-24-opaque-core/source-package-check.json new file mode 100644 index 0000000..898639f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-opaque-core/source-package-check.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "c0c3983f25088eb75ccf621cee264c210ea99d888f675e3bbb2ca2c59ea3246e", + "archive_bytes": 334682, + "source_files": 171, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/app-asan.json b/prototypes/connections/results/2026-09-24-paged-storage/app-asan.json new file mode 100644 index 0000000..121a548 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/app-asan.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11679, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 1, + "commits": 0, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 2, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 114, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "52e81f7277f11cddc8178a6be1f0693a39210445b7d338e3907c50c2f696f6b6", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "4e4bf4bd1f107510521703d49b207cad40b4d05e4d391fb997a34c1128cf2739", + "editor_sha256": "d2e3720a54597506cbe1262fdd6ef380a7bf3a7e7ef9012f4ae7dabf07fc781d", + "binary_sha256": "14e8472baf7c536c40dfbd1197b4e8be03099d1829e361456370ff678bb31cb6", + "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/store-paged.c": "98ac90368b5be8fba5f9cd998145739d13c0003b822f048e4acbc517228e147e", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "26c44763b13cbe80800ece1c6f1ab6c330d1d201edb9ea421e67cf24e8546fd2", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-paged-storage/app-release.json b/prototypes/connections/results/2026-09-24-paged-storage/app-release.json new file mode 100644 index 0000000..bdaf2f7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/app-release.json @@ -0,0 +1,635 @@ +{ + "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", + "text client uses character selection; named numeric fields select and persist the matching UTF-8 bytes", + "truncated layouts and invalid data-dependent ranges never change storage; empty and end selections remain valid", + "changed declaration order and unaligned numeric fields determine access; aliases add no dynamic steps", + "unused dynamic field selection retains its validation before the write effect", + "named variable tail reads use the same file version and charge only the selected suffix", + "data-dependent response keeps and charges the original input through partial transmission", + "native authored byte edit and fresh native process restore UTF-8 content", + "native stale revision and invalid byte position preserve the committed document", + "native convenience editor derives unaligned reordered field positions from the authored program", + "raw native apply runs another authored input contract without changing the executor", + "native CLI and external HTTP share revisions and the same authored storage effect in both directions", + "partial or invalid edit input never modifies the stored document", + "two processes cannot both splice the same expected document revision", + "1 MiB document changes with 11 input credits; larger input is rejected before an effect" + ], + "prepared_connections": { + "excerpt": { + "read": { + "to_end": false, + "offset": 32, + "length": 16, + "minimum_source_bytes": 80 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 2, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "write": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 5, + "length": 4, + "minimum_source_bytes": 12 + }, + "reply_input": true, + "view_definitions": 3, + "range_definitions": 2, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "field_connections": { + "selection": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 8 + }, + "reply_input": true, + "view_definitions": 2, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 8, + "length": 0, + "to_end": true + }, + "offset": { + "value": 0, + "from_u32le": true + }, + "length": { + "value": 4, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "reordered": { + "read": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "write": { + "to_end": false, + "offset": 0, + "length": 0, + "minimum_source_bytes": 11 + }, + "reply_input": true, + "view_definitions": 4, + "range_definitions": 1, + "runtime_view_steps": 1, + "write_step": 1, + "reply_step": 1, + "prepared_program_bytes": 680, + "steps": [ + { + "source_step": 0, + "source": { + "offset": 11, + "length": 0, + "to_end": true + }, + "offset": { + "value": 7, + "from_u32le": true + }, + "length": { + "value": 3, + "from_u32le": true + } + } + ], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + }, + "field_get": { + "read": { + "to_end": true, + "offset": 11, + "length": 0, + "minimum_source_bytes": 11 + }, + "write": { + "to_end": true, + "offset": 0, + "length": 0, + "minimum_source_bytes": 0 + }, + "reply_input": false, + "view_definitions": 1, + "range_definitions": 0, + "runtime_view_steps": 0, + "write_step": 0, + "reply_step": 0, + "prepared_program_bytes": 680, + "steps": [], + "write_effect": "write", + "splice_offset": { + "value": 0, + "from_u32le": false + }, + "splice_remove": { + "value": 0, + "from_u32le": false + } + } + }, + "document_edits": { + "large_document_bytes": 1048576, + "small_edit_input_bytes": 11, + "storage_rewrites_full_extent": true, + "storage_scratch_bytes": 4096, + "kind": "correctness-and-admission-not-total-memory-or-performance" + }, + "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": 6, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048583, + "payload_budget": 4194304, + "peak_connections": 2, + "accepted": 42, + "commits": 12, + "gets": 15, + "conflicts": 4, + "budget_rejections": 0, + "expired": 0, + "releases": 32, + "ingress_prefix_copy_bytes": 11674, + "borrowed_output_bytes": 2115420, + "server_structure_bytes": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "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": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 54, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 20, + "commits": 3, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 17, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 57, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 19, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 8, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 14, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 4, + "commits": 1, + "gets": 1, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 34, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 3, + "commits": 3, + "gets": 0, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 0, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 23, + "payload_budget": 23, + "peak_connections": 1, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 23, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 1048584, + "peak_connections": 2, + "accepted": 4, + "commits": 1, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 3, + "ingress_prefix_copy_bytes": 3998, + "borrowed_output_bytes": 2097091, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 9, + "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": 1, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 29, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 10, + "commits": 1, + "gets": 4, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 10, + "ingress_prefix_copy_bytes": 7, + "borrowed_output_bytes": 113, + "server_structure_bytes": 41528 + }, + { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 11, + "payload_budget": 11, + "peak_connections": 1, + "accepted": 2, + "commits": 1, + "gets": 0, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 1, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 3, + "server_structure_bytes": 41528 + } + ], + "storage": "immutable-byte-extents-v1", + "execution_core": { + "runtime_tests": "passed", + "domain_bytes": 200, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "forwarding_chain": 1024, + "native_storage_pipeline": true + }, + "execution_core_test_sha256": "ecc89dcd0868ae701fa3d3ffbebae1f90447899d7802441a0062adc45e4f171f", + "document_edit_units": { + "splice_tests": "passed", + "checks": [ + "authored-fields-and-atomic-parse-failure", + "binary-edit-oracle-and-old-snapshot", + "bounds-conflict-size-overflow", + "publication-failures-and-uncertain-durability", + "cancel-admission-and-borrowed-reply" + ] + }, + "document_edit_test_sha256": "2d230f27ab5e3e6cd4d65d52015e456995aabc55b50ef665880755b9d01cca5d", + "editor_sha256": "497577bf9811b3ce787e27c6170a322ea553cdab2371b4f71eb51115a9045a16", + "binary_sha256": "b3df35ce2e25dc00578c79e47f6c5a21334da31df38cd482d9e71b835597f9cf", + "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/store-paged.c": "98ac90368b5be8fba5f9cd998145739d13c0003b822f048e4acbc517228e147e", + "prototypes/connections/flow.c": "3957de06e33985a8310954af07b33c2a1d02a4afdf9bd112d06c99c0b5df7749", + "prototypes/connections/flow.h": "289ff43d191270066e238235023f096946f3f561ec5825d2f6c26006db5fc421", + "prototypes/connections/store.h": "e3cf5f169be02e40f1274105046f29a63a46cd153beaf204d33d6100b9c869b8", + "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389", + "prototypes/connections/program.h": "b9ea0af3759150637ccba9362ccb73be374ed85b449b0461fc61fb14ee50e8bc", + "prototypes/connections/runtime.c": "a6c068c6a2cee214b3fe30241459394833917dfb6c0d33052460eab4c6a539a2", + "prototypes/connections/runtime.h": "ff03c0be92dcc482247a58a0be54d2dc5c270325b51ceeed37afc2ffa5480b1f", + "prototypes/connections/result.h": "00b6cf02262039fd90b197eb781f38c9771beaa8d65f394cba6d3f8d0b2ae79d", + "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04", + "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f", + "prototypes/connections/store-port.h": "a262bac93463bfe9939ca0bdc5989d1665aafc007d61e93c28ffc36478b1fde6", + "prototypes/connections/test-runtime.c": "58526592ee906833718101f1d33878d66c5e357097d5347d02755d922c36dda9", + "prototypes/connections/server.c": "76645bafe8e4e429155efe96c2d05a59aed506ba89736283e4fdae71a3d9a60f", + "prototypes/connections/check.py": "546ab85ba3495daa09a6299b23e05308718287b77bde326849ce9c26c8068eb2", + "prototypes/connections/check_edits.py": "7699eb4bda57ad860acf7be6da3e6de56d6b9a3dcf3b9ff9c7df104f5bcb9224", + "prototypes/connections/edit.c": "793d0bde277563c9e11f1225804fb1646e4ce495979d16e8d70d00322a7bec7b", + "prototypes/connections/test-splice.c": "2415188c89ff413f10d55a8cf1902e76c585cc4102f01d504bbf84dbe8d90a8f", + "prototypes/connections/test-flow.c": "2cd2618015ba6806b1a733af0fc7d0ed6aec48fdec117d18c3a8160f534e130b", + "prototypes/connections/test-program.c": "a1b65be05ab2e4e5ccbc56cd1e03e17b0d885f15dc027bf295371ec821e19cc8", + "prototypes/connections/test-fields.c": "e5856ae1985dc8407565ebf7b65702478a34a05081c955f127a10d3cdc900507", + "prototypes/connections/Makefile": "26c44763b13cbe80800ece1c6f1ab6c330d1d201edb9ea421e67cf24e8546fd2", + "prototypes/connections/core-layout.c": "e802b4976c53755ffc045ae9114ebdcf2989d526d8c871c3cfe89d057f4b82b0", + "prototypes/connections/check_core.py": "f80ead11764e870f0bde434c93233d518e5874fdb32f77444e7bd8bbe7398a10", + "prototypes/connections/core-budget.json": "b4eceaffce900144470dd29076164039b8729bad4ba849789d212e7c952f5082", + "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a", + "prototypes/connections/examples/storage.pure": "6f2fd3733f46b762836405f40a9720de0b865f92a8bcbf9388dda0532c903c74", + "prototypes/connections/examples/excerpt.pure": "7ca1b9a4b6a06c32b95e37bcdeb2202ef912ffbf16163f7eea2b00574c703c16", + "prototypes/connections/examples/selection.pure": "90a19a080dd31b532163b8063057beee4701d8f74163ac4083833a1d664fe986", + "prototypes/connections/examples/select-text.py": "3211ce21bf6058f0131544959272587af4edb3fd5a956aee0a940ab19888f944", + "prototypes/workbench/remote.mjs": "6f5aa2bc5a3c88749b2b688c5631eed9e4a5ab41d72eca168bfb67941a8cff2f", + "prototypes/workbench/cli.mjs": "55a5ead56fc8ad4c3f2391505fc3dbfa477ccff5b68fc208f700da24d5e194da", + "prototypes/workbench/web/core.wasm": "e5187c44f8d2df952a85e4cf8102b71dbec6c43420893b3544a6f8514a1bff5c", + "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-24-paged-storage/gtk-flow.json b/prototypes/connections/results/2026-09-24-paged-storage/gtk-flow.json new file mode 100644 index 0000000..4a58c4b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/gtk-flow.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_window_rss_kib": 44656, "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/memory.json b/prototypes/connections/results/2026-09-24-paged-storage/memory.json new file mode 100644 index 0000000..512da26 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/memory.json @@ -0,0 +1,14 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-native-document-cli-memory", + "scope": "Peak process RSS via a small fork/wait4 helper on Linux AArch64. Covers this CLI, libc, program parsing, store, and one 1 MiB document workload; excludes filesystem cache, kernel buffers, other processes, selected TLS/GTK hosts, and other applications. These are regression ceilings for this toolchain, not a universal memory guarantee.", + "samples": { + "small_edit_rss_kib": 1104, + "one_mib_edit_rss_kib": 2128, + "one_mib_show_rss_kib": 1056 + }, + "budget_sha256": "53144cabe646e0478f25f4e770ce01bad06f4984ef47f5160d212c02c0b7569f", + "binary_sha256": "497577bf9811b3ce787e27c6170a322ea553cdab2371b4f71eb51115a9045a16", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41" +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/package-full.json b/prototypes/connections/results/2026-09-24-paged-storage/package-full.json new file mode 100644 index 0000000..88f0795 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/package-full.json @@ -0,0 +1,20 @@ +{ + "archive_sha256": "08565ad139a5f9ad8ab52f96adde37dc092524e9bf699d9441787c489d8d11f1", + "archive_bytes": 111765, + "selected_hosts": [ + "pure-edit", + "pure-connections", + "pure-document-peer", + "pure-edit-window" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted HTTP host updates the same document as the native CLI", + "extracted C/Python TLS peers share a 1 MiB document and recover", + "extracted GTK window and selected external host edit the same document" + ], + "peer_idle_rss_kib": 7856, + "peer_after_large_work_rss_kib": 9552 +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/package-micro.json b/prototypes/connections/results/2026-09-24-paged-storage/package-micro.json new file mode 100644 index 0000000..552cea9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/package-micro.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "b247ba1a453c2500ec41e9161e130ec9a3b6c9316910fe655920d6fcfb62ec45", + "archive_bytes": 25456, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/paged-profile.json b/prototypes/connections/results/2026-09-24-paged-storage/paged-profile.json new file mode 100644 index 0000000..d898c44 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/paged-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-paged-document-storage", + "scope": "Selected 16 KiB-page document storage backend on Linux AArch64/GCC 14. Same linked-code and direct-dependency ceilings as the flat backend; excludes libc, process RAM, filesystem cache and journal, and optional TLS/GTK. Any failure requires review, not automatic relaxation.", + "budget_sha256": "728cbe5488a86c7cb588a1b9e53d5ddc8aafe617c229624d7abe3e3a68f50699", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 39960, + "data": 1000, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "497577bf9811b3ce787e27c6170a322ea553cdab2371b4f71eb51115a9045a16" + }, + "pure-connections": { + "sections": { + "text": 46824, + "data": 1088, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "b3df35ce2e25dc00578c79e47f6c5a21334da31df38cd482d9e71b835597f9cf" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/peer.json b/prototypes/connections/results/2026-09-24-paged-storage/peer.json new file mode 100644 index 0000000..fa238e3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/peer.json @@ -0,0 +1,16 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7840, + "server_after_large_work_rss_kib": 9536 +} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-authoring-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-authoring-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..259dac1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-authoring-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-micro-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-micro-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..ed06d92 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/pure-document-paged-micro-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-paged-storage/unit-asan.json b/prototypes/connections/results/2026-09-24-paged-storage/unit-asan.json new file mode 100644 index 0000000..697b69c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/unit-asan.json @@ -0,0 +1 @@ +{"paged_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/unit.json b/prototypes/connections/results/2026-09-24-paged-storage/unit.json new file mode 100644 index 0000000..697b69c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/unit.json @@ -0,0 +1 @@ +{"paged_storage":"snapshot-patch-range-compaction-crash-conflict-uncertain","mixed_edits":128} diff --git a/prototypes/connections/results/2026-09-24-paged-storage/write-cost.json b/prototypes/connections/results/2026-09-24-paged-storage/write-cost.json new file mode 100644 index 0000000..f3197a8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-paged-storage/write-cost.json @@ -0,0 +1,49 @@ +{ + "profile": "linux-aarch64-16k-page-document-replacement", + "workloads": [ + "replace last byte of 1 MiB document", + "replace one-byte document" + ], + "unit": "successful user-space write syscall bytes targeting object files", + "excludes": "physical device writes, filesystem journal, metadata and directory sync cost", + "one_mib": { + "flat": { + "object_write_syscall_bytes": 1048600, + "object_write_calls": 258, + "object_files": { + "obj-large": 1048600 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "paged": { + "object_write_syscall_bytes": 16944, + "object_write_calls": 3, + "object_files": { + "obj-large": 560, + "dat-large-0000000000000001": 1064960 + }, + "binary_sha256": "497577bf9811b3ce787e27c6170a322ea553cdab2371b4f71eb51115a9045a16" + }, + "flat_to_paged_ratio": 61.8862134088763 + }, + "one_byte": { + "flat": { + "object_write_syscall_bytes": 25, + "object_write_calls": 2, + "object_files": { + "obj-tiny": 25 + }, + "binary_sha256": "b5c6ec82a1e9c8a1391d1c4ac680e7f33e53dafe5c29ac8a956d5097653b1a03" + }, + "paged": { + "object_write_syscall_bytes": 16440, + "object_write_calls": 3, + "object_files": { + "obj-tiny": 56, + "dat-tiny-0000000000000001": 32768 + }, + "binary_sha256": "497577bf9811b3ce787e27c6170a322ea553cdab2371b4f71eb51115a9045a16" + }, + "paged_to_flat_ratio": 657.6 + } +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-portable-peer-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..ac8ed09 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-portable-peer-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-peer-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..0b54aff --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer-source/source-check-linux-aarch64.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "bf51f79c400f820b48dbf78522449cda6625563ce860809e86d76a8023377dbb", + "archive_bytes": 321958, + "source_files": 165, + "checks": [ + "every extracted source file matches the manifest", + "clean C17 build and host-neutral behavior tests pass", + "clean fixed and source-loaded native terminal PTY scenarios pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean native TLS client and both server profiles pass authenticated document work", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-aarch64.json new file mode 100644 index 0000000..b967ae6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-aarch64.json @@ -0,0 +1,17 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": null, + "server_after_large_work_rss_kib": null, + "portable": true +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-rss-regression.json b/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-rss-regression.json new file mode 100644 index 0000000..635361a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/client-server-linux-rss-regression.json @@ -0,0 +1,17 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7968, + "server_after_large_work_rss_kib": 9712, + "portable": false +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/client-server-profile.json b/prototypes/connections/results/2026-09-24-portable-peer/client-server-profile.json new file mode 100644 index 0000000..f8e8511 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/client-server-profile.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-document-peer", + "scope": "Review threshold for the explicitly selected authenticated document host on Linux AArch64/GCC 14. Includes linked application code but excludes the shared OpenSSL/libc implementations, process heap, stack, mappings and kernel buffers. Check measured whole-process RSS separately.", + "budget_sha256": "defe8efb4d544cdbf615de4d43d4ceba78fc7f1d958f245731202625052eb2ff", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-document-peer": { + "sections": { + "text": 46934, + "data": 1384, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libcrypto.so.3", + "libssl.so.3" + ], + "sha256": "06fc08105cb9833599b9c778797cf2be4ba8a6c1db514c836bb76667a8c7265e" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-aarch64.json new file mode 100644 index 0000000..b967ae6 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-aarch64.json @@ -0,0 +1,17 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": null, + "server_after_large_work_rss_kib": null, + "portable": true +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-rss-regression.json b/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-rss-regression.json new file mode 100644 index 0000000..635361a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/server-only-linux-rss-regression.json @@ -0,0 +1,17 @@ +{ + "profile": "selected-authenticated-document-peer", + "checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "server_idle_rss_kib": 7968, + "server_after_large_work_rss_kib": 9712, + "portable": false +} diff --git a/prototypes/connections/results/2026-09-24-portable-peer/server-only-profile.json b/prototypes/connections/results/2026-09-24-portable-peer/server-only-profile.json new file mode 100644 index 0000000..0f6a3ce --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-peer/server-only-profile.json @@ -0,0 +1,23 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-selected-document-server", + "scope": "Reviewed server-only TLS peer ceiling on Linux AArch64/GCC 14. Excludes linked native client code and shared OpenSSL/libc implementations; process RSS is measured separately.", + "budget_sha256": "deae8827323061543193779ada90d1bca4db3a6dda80293e4ad28a9526be9819", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-document-server": { + "sections": { + "text": 43462, + "data": 1352, + "bss": 16 + }, + "needed": [ + "libc.so.6", + "libcrypto.so.3", + "libssl.so.3" + ], + "sha256": "728025cfbdbcdb6d212a2ec8297a0740967653c77ae8908d923c59ddd4ea0315" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-portable-source/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-portable-source/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..8ef47db --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-source/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-portable-source/source-check-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-source/source-check-linux-aarch64.json new file mode 100644 index 0000000..57a1027 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-source/source-check-linux-aarch64.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "5f1766fefe2c319d86c3ff57c881638a9d1ee24374dea33c614e25577d087814", + "archive_bytes": 321399, + "source_files": 165, + "checks": [ + "every extracted source file matches the manifest", + "clean C17 build and host-neutral behavior tests pass", + "clean fixed and source-loaded native terminal PTY scenarios pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-aarch64.json new file mode 100644 index 0000000..3e0fe7d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-aarch64.json @@ -0,0 +1,24 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": null, + "one_mib_peak_rss_kib": null, + "one_thousand_key_list_peak_rss_kib": null, + "one_mib_peak_rss_limit_kib": 0, + "portable": true, + "terminal_locale": "C.UTF-8", + "binary_sha256": "45491c3e28193024d6eb77cbd6569ee8b7470d11ec4126ff4515dba116790308" +} diff --git a/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-rss-regression.json b/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-rss-regression.json new file mode 100644 index 0000000..19090b0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-tui/fixed-linux-rss-regression.json @@ -0,0 +1,24 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1776, + "one_mib_peak_rss_kib": 3168, + "one_thousand_key_list_peak_rss_kib": 1888, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "45491c3e28193024d6eb77cbd6569ee8b7470d11ec4126ff4515dba116790308" +} diff --git a/prototypes/connections/results/2026-09-24-portable-tui/source-linux-aarch64.json b/prototypes/connections/results/2026-09-24-portable-tui/source-linux-aarch64.json new file mode 100644 index 0000000..53b3218 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-tui/source-linux-aarch64.json @@ -0,0 +1,27 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": null, + "one_mib_peak_rss_kib": null, + "one_thousand_key_list_peak_rss_kib": null, + "one_mib_peak_rss_limit_kib": 0, + "portable": true, + "terminal_locale": "C.UTF-8", + "binary_sha256": "e4fb890935f77a7b359163758ce4c44f3710d1a99130a7d883a4f7329a20a70d" +} diff --git a/prototypes/connections/results/2026-09-24-portable-tui/source-linux-rss-regression.json b/prototypes/connections/results/2026-09-24-portable-tui/source-linux-rss-regression.json new file mode 100644 index 0000000..b112863 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-portable-tui/source-linux-rss-regression.json @@ -0,0 +1,27 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1856, + "one_mib_peak_rss_kib": 3664, + "one_thousand_key_list_peak_rss_kib": 1936, + "one_mib_peak_rss_limit_kib": 4096, + "portable": false, + "terminal_locale": "C.UTF-8", + "binary_sha256": "e4fb890935f77a7b359163758ce4c44f3710d1a99130a7d883a4f7329a20a70d" +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/fixed-profile.json b/prototypes/connections/results/2026-09-24-posix-idle/fixed-profile.json new file mode 100644 index 0000000..b56deb9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8" + }, + "pure-connections-fixed": { + "sections": { + "text": 26261, + "data": 1028, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "1999ad534e9091fbaca096402ba2fbeaeb92c9fb4d4150cacaf309e9a9cd9555" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/fixed-server-regression.json b/prototypes/connections/results/2026-09-24-posix-idle/fixed-server-regression.json new file mode 100644 index 0000000..d96f70c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/fixed-server-regression.json @@ -0,0 +1,68 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 11, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1520, + "source_idle_rss_kib": 1552, + "fixed_after_large_rss_kib": 1648, + "source_after_large_rss_kib": 1680, + "fixed_peak_rss_kib": 2144, + "source_peak_rss_kib": 1680, + "fixed_peak_rss_limit_kib": 4096, + "fixed_sha256": "1999ad534e9091fbaca096402ba2fbeaeb92c9fb4d4150cacaf309e9a9cd9555", + "source_sha256": "1d4e858f18e7cea6df263e2d716898ba7a15b966ecda7e1145b21b80f8fe8f88" +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/package.json b/prototypes/connections/results/2026-09-24-posix-idle/package.json new file mode 100644 index 0000000..5085bdd --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "47f8a4943187edc3733f5900790d5c240d25e2042a0b6c3567eb5da2ece68dd1", + "archive_bytes": 32346, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/pure-document-fixed-http-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-posix-idle/pure-document-fixed-http-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..8b50236 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/pure-document-fixed-http-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-posix-idle/reproducible-comparison.json b/prototypes/connections/results/2026-09-24-posix-idle/reproducible-comparison.json new file mode 100644 index 0000000..7548186 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/reproducible-comparison.json @@ -0,0 +1,21 @@ +{ + "profile": "idle-http-host-poll-syscalls", + "observation_seconds": 1.25, + "before": { + "poll_calls": 25, + "timeout_values_ms": [ + 50 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=PO", + "binary_sha256": "7dd0010fa6d6024afa2fe35d57a4c73f33ac632e732616ac8f8853d7b22d79b3" + }, + "after": { + "poll_calls": 1, + "timeout_values_ms": [ + -1 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=4, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 10, NULL, NULL, 0) = ? ERESTARTNOHAND (To be restarted if no handler)\n--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=941369, si_uid=1000} ---\n", + "binary_sha256": "1999ad534e9091fbaca096402ba2fbeaeb92c9fb4d4150cacaf309e9a9cd9555" + }, + "deadline_driven_idle_wait": "pass" +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/service-scale.json b/prototypes/connections/results/2026-09-24-posix-idle/service-scale.json new file mode 100644 index 0000000..c1396ab --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/service-scale.json @@ -0,0 +1,62 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "1999ad534e9091fbaca096402ba2fbeaeb92c9fb4d4150cacaf309e9a9cd9555", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1504, + "peak_rss_kib": 1680, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49185, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 326623, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "1d4e858f18e7cea6df263e2d716898ba7a15b966ecda7e1145b21b80f8fe8f88", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1552, + "peak_rss_kib": 1712, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49168, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 366236, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-posix-idle/source-profile.json b/prototypes/connections/results/2026-09-24-posix-idle/source-profile.json new file mode 100644 index 0000000..d5795da --- /dev/null +++ b/prototypes/connections/results/2026-09-24-posix-idle/source-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7" + }, + "pure-connections": { + "sections": { + "text": 43778, + "data": 1068, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "1d4e858f18e7cea6df263e2d716898ba7a15b966ecda7e1145b21b80f8fe8f88" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-prefix-find/cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-prefix-find/cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..dff004b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-prefix-find/cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-prefix-find/package-check.json b/prototypes/connections/results/2026-09-24-prefix-find/package-check.json new file mode 100644 index 0000000..440bc20 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-prefix-find/package-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "54a579102d6f9563ee8d077e49eaf156ad3fc826e42532607fce45a75283b10d", + "archive_bytes": 32233, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-prefix-find/tui-profile.json b/prototypes/connections/results/2026-09-24-prefix-find/tui-profile.json new file mode 100644 index 0000000..fd11cb1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-prefix-find/tui-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app, including the bounded document browser. The raised text ceiling reviews its measured 2,541-byte addition; default CLI and core ceilings are unchanged. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS, including 1,024-key browsing, is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "4d3097c9ec3c0139cbdd82161a74d610c15d31c6ee5625101410627664f925e7", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 32175, + "data": 1036, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "39b5bcf605739eabd731e471a3f54e03592e2bfc0a405db5faacdb4df84d0b22" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-prefix-find/tui.json b/prototypes/connections/results/2026-09-24-prefix-find/tui.json new file mode 100644 index 0000000..6588e17 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-prefix-find/tui.json @@ -0,0 +1,22 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1824, + "one_mib_peak_rss_kib": 3200, + "one_thousand_key_list_peak_rss_kib": 1904, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "39b5bcf605739eabd731e471a3f54e03592e2bfc0a405db5faacdb4df84d0b22" +} diff --git a/prototypes/connections/results/2026-09-24-release-callback/async-core.json b/prototypes/connections/results/2026-09-24-release-callback/async-core.json new file mode 100644 index 0000000..6e5371e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/async-core.json @@ -0,0 +1,55 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "selected-owner-thread-async-effects", + "max_text_bytes": 8192, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "effect_bytes": 128 + }, + "archive_members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Selected compact owner-thread completion extension without opt-in PURE_RUNTIME_METRICS. Host I/O and worker stacks are excluded. The default execution core and fixed document packages do not link this profile." + }, + "layout": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8, + "effect_bytes": 128 + }, + "sections": { + "text": 7372, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime-async.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 6452\t 0\t 0\t 6452\t 1934\truntime-async.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution-async.a)\n", + "archive_sha256": "e6b90a42ca7dc894483c920d59c606895a83d16fe2e6b4f1d0a6d36a83d7efb5", + "layout_binary_sha256": "be9307acf8264bc1b333dc05fa0e4b162f912ce8e8b82e3d20eddf49e269d42c", + "budget_sha256": "33afabc20949bc322af9e86b946d39147cd714e32d4e095bc0cc404d11aa79ad", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-release-callback/core.json b/prototypes/connections/results/2026-09-24-release-callback/core.json new file mode 100644 index 0000000..6182c51 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Compact release object sections and C bookkeeping, without opt-in PURE_RUNTIME_METRICS, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5980, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 5060\t 0\t 0\t 5060\t 13c4\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "c5f05b1fb9d51f244687425a1b1d2f8922f6f899f0b617b4de490c730d106e97", + "layout_binary_sha256": "04c008fb7e0b1ecc8435bce6fbc4478de6180a54da6538d8742acb9b51992fe3", + "budget_sha256": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-release-callback/micro-package-check.json b/prototypes/connections/results/2026-09-24-release-callback/micro-package-check.json new file mode 100644 index 0000000..9db433b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/micro-package-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "722d68e58d948810c4a181d0dc832ad97bc5c65b5f5629d1af5866aff920e0b5", + "archive_bytes": 22758, + "selected_hosts": [ + "pure-edit" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply" + ] +} diff --git a/prototypes/connections/results/2026-09-24-release-callback/pure-document-micro-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-release-callback/pure-document-micro-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..44f9c7f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/pure-document-micro-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-release-callback/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-release-callback/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..68e4f0a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-release-callback/runtime.json b/prototypes/connections/results/2026-09-24-release-callback/runtime.json new file mode 100644 index 0000000..6f6527d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/runtime.json @@ -0,0 +1 @@ +{"runtime_tests":"passed","domain_bytes":200,"work_slot_bytes":224,"ticket_bytes":24,"prepared_program_bytes":680,"forwarding_chain":1024,"native_storage_pipeline":true,"idle_slot_binding":true,"release_reentrancy_guard":true} diff --git a/prototypes/connections/results/2026-09-24-release-callback/source-package-check.json b/prototypes/connections/results/2026-09-24-release-callback/source-package-check.json new file mode 100644 index 0000000..459429c --- /dev/null +++ b/prototypes/connections/results/2026-09-24-release-callback/source-package-check.json @@ -0,0 +1,15 @@ +{ + "archive_sha256": "cff657597c0cc33bd9185c8460e0d84f0843da20a786578324ac612fa255c2b4", + "archive_bytes": 333037, + "source_files": 170, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-server-only-package/combined-peer-package-check.json b/prototypes/connections/results/2026-09-24-server-only-package/combined-peer-package-check.json new file mode 100644 index 0000000..d8ed889 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-server-only-package/combined-peer-package-check.json @@ -0,0 +1,27 @@ +{ + "archive_sha256": "d1592ec4e4085ebe328c01ccf49911c383fc87908344cfea5fb9bca56a0fef96", + "archive_bytes": 49382, + "selected_hosts": [ + "pure-edit", + "pure-document-peer" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted C/Python TLS peers share a 1 MiB document and recover" + ], + "peer_checks": [ + "mutual certificate identity, server hostname and ALPN separation", + "program source identity checked before C client sends work", + "persistent external TLS client: create, read, conflict and recovery", + "native peer edit and local CLI use the same authored document and storage", + "fragmented 1 MiB direct ingress, demand-selected reply and one-byte splice", + "native C peer uploads 1 MiB and receives only the requested metadata", + "incomplete, invalid and oversized frames do not publish a change", + "idle authenticated session closes after deadline", + "server restart and native C peer preserve the committed large document" + ], + "peer_idle_rss_kib": 7968, + "peer_after_large_work_rss_kib": 9712 +} diff --git a/prototypes/connections/results/2026-09-24-server-only-package/pure-document-combined-peer-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-server-only-package/pure-document-combined-peer-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..ebd24da --- /dev/null +++ b/prototypes/connections/results/2026-09-24-server-only-package/pure-document-combined-peer-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-server-only-package/pure-document-server-only-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-server-only-package/pure-document-server-only-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..2a47d7e --- /dev/null +++ b/prototypes/connections/results/2026-09-24-server-only-package/pure-document-server-only-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-server-only-package/server-only-package-check.json b/prototypes/connections/results/2026-09-24-server-only-package/server-only-package-check.json new file mode 100644 index 0000000..997287d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-server-only-package/server-only-package-check.json @@ -0,0 +1,23 @@ +{ + "archive_sha256": "34fb507c160e4aa719cf44fb4f8bbcb422d2c0ee6dc3f206f6a5fe3bc4d089b6", + "archive_bytes": 47686, + "selected_hosts": [ + "pure-edit", + "pure-document-server" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "extracted server-only TLS peer and external client share a 1 MiB document and recover" + ], + "peer_checks": [ + "relocated server rejects invalid client identity, ALPN and hostname", + "external client and extracted CLI share a conditional document", + "fragmented one MiB ingress, selected reply and CLI-visible splice", + "truncated and invalid frames do not publish a change", + "relocated server restart restores the committed one MiB document" + ], + "peer_idle_rss_kib": 7968, + "peer_after_large_work_rss_kib": 8672 +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-profile.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-profile.json new file mode 100644 index 0000000..733fa60 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-runner", + "scope": "Release-linked CLI and HTTP document hosts with one build-time prepared app. The source parser and compiler are separate build tools. These text ceilings apply only to the recorded Linux AArch64/GCC 14 toolchain and exclude libc, heap, stack, and storage files.", + "budget_sha256": "2b7e550992abb1872e52846dec712fab59b78692b776fd7e648d7976299f4b2a", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-fixed": { + "sections": { + "text": 19244, + "data": 920, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8" + }, + "pure-connections-fixed": { + "sections": { + "text": 25790, + "data": 1024, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "fd474f7331ac47d22360f2bfe8ba491ff8ffac448107b41da74a00142ae97958" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-server-regression.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-server-regression.json new file mode 100644 index 0000000..7857bde --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/fixed-server-regression.json @@ -0,0 +1,68 @@ +{ + "profile": "build-time-prepared-http-document-host", + "fixed_source_cli_same_document": "pass", + "conditional_conflict_and_invalid_splice": "pass", + "one_mib_roundtrip_and_native_edit": "pass", + "slow_consumer_budget_and_release": "pass", + "runtime_parser_symbols_absent": "pass", + "fixed_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048584, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 7, + "commits": 2, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 7, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097177, + "server_structure_bytes": 40848 + }, + "source_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 4194304, + "peak_connections": 1, + "accepted": 6, + "commits": 1, + "gets": 3, + "conflicts": 1, + "budget_rejections": 0, + "expired": 0, + "releases": 6, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 2097162, + "server_structure_bytes": 41528 + }, + "limited_stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 1048576, + "payload_budget": 1048576, + "peak_connections": 2, + "accepted": 3, + "commits": 0, + "gets": 2, + "conflicts": 0, + "budget_rejections": 1, + "expired": 0, + "releases": 2, + "ingress_prefix_copy_bytes": 0, + "borrowed_output_bytes": 1048586, + "server_structure_bytes": 40848 + }, + "fixed_idle_rss_kib": 1504, + "source_idle_rss_kib": 1552, + "fixed_after_large_rss_kib": 1632, + "source_after_large_rss_kib": 1680, + "fixed_peak_rss_kib": 2112, + "source_peak_rss_kib": 2096, + "fixed_peak_rss_limit_kib": 4096, + "fixed_sha256": "fd474f7331ac47d22360f2bfe8ba491ff8ffac448107b41da74a00142ae97958", + "source_sha256": "9818530bdadd3da2c3cd63feb24f0585f8527887bcc9c8528d67736ee3a97653" +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/package.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/package.json new file mode 100644 index 0000000..8304c17 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "413f88a31272c5a0851ab598673de5523fa12d913e0a077441aa342b506498e2", + "archive_bytes": 32023, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document" + ] +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/pure-document-fixed-http-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-signal-safe-idle/pure-document-fixed-http-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..54c21a2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/pure-document-fixed-http-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/reproducible-comparison.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/reproducible-comparison.json new file mode 100644 index 0000000..79f8c49 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/reproducible-comparison.json @@ -0,0 +1,21 @@ +{ + "profile": "idle-http-host-poll-syscalls", + "observation_seconds": 1.25, + "before": { + "poll_calls": 25, + "timeout_values_ms": [ + 50 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, {tv_sec=0, tv_nsec=50000000}, NULL, 0) = 0 (Timeout)\nppoll([{fd=3, events=PO", + "binary_sha256": "7dd0010fa6d6024afa2fe35d57a4c73f33ac632e732616ac8f8853d7b22d79b3" + }, + "after": { + "poll_calls": 1, + "timeout_values_ms": [ + -1 + ], + "trace_excerpt": "ppoll([{fd=3, events=POLLIN}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}, {fd=-1}], 9, NULL, [], 8) = ? ERESTARTNOHAND (To be restarted if no handler)\n--- SIGTERM {si_signo=SIGTERM, si_code=SI_USER, si_pid=932958, si_uid=1000} ---\n", + "binary_sha256": "fd474f7331ac47d22360f2bfe8ba491ff8ffac448107b41da74a00142ae97958" + }, + "deadline_driven_idle_wait": "pass" +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale-restored.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale-restored.json new file mode 100644 index 0000000..a4a9764 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale-restored.json @@ -0,0 +1,62 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "fd474f7331ac47d22360f2bfe8ba491ff8ffac448107b41da74a00142ae97958", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1520, + "peak_rss_kib": 1696, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49200, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 696751, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "9818530bdadd3da2c3cd63feb24f0585f8527887bcc9c8528d67736ee3a97653", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "fresh_cli_restore_samples": 4, + "idle_rss_kib": 1552, + "peak_rss_kib": 1712, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 40993, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 410074, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale.json new file mode 100644 index 0000000..4bedc63 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/service-scale.json @@ -0,0 +1,60 @@ +{ + "profile": "document-app-many-objects-concurrent-clients", + "documents_per_variant": 1024, + "clients": 8, + "body_bytes": 8192, + "operations_per_variant": 4096, + "server_budget_bytes": 1048576, + "peak_rss_limit_kib": 4096, + "same_final_documents": "pass", + "fixed": { + "binary_sha256": "fd474f7331ac47d22360f2bfe8ba491ff8ffac448107b41da74a00142ae97958", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "idle_rss_kib": 1520, + "peak_rss_kib": 1712, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 49201, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 899986, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 40848 + } + }, + "source": { + "binary_sha256": "9818530bdadd3da2c3cd63feb24f0585f8527887bcc9c8528d67736ee3a97653", + "document_digest": "e54baed00186fb020edbbcd407d86471c136287de346e3386ce52d16883e488b", + "stored_objects": 1024, + "stored_file_bytes": 8413184, + "idle_rss_kib": 1552, + "peak_rss_kib": 1728, + "stats": { + "active": 0, + "retained_payload_bytes": 0, + "peak_retained_payload_bytes": 41010, + "payload_budget": 1048576, + "peak_connections": 8, + "accepted": 4096, + "commits": 2048, + "gets": 2048, + "conflicts": 0, + "budget_rejections": 0, + "expired": 0, + "releases": 4096, + "ingress_prefix_copy_bytes": 939752, + "borrowed_output_bytes": 25166848, + "server_structure_bytes": 41528 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-signal-safe-idle/source-profile.json b/prototypes/connections/results/2026-09-24-signal-safe-idle/source-profile.json new file mode 100644 index 0000000..5bc9266 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-signal-safe-idle/source-profile.json @@ -0,0 +1,32 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-default-document-hosts", + "scope": "Review thresholds for release-built default CLI and HTTP adapter on the recorded Linux AArch64/GCC 14 toolchain. These section sizes exclude shared libc, heap, stack, mapped files, kernel buffers, and optional GTK/TLS hosts. Any failure requires a cost review, not automatic relaxation.", + "budget_sha256": "8b1eb0dd4844757705c24a0674142e76d9ee83fdce80a841e87a08e757f1183d", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit": { + "sections": { + "text": 35807, + "data": 944, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "bce466328ac730ccb7cd22c7a0c4e2960bc94a0f20ac9f036e232bd499521ff7" + }, + "pure-connections": { + "sections": { + "text": 43451, + "data": 1064, + "bss": 16 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "9818530bdadd3da2c3cd63feb24f0585f8527887bcc9c8528d67736ee3a97653" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-source-preview/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-source-preview/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..45e83a0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-preview/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-source-preview/source-check.json b/prototypes/connections/results/2026-09-24-source-preview/source-check.json new file mode 100644 index 0000000..a3b7681 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-preview/source-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "a11a058fa0ba71878a0fed0ee63100566f580721fa5cb602ad25cd46e6b94f6f", + "archive_bytes": 316748, + "source_files": 161, + "checks": [ + "every extracted source file matches the manifest", + "clean C17 build and host-neutral behavior tests pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean Linux release behavior, code, dependency and RSS gates pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/core.json b/prototypes/connections/results/2026-09-24-source-tui/core.json new file mode 100644 index 0000000..63eb227 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/core.json @@ -0,0 +1,53 @@ +{ + "passed": true, + "failures": [], + "budget": { + "profile": "current-storage-execution-core", + "max_text_bytes": 6144, + "max_data_bytes": 0, + "max_bss_bytes": 0, + "layout_max_bytes": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680 + }, + "archive_members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols_allowed": [ + "memcpy", + "memmove", + "memset", + "strlen" + ], + "scope": "Compact release object sections and C bookkeeping, without opt-in PURE_RUNTIME_METRICS, validated with GNU tools on Linux AArch64. Not total RAM, process RSS, stack, linked libraries, parser, storage, network or UI. 6 KiB is a regression ceiling for this profile, not a universal runtime size promise." + }, + "layout": { + "domain_bytes": 128, + "work_slot_bytes": 224, + "ticket_bytes": 24, + "prepared_program_bytes": 680, + "pointer_bytes": 8 + }, + "sections": { + "text": 5908, + "data": 0, + "bss": 0 + }, + "members": [ + "runtime.o", + "evaluate.o" + ], + "external_symbols": [ + "memcpy", + "strlen" + ], + "size_output": " text\t data\t bss\t dec\t hex\tfilename\n 4988\t 0\t 0\t 4988\t 137c\truntime.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n 920\t 0\t 0\t 920\t 398\tevaluate.o (ex /home/eunhhu/work/pure/prototypes/connections/build/libpure-execution.a)\n", + "archive_sha256": "bb5750a47f6f8ac4307dd633d836460d6045347880a6c53df9669e675e49929e", + "layout_binary_sha256": "04c008fb7e0b1ecc8435bce6fbc4478de6180a54da6538d8742acb9b51992fe3", + "budget_sha256": "e418419db6231305342ea8780149948e0e41e28b3afb937268e80b4695d3d5cd", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "size_tool": "GNU size (GNU Binutils for Debian) 2.44" +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui-check.json b/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui-check.json new file mode 100644 index 0000000..49d2f70 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "c0d600fd9b80a5bdeacb4c015d0050ddf3fde6f8a3ff1dd86905e3fcb681fd30", + "archive_bytes": 31929, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..e576af8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/fixed-cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-source-tui/fixed-tui-profile.json b/prototypes/connections/results/2026-09-24-source-tui/fixed-tui-profile.json new file mode 100644 index 0000000..1c5a451 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/fixed-tui-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app, including the bounded document browser. The raised text ceiling reviews its measured 2,541-byte addition; default CLI and core ceilings are unchanged. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS, including 1,024-key browsing, is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "4d3097c9ec3c0139cbdd82161a74d610c15d31c6ee5625101410627664f925e7", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 31999, + "data": 1036, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "45491c3e28193024d6eb77cbd6569ee8b7470d11ec4126ff4515dba116790308" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-source-tui/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..265a111 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-source-tui/source-check.json b/prototypes/connections/results/2026-09-24-source-tui/source-check.json new file mode 100644 index 0000000..e7569bb --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/source-check.json @@ -0,0 +1,13 @@ +{ + "archive_sha256": "6134ff966d09c76134b921008d16b828a349b7031bdf029379de6eea9e745eaa", + "archive_bytes": 318619, + "source_files": 162, + "checks": [ + "every extracted source file matches the manifest", + "clean C17 build and host-neutral behavior tests pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui-check.json b/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui-check.json new file mode 100644 index 0000000..1c3dd89 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "6b2671f7e2faf91e96ec9c29d03f5087a1e66f26fbfcc93865700a22e90df9de", + "archive_bytes": 50942, + "selected_hosts": [ + "pure-edit", + "pure-edit-tui" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "extracted native CLI creates and restores the real document", + "editing the packaged .pure source changes the executed reply", + "relocated source-loaded TUI and CLI share UTF-8, large and many-document paths" + ] +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..0e2e38f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/source-cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-source-tui/tui-profile.json b/prototypes/connections/results/2026-09-24-source-tui/tui-profile.json new file mode 100644 index 0000000..348807d --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/tui-profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-source-document-terminal-editor", + "scope": "Optional source-loaded POSIX terminal editor for the same document app. Includes the runtime .pure parser and bounded document browser. Default core, fixed CLI and fixed TUI do not link the parser. These are Linux AArch64/GCC 14 section regression ceilings, not whole-process RAM; check_tui.py measures the selected whole process separately.", + "budget_sha256": "52df1c0a75119edbe0cfd75d6961c1713237146d44a26cd3b2ce096e4d80d624", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui": { + "sections": { + "text": 48604, + "data": 1084, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "e4fb890935f77a7b359163758ce4c44f3710d1a99130a7d883a4f7329a20a70d" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-source-tui/tui.json b/prototypes/connections/results/2026-09-24-source-tui/tui.json new file mode 100644 index 0000000..184936b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-source-tui/tui.json @@ -0,0 +1,25 @@ +{ + "profile": "native-source-document-terminal-editor", + "checks": [ + "source parse and incompatible definitions fail before store creation", + "source CLI and TUI read the same document", + "changed source field layout drives TUI input encoding", + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, searched, resized and opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1856, + "one_mib_peak_rss_kib": 3216, + "one_thousand_key_list_peak_rss_kib": 1952, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "e4fb890935f77a7b359163758ce4c44f3710d1a99130a7d883a4f7329a20a70d" +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli-check.json b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-check.json new file mode 100644 index 0000000..d0bb1fc --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-check.json @@ -0,0 +1,12 @@ +{ + "archive_sha256": "19573b079d8d28b08aebeb185acd1cdfc66c006f183e955fba156f8d94d3c4c8", + "archive_bytes": 14706, + "selected_hosts": [ + "pure-edit-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits" + ] +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui-check.json b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui-check.json new file mode 100644 index 0000000..47d26f1 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "9948083ef0b3b4db1d4efbc4991da1887b2b21a33670c435cd364fcada953f56", + "archive_bytes": 53385, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui.tar.gz b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui.tar.gz Binary files differnew file mode 100644 index 0000000..5d5c967 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-http-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui-check.json b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui-check.json new file mode 100644 index 0000000..6dfc3e8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui-check.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "a497e58e0c8942386f31a3fdbed11f038aa796afb87aff26eeea81871f2465ed", + "archive_bytes": 35642, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui.tar.gz b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui.tar.gz Binary files differnew file mode 100644 index 0000000..ddcd49f --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli-tui.tar.gz diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/cli.tar.gz b/prototypes/connections/results/2026-09-24-terminal-document-list/cli.tar.gz Binary files differnew file mode 100644 index 0000000..7c121af --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/cli.tar.gz diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/profile.json b/prototypes/connections/results/2026-09-24-terminal-document-list/profile.json new file mode 100644 index 0000000..a46d089 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app, including the bounded document browser. The raised text ceiling reviews its measured 2,541-byte addition; default CLI and core ceilings are unchanged. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS, including 1,024-key browsing, is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "4d3097c9ec3c0139cbdd82161a74d610c15d31c6ee5625101410627664f925e7", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 31303, + "data": 1036, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "8b831e7582a0872b90c8b8b3dc4d30088393c3a88713a4eae938c8816bdcda68" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/sanitized.json b/prototypes/connections/results/2026-09-24-terminal-document-list/sanitized.json new file mode 100644 index 0000000..d49a34b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/sanitized.json @@ -0,0 +1,22 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, resized and last key opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 9856, + "one_mib_peak_rss_kib": 11744, + "one_thousand_key_list_peak_rss_kib": 9904, + "one_mib_peak_rss_limit_kib": 0, + "binary_sha256": "4b4b553289e4cec10aa58e0ef5442905f84d2ddef72fa3d36f301825b2b9ac84" +} diff --git a/prototypes/connections/results/2026-09-24-terminal-document-list/tui.json b/prototypes/connections/results/2026-09-24-terminal-document-list/tui.json new file mode 100644 index 0000000..d8d45f8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-terminal-document-list/tui.json @@ -0,0 +1,22 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "bounded document browser selects keys and protects unsaved draft", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "1,024 real documents listed, resized and last key opened within RSS gate", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1824, + "one_mib_peak_rss_kib": 2832, + "one_thousand_key_list_peak_rss_kib": 1856, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "8b831e7582a0872b90c8b8b3dc4d30088393c3a88713a4eae938c8816bdcda68" +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/latency-baseline.json b/prototypes/connections/results/2026-09-24-tui-viewport/latency-baseline.json new file mode 100644 index 0000000..03c1ff9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/latency-baseline.json @@ -0,0 +1,176 @@ +{ + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "terminal": "PTY 24x80, C.UTF-8", + "tui_sha256": "6a38883fef9fcfaf0b24516d0ca1b46edb595f988a9f756c030a3a84ef215107", + "cli_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "workloads": { + "256-byte-ascii": { + "document_bytes": 256, + "samples": [ + { + "nanoseconds": 42778, + "frame_bytes": 467, + "frame_sha256": "79ce175afbd5e1168173ac158c7cba370251a226142ec00780e735b7d71de36d" + }, + { + "nanoseconds": 32612, + "frame_bytes": 468, + "frame_sha256": "84aeb3b92beaccfaa8b4b537280122c8aece74222c9ece27a7c2e0a7a5575371" + }, + { + "nanoseconds": 28296, + "frame_bytes": 469, + "frame_sha256": "b04bcefe68401c190b7dab9c3c29bb09cec9f4db6140d285bd2fe09ff0fa5203" + }, + { + "nanoseconds": 28074, + "frame_bytes": 470, + "frame_sha256": "01c628338d94e9cc3371dcdb33d643c6d87d17808e04e388dc0305f2e2bf0a01" + }, + { + "nanoseconds": 27944, + "frame_bytes": 471, + "frame_sha256": "02d4c8ab2f98fe5e99cf17237d3daa75b484472928e4105d241c06c68b082ccc" + }, + { + "nanoseconds": 28612, + "frame_bytes": 472, + "frame_sha256": "825d74dda937507686530ce7acbff1aafcc3c236feb6befa218f2ccbe33d480b" + }, + { + "nanoseconds": 32815, + "frame_bytes": 473, + "frame_sha256": "9458d218e52f093480dccf23b816dac00c5cc5517b4b145820061f98b61d2b32" + }, + { + "nanoseconds": 28093, + "frame_bytes": 474, + "frame_sha256": "02d7602be4f267cdc2db7b853ecfb743dc594c941b9f6a4be9d3262b16d71fda" + }, + { + "nanoseconds": 27556, + "frame_bytes": 475, + "frame_sha256": "21426683adeee1036c8fe02233ab5a8dde1e6f5d2da8f97d67f9b41419c745ca" + }, + { + "nanoseconds": 28297, + "frame_bytes": 476, + "frame_sha256": "d5c581fddffb80146c1b617be580a65f5936c79dc04deeada0c5b90eaa10b193" + } + ], + "median_nanoseconds": 28296 + }, + "one-mib-ascii": { + "document_bytes": 1048576, + "samples": [ + { + "nanoseconds": 15694866, + "frame_bytes": 1950, + "frame_sha256": "1ee95680f91eda6c69e7b4009274591735b5b9d2825ed2076cf372acc70c613f" + }, + { + "nanoseconds": 15542421, + "frame_bytes": 1951, + "frame_sha256": "5121e7e36f445a218af46da6796e3124997a7e25b652225f25d93b60d5e09b40" + }, + { + "nanoseconds": 15586588, + "frame_bytes": 1952, + "frame_sha256": "c7b503e012baefa46d8663b98a59f1d1faf4d2b3831750f736984e6443245075" + }, + { + "nanoseconds": 15921866, + "frame_bytes": 1953, + "frame_sha256": "b7edf856b10b641faf4825a8444f386f3faf1ac1e1ccf9a187db6f1465b5264b" + }, + { + "nanoseconds": 15594088, + "frame_bytes": 1954, + "frame_sha256": "cbaaf4f2990d9c060f48cf4dd6e6fab3d872c2370302960a893bcd4c13fa4c47" + }, + { + "nanoseconds": 15704829, + "frame_bytes": 1955, + "frame_sha256": "95ce7a29f9dd111a0a319455231b79bcc41fdf897e8db3411a24821c6cc298c6" + }, + { + "nanoseconds": 15593014, + "frame_bytes": 1956, + "frame_sha256": "440d25b06f6d7dda71bd02225191f0f430a4f4278a10d3bb21b56788ccbd62c4" + }, + { + "nanoseconds": 15696681, + "frame_bytes": 1957, + "frame_sha256": "5a0f58e4f0e16194202e3f56608a6411488aa517378abedf8ff428644e63c9cb" + }, + { + "nanoseconds": 15719440, + "frame_bytes": 1958, + "frame_sha256": "4f3d92e9f165d680c4546040e0ac141db54176f6576893d1a24e89053b487b03" + }, + { + "nanoseconds": 15660421, + "frame_bytes": 1959, + "frame_sha256": "ee37bf811fd99378f57715a638fd589d5c518b34dfdc76d973c64203c2ef16f8" + } + ], + "median_nanoseconds": 15677643 + }, + "one-mib-utf8": { + "document_bytes": 1048572, + "samples": [ + { + "nanoseconds": 37168940, + "frame_bytes": 2745, + "frame_sha256": "e7275636645244ab12463d96a08bacd5c87aec3307bf86f91399ca654531140a" + }, + { + "nanoseconds": 37198756, + "frame_bytes": 2746, + "frame_sha256": "7d183f69c1dce6fee9b86e6b2c10e01f3779b13753fb1c11160de78505f9a8e0" + }, + { + "nanoseconds": 37112959, + "frame_bytes": 2747, + "frame_sha256": "7d0190b9b944eaa7fcd07af573882df6d436a7847e51dca759a65e93d9100c8e" + }, + { + "nanoseconds": 37318718, + "frame_bytes": 2748, + "frame_sha256": "858d20af32237847e72800093ec28e1998e71c22d0991d75011bf661682323e8" + }, + { + "nanoseconds": 37213200, + "frame_bytes": 2749, + "frame_sha256": "2e848f851e174be238ce270f71749536e473c97ad2f075a55aaf020fc5438c77" + }, + { + "nanoseconds": 37296497, + "frame_bytes": 2750, + "frame_sha256": "77d4bb9a8028ea5420435bdbe23b0c188d6f6065a8eda79c6357defb32605be8" + }, + { + "nanoseconds": 37084125, + "frame_bytes": 2751, + "frame_sha256": "18d8fcba8c9d11f0cf44e2171fb613d49e45f94408b4307526fc90bec5b61e5a" + }, + { + "nanoseconds": 37309700, + "frame_bytes": 2752, + "frame_sha256": "977adff244a908e5b59448e1b8075139fbcc7b35410d0d335ad67580d3d54c44" + }, + { + "nanoseconds": 37275311, + "frame_bytes": 2753, + "frame_sha256": "1e6c90831630b4573e9dec18872d26056e6e96d68e2cfcbe7da8102f0ef8b490" + }, + { + "nanoseconds": 37298940, + "frame_bytes": 2754, + "frame_sha256": "b3599665cc5fb2a72f9a582604c5caaeceff475fa2986f3242adfb4799089600" + } + ], + "median_nanoseconds": 37244255 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/latency-current.json b/prototypes/connections/results/2026-09-24-tui-viewport/latency-current.json new file mode 100644 index 0000000..92f6cd0 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/latency-current.json @@ -0,0 +1,176 @@ +{ + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "terminal": "PTY 24x80, C.UTF-8", + "tui_sha256": "4373eab2cdb033ce54b812232c7787a3a572de26e303daa9dd5001489638c899", + "cli_sha256": "8b594a7b1a4ae5b33cde5c842ec1e11c79b6368babc08ac2ffea378b38ac24f8", + "workloads": { + "256-byte-ascii": { + "document_bytes": 256, + "samples": [ + { + "nanoseconds": 59019, + "frame_bytes": 467, + "frame_sha256": "79ce175afbd5e1168173ac158c7cba370251a226142ec00780e735b7d71de36d" + }, + { + "nanoseconds": 39463, + "frame_bytes": 468, + "frame_sha256": "84aeb3b92beaccfaa8b4b537280122c8aece74222c9ece27a7c2e0a7a5575371" + }, + { + "nanoseconds": 32555, + "frame_bytes": 469, + "frame_sha256": "b04bcefe68401c190b7dab9c3c29bb09cec9f4db6140d285bd2fe09ff0fa5203" + }, + { + "nanoseconds": 30074, + "frame_bytes": 470, + "frame_sha256": "01c628338d94e9cc3371dcdb33d643c6d87d17808e04e388dc0305f2e2bf0a01" + }, + { + "nanoseconds": 26759, + "frame_bytes": 471, + "frame_sha256": "02d4c8ab2f98fe5e99cf17237d3daa75b484472928e4105d241c06c68b082ccc" + }, + { + "nanoseconds": 27667, + "frame_bytes": 472, + "frame_sha256": "825d74dda937507686530ce7acbff1aafcc3c236feb6befa218f2ccbe33d480b" + }, + { + "nanoseconds": 27371, + "frame_bytes": 473, + "frame_sha256": "9458d218e52f093480dccf23b816dac00c5cc5517b4b145820061f98b61d2b32" + }, + { + "nanoseconds": 29278, + "frame_bytes": 474, + "frame_sha256": "02d7602be4f267cdc2db7b853ecfb743dc594c941b9f6a4be9d3262b16d71fda" + }, + { + "nanoseconds": 28167, + "frame_bytes": 475, + "frame_sha256": "21426683adeee1036c8fe02233ab5a8dde1e6f5d2da8f97d67f9b41419c745ca" + }, + { + "nanoseconds": 37815, + "frame_bytes": 476, + "frame_sha256": "d5c581fddffb80146c1b617be580a65f5936c79dc04deeada0c5b90eaa10b193" + } + ], + "median_nanoseconds": 29676 + }, + "one-mib-ascii": { + "document_bytes": 1048576, + "samples": [ + { + "nanoseconds": 111481, + "frame_bytes": 1950, + "frame_sha256": "1ee95680f91eda6c69e7b4009274591735b5b9d2825ed2076cf372acc70c613f" + }, + { + "nanoseconds": 73685, + "frame_bytes": 1951, + "frame_sha256": "5121e7e36f445a218af46da6796e3124997a7e25b652225f25d93b60d5e09b40" + }, + { + "nanoseconds": 71222, + "frame_bytes": 1952, + "frame_sha256": "c7b503e012baefa46d8663b98a59f1d1faf4d2b3831750f736984e6443245075" + }, + { + "nanoseconds": 69871, + "frame_bytes": 1953, + "frame_sha256": "b7edf856b10b641faf4825a8444f386f3faf1ac1e1ccf9a187db6f1465b5264b" + }, + { + "nanoseconds": 69945, + "frame_bytes": 1954, + "frame_sha256": "cbaaf4f2990d9c060f48cf4dd6e6fab3d872c2370302960a893bcd4c13fa4c47" + }, + { + "nanoseconds": 69833, + "frame_bytes": 1955, + "frame_sha256": "95ce7a29f9dd111a0a319455231b79bcc41fdf897e8db3411a24821c6cc298c6" + }, + { + "nanoseconds": 70259, + "frame_bytes": 1956, + "frame_sha256": "440d25b06f6d7dda71bd02225191f0f430a4f4278a10d3bb21b56788ccbd62c4" + }, + { + "nanoseconds": 69537, + "frame_bytes": 1957, + "frame_sha256": "5a0f58e4f0e16194202e3f56608a6411488aa517378abedf8ff428644e63c9cb" + }, + { + "nanoseconds": 69648, + "frame_bytes": 1958, + "frame_sha256": "4f3d92e9f165d680c4546040e0ac141db54176f6576893d1a24e89053b487b03" + }, + { + "nanoseconds": 70092, + "frame_bytes": 1959, + "frame_sha256": "ee37bf811fd99378f57715a638fd589d5c518b34dfdc76d973c64203c2ef16f8" + } + ], + "median_nanoseconds": 70018 + }, + "one-mib-utf8": { + "document_bytes": 1048572, + "samples": [ + { + "nanoseconds": 239129, + "frame_bytes": 2745, + "frame_sha256": "e7275636645244ab12463d96a08bacd5c87aec3307bf86f91399ca654531140a" + }, + { + "nanoseconds": 228593, + "frame_bytes": 2746, + "frame_sha256": "7d183f69c1dce6fee9b86e6b2c10e01f3779b13753fb1c11160de78505f9a8e0" + }, + { + "nanoseconds": 227148, + "frame_bytes": 2747, + "frame_sha256": "7d0190b9b944eaa7fcd07af573882df6d436a7847e51dca759a65e93d9100c8e" + }, + { + "nanoseconds": 227500, + "frame_bytes": 2748, + "frame_sha256": "858d20af32237847e72800093ec28e1998e71c22d0991d75011bf661682323e8" + }, + { + "nanoseconds": 237037, + "frame_bytes": 2749, + "frame_sha256": "2e848f851e174be238ce270f71749536e473c97ad2f075a55aaf020fc5438c77" + }, + { + "nanoseconds": 228370, + "frame_bytes": 2750, + "frame_sha256": "77d4bb9a8028ea5420435bdbe23b0c188d6f6065a8eda79c6357defb32605be8" + }, + { + "nanoseconds": 226463, + "frame_bytes": 2751, + "frame_sha256": "18d8fcba8c9d11f0cf44e2171fb613d49e45f94408b4307526fc90bec5b61e5a" + }, + { + "nanoseconds": 261797, + "frame_bytes": 2752, + "frame_sha256": "977adff244a908e5b59448e1b8075139fbcc7b35410d0d335ad67580d3d54c44" + }, + { + "nanoseconds": 272018, + "frame_bytes": 2753, + "frame_sha256": "1e6c90831630b4573e9dec18872d26056e6e96d68e2cfcbe7da8102f0ef8b490" + }, + { + "nanoseconds": 235852, + "frame_bytes": 2754, + "frame_sha256": "b3599665cc5fb2a72f9a582604c5caaeceff475fa2986f3242adfb4799089600" + } + ], + "median_nanoseconds": 232222 + } + } +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/package-http-tui.json b/prototypes/connections/results/2026-09-24-tui-viewport/package-http-tui.json new file mode 100644 index 0000000..2730260 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/package-http-tui.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "270edeb0f897b3ea1eeacc4e89bcff2c45fefbbe77fd1546307298d801a2ed9b", + "archive_bytes": 51844, + "selected_hosts": [ + "pure-edit-fixed", + "pure-connections-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated parser-free HTTP host and CLI share the document", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/package.json b/prototypes/connections/results/2026-09-24-tui-viewport/package.json new file mode 100644 index 0000000..c16b305 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/package.json @@ -0,0 +1,14 @@ +{ + "archive_sha256": "aa29b551905788ec436628fac1cca119fbf783d2c620ead866fd2e842429fb12", + "archive_bytes": 34216, + "selected_hosts": [ + "pure-edit-fixed", + "pure-edit-tui-fixed" + ], + "checks": [ + "relocated archive has only declared files and every file hash matches", + "relocated parser-free runner creates and restores a document", + "relocated parser-free runner performs revision-checked edits", + "relocated terminal editor saves UTF-8 and one MiB documents" + ] +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/profile.json b/prototypes/connections/results/2026-09-24-tui-viewport/profile.json new file mode 100644 index 0000000..84e6783 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/profile.json @@ -0,0 +1,21 @@ +{ + "passed": true, + "failures": [], + "profile": "linux-aarch64-gcc14-fixed-document-terminal-editor", + "scope": "Release-linked optional POSIX terminal editor for one build-time prepared document app. Excludes libc, terminal emulator, kernel TTY buffers, heap and stack. Whole-process RSS is checked by check_tui.py on the recorded Linux AArch64 host.", + "budget_sha256": "e378cb302b2a4bcd2ee674af048116f139b214b59f24244d6c825bc71a8e8a31", + "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41", + "binaries": { + "pure-edit-tui-fixed": { + "sections": { + "text": 28762, + "data": 988, + "bss": 24 + }, + "needed": [ + "libc.so.6" + ], + "sha256": "4373eab2cdb033ce54b812232c7787a3a572de26e303daa9dd5001489638c899" + } + } +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/pty-flow.json b/prototypes/connections/results/2026-09-24-tui-viewport/pty-flow.json new file mode 100644 index 0000000..a8b63d4 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/pty-flow.json @@ -0,0 +1,19 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 1792, + "one_mib_peak_rss_kib": 3168, + "one_mib_peak_rss_limit_kib": 4096, + "binary_sha256": "4373eab2cdb033ce54b812232c7787a3a572de26e303daa9dd5001489638c899" +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/pty-sanitized.json b/prototypes/connections/results/2026-09-24-tui-viewport/pty-sanitized.json new file mode 100644 index 0000000..218e7d8 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/pty-sanitized.json @@ -0,0 +1,19 @@ +{ + "profile": "native-fixed-document-terminal-editor", + "checks": [ + "cursor insertion and save through terminal", + "open another key", + "UTF-8 codepoint movement, visible redraw and edit", + "resize redraw", + "external revision conflict preserves draft", + "stored multiline and tabbed document survives edit and reopen", + "cached viewport preserves vertical movement across many rows", + "one MiB document viewport, edit and fresh CLI restore", + "one MiB UTF-8 viewport and edit before cached anchor", + "terminal mode restored after key and signal exit" + ], + "small_peak_rss_kib": 9696, + "one_mib_peak_rss_kib": 11680, + "one_mib_peak_rss_limit_kib": 0, + "binary_sha256": "0f1e1a886d3c1617c8aa73e3348892681bae0c1c1428e4b89eac5985410cc988" +} diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-http-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-http-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..3cc8f4a --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-http-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..d8e21cf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-tui-viewport/pure-document-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/final-native-window.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/final-native-window.json new file mode 100644 index 0000000..9b952aa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/final-native-window.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 15600, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 16880, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 18720, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 20576, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 22672, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 24528, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 25568, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47216, 48432, 49712, 51552, 53536, 55632, 57488, 58528], "large_window_rss_kib": 44000, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/line-width-4096.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/line-width-4096.json new file mode 100644 index 0000000..f595f21 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/line-width-4096.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_line_width": 4096, "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31344, "heap": 13088, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31344, "heap": 15984, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31344, "heap": 17936, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31344, "heap": 20544, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31472, "heap": 23440, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31472, "heap": 25392, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31472, "heap": 28000, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31472, "heap": 30880, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [46000, 48896, 50848, 53456, 56480, 58432, 61040, 63920], "large_window_rss_kib": 42736, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/mallinfo.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/mallinfo.json new file mode 100644 index 0000000..9c77082 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/mallinfo.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_malloc_samples_bytes": [{"arena": 5849088, "free": 1371840, "key": "large", "mapped": 1392640, "used": 4477248}, {"arena": 14450688, "free": 5148272, "key": "large_other", "mapped": 606208, "used": 9302416}, {"arena": 16990208, "free": 7444160, "key": "large", "mapped": 606208, "used": 9546048}, {"arena": 16990208, "free": 7217216, "key": "large_other", "mapped": 606208, "used": 9772992}, {"arena": 16990208, "free": 6992480, "key": "large", "mapped": 606208, "used": 9997728}, {"arena": 16990208, "free": 6767584, "key": "large_other", "mapped": 606208, "used": 10222624}, {"arena": 16990208, "free": 6543008, "key": "large", "mapped": 606208, "used": 10447200}, {"arena": 16990208, "free": 6317776, "key": "large_other", "mapped": 606208, "used": 10672432}, {"arena": 16990208, "free": 6092896, "key": "large", "mapped": 606208, "used": 10897312}, {"arena": 16990208, "free": 5867616, "key": "large_other", "mapped": 606208, "used": 11122592}, {"arena": 16990208, "free": 5643472, "key": "large", "mapped": 606208, "used": 11346736}, {"arena": 16990208, "free": 5417776, "key": "large_other", "mapped": 606208, "used": 11572432}, {"arena": 17711104, "free": 5915184, "key": "large", "mapped": 606208, "used": 11795920}, {"arena": 17711104, "free": 5689968, "key": "large_other", "mapped": 606208, "used": 12021136}, {"arena": 17711104, "free": 5465984, "key": "large", "mapped": 606208, "used": 12245120}, {"arena": 17711104, "free": 5236704, "key": "large_other", "mapped": 606208, "used": 12474400}, {"arena": 17711104, "free": 5013232, "key": "large", "mapped": 606208, "used": 12697872}, {"arena": 18759680, "free": 5836384, "key": "large_other", "mapped": 606208, "used": 12923296}, {"arena": 18759680, "free": 5613104, "key": "large", "mapped": 606208, "used": 13146576}, {"arena": 18759680, "free": 5387408, "key": "large_other", "mapped": 606208, "used": 13372272}, {"arena": 18759680, "free": 5163520, "key": "large", "mapped": 606208, "used": 13596160}, {"arena": 19611648, "free": 5790528, "key": "large_other", "mapped": 606208, "used": 13821120}, {"arena": 19611648, "free": 5567040, "key": "large", "mapped": 606208, "used": 14044608}, {"arena": 19611648, "free": 5341600, "key": "large_other", "mapped": 606208, "used": 14270048}, {"arena": 19611648, "free": 5118352, "key": "large", "mapped": 606208, "used": 14493296}, {"arena": 20660224, "free": 5941504, "key": "large_other", "mapped": 606208, "used": 14718720}, {"arena": 20660224, "free": 5717552, "key": "large", "mapped": 606208, "used": 14942672}, {"arena": 20660224, "free": 5492400, "key": "large_other", "mapped": 606208, "used": 15167824}, {"arena": 20660224, "free": 5268896, "key": "large", "mapped": 606208, "used": 15391328}, {"arena": 20660224, "free": 5043504, "key": "large_other", "mapped": 606208, "used": 15616720}, {"arena": 21708800, "free": 5868816, "key": "large", "mapped": 606208, "used": 15839984}, {"arena": 21708800, "free": 5643168, "key": "large_other", "mapped": 606208, "used": 16065632}, {"arena": 21708800, "free": 5419264, "key": "large", "mapped": 606208, "used": 16289536}, {"arena": 21708800, "free": 5194128, "key": "large_other", "mapped": 606208, "used": 16514672}, {"arena": 21708800, "free": 4969600, "key": "large", "mapped": 606208, "used": 16739200}, {"arena": 22806528, "free": 5842912, "key": "large_other", "mapped": 606208, "used": 16963616}, {"arena": 22806528, "free": 5619648, "key": "large", "mapped": 606208, "used": 17186880}, {"arena": 22806528, "free": 5441248, "key": "large_other", "mapped": 606208, "used": 17365280}, {"arena": 22806528, "free": 5217472, "key": "large", "mapped": 606208, "used": 17589056}, {"arena": 23658496, "free": 5843872, "key": "large_other", "mapped": 606208, "used": 17814624}, {"arena": 23658496, "free": 5620592, "key": "large", "mapped": 606208, "used": 18037904}, {"arena": 23658496, "free": 5395136, "key": "large_other", "mapped": 606208, "used": 18263360}, {"arena": 23658496, "free": 5171792, "key": "large", "mapped": 606208, "used": 18486704}, {"arena": 24707072, "free": 5994912, "key": "large_other", "mapped": 606208, "used": 18712160}, {"arena": 24707072, "free": 5770480, "key": "large", "mapped": 606208, "used": 18936592}, {"arena": 24707072, "free": 5545520, "key": "large_other", "mapped": 606208, "used": 19161552}, {"arena": 24707072, "free": 5321792, "key": "large", "mapped": 606208, "used": 19385280}, {"arena": 24707072, "free": 5096368, "key": "large_other", "mapped": 606208, "used": 19610704}, {"arena": 25755648, "free": 5921760, "key": "large", "mapped": 606208, "used": 19833888}, {"arena": 25755648, "free": 5696016, "key": "large_other", "mapped": 606208, "used": 20059632}, {"arena": 25755648, "free": 5471760, "key": "large", "mapped": 606208, "used": 20283888}, {"arena": 25755648, "free": 5247072, "key": "large_other", "mapped": 606208, "used": 20508576}, {"arena": 25755648, "free": 5022720, "key": "large", "mapped": 606208, "used": 20732928}, {"arena": 26853376, "free": 5895392, "key": "large_other", "mapped": 606208, "used": 20957984}, {"arena": 26853376, "free": 5671920, "key": "large", "mapped": 606208, "used": 21181456}, {"arena": 26853376, "free": 5446704, "key": "large_other", "mapped": 606208, "used": 21406672}, {"arena": 26853376, "free": 5222576, "key": "large", "mapped": 606208, "used": 21630800}, {"arena": 27705344, "free": 5849056, "key": "large_other", "mapped": 606208, "used": 21856288}, {"arena": 27705344, "free": 5625392, "key": "large", "mapped": 606208, "used": 22079952}, {"arena": 27705344, "free": 5400048, "key": "large_other", "mapped": 606208, "used": 22305296}, {"arena": 27705344, "free": 5176640, "key": "large", "mapped": 606208, "used": 22528704}, {"arena": 28753920, "free": 5999456, "key": "large_other", "mapped": 606208, "used": 22754464}, {"arena": 28753920, "free": 5775264, "key": "large", "mapped": 606208, "used": 22978656}, {"arena": 28753920, "free": 5550336, "key": "large_other", "mapped": 606208, "used": 23203584}, {"arena": 28753920, "free": 5326416, "key": "large", "mapped": 606208, "used": 23427504}], "large_switch_map_samples_kib": [{"anonymous": 2352, "file": 31280, "heap": 14464, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31280, "heap": 14928, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31280, "heap": 16784, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31280, "heap": 18832, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31408, "heap": 20736, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31408, "heap": 22016, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31408, "heap": 23856, "other": 16, "stack": 112}, {"anonymous": 2352, "file": 31408, "heap": 25712, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [48224, 48688, 50544, 52592, 54624, 55904, 57744, 59600], "large_window_rss_kib": 44800, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/no-document-undo.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-document-undo.json new file mode 100644 index 0000000..7b8cf24 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-document-undo.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31280, "heap": 14576, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31280, "heap": 15024, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31280, "heap": 17152, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31280, "heap": 19008, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31408, "heap": 20240, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31408, "heap": 22144, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31408, "heap": 23984, "other": 16, "stack": 128}, {"anonymous": 1440, "file": 31408, "heap": 26016, "other": 16, "stack": 128}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47440, 47888, 50016, 51872, 53232, 55136, 56976, 59008], "large_window_rss_kib": 44048, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/no-key-undo.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-key-undo.json new file mode 100644 index 0000000..6009187 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-key-undo.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1456, "file": 31280, "heap": 15600, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 16784, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 18608, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 20320, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 21488, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 23584, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 26800, "other": 16, "stack": 112}, {"anonymous": 1456, "file": 31280, "heap": 27344, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [48464, 49648, 51472, 53184, 54352, 56448, 59664, 60208], "large_window_rss_kib": 44048, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/no-wrap.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-wrap.json new file mode 100644 index 0000000..b51a374 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/no-wrap.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31264, "heap": 13056, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 13904, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 15184, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 17024, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 19168, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 20448, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 22288, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31392, "heap": 24480, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [45888, 46736, 48016, 49856, 52128, 53408, 55248, 57440], "large_window_rss_kib": 44000, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/replace-view.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/replace-view.json new file mode 100644 index 0000000..409e1d9 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/replace-view.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_line_width": 80, "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31280, "heap": 14416, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 15632, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 17696, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 19280, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 21248, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 22608, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 24624, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 25968, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47264, 48480, 50544, 52128, 54096, 55456, 57472, 58816], "large_window_rss_kib": 44032, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/same-body.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/same-body.json new file mode 100644 index 0000000..c8b8149 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/same-body.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31280, "heap": 14368, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 16416, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 18512, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 19696, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 21344, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 23104, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 25280, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 26832, "other": 16, "stack": 112}], "large_switch_mode": "same-body", "large_switch_rss_samples_kib": [47216, 49264, 51360, 52544, 54192, 55952, 58128, 59680], "large_window_rss_kib": 44016, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/same-key.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/same-key.json new file mode 100644 index 0000000..fac529b --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/same-key.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31264, "heap": 14224, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14224, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14224, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14224, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14224, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14240, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14240, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14240, "other": 16, "stack": 112}], "large_switch_mode": "same", "large_switch_rss_samples_kib": [47056, 47056, 47056, 47056, 47056, 47072, 47072, 47072], "large_window_rss_kib": 44016, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/settled-final.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/settled-final.json new file mode 100644 index 0000000..bdbdd78 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/settled-final.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31328, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 16336, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 17600, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 19776, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 21872, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 23056, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 25184, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31328, "heap": 26432, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47280, 49232, 50496, 52672, 54768, 55952, 58080, 59328], "large_switch_settled_map_kib": {"anonymous": 1440, "file": 31456, "heap": 26432, "other": 16, "stack": 112}, "large_switch_settled_rss_kib": 59456, "large_window_rss_kib": 44064, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/skip-text.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/skip-text.json new file mode 100644 index 0000000..86c3fe7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/skip-text.json @@ -0,0 +1 @@ +{"cli_conflict_preserves_draft": "pass", "definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31264, "heap": 14384, "other": 16, "stack": 112}], "large_switch_mode": "alternate", "large_switch_rss_samples_kib": [47216, 47216, 47216, 47216, 47216, 47216, 47216, 47216], "large_window_rss_kib": 44000, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-window-memory-analysis/smaps.json b/prototypes/connections/results/2026-09-24-window-memory-analysis/smaps.json new file mode 100644 index 0000000..1b88acf --- /dev/null +++ b/prototypes/connections/results/2026-09-24-window-memory-analysis/smaps.json @@ -0,0 +1 @@ +{"definition_restores_in_new_window": "pass", "empty_document_restore": "pass", "gtk_cli_write": "pass", "http_conflict_preserves_draft": "pass", "invalid_and_external_definition_preserve_draft": "pass", "large_switch_map_samples_kib": [{"anonymous": 1440, "file": 31280, "heap": 14624, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 15328, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 17184, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31280, "heap": 19040, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31408, "heap": 21136, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31408, "heap": 22992, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31408, "heap": 24032, "other": 16, "stack": 112}, {"anonymous": 1440, "file": 31408, "heap": 25920, "other": 16, "stack": 112}], "large_switch_rss_samples_kib": [47472, 48176, 50032, 51888, 54112, 55968, 57008, 58896], "large_window_rss_kib": 44032, "multi_document_switch_preserves_draft": "pass", "native_definition_apply_and_running_output": "pass", "one_mib_document_one_byte_edit": "pass", "partial_read_definition_rejected": "pass", "profile": "optional-native-document-window", "reloaded_definition_installs_without_rewriting_source": "pass", "source_write_failure_preserves_active_definition": "pass", "unchanged_save_keeps_revision": "pass", "unsaved_close_guard": "pass", "utf8_boundary_and_restart": "pass"} diff --git a/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..b2f35c3 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..298e8c2 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/pure-document-fixed-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..57a22b7 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-tui-linux-aarch64.tar.gz b/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-tui-linux-aarch64.tar.gz Binary files differnew file mode 100644 index 0000000..38ec704 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/pure-document-source-cli-tui-linux-aarch64.tar.gz diff --git a/prototypes/connections/results/2026-09-24-work-slot/pure-source-preview.tar.gz b/prototypes/connections/results/2026-09-24-work-slot/pure-source-preview.tar.gz Binary files differnew file mode 100644 index 0000000..458b215 --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/pure-source-preview.tar.gz diff --git a/prototypes/connections/results/2026-09-24-work-slot/source-package-check.json b/prototypes/connections/results/2026-09-24-work-slot/source-package-check.json new file mode 100644 index 0000000..708e0fa --- /dev/null +++ b/prototypes/connections/results/2026-09-24-work-slot/source-package-check.json @@ -0,0 +1,16 @@ +{ + "archive_sha256": "a1ba85942463f765c1f908f19419969423f3e474e608dc023e8a3d09b61de50f", + "archive_bytes": 334267, + "source_files": 171, + "checks": [ + "every extracted source file matches the manifest", + "clean C17, both real terminal PTY scenarios and fixed/source CLI document RSS checks pass", + "source-loaded CLI and parser-free CLI share the real stored document", + "editing .pure changes the native source-loaded reply", + "clean selected page store exports mixed revisions to the default flat store", + "clean one-MiB document fans out to two same-process Pure domains with independent conflict and release", + "clean selected page store runs the source-loaded TUI and relocatable CLI+TUI package", + "clean Linux release behavior, code, dependency and RSS gates pass", + "clean source-loaded terminal editor PTY scenarios pass" + ] +} |
