aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/005-direct-document-peer
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/005-direct-document-peer')
-rw-r--r--research/experiments/005-direct-document-peer/README.md60
-rw-r--r--research/experiments/005-direct-document-peer/direct-peer-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/direct-peer-sanitized-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/direct-peer.c207
-rw-r--r--research/experiments/005-direct-document-peer/pure-peer-after-nodelay-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/pure-peer-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/pure-server-after-nodelay-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/pure-server-after-nodelay-profile-linux-aarch64.json23
-rw-r--r--research/experiments/005-direct-document-peer/pure-server-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/pure-server-profile-linux-aarch64.json23
-rw-r--r--research/experiments/005-direct-document-peer/pure-server-sanitized-parity-linux-aarch64.json16
-rw-r--r--research/experiments/005-direct-document-peer/results/2026-09-24-after-nodelay-8k-linux-aarch64.json109
-rw-r--r--research/experiments/005-direct-document-peer/results/2026-09-24-before-nodelay-8k-linux-aarch64.json109
-rw-r--r--research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-1m-linux-aarch64.json157
-rw-r--r--research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-8k-linux-aarch64.json205
-rw-r--r--research/experiments/005-direct-document-peer/run.py145
-rw-r--r--research/experiments/005-direct-document-peer/scalar-peer-after-nodelay-parity-linux-aarch64.json1
17 files changed, 1151 insertions, 0 deletions
diff --git a/research/experiments/005-direct-document-peer/README.md b/research/experiments/005-direct-document-peer/README.md
new file mode 100644
index 0000000..d35ba20
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/README.md
@@ -0,0 +1,60 @@
+# Authenticated document peer: direct versus Pure
+
+This experiment uses the real `editor.pure` document app over the same TLS
+1.3/TCP transport and `pure-doc/1` wire format. `direct-peer.c` is a
+research-only server that parses the same authored program and calls the same
+store and evaluator directly. `pure-document-server` is the selected product
+server build without its native client commands. The baseline is limited to
+the tested synchronous document workflow: it has no Pure work tickets,
+admission/lifetime contract, cross-domain forwarding, or cancellation.
+
+Both servers passed the existing `check_document_peer.py` suite: mutual TLS
+identity, ALPN, source identity, external Python and native C clients, version
+conflicts, one-byte replacement of a 1 MiB value, fragmented/invalid/truncated
+frames, deadlines, and restart. The direct and Pure server builds passed the
+same suite under ASan/UBSan. These checks do not establish full feature or
+security equivalence under concurrent clients or failure networks.
+
+To run the selected product checks on Linux AArch64:
+
+```sh
+make -C prototypes/connections check-document-peer
+make -C prototypes/connections check-document-server
+```
+
+The first command checks the existing combined client/server executable; the
+second checks the server-only code ceiling and runs the same peer scenarios
+with the native C client from the combined executable. Both need permission
+to bind a local TCP port. The server-only executable and budget are optional;
+the default core and CLI do not link TLS.
+
+To reproduce the read comparison:
+
+```sh
+python3 research/experiments/005-direct-document-peer/run.py \
+ --body-bytes 8192 --reads 1024 --pairs 6 \
+ --output research/experiments/005-direct-document-peer/results/local-8k.json
+python3 research/experiments/005-direct-document-peer/run.py \
+ --body-bytes 1048576 --reads 64 --pairs 4 \
+ --output research/experiments/005-direct-document-peer/results/local-1m.json
+```
+
+The runner compiles both servers from source. Each trial creates and reads a
+real stored document, then times repeated full reads on one authenticated,
+persistent Python TLS connection. It verifies every response and the final
+stored file hash. TLS handshake, certificate creation, file creation, and the
+first cache-warming read are outside the timed batch. The server process RSS
+is recorded before and after. Fresh stores and alternating order reduce some
+order effects but do not control CPU frequency, OpenSSL or Python overhead,
+network behavior beyond local loopback, or host scheduling. The JSON files
+contain all individual samples, source hashes, compiler and OpenSSL versions.
+
+The short `before-nodelay` and `after-nodelay` artifacts repeat exactly 32
+authenticated 8 KiB reads through each server, differing only in the common
+TLS transport's TCP_NODELAY option. This intervention supports a Nagle/delayed
+ACK explanation for the prior ~44 ms per-request stall; no packet trace was
+captured, so that exact kernel interaction remains an inference. The longer
+post-change runs are for direct-versus-Pure cost, not a claim of Internet or
+Kitex/QUIC performance.
+
+See [the result report](../../../docs/research/055-authenticated-peer-transport-cost.md).
diff --git a/research/experiments/005-direct-document-peer/direct-peer-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/direct-peer-parity-linux-aarch64.json
new file mode 100644
index 0000000..05c62cc
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/direct-peer-parity-linux-aarch64.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/research/experiments/005-direct-document-peer/direct-peer-sanitized-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/direct-peer-sanitized-parity-linux-aarch64.json
new file mode 100644
index 0000000..356128d
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/direct-peer-sanitized-parity-linux-aarch64.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": 22528,
+ "server_after_large_work_rss_kib": 31632
+}
diff --git a/research/experiments/005-direct-document-peer/direct-peer.c b/research/experiments/005-direct-document-peer/direct-peer.c
new file mode 100644
index 0000000..437672d
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/direct-peer.c
@@ -0,0 +1,207 @@
+#define _POSIX_C_SOURCE 200809L
+/* Research-only server. It shares the product peer's TLS transport, wire
+ * format, program parser/evaluator, and store, but calls the store directly. */
+#include "program.h"
+#include "store.h"
+#include "../../../prototypes/common/tls-link.h"
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+enum { REQUEST_BYTES = 14, REPLY_BYTES = 13, HELLO_BYTES = 40,
+ OP_READ = 1, OP_WRITE = 2, WANT_BODY = 0x80 };
+static const unsigned char alpn[] = {10, 'p','u','r','e','-','d','o','c','/','1'};
+static volatile sig_atomic_t stopping;
+static void signal_stop(int signum) { (void)signum; stopping = 1; }
+
+typedef struct { PureResult status; int64_t revision; PureSpan body; } DirectOutcome;
+
+static uint32_t get32(const unsigned char *p) {
+ return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
+}
+static uint64_t get64(const unsigned char *p) {
+ uint64_t value = 0;
+ for (unsigned i = 0; i < 8; ++i) value = (value << 8) | p[i];
+ return value;
+}
+static void put32(unsigned char *p, uint32_t value) {
+ for (unsigned i = 0; i < 4; ++i) p[i] = (unsigned char)(value >> (24 - 8 * i));
+}
+static void put64(unsigned char *p, uint64_t value) {
+ for (unsigned i = 0; i < 8; ++i) p[i] = (unsigned char)(value >> (56 - 8 * i));
+}
+static bool number(const char *text, uint64_t limit, uint64_t *out) {
+ if (!text || !*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 bool valid_key(const char *key) {
+ if (!key || !*key) return false;
+ size_t size = 0;
+ for (; size <= 64 && key[size]; ++size) {
+ unsigned char c = (unsigned char)key[size];
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || c == '-' || c == '_')) return false;
+ }
+ return size <= 64;
+}
+static bool load_program(const char *path, PureProgram *program, unsigned char digest[32]) {
+ FILE *file = fopen(path, "rb");
+ if (!file) 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;
+ char error[160];
+ if (bad || !pure_program_parse(source, length, program, error, sizeof(error))) {
+ if (!bad) fprintf(stderr, "Program: %s\n", error);
+ return false;
+ }
+ return SHA256((const unsigned char *)source, length, digest) != NULL;
+}
+static bool write_hello(PureLink *link, const unsigned char digest[32], uint64_t deadline) {
+ unsigned char hello[HELLO_BYTES] = {'P','D','O','C',1,0,0,0};
+ memcpy(hello + 8, digest, 32);
+ return pure_link_write(link, hello, sizeof(hello), deadline);
+}
+static bool send_result(PureLink *link, DirectOutcome outcome, bool want_body, uint64_t deadline) {
+ if (outcome.status > PURE_ALLOCATION || outcome.revision < 0 ||
+ outcome.body.size > PURE_OBJECT_LIMIT || (outcome.body.size && !outcome.body.data)) return false;
+ unsigned char header[REPLY_BYTES];
+ header[0] = (unsigned char)outcome.status;
+ put64(header + 1, (uint64_t)outcome.revision);
+ put32(header + 9, (uint32_t)outcome.body.size);
+ return pure_link_write(link, header, sizeof(header), deadline) &&
+ (!want_body || pure_link_write(link, outcome.body.data, outcome.body.size, deadline));
+}
+static bool serve_request(PureLink *link, const PureProgram *program, const char *store, int *end) {
+ uint64_t deadline = pure_link_now_ms() + PURE_LINK_DEADLINE_MS;
+ unsigned char header[REQUEST_BYTES];
+ int got = pure_link_read(link, header, sizeof(header), deadline);
+ if (!got) { *end = 1; return true; }
+ if (got != 1) return false;
+ unsigned op = header[0] & ~WANT_BODY, key_size = header[1];
+ bool want_body = (header[0] & WANT_BODY) != 0;
+ uint64_t expected = get64(header + 2);
+ uint32_t body_size = get32(header + 10);
+ if ((op != OP_READ && op != OP_WRITE) || !key_size || key_size > 64 ||
+ expected > INT64_MAX || body_size > PURE_OBJECT_LIMIT ||
+ (op == OP_READ && (expected || body_size))) return false;
+ char key[65];
+ if (pure_link_read(link, key, key_size, deadline) != 1) return false;
+ if (memchr(key, 0, key_size)) return false;
+ key[key_size] = 0;
+ if (!valid_key(key)) {
+ (void)send_result(link, (DirectOutcome){.status = PURE_INVALID}, want_body, deadline);
+ return false;
+ }
+ DirectOutcome outcome = {0};
+ if (op == OP_READ) {
+ PureStoredView view = {0};
+ outcome.status = pure_store_select(store, key, program->read.offset,
+ program->read.length, program->read.to_end, program->read_required,
+ 4u * 1024u * 1024u, &view);
+ if (outcome.status == PURE_OK) {
+ outcome.body = view.body;
+ outcome.revision = view.revision;
+ }
+ bool sent = send_result(link, outcome, want_body, deadline);
+ pure_store_release(&view);
+ return sent;
+ }
+ unsigned char *input = body_size ? malloc(body_size) : NULL;
+ if (body_size && !input) {
+ (void)send_result(link, (DirectOutcome){.status = PURE_ALLOCATION}, want_body, deadline);
+ return false; /* Body deliberately not consumed after rejected admission. */
+ }
+ if (body_size && pure_link_read(link, input, body_size, deadline) != 1) {
+ free(input);
+ return false;
+ }
+ PureSpan value = {0}, reply = {0}, source = {input, body_size};
+ size_t offset = 0, remove = 0;
+ if (!pure_program_input(program, source, &value, &reply) ||
+ (program->splice && !pure_program_splice(program, source, &offset, &remove)))
+ outcome.status = PURE_LIMIT;
+ else if (program->splice)
+ outcome.status = pure_store_splice(store, key, (int64_t)expected,
+ offset, remove, value, &outcome.revision);
+ else outcome.status = pure_store_put(store, key, (int64_t)expected, value,
+ &outcome.revision);
+ if (outcome.status == PURE_OK) outcome.body = reply;
+ bool sent = send_result(link, outcome, want_body, deadline);
+ free(input);
+ return sent;
+}
+static bool serve_connection(PureLink *link, const PureProgram *program, const char *store,
+ const unsigned char digest[32]) {
+ if (!write_hello(link, digest, pure_link_now_ms() + PURE_LINK_DEADLINE_MS)) return false;
+ int end = 0;
+ while (!end) if (!serve_request(link, program, store, &end)) return false;
+ return true;
+}
+static int serve(const char *bind_address, const char *port, const char *program_path,
+ const char *store, const char *ca, const char *cert,
+ const char *key, const char *client_name, unsigned max_connections) {
+ if (!pure_link_port_number(port, true) || !client_name || !*client_name) return 2;
+ PureProgram program;
+ unsigned char digest[32];
+ if (!load_program(program_path, &program, digest) || pure_store_init(store) != PURE_OK) return 2;
+ SSL_CTX *ctx = pure_link_context(true, ca, cert, key, alpn);
+ if (!ctx) { ERR_print_errors_fp(stderr); return 2; }
+ int listener = pure_link_listen_at(bind_address, port);
+ if (listener < 0) { perror("listen"); SSL_CTX_free(ctx); return 2; }
+ struct sockaddr_storage address;
+ socklen_t address_size = sizeof(address);
+ if (getsockname(listener, (struct sockaddr *)&address, &address_size)) abort();
+ unsigned actual_port = address.ss_family == AF_INET
+ ? ntohs(((struct sockaddr_in *)&address)->sin_port)
+ : ntohs(((struct sockaddr_in6 *)&address)->sin6_port);
+ printf("READY %u\n", actual_port); fflush(stdout);
+ signal(SIGTERM, signal_stop); signal(SIGINT, signal_stop);
+ unsigned accepted = 0;
+ while (!stopping && (!max_connections || accepted < max_connections)) {
+ struct pollfd p = {.fd = listener, .events = POLLIN};
+ int result = poll(&p, 1, 1000);
+ if (result < 0) { if (errno == EINTR) continue; break; }
+ if (!result) continue;
+ int fd = accept(listener, NULL, NULL);
+ if (fd < 0) { if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue; break; }
+ ++accepted;
+ PureLink link = {.fd = fd};
+ bool healthy = pure_link_nonblocking(fd) &&
+ pure_link_handshake(&link, ctx, true, client_name, alpn);
+ if (healthy) healthy = serve_connection(&link, &program, store, digest);
+ pure_link_close(&link, healthy);
+ }
+ close(listener);
+ SSL_CTX_free(ctx);
+ return 0;
+}
+int main(int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+ if ((argc == 10 || argc == 11) && !strcmp(argv[1], "serve")) {
+ uint64_t limit = 0;
+ if (argc == 11 && (!number(argv[10], UINT32_MAX, &limit) || !limit)) return 2;
+ return serve(argv[2], argv[3], argv[4], argv[5], argv[6], argv[7], argv[8], argv[9],
+ (unsigned)limit);
+ }
+ fputs("direct-peer serve BIND_IP PORT PROGRAM STORE_DIR CA CERT KEY CLIENT_DNS [MAX_CONNECTIONS]\n", stderr);
+ return 2;
+}
diff --git a/research/experiments/005-direct-document-peer/pure-peer-after-nodelay-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-peer-after-nodelay-parity-linux-aarch64.json
new file mode 100644
index 0000000..45598a9
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-peer-after-nodelay-parity-linux-aarch64.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": 7952,
+ "server_after_large_work_rss_kib": 9696
+}
diff --git a/research/experiments/005-direct-document-peer/pure-peer-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-peer-parity-linux-aarch64.json
new file mode 100644
index 0000000..05c62cc
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-peer-parity-linux-aarch64.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/research/experiments/005-direct-document-peer/pure-server-after-nodelay-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-server-after-nodelay-parity-linux-aarch64.json
new file mode 100644
index 0000000..05c62cc
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-server-after-nodelay-parity-linux-aarch64.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/research/experiments/005-direct-document-peer/pure-server-after-nodelay-profile-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-server-after-nodelay-profile-linux-aarch64.json
new file mode 100644
index 0000000..0f6a3ce
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-server-after-nodelay-profile-linux-aarch64.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/research/experiments/005-direct-document-peer/pure-server-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-server-parity-linux-aarch64.json
new file mode 100644
index 0000000..05c62cc
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-server-parity-linux-aarch64.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/research/experiments/005-direct-document-peer/pure-server-profile-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-server-profile-linux-aarch64.json
new file mode 100644
index 0000000..c74cdf9
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-server-profile-linux-aarch64.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": "ea88a146270cea96faf976b548ceba7179c535042ec3a8656e92c3596372f9aa"
+ }
+ }
+}
diff --git a/research/experiments/005-direct-document-peer/pure-server-sanitized-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/pure-server-sanitized-parity-linux-aarch64.json
new file mode 100644
index 0000000..161dd4f
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/pure-server-sanitized-parity-linux-aarch64.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": 22640,
+ "server_after_large_work_rss_kib": 31776
+}
diff --git a/research/experiments/005-direct-document-peer/results/2026-09-24-after-nodelay-8k-linux-aarch64.json b/research/experiments/005-direct-document-peer/results/2026-09-24-after-nodelay-8k-linux-aarch64.json
new file mode 100644
index 0000000..f739b2b
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/results/2026-09-24-after-nodelay-8k-linux-aarch64.json
@@ -0,0 +1,109 @@
+{
+ "profile": "authenticated-document-read-direct-versus-pure",
+ "definition_sha256": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a",
+ "body_bytes": 8192,
+ "reads_per_trial": 32,
+ "pairs": 2,
+ "filesystem": {
+ "device": "/dev/mmcblk0p2",
+ "type": "ext4",
+ "mount": "/"
+ },
+ "sections": {
+ "direct": {
+ "text": 37862,
+ "data": 1352,
+ "bss": 16
+ },
+ "pure": {
+ "text": 43454,
+ "data": 1352,
+ "bss": 16
+ }
+ },
+ "binary_sha256": {
+ "direct": "9177aebfae07d84af5b6f23fbfe28a3a89a494943b8d0c8d0d35b68593b1f704",
+ "pure": "be9f569ab444085c761a7f7c9aa91c2acd6581534059785964b3a94da89fff25"
+ },
+ "samples": [
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 3028977,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "pure": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 2564107,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 3016236,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "direct": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 3019977,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ }
+ ],
+ "medians": {
+ "direct": {
+ "elapsed_ms": 3.024477,
+ "server_rss_before_kib": 8632.0,
+ "server_rss_after_kib": 8632.0
+ },
+ "pure": {
+ "elapsed_ms": 2.7901715,
+ "server_rss_before_kib": 8640.0,
+ "server_rss_after_kib": 8640.0
+ }
+ },
+ "sources_sha256": {
+ "research/experiments/005-direct-document-peer/direct-peer.c": "b1ee6ba5ec3d09b842ac42d4ca158b339ec2d23b157d2c568180af2731737501",
+ "research/experiments/005-direct-document-peer/run.py": "b53539f0a915376397011713ebbc9438a9b289d3b4dd745ffab5d6039fccd958",
+ "prototypes/connections/document-peer.c": "0c72121a6929239f618f8a642247af13fb5b68cca2999c125e52de685e5b2e50",
+ "prototypes/connections/check_document_peer.py": "d9dc789148951baa6f5d47f8cdea4009fe4d2dcf67b9dfaf2e6501f8e66a0f5c",
+ "prototypes/common/tls-link.c": "5d47ce411d6a81ce1a29d288c93ff507539fda1f202f2f41c0ff5560564b4f5a",
+ "prototypes/connections/store.c": "733ddcacaffd9e9ffbadfc385eadd4c9e62736a2b97b7bc048ad14845d83070f",
+ "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f",
+ "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389",
+ "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04",
+ "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f",
+ "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a"
+ },
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "compiler": "cc (Debian 14.2.0-19) 14.2.0",
+ "openssl": "OpenSSL 3.6.4 25 Aug 2026 (Library: OpenSSL 3.6.4 25 Aug 2026)"
+ }
+}
diff --git a/research/experiments/005-direct-document-peer/results/2026-09-24-before-nodelay-8k-linux-aarch64.json b/research/experiments/005-direct-document-peer/results/2026-09-24-before-nodelay-8k-linux-aarch64.json
new file mode 100644
index 0000000..019fd1e
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/results/2026-09-24-before-nodelay-8k-linux-aarch64.json
@@ -0,0 +1,109 @@
+{
+ "profile": "authenticated-document-read-direct-versus-pure",
+ "definition_sha256": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a",
+ "body_bytes": 8192,
+ "reads_per_trial": 32,
+ "pairs": 2,
+ "filesystem": {
+ "device": "/dev/mmcblk0p2",
+ "type": "ext4",
+ "mount": "/"
+ },
+ "sections": {
+ "direct": {
+ "text": 37862,
+ "data": 1352,
+ "bss": 16
+ },
+ "pure": {
+ "text": 43454,
+ "data": 1352,
+ "bss": 16
+ }
+ },
+ "binary_sha256": {
+ "direct": "edce33757c398fb23f6bc9e70bb31072714320324912f21599d23469e8381816",
+ "pure": "22560da4dd66f91f8b899569cfc771835034d7e07a68dd4da44e1605edb46c45"
+ },
+ "samples": [
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 1415860581,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "pure": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 1411832180,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 1411873125,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "direct": {
+ "reads": 32,
+ "body_bytes": 8192,
+ "elapsed_ns": 1407871983,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ }
+ ],
+ "medians": {
+ "direct": {
+ "elapsed_ms": 1411.866282,
+ "server_rss_before_kib": 8632.0,
+ "server_rss_after_kib": 8632.0
+ },
+ "pure": {
+ "elapsed_ms": 1411.8526525,
+ "server_rss_before_kib": 8632.0,
+ "server_rss_after_kib": 8632.0
+ }
+ },
+ "sources_sha256": {
+ "research/experiments/005-direct-document-peer/direct-peer.c": "b1ee6ba5ec3d09b842ac42d4ca158b339ec2d23b157d2c568180af2731737501",
+ "research/experiments/005-direct-document-peer/run.py": "b53539f0a915376397011713ebbc9438a9b289d3b4dd745ffab5d6039fccd958",
+ "prototypes/connections/document-peer.c": "0c72121a6929239f618f8a642247af13fb5b68cca2999c125e52de685e5b2e50",
+ "prototypes/connections/check_document_peer.py": "d9dc789148951baa6f5d47f8cdea4009fe4d2dcf67b9dfaf2e6501f8e66a0f5c",
+ "prototypes/common/tls-link.c": "df259073c5e1cd911495b48d4ccd43c8346d955a19a8773964abe58089fbebf9",
+ "prototypes/connections/store.c": "733ddcacaffd9e9ffbadfc385eadd4c9e62736a2b97b7bc048ad14845d83070f",
+ "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f",
+ "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389",
+ "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04",
+ "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f",
+ "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a"
+ },
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "compiler": "cc (Debian 14.2.0-19) 14.2.0",
+ "openssl": "OpenSSL 3.6.4 25 Aug 2026 (Library: OpenSSL 3.6.4 25 Aug 2026)"
+ }
+}
diff --git a/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-1m-linux-aarch64.json b/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-1m-linux-aarch64.json
new file mode 100644
index 0000000..9e65005
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-1m-linux-aarch64.json
@@ -0,0 +1,157 @@
+{
+ "profile": "authenticated-document-read-direct-versus-pure",
+ "definition_sha256": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a",
+ "body_bytes": 1048576,
+ "reads_per_trial": 64,
+ "pairs": 4,
+ "filesystem": {
+ "device": "/dev/mmcblk0p2",
+ "type": "ext4",
+ "mount": "/"
+ },
+ "sections": {
+ "direct": {
+ "text": 37862,
+ "data": 1352,
+ "bss": 16
+ },
+ "pure": {
+ "text": 43454,
+ "data": 1352,
+ "bss": 16
+ }
+ },
+ "binary_sha256": {
+ "direct": "9177aebfae07d84af5b6f23fbfe28a3a89a494943b8d0c8d0d35b68593b1f704",
+ "pure": "be9f569ab444085c761a7f7c9aa91c2acd6581534059785964b3a94da89fff25"
+ },
+ "samples": [
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 148966030,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ },
+ "pure": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 149719696,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 149739381,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ },
+ "direct": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 150962435,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ }
+ },
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 137503567,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ },
+ "pure": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 155644335,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 155630742,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ },
+ "direct": {
+ "reads": 64,
+ "body_bytes": 1048576,
+ "elapsed_ns": 155552075,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 1048600,
+ "stored_file_sha256": "a89fd11658d8139f5cb9723120126df35590f80d02f72fdfa46c1d7c6cf44383"
+ }
+ }
+ ],
+ "medians": {
+ "direct": {
+ "elapsed_ms": 149.9642325,
+ "server_rss_before_kib": 8632.0,
+ "server_rss_after_kib": 8632.0
+ },
+ "pure": {
+ "elapsed_ms": 152.6850615,
+ "server_rss_before_kib": 8640.0,
+ "server_rss_after_kib": 8640.0
+ }
+ },
+ "sources_sha256": {
+ "research/experiments/005-direct-document-peer/direct-peer.c": "b1ee6ba5ec3d09b842ac42d4ca158b339ec2d23b157d2c568180af2731737501",
+ "research/experiments/005-direct-document-peer/run.py": "b53539f0a915376397011713ebbc9438a9b289d3b4dd745ffab5d6039fccd958",
+ "prototypes/connections/document-peer.c": "0c72121a6929239f618f8a642247af13fb5b68cca2999c125e52de685e5b2e50",
+ "prototypes/connections/check_document_peer.py": "d9dc789148951baa6f5d47f8cdea4009fe4d2dcf67b9dfaf2e6501f8e66a0f5c",
+ "prototypes/common/tls-link.c": "5d47ce411d6a81ce1a29d288c93ff507539fda1f202f2f41c0ff5560564b4f5a",
+ "prototypes/connections/store.c": "733ddcacaffd9e9ffbadfc385eadd4c9e62736a2b97b7bc048ad14845d83070f",
+ "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f",
+ "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389",
+ "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04",
+ "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f",
+ "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a"
+ },
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "compiler": "cc (Debian 14.2.0-19) 14.2.0",
+ "openssl": "OpenSSL 3.6.4 25 Aug 2026 (Library: OpenSSL 3.6.4 25 Aug 2026)"
+ }
+}
diff --git a/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-8k-linux-aarch64.json b/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-8k-linux-aarch64.json
new file mode 100644
index 0000000..153842b
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/results/2026-09-24-nodelay-8k-linux-aarch64.json
@@ -0,0 +1,205 @@
+{
+ "profile": "authenticated-document-read-direct-versus-pure",
+ "definition_sha256": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a",
+ "body_bytes": 8192,
+ "reads_per_trial": 1024,
+ "pairs": 6,
+ "filesystem": {
+ "device": "/dev/mmcblk0p2",
+ "type": "ext4",
+ "mount": "/"
+ },
+ "sections": {
+ "direct": {
+ "text": 37862,
+ "data": 1352,
+ "bss": 16
+ },
+ "pure": {
+ "text": 43454,
+ "data": 1352,
+ "bss": 16
+ }
+ },
+ "binary_sha256": {
+ "direct": "9177aebfae07d84af5b6f23fbfe28a3a89a494943b8d0c8d0d35b68593b1f704",
+ "pure": "be9f569ab444085c761a7f7c9aa91c2acd6581534059785964b3a94da89fff25"
+ },
+ "samples": [
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 76563753,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 93622467,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 90777415,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 92965246,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 76165419,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 92132358,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 80493468,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 77076696,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "direct",
+ "pure"
+ ],
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 91795043,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 90660842,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ },
+ {
+ "order": [
+ "pure",
+ "direct"
+ ],
+ "pure": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 93672077,
+ "server_rss_before_kib": 8640,
+ "server_rss_after_kib": 8640,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ },
+ "direct": {
+ "reads": 1024,
+ "body_bytes": 8192,
+ "elapsed_ns": 116319172,
+ "server_rss_before_kib": 8624,
+ "server_rss_after_kib": 8624,
+ "stored_file_bytes": 8216,
+ "stored_file_sha256": "286cfc86e3ebe6d82dd923ef777eba30f748e8b1dd58d5c4352babbf85d0d64f"
+ }
+ }
+ ],
+ "medians": {
+ "direct": {
+ "elapsed_ms": 84.4358695,
+ "server_rss_before_kib": 8640.0,
+ "server_rss_after_kib": 8640.0
+ },
+ "pure": {
+ "elapsed_ms": 91.4548865,
+ "server_rss_before_kib": 8640.0,
+ "server_rss_after_kib": 8640.0
+ }
+ },
+ "sources_sha256": {
+ "research/experiments/005-direct-document-peer/direct-peer.c": "b1ee6ba5ec3d09b842ac42d4ca158b339ec2d23b157d2c568180af2731737501",
+ "research/experiments/005-direct-document-peer/run.py": "b53539f0a915376397011713ebbc9438a9b289d3b4dd745ffab5d6039fccd958",
+ "prototypes/connections/document-peer.c": "0c72121a6929239f618f8a642247af13fb5b68cca2999c125e52de685e5b2e50",
+ "prototypes/connections/check_document_peer.py": "d9dc789148951baa6f5d47f8cdea4009fe4d2dcf67b9dfaf2e6501f8e66a0f5c",
+ "prototypes/common/tls-link.c": "5d47ce411d6a81ce1a29d288c93ff507539fda1f202f2f41c0ff5560564b4f5a",
+ "prototypes/connections/store.c": "733ddcacaffd9e9ffbadfc385eadd4c9e62736a2b97b7bc048ad14845d83070f",
+ "prototypes/connections/store-port.c": "d6bf17878b325e039360816edcbf8d68e41b373cf3e6128d7795e11ae21fdd6f",
+ "prototypes/connections/program.c": "d7186634d2bfd83bbaf87ea3bafdec8d14be8f556cbf3d9877d07e0074b4b389",
+ "prototypes/connections/evaluate.c": "ee8b91efc733b9d305711b7cbb7b502ae5d3917e06f750b7f62b632ed0ea6d04",
+ "prototypes/connections/runtime.c": "1ced6734ee07f3688e5c9cc0a8af639da88a1741dc3e6f65ec109baa2d826e6f",
+ "prototypes/connections/examples/editor.pure": "8ca6c191b98bf53a592ce24f2b5cd040e41f05c4ccdda7a072d5d7f79870c06a"
+ },
+ "environment": {
+ "platform": "Linux-6.18.39+rpt-rpi-2712-aarch64-with-glibc2.41",
+ "machine": "aarch64",
+ "compiler": "cc (Debian 14.2.0-19) 14.2.0",
+ "openssl": "OpenSSL 3.6.4 25 Aug 2026 (Library: OpenSSL 3.6.4 25 Aug 2026)"
+ }
+}
diff --git a/research/experiments/005-direct-document-peer/run.py b/research/experiments/005-direct-document-peer/run.py
new file mode 100644
index 0000000..ff4654f
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/run.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+"""Compare authenticated document reads through direct and Pure server paths."""
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import platform
+import statistics
+import subprocess
+import sys
+import tempfile
+import time
+
+HERE = Path(__file__).resolve().parent
+ROOT = HERE.parents[2]
+CONNECTIONS = ROOT / 'prototypes/connections'
+COMMON = ROOT / 'prototypes/common'
+sys.path.insert(0, str(CONNECTIONS))
+import check_document_peer as peer # noqa: E402
+
+
+def sha(path):
+ return hashlib.sha256(path.read_bytes()).hexdigest()
+
+
+def command(*args):
+ result = subprocess.run([str(arg) for arg in args], capture_output=True, text=True,
+ timeout=60)
+ if result.returncode:
+ raise AssertionError((result.args, result.returncode, result.stdout, result.stderr))
+ return result.stdout
+
+
+def compile_servers(directory, cc):
+ flags = [cc, '-O2', '-g', '-std=c17', '-Wall', '-Wextra', '-Wpedantic', '-Werror',
+ '-I' + str(CONNECTIONS)]
+ common = [COMMON / 'tls-link.c', CONNECTIONS / 'store.c',
+ CONNECTIONS / 'program.c', CONNECTIONS / 'evaluate.c']
+ direct = directory / 'direct-peer'
+ pure = directory / 'pure-server'
+ command(*flags, HERE / 'direct-peer.c', *common, '-lssl', '-lcrypto', '-o', direct)
+ command(*flags, '-DPURE_PEER_SERVER_ONLY', CONNECTIONS / 'document-peer.c',
+ *common, CONNECTIONS / 'store-port.c', CONNECTIONS / 'runtime.c',
+ '-lssl', '-lcrypto', '-o', pure)
+ return {'direct': direct, 'pure': pure}
+
+
+def sections(binary):
+ row = command('size', binary).splitlines()[1].split()
+ return dict(zip(('text', 'data', 'bss'), map(int, row[:3])))
+
+
+def trial(binary, program, root, cert_root, ca, body, reads):
+ store = root / 'store'
+ server, port = peer.start(binary, program, store, cert_root, ca)
+ try:
+ with peer.connect(cert_root, ca, port) as sock:
+ peer.hello(sock, program)
+ assert peer.request(sock, 2, 'bench', 0, peer.edit(0, 0, body),
+ want_body=False) == (0, 1, b'', len(body))
+ assert peer.request(sock, 1, 'bench') == (0, 1, body, len(body))
+ rss_before = peer.rss_kib(server.pid)
+ start = time.perf_counter_ns()
+ for _ in range(reads):
+ assert peer.request(sock, 1, 'bench') == (0, 1, body, len(body))
+ elapsed_ns = time.perf_counter_ns() - start
+ rss_after = peer.rss_kib(server.pid)
+ finally:
+ peer.stop(server)
+ stored = store / 'obj-bench'
+ return {'reads': reads, 'body_bytes': len(body), 'elapsed_ns': elapsed_ns,
+ 'server_rss_before_kib': rss_before, 'server_rss_after_kib': rss_after,
+ 'stored_file_bytes': stored.stat().st_size,
+ 'stored_file_sha256': sha(stored)}
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--cc', default=os.environ.get('CC', 'cc'))
+ parser.add_argument('--body-bytes', type=int, default=8192)
+ parser.add_argument('--reads', type=int, default=512)
+ parser.add_argument('--pairs', type=int, default=4)
+ parser.add_argument('--work-root', type=Path, default=HERE / 'results')
+ parser.add_argument('--output', type=Path)
+ args = parser.parse_args()
+ if not 1 <= args.body_bytes <= 1024 * 1024 or not 1 <= args.reads <= 4096 or not 1 <= args.pairs <= 16:
+ parser.error('body-bytes 1..1048576, reads 1..4096, pairs 1..16')
+ if args.output and args.output.exists():
+ parser.error('evidence file already exists')
+ args.work_root.mkdir(parents=True, exist_ok=True)
+ filesystem = command('df', '-T', args.work_root).splitlines()[1].split()
+ program = CONNECTIONS / 'examples/editor.pure'
+ body = bytes((ord('a') + i % 26 for i in range(args.body_bytes)))
+ with tempfile.TemporaryDirectory(prefix='pure-direct-peer-', dir=args.work_root) as name:
+ root = Path(name)
+ binaries = compile_servers(root, args.cc)
+ sizes = {variant: sections(binary) for variant, binary in binaries.items()}
+ binary_hashes = {variant: sha(binary) for variant, binary in binaries.items()}
+ ca = peer.certificates(root)
+ samples = []
+ for pair in range(args.pairs):
+ order = ('direct', 'pure') if pair % 2 == 0 else ('pure', 'direct')
+ entry = {'order': list(order)}
+ for variant in order:
+ path = root / f'{pair}-{variant}'
+ path.mkdir()
+ # The certificates live at the common root. The store is unique
+ # to each server so conditional revision 0 always means create.
+ entry[variant] = trial(binaries[variant], program, path, root, ca,
+ body, args.reads)
+ assert entry['direct']['stored_file_sha256'] == entry['pure']['stored_file_sha256']
+ samples.append(entry)
+ medians = {variant: {'elapsed_ms': statistics.median(
+ sample[variant]['elapsed_ns'] for sample in samples) / 1e6,
+ 'server_rss_before_kib': statistics.median(
+ sample[variant]['server_rss_before_kib'] for sample in samples),
+ 'server_rss_after_kib': statistics.median(
+ sample[variant]['server_rss_after_kib'] for sample in samples)}
+ for variant in ('direct', 'pure')}
+ sources = [HERE / 'direct-peer.c', HERE / 'run.py',
+ CONNECTIONS / 'document-peer.c', CONNECTIONS / 'check_document_peer.py',
+ COMMON / 'tls-link.c', CONNECTIONS / 'store.c',
+ CONNECTIONS / 'store-port.c', CONNECTIONS / 'program.c',
+ CONNECTIONS / 'evaluate.c', CONNECTIONS / 'runtime.c', program]
+ result = {'profile': 'authenticated-document-read-direct-versus-pure',
+ 'definition_sha256': sha(program), 'body_bytes': args.body_bytes,
+ 'reads_per_trial': args.reads, 'pairs': args.pairs,
+ 'filesystem': {'device': filesystem[0], 'type': filesystem[1],
+ 'mount': filesystem[-1]},
+ 'sections': sizes, 'binary_sha256': binary_hashes,
+ 'samples': samples, 'medians': medians,
+ 'sources_sha256': {str(path.relative_to(ROOT)): sha(path) for path in sources},
+ 'environment': {'platform': platform.platform(), 'machine': platform.machine(),
+ 'compiler': command(args.cc, '--version').splitlines()[0],
+ 'openssl': command('openssl', 'version').strip()}}
+ output = json.dumps(result, indent=2) + '\n'
+ if args.output:
+ args.output.parent.mkdir(parents=True, exist_ok=True)
+ args.output.write_text(output)
+ print(output, end='')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/research/experiments/005-direct-document-peer/scalar-peer-after-nodelay-parity-linux-aarch64.json b/research/experiments/005-direct-document-peer/scalar-peer-after-nodelay-parity-linux-aarch64.json
new file mode 100644
index 0000000..1316e2e
--- /dev/null
+++ b/research/experiments/005-direct-document-peer/scalar-peer-after-nodelay-parity-linux-aarch64.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": 8640, "server_hostname": "pass", "server_idle_rss_kib": 7968}