<feed xmlns='http://www.w3.org/2005/Atom'>
<title>pure/link/test-link.c, branch master</title>
<subtitle>Verified execution runtime unifying state, persistence, and replication.
</subtitle>
<id>http://git.yeowoo.org/pure/atom?h=master</id>
<link rel='self' href='http://git.yeowoo.org/pure/atom?h=master'/>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/'/>
<updated>2026-10-04T09:06:41Z</updated>
<entry>
<title>Let a link's receiver find its streams' inputs again (017, stage 3)</title>
<updated>2026-10-04T09:06:41Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-10-04T09:06:41Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=578d0ab47f22e0185987ed3f8c091129985cec6e'/>
<id>urn:sha1:578d0ab47f22e0185987ed3f8c091129985cec6e</id>
<content type='text'>
A stream finds its input once, when it is made; an app that starts again
with another module may have the input at another number, of another
shape, or none. pure_link_receiver_rebind marks the streams to inputs of
a prefix stale: each finds its input again by name, with the fields it
was laid out with, before its next delivery and at its sender's next
packet, keeping what it holds; one none takes is refused as a new one
would be. pure_link_listener_rebind asks it of the listener's thread.
test-link's rebound case stops its app, starts it with the input at
another number, then of another shape taken by name: all 3,000 records
come once, in order (without the rebind, none after the stop).
</content>
</entry>
<entry>
<title>Take a delivery's fields by name, dropping none</title>
<updated>2026-09-30T04:58:38Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-30T04:58:38Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=5ce7655d613689223050715ff3f8fe5c456ebf11'/>
<id>urn:sha1:5ce7655d613689223050715ff3f8fe5c456ebf11</id>
<content type='text'>
A message from another node was taken only in its input's exact shape
(the link's STREAM shape, fields and order alike; over HTTP, every
field). So a board whose nodes are upgraded one after the other stopped
both ways until both were, and an output and its input whose fields
differ only in order were refused for good (docs/design/014).

An input now takes a message when it has every field the sender sends,
by name, of the same type or one that holds its values (a u32 in a
number, text in bytes); a field only the input has is 0 or empty. One
that sends a field the input lacks is refused: taking it would drop the
value, and with its number heard the value would never come again (two
boards apart for good). So add fields to receivers first and take them
from senders first, and nothing stops or is lost.

The link carries a sender's fields in a new frame, FIELDS (0x0d: names,
shape, u8 count, each {type, name length, name}): after a REFUSED for
another shape the sender puts it in its packets (after OFFER, before
the records, so a receiver that knows no FIELDS reads what it read
before) until the stream is taken, the first time at once. A receiver
seeing another shape waits for the packet's FIELDS before refusing, and
asks its host with them (PureLinkHost.input takes them); REFUSED says 4
when the input lacks a field. Deliveries carry the fields and the host
lays each record out anew (pure_module_takes, pure_module_recast,
pure_module_fields, over pure_send_recast's rule). A stream whose sender
starts again in another shape lets go what it held; the sender sends it
again laid out anew. A refusal already queued for a stream now says the
last reason judged, not the first. Over HTTP a delivery (X-Pure-Source)
may lack fields; an unknown member is still refused.

test-link named: a stream of another shape naming its fields is taken,
200 stored in 47 ms with every delivery naming them; an input that does
not take them refuses (4) and the sender backs off. check.py
server_fields: five outputs, three linked and two over HTTP, fields in
another order taken, an input with a field more taking messages without
it, one lacking a field refusing until it starts again with it, then
taking all. server_upgrade's new receiver declares its fields in another
order. 012 names the frame and the rules.
</content>
</entry>
<entry>
<title>Try a refused stream again when its try is gone</title>
<updated>2026-09-30T04:33:14Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-30T04:33:14Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=baaf955959d4b2187e93e35b64cd3f5b2330a59a'/>
<id>urn:sha1:baaf955959d4b2187e93e35b64cd3f5b2330a59a</id>
<content type='text'>
A sender whose stream the receiver refuses sends one packet of records
a wait, and the refusal is over when that packet is acknowledged with
no REFUSED. When the try itself went to no one (the receiver started
again, as a node upgraded one after the other does, or the path lost
it) nothing acknowledged it: the sender took its epoch for lost, began
another, and then sent nothing more, still waiting on the try, with no
record in flight to probe for. Two nodes of a shared board stayed apart
for good after the second one's upgrade.

A try declared lost, or dropped with the epoch's flights, now lets the
next packet try again. test-link's refused_restarted case: the receiver
refuses a stream, is killed while the next try is due and comes back 8
s later with the input; before, nothing was stored in 120 s, now all
200 are by 18.2 s. Streams refused for good send as few packets as
before (9 in 90 s).
</content>
</entry>
<entry>
<title>Let a receiver whose app is full keep what came, and say so</title>
<updated>2026-09-30T02:03:52Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-30T02:03:52Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=c9c0edf5a8fc9f79fff42064db09bc297e117a88'/>
<id>urn:sha1:c9c0edf5a8fc9f79fff42064db09bc297e117a88</id>
<content type='text'>
A link's delivery the app had no room for was handed over again at the
next turn: a receiver over its memory budget would refuse the same
records as fast as the owner could apply them. And its sender, seeing
records received but not stored for a second, sent the whole window
again every second.

Now a host says a delivery found it full (PureLinkDone.full). The
receiver keeps the records and hands them over again after a pause, 100
ms doubling to 500 ms, and meanwhile its STORED frames carry flag 2: the
app is full and what came is kept. A sender hearing it starts its
one-second wait for the save again, and sends none of it again. The
spec (012) says both sides; the pause is a local choice that must stay
under the sender's wait.

pure_link_done takes the PureLinkDone itself. test-link's new case keeps
the app full for 5 s: 12 hand-overs meanwhile, taken within 0.5 s of
having room, and no record sent again (21,640 without the flag).
</content>
</entry>
<entry>
<title>Remember the epoch of each of a peer's runs that send, not one</title>
<updated>2026-09-29T15:55:04Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T15:55:04Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=c494ec7a37ff4863d420b4a9ccbe1df93e88de2d'/>
<id>urn:sha1:c494ec7a37ff4863d420b4a9ccbe1df93e88de2d</id>
<content type='text'>
A receiver's memo kept one run's epoch a peer. A node whose outputs
send to this one sends in a run each, so after the receiver started
again (or let the peer rest) all runs but the last accepted were told
LOST and began their epochs again: records under the static keys for a
round trip, and an X25519 each. Now a run whose peer's place holds
another run still sending keeps its epoch beside, in one of two of
1,024 places its peer and run find (160 KiB at the file's head: the
memo is PLM3, and a PLM2 one is begun again). A run's place beside goes
once its peer's place has it, so an epoch's secrets stay only until the
next one's are.

test-link's three outputs of one node, each in epoch 1, go on after the
receiver starts again: no LOST, no X25519 (before: two LOSTs, an
accept). 012 says so.
</content>
</entry>
<entry>
<title>Say why a stream is refused (REFUSED), and try it again seldom</title>
<updated>2026-09-29T15:42:45Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T15:42:45Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=a06b652768a8643fe76efdc80a0c6eb729268fc5'/>
<id>urn:sha1:a06b652768a8643fe76efdc80a0c6eb729268fc5</id>
<content type='text'>
A receiver that took no records of a stream (a source the key may not
send as, no input of that name, an input of another shape) said nothing:
its sender heard them acknowledged, never stored, and sent them all
again every second, for ever, and its operator saw nothing. Now the
receiver answers REFUSED (0x0c: the names, and why), and the sender
sends no records for a second, then a packet of them to learn whether
they are taken now, the wait twice as long each time (64 s at most). A
refusal ends when that packet is acknowledged with no REFUSED beside, or
a record is stored. link.c says it once on stderr, as a wire says a
receiver does not take its sends; serve's input says which of the two
the app lacks.

test-link: each of the three is refused seven times in 90 s with nine
packets, and a stream refused until the app has its input goes on then,
all 200 records stored. check.py links a node to an input its receiver
lacks: said once, two packets in 2.5 s.
</content>
</entry>
<entry>
<title>Give each of a peer's runs a place, however many send at once</title>
<updated>2026-09-29T15:42:07Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T15:42:07Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=393804bee74311ef00e39502d7979ca00bd38cb2'/>
<id>urn:sha1:393804bee74311ef00e39502d7979ca00bd38cb2</id>
<content type='text'>
A receiver kept two runs a peer: the sender's, and the one before it
restarted. A node with three outputs to another sends in three runs with
one key, and they pushed one another out: each packet keyed its run
again, and an answer marked for one went to the run that took its place.
Now more places are made beside when the run seen longest ago is still
live (up to 64), and one is reused only once its run is quiet the idle
period. test-link's three outputs of one node key three runs, not seven,
and are all stored in 16 ms, not 1016.
</content>
</entry>
<entry>
<title>Take no send larger than a receiver holds: 16 MiB, not 64</title>
<updated>2026-09-29T15:41:24Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T15:41:24Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=3bd9b6c924c801dc5033510391ac251a7309219f'/>
<id>urn:sha1:3bd9b6c924c801dc5033510391ac251a7309219f</id>
<content type='text'>
A sender took sends up to 64 MiB, but a receiver holds 16 MiB of a
stream not yet stored, so a send between the two went again forever.
The most a send may be is now PURE_LINK_RECORD_MOST, what a receiver
holds; test-link sends one that large whole and is refused one byte more.
</content>
</entry>
<entry>
<title>Say LOST when a run's epoch is not known, and let its sender begin again at once</title>
<updated>2026-09-29T14:59:44Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T14:59:44Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=d0be140549b67708039f3c69d0b039440eb3a063'/>
<id>urn:sha1:d0be140549b67708039f3c69d0b039440eb3a063</id>
<content type='text'>
A receiver that started again without its memo could open none of its
senders' packets (their epochs were gone), said nothing, and each sender
waited 2 s and four probes, then offered with no records. Worse, those
packets failed to open, so each cost a token (256 a second, the flood
guard): ten thousand senders came back 256 a second. M2 without a memo
stopped 0.8 s and caught up in 7.7 s.

Now the receiver answers a packet of a run it does not know, in an epoch
past 0, with LOST: sealed in epoch 0 under the nodes' keys, naming the
packet. As QUIC's stateless reset, it is smaller than the packet and
comes out of a bucket of its own (4,096 a second). A sender that has that
packet out begins again as a run begins: epoch 0 with its records at
once, epoch 1 offered. Only the receiver can say LOST, and one of the past
names a packet no longer out; silence alone (an attacker's doing, maybe)
still gets offers only. Such a packet costs no key token: no keys were
made for it, and the secret made is remembered, once a sender.

M2 without a memo (ten thousand devices, twice): no 100 ms step stopped,
caught up in 0.4-0.5 s, p99 5.8-5.9 ms, the most 48-57 ms (was 4.1 and
7.8 s). test-link: a changed packet may get a LOST smaller than itself
and nothing else; half of a flood in epoch 1 got 267 LOSTs, 12 KB for its
6 MB; a record goes under the nodes' keys after an epoch began only after
the receiver forgot, until the next epoch.
</content>
</entry>
<entry>
<title>See a path that stops carrying larger datagrams, and go back to the first size</title>
<updated>2026-09-29T14:41:03Z</updated>
<author>
<name>eunhhu</name>
<email>necpition205@gmail.com</email>
</author>
<published>2026-09-29T14:41:03Z</published>
<link rel='alternate' type='text/html' href='http://git.yeowoo.org/pure/commit/?id=ccf35df074bcb450ebdc56eb27762a9096252a3f'/>
<id>urn:sha1:ccf35df074bcb450ebdc56eb27762a9096252a3f</id>
<content type='text'>
A link that found the path carries 1,452 bytes kept sending them when a
route changed and larger packets were dropped with nothing said (a
black hole): it lost them one after another and stood still (test-link:
8 of 3,000 records in ten minutes). Now, as RFC 8899 §4.3 has it, it goes
back to 1,200 bytes when three larger packets in a row are lost while an
acknowledgment covers only smaller ones, or when two probes in a row go
unanswered, and tries the larger size again a second on (three tries,
then ten minutes). The case completes in 1.2 s of virtual time; the M6
simulator's results are the same to the byte in eight paths of loss,
reordering and a bottleneck.
</content>
</entry>
</feed>
