aboutsummaryrefslogtreecommitdiffstats
path: root/CHECKPOINT.md
Commit message (Collapse)AuthorAgeFilesLines
* Research 140: a suite of eleven specs from different fields (DNS, SHA-256, ↵eunhhu7 days1-0/+1
| | | | inflate, JSON, UTF-8 and line breaking, relational queries and graphs, text editing, box layout, HTTP/1.1 by packets, retries and timeouts, constant-time crypto) with five criteria (written in aup and Pure alone, no workaround, lines against a Python reference, time against C, the spec's own vectors passing in Pure's checks on three machines); ten probes (044) show today's aup stops at a byte by position, bit operations, nesting, recursion, variants and reading a character; the core's minimal candidates and the rule against piling up (only what two specs need); SHA-256 and JSON first
* Research 138: the batch executor's stage 5, design 025 made: sums into cells ↵eunhhu11 days1-1/+1
| | | | at once in integers (join 116.0 to 110.5 ms, short of 108: what is left is the same cells' memory, read, add and write again), decide's bit found once (BFS 23.2, short of 21.5: the tail's time was its loads more than its instructions), and the batch executor's loops aligned to 64-byte lines (the hot loops' placement had swung knapsack 6% at the same instructions; the lists' loops aligned too cost the bank's queries 1-2% more instructions, so not they); against b9 matmul -36%, knapsack -28%, roots -7.6%, join -6.2%, BFS -3.9%, the executor alone, the bank queries and TPC-C (+0.7%) as they were; 025's bars and what each stage measured; 024 and CHECKPOINT say so
* Research 137: the batch executor's stage 4 (columns read in place, ↵eunhhu12 days1-1/+1
| | | | comparisons counting their lanes): knapsack 10.07 to 8.25 ms (bar 8.6; b9 11.46, the compiled code 7.9), its instructions 12.2% below b9's; what is left of knapsack's gap is the per-batch interpretation (about 3 ms), not the kernels (ADD 1.7, GT 1.7, the masked set 0.55 ms); the others the same by instructions; make check's server_view_workers timed out once (the known flake), passed 3/3 alone and in a full rerun; bank queries' instructions within 0.1% of b9's, TPC-C -0.3%; the two large gaps left (matmul's nested rounds, BFS's candidate tail) the user's to choose
* Research 136: the batch executor's stage 3 (four-byte MUL, reductions with ↵eunhhu12 days1-1/+1
| | | | no lanes while every lane's e is the same, a list's cells once a batch): against b9, interleaved, matmul 40.01 to 25.97 ms, knapsack 11.47 to 10.07, roots 8.97 to 8.29, join 117.2 to 115.8, BFS and sort the same; sum0 8.69 to 1.01 (bar 1.5); matmul short of its bar (21): the nested rounds' interpretation (about 14 ms) and the matrices' making (4-5) were left out of the arithmetic; the executor alone and the bank queries' instructions as b9 (0.08%), TPC-C +0.7%; stage 4 next (knapsack's six passes to three)
* Research 135: batch plans' stage 2 measured (b11 = 7959885 against b10, ↵eunhhu12 days1-1/+1
| | | | interleaved): matmul 38.18 to 36.10 ms with 4.5% fewer instructions (segments in its nested rounds), knapsack's per-batch cost 0.68 to 0.63 us but knapsack 10.35 to 10.51 ms with 1% fewer instructions (its per-element loops placed elsewhere), sum0 1,743 instructions a batch: short of 025's stage-2 bars (9.2 ms, 0.45 us, 33 ms): 025 priced a segment's instruction at 50 cycles, the counts show about 25 instructions, and a batch's cost sits in finds, bounds, read records, lane setup, masks and the deferred store's check; a list's cells once a batch measured at 3% and not made; next, per element: stage 3's four-byte MUL (matmul 21 ms)
* Research 134: batch plans' stage 1 measured (b10 = a6fabf0 against b9, ↵eunhhu12 days1-1/+1
| | | | interleaved): knapsack's per-batch cost 0.90 to 0.68 us and knapsack 11.48 to 10.34 ms, short of 025's bars (0.60 us, 10.3 ms): 025 counted the pass's per-instruction head as lookups; sum0 2,330 to 1,822 instructions a batch (bar 1,550); matmul 40.0 to 38.2 ms with the same instructions and BFS 24.04 to 24.31 with 0.2% fewer (placement); sum0's 8.69 to 6.13 ms mostly the compiler now vectorizing the reductions' first loop; the executor alone the same instructions; what is left a batch is the pass's Val handling (stage 2); 041 runs.sh pairs, pairs and counts data
* Design 025: batch programs (132's A), the block's facts once at load and ↵eunhhu12 days1-1/+1
| | | | only values each batch: knapsack's per-batch cost measured at 0.90 us, 38% of its 11.5 ms (BATCH 1024 against 256 under a sampled profile: re-reading the block about 39%, value kinds 26%, list facts and dependence 22%, dispatch 14%), sum0's 0.33 us, matmul's inner-round dispatch about 13 ms and float-checked arithmetic about 22; five stages (facts at load, static kinds and list facts once a batch, typed kernels, fewer passes, the tails) with bars from those shares; 041 batch-costs data
* Research 133: register tiles measured (132's deciding experiment): past the ↵eunhhu12 days1-1/+1
| | | | bar on roots (0.71x the compiled code's cycles a step), not on knapsack (4x) or BFS (2.4x), as NEON has no gather and a tile's lanes go out and in one by one; tight passes of the batch executor's own shape in the same run at 1.2x (BFS) and 0.4x (knapsack, clang): the shape is right and the 3x is implementation, so 132's A (a batch program made once at load, loops tight by type, width and stride, fewer passes) is the way; tiles kept aside for chains of arithmetic; 132, 024 and CHECKPOINT say so
* Research 131: the batch executor's general cuts, by the user's choice of the ↵eunhhu12 days1-1/+1
| | | | general way over a table of fused operations: values computed by the same code taken from the first, if !c one branch, selections (an arm's few lanes alone), keys as bits asked in one loop, L.first + i affine, a slice's bounds at once: the suite's BFS 211 to 112 instructions an edge, 41 to 24 ms (the compiled code 38.5, 7.7); where the rest goes and the floor of the general way (about twice the compiled code); knapsack and matmul 10% slower, cause not fully told; 024 and CHECKPOINT say so
* Research 130: the batch executor's frontiers (a count's guard, walks in a ↵eunhhu12 days1-1/+1
| | | | batch's rounds together or spread into lanes, filters asked as a batch opens and again at its end): the suite's BFS batched, 93 to 41 ms (the compiled code 7.7, C 10); where it goes (211 instructions an edge against 38.5: a filter's code over every lane the most); the floor of operations each a pass over the lanes (about 50 an edge, 1.3x the compiled code) and what would pass it (fused operations from a table, 024's); the choice left: a table of them, or the compiled code kept for such work; 024 and CHECKPOINT say so
* Research 129: the batch executor's costs cut (appends at once 5.18 to 0.65 ↵eunhhu2026-10-061-1/+1
| | | | ms, a recurrence's steps as numbered values and one loop of actions 44.4 to 21.8, reductions' ends in any order where it cannot tell 9.80 to 4.00): knapsack 10.8, matmul 36.4, join 118, sort 55.5 ms without machine code; where each work's time goes now; BFS's arithmetic (a tail of about a tenth of the edges, done batch-side; edges spread into lanes, not walked together) and the three pieces it needs; the view-workers check's flake; 024 and CHECKPOINT say so
* Research 128: the batch executor's appends, recurrences, sums into cells and ↵eunhhu2026-10-061-1/+1
| | | | walks (join 423 to 161 ms without machine code, the tier 74) and what a batch pays for, measured apart: an instruction about 20 ns a batch (0.02 ns a round at 1024 lanes), a reduction's end 4-9 ns a round, an append about 100 ns (the executor's), join's recurrence about 60 ns a round; BFS not batched (a count that grows, a filter that only grows, nested walks), so the JIT stays for now; the next in that order: appends at once, the recurrence's steps in registers, reductions' ends; 022 and 024 and CHECKPOINT say so
* Research 127: the batch executor's stage 2 (nested loops' rounds run ↵eunhhu2026-10-051-1/+1
| | | | together lane by lane, breaks, reductions folded in the rounds' order) and the measured passes after it (finite checks in the arithmetic's loop, tests as four-byte masks, the loop's value made once): roots 9.13 ms without machine code, below the tier's 9.57; matmul 50 (the tier 14.3: its table's loop and 15 instructions a nested round left); 685 random programs alike; 024 and CHECKPOINT say so
* Research 126: the batch executor's first stage (024) as built and measured: ↵eunhhu2026-10-051-1/+1
| | | | knapsack 1,041 to 13.6 ms in the executor (the compiled code 7.5; its inner loop 1.17x the tier's, the stage's criterion missed; the table's adds left to stage 4), the executor without batch loops unchanged; what measuring overturned (a call among the steps cost 20% of the instructions, so two copies; run aligned to 64 for its cycles; results apart from operands for vector code); tests; 1024 lanes, 134 KB a thread; 024 and CHECKPOINT say so
* 024: the table with operations apart and interpreted beside the hand-fused ↵eunhhu2026-10-051-1/+1
| | | | forms (geometric mean 1.58 and 1.24, the tier 1.92), the prototypes as a ceiling (124's floor 0.90x became 1.2-2.5x), division a third of the interpreted recurrence, the distance rule for equal strides only, why not copy-and-patch, matmul's miss without a fused multiply-add; CHECKPOINT says so
* Design 024: a batch executor, loops run 256 rounds at a time by operations ↵eunhhu2026-10-051-1/+1
| | | | the C compiler makes at build time (no code made at run time, 32 KB of lanes), checks as a batch's flag and a failed batch run again round by round; which loops batch (sums, affine keys at a nonnegative distance, filters that only grow, a count that only grows), sections run round by round interpreted, fused operations from a table, the JIT retired if it is not slower; 041's prototypes against C: roots 0.99, knapsack 0.71, matmul 0.94, BFS 1.03, join 1.59 (the tier 1.20-2.46); CHECKPOINT says so
* Research 125: the tier's first steps against C (e2e56dd: roots 1.40, ↵eunhhu2026-10-051-1/+1
| | | | knapsack 1.19, sort 1.37, join 2.54, words 5.58, matmul 1.85, BFS 1.61; geometric mean 1.92, from 3-300x at 6b45806), what each change did, the forms measured in assembly, the apps unchanged, what is left (text in the executor, BFS's appends, spills); CHECKPOINT says so
* Research 124: 022's BFS past C only by reading ahead (15.7 ms against 9.9 ↵eunhhu2026-10-051-1/+1
| | | | without), the suite of seven kinds of work at 3-300x C, floors at 0.90x C with the meaning's checks hoisted to 0-3%; CHECKPOINT says so
* Measure 021: BFS 190 to 90 ms over HTTP against C's 9.1, short of C; Pure's ↵eunhhu2026-10-051-1/+1
| | | | forms by hand beat C only packed, narrow and read ahead (123)
* Measure views off the connection threads: writes' tail as with the CPU ↵eunhhu2026-10-041-3/+3
| | | | shared, two askers 1.95 times one (122)
* Say what pushing views costs and how fresh it keeps them (019, research 120)eunhhu2026-10-041-0/+1
|
* Put lifetimes and computing in the checkpoint (018)eunhhu2026-10-041-1/+2
|
* Say what an app's tokens cost (017 3b, research 116)eunhhu2026-10-041-2/+2
| | | | | | | | Requests to an app that checks tokens go 2.92% slower than to an open one on 64 keep-alive connections, 2.2% of it the head the token makes longer (an open app sent the same head is as slow); hashing every request on a new connection costs 0.91%. 031 measures the three in turns.
* Say what apps that change on a running host showed (017 stage 3, research 115)eunhhu2026-10-041-3/+3
| | | | | | | | | | Changing an app in place keeps a million items' app answering but for 1.12 ms (a host restart: 150 ms; stopping and starting it again from its store, as first built, 150.4 ms), 171.5 ms when its records change shape (332 ms). Another app's p99 stays 0.97 times as it was while one changes every 0.2 s; a host of one app is as it was (TPC-C +0.7%, instructions at most +0.02%); each module change leaves 19 KB. 030 holds the windows of both builds, the isolation, the pairs, the counts and the memory.
* Trade stores with the WSL2 too: its bytes are the Pi's (029, research 114)eunhhu2026-10-041-1/+1
| | | | | | The same events make byte-identical stores on the x86_64 WSL2 (4 KiB pages) as on the Pi and the Mac, and a store made on either of the Pi and the WSL2 opens on the other and goes on the same.
* Say what running on an x86_64 WSL2 and an M3 Mac showed (research 114)eunhhu2026-10-021-1/+4
| | | | | | | | | The same modules and inputs give the same outputs, failures and store bytes on the Pi, an x86_64 WSL2 and an M3 Mac; other compilers found an argument read before it was set, and the core and store build on macOS again. WSL2's wall clock steps back about 2 s every 32 s. The Pi froze twice under a measuring tool that held its output: it now streams it, and experiments run under research/guard.py.
* Say what one sending loop showed (017 2b, research 113)eunhhu2026-10-011-2/+2
| | | | | | | The loop alone slowed four links to half: woken by every record queued, it chased the owner a record at a time. Pushing once a turn made links 1.6 times as fast, with threads or the loop alike; the loop's own cost is within the noise. Twenty outputs now add two threads, not 21.
* Say that apps of a host send to each other and to other nodes (017)eunhhu2026-10-011-2/+2
| | | | | | | | Stage 2 is built and measured: what was learned (the receiving side needed nothing new, an output goes one way only) and the numbers. The checkpoint adds sending to the many-apps row, and keeps 2b (one loop for the send threads) and stage 3 (apps added and replaced while the host runs) among what is left.
* docs: link aup and vega via clean web paths instead of raw server pathseunhhu2026-10-011-2/+2
|
* Say that one host runs many apps, and what its first stage found (017)eunhhu2026-10-011-1/+2
| | | | | | | | | | 017's first stage is built and measured: the shape now says what was built where it differs from the design (work counted as written, nothing counted without a budget, every app of --apps run by workers, a busy app kept on its worker, a home thread for each app's connections), and what was learned on the way. The checkpoint lists many apps on one host among what Pure shows, and stages 2 and 3 among what is left.
* Say that i64 is in, and what it found (016)eunhhu2026-09-301-2/+2
| | | | | | | | | | | 016 is built: bool and i64. The second stage's findings: an integer literal takes the other side's type, found by compiling it on trial (linear once the wanted type is passed down); a literal with no context stays a number, so no program changes meaning and modules without i64 are the same bytes; one rule carries values between numbers, bools and i64s; and the number paths passed the timing criterion, with what i64 had added to them counted and halved (110). The checkpoint lists bool and i64 in the app model and drops i64 from what is left.
* Say that bool is in, and what its first stage found (016)eunhhu2026-09-301-1/+1
| | | | | | | 016's first stage is built and checked: no opcode was new, a stored send's bool was refused on the way back in (found by server_meaning's restart, fixed), -0 becomes false as 015 says zero has no sign, and True in JSON is no JSON at all. i64 is what is left of 016.
* Give bools their own type in the module format (v6, 016 stage 1)eunhhu2026-09-301-1/+1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A condition was a number, non-zero being true: a flag was a number field, a view showed 1 for true, and nothing kept a number from standing for a bool or a bool from being added to. Now the verifier has a bool among its stack types, and a verified module never mixes the two. No opcode is new: - CONST with argument 1 is a bool (+0 or 1), NUM_TEXT with argument 1 writes one as true or false; LOAD and STORE take a number or a bool state, locals may hold a bool (2). - Comparisons (LT to NE, TEXT_COMPARE) and LIST_HAS give a bool; AND, OR and NOT take and give them; JZ, REQUIRE, filters and folds' tests take one (count and first leave a bool, sum, max, min and distinct a number and a bool); EQ and NE compare two bools too. - Type codes: state 5, record field 2, message field 6 (one byte in the prefix, 0 or 1, or the message fails as limit), view 3. A bool's constant, initial value and default are +0 or 1; a list is not keyed by one. PUREMOD6: v5 modules are built again. At run time a bool is a number's 0 or 1, so leaf-at-a-time folds run as before. A number that becomes a bool (a module replaced, a stored state or item read again, a waiting send recast) keeps 0 (either sign) and 1 and takes the default otherwise; a bool that becomes a number is 0 or 1. The host carries bools on every boundary: JSON true and false only (a row's field too), a query's true and false, true and false in views, table JSON and HTTP deliveries; a link takes a bool only into a bool. A stored outbox's field types were read back with a check that refused bool (at or past rows): a node whose output had a bool field and a send waiting could not start again. test-module now stores such a send and opens it (it fails on the old check), beside 28 bool refusals of the verifier and bool states, fields, views, sends, links and migration. The lane generator makes numbers and bools apart.
* Say what is left: the memory steps are done, finding items in large lists is noteunhhu2026-09-301-1/+2
|
* Say TPC-C's RAM numbers since snapshot nodes are reusedeunhhu2026-09-301-3/+4
|
* Compare TPC-C on the SD card with SQLite committing in groups, not each ↵eunhhu2026-09-301-2/+4
| | | | | | | | transaction The 90x of 092 was against SQLite's defaults committing each transaction; tuned for the card and grouped as Pure groups, SQLite is 4-11x behind, the bytes it writes a transaction being the difference.
* Say how growing a board two nodes share went (105)eunhhu2026-09-301-1/+1
| | | | | | | | | | | | | | | | | | 103's next step: grow a real app, adding and reordering fields, cutting the connection, starting again, and write down what gets in the way. Two nodes share a board (from Vega's examples/board.aup), each sending its changes to the other, the later change winning. Upgrading them one after the other deadlocked: fixed by recasting waiting sends by name (9aa123e) and taking fields by name (014, 5ce7655), and under it a link that stopped for good when a refused stream's try was lost (baaf955). Now the older node's changes reach the newer one during the upgrade, the newer one's new field waits for the older, and the boards agree a second after. Fields in another order agree at once; a partition healed agrees on the later change; both nodes started again go on. Two rough edges eased on the way (the node's name, message like in aup); sends and applies are still written field by field. CHECKPOINT's row for links says so.
* Say where a node's memory goes, and the budget it now keeps (104, 013)eunhhu2026-09-301-1/+2
| | | | | | | | | | | | | | | | | | 104 measures a node's memory under a receiver that is down and under a snapshot held for long (021's run.py: resident memory against what was posted, 30 s a case). A held snapshot grew the node without end (6.4 to 678 MiB, fixed in d1e8f38); a slow receiver grows it by what was sent, 221 B a send, which is state until delivered, so the budget bounds state and refuses past it. 013 is that budget's design: what it counts (state bytes, deterministic), what an event over it does, why an answer takes its send's room, how links and HTTP wait, what it does not count and those bounds, and the checks. With --memory 4000000 the same load fills it in about 4 s and the node stays at 9.6-9.8 MiB (45.9 MiB and growing without). 012 records the hole the budget found in the link (the full flag), and 103 that its clock fix fell short (385754c). CHECKPOINT gains the row.
* Say what adopting the link took, and how it fares against ngtcp2 (102)eunhhu2026-09-291-0/+1
| | | | | | | | | | | | | | | | | | 102: the spec (012) and a receiver written from it alone; the four holes writing it found (REFUSED, the record's size, a peer's run places, the memo's places beside) and the credit M6 showed fixed at 1,024 records; M6 with ngtcp2 as a third, two rounds. Under reordering Pure carries the most (45 Mbit/s; ngtcp2 29-47, quinn 1-2), under random loss the three are the same, on a clean bottleneck Pure carries 3-5 % less (its framing, some 112 B a packet to QUIC's 41), and where loss and reordering meet ngtcp2 carries 1.3-3.6 times as much: undoing a loss it took for reordering, it restores the window a real loss had cut too (its log: 22 of 34 undos past the window before the last cut; none without reordering). Shares beside TCP stay within 25 %. 011 and the status table say so; 012 also says that a receiver answers within 25 ms (a sender's PTO counts on it), that a shape and a record's bytes are the app's, and why a packet of the past does no harm.
* Measure TPC-C again with compact log recordseunhhu2026-09-281-1/+1
|
* Check TPC-C's consistency from one snapshot a side, and measure TPC-C on RAM ↵eunhhu2026-09-281-1/+2
| | | | and an SD card
* Record queries on the bank beside SQLite (091)eunhhu2026-09-281-0/+1
| | | | | | | | | | | | 091 measures four queries on pgbench's tables of 100,000 accounts, the same rows and answers on Pure (registered queries, HTTP and JSON included) and SQLite 3.46.1, through each step: items field by field in leaves, folds and filters a leaf at a time, and one pass answering a fold in a column for every row. Per-branch totals went from 398 ms to 4.9 ms (SQLite 45, 14.3 with a DBA's indexes), a three-table join from 3.3 to 0.48 ms (1.47), and each rich account's rank from 9.2 s to 6.8 ms (2.84 s, 7.6 ms with an index); counting accounts in credit takes 1.05 ms where an index lets SQLite count 0.16 ms. Pure declares no index.
* Run pgbench's transaction on a bank of 100,000 accounts, beside SQLite (090)eunhhu2026-09-271-0/+1
| | | | | | | | | | | | | | | | | benchmarks/bank/bank.aup keeps accounts, tellers and branches as keyed lists and a history list; a transfer changes a row of each and appends a history row in one event, as pgbench's TPC-B-like transaction does in SQL. run.py makes a scale's rows and drives transfers through the load generator's new transfer kind; sqlite-bank.c runs the same tables and SQL on SQLite 3.46.1, a connection a thread, WAL. With lists as trees, transfers run at 30.5k a second on 1,000 accounts and 24.5k on 100,000 (RAM store, one connection thread, HTTP and JSON included), where they ran at 3.2k on 30,000 before. Durable on the SD card, Pure makes 1,339 a second and SQLite with synchronous=FULL 84 to 153. SQLite in process with no HTTP makes about 40k in RAM: recorded as it is, with the write amplification of 100,000-row tables on the card and ad hoc queries as what comes next.
* Record the messenger between two nodes and its latency (089)eunhhu2026-09-271-1/+2
| | | | | | | | | | | | | | The roadmap's E2EE messenger made of the runtime's general parts, with no messenger code in it: sealed, authenticated sessions between nodes (086, 088), Vega on each node's display as a surface (design 010), the app in Vega's examples. Vega's check runs two nodes through a recording relay: a line typed on one display is drawn on the other, frames match the vega command's, and the relay sees no text. A line takes 0.79 ms at p50 and 1.1 ms at p99 from one node's program to the other node's views with stores in RAM, both commits, the HTTP delivery and the seal included (sealing adds nothing measurable); on the Pi's SD card 3.5 ms at p50, the long tail being the card's syncs.
* Seal in one session a wire, authenticated by the sender's key (088)eunhhu2026-09-271-1/+1
| | | | | | | | | | | | | | | | | | | | | | A sealed request cost three X25519 operations (about 0.5 ms), so sealed sends drained at half the plain rate. A wire now keeps one HPKE context, encapsulated at its first seal, and seals each request as the context's next number (a failed seal or a restart starts a new session); the header carries the session's encapsulated key and the number, so a receiver opens a session once per connection thread (16 remembered, the least used replaced) and each seal by its number, also after it restarts, unasked. A sender with a key of its own seals in HPKE auth mode and names that key in the header; a receiver started with --sender NAME=KEY takes deliveries only from the senders it names, each sealed with its key (403 sender otherwise). The body is now the ciphertext and its tag. Sealed sends, sender authenticated, drain at 461-464k a second against 462-470k plain and 207-234k sealed a request at a time (Raspberry Pi 5). The check adds: a wire's requests share one session numbered from 0; a changed number or sender key does not open; a receiver restarted naming its sender opens the same session again; another node claiming that sender's name with its own key is refused and applies nothing.
* Record the small reads and idle memory work, next to axum and drogon (087)eunhhu2026-09-271-1/+1
| | | | | | | | | | | | What the app model, snapshot reads and delivery had cost small reads (126.7k -> 116.1k GETs a second) and idle memory (2.0 -> 4.5 MiB), how the sampler found it, and the four steps and the crypto split that took it back: 139.9k and 2.3 MiB. Measured again today beside axum 0.8.9 and drogon 1.9.0 on the same Pi: a one-thread pure-serve answers small GETs at 139k/s (2.2x axum and drogon a CPU second), 1 MiB GETs at 2,362/s (axum 1,304, drogon 754), durable app writes at 21.3k/s (both about 200), with the least idle memory of the three.
* Seal delivered sends to the receiver's key (086)eunhhu2026-09-271-1/+1
| | | | | | | | | | | | | | | | | | | | --seal OUTPUT=KEY seals each request's body whole to the receiving node's public key with HPKE (RFC 9180, base mode: X25519, HKDF-SHA256, AES-128-GCM where the CPU has AES, else ChaCha20-Poly1305), bound to the request's source, first number and count; X-Pure-Seal names the suite. A receiver's --key is a raw 32-byte file, made 0600 when missing, held in OpenSSL's secure heap; it opens the body where it lies (400 seal when it does not open) and zeroes the plain text when the request's ticket goes. --sealed-only refuses unsealed deliveries (403 unsealed). The check puts a recording relay between two apps: every order applies once, no body shows an item or a field name, a flipped bit is refused, a replayed request applies nothing twice, and a seal to another key does not open. Its hosts choose their own ports (--port 0). Sealed sends drain at 233-235k a second against 469-470k plain (Raspberry Pi 5): three X25519 operations a request, 164 us each. The plain request path measured the same as before.
* Bring the checkpoint's open items up to 084 and 085eunhhu2026-09-271-2/+6
|
* Look for new sends newest first, only as far back as they goeunhhu2026-09-271-1/+1
| | | | | | | | | After each save the owner walked the whole outbox to find the sends not yet queued, so a receiver long down made every save walk the backlog. pure_app_outbox_newest visits saved sends newest first, and the owner stops once every wired output reaches the number it queued last, then queues what it found in order.
* Make an API's answers the app's events (--reply OUTPUT=INPUT)eunhhu2026-09-271-1/+1
| | | | | | | | | | | | | | | | | | | | | The final answer to each send of a wired output (a 2xx, or a 4xx but 408 and 429) becomes a message event of the given input: fields from the answer's JSON object by name, other members skipped; a field the answer lacks from the send's field of that name and type, so the request's own id is there; a u32 http_status field is the status. It is applied with the send's number from the wire's source, before the send is trimmed and in the same save, so an answer that comes again after a restart (the API repeating itself for the Idempotency-Key) applies once. Sends that wait for answers go one per request; answers are saved with the next round, or at once when none waits. A server that writes an answer in pieces holds the second until the first is acknowledged, and a delayed acknowledgement stalled every answer 40 ms: the client now acknowledges at once (TCP_QUICKACK) after each read. Against a payments API that answers 200 or 402, with the sender killed (-9) midway, paid and declined orders matched the API's answers one to one.