The pull works end to end; the push now dies at the first data burst with a named XENSE¶
Date: 2026-08-11. Lab: D100 (system 100) over the COSMOS Ethernet segment on the TCP hub 5010, our node D19999.
1. SOLVED: reading a file off a real ND, byte for byte¶
Xmsg.Live.Runner --config topology-d19999.json --pull BIGPSH3:TXT --pull-from 100 \
--pull-to pulled-verify.txt 127.0.0.1 10364 19999 180
Result: 20400 bytes, and
original bigpush.txt D523BDDC33C913F5E7C1CCDAA92B78DC2A329987BEA61EC4058B34758D5AEBDA
pulled pulled-verify.txt D523BDDC33C913F5E7C1CCDAA92B78DC2A329987BEA61EC4058B34758D5AEBDA
Identical. The file was pushed to D100 earlier, stored by a real SINTRAN, and read back by us: a complete round trip through someone else's file system. The runner's own "finished" line is NOT the evidence - the hash comparison is, and it was run separately.
2. The two diagnostics that were never connected¶
Both found the same day, both hiding real answers in plain sight.
XmsgServerHost.Logwas wired on the relay path and NOT on the Ethernet path, so every[seq]line - the value a link starts from, every Flags 1 we stamp, every store advance - was thrown away. An afternoon of sequence work ran on inference instead of on the numbers.XmsgLayerwrappedXmsgNodeprivately and offered no way to reach itsLog, andXmsgNodeprinted nothing at all for a network error it could not act on. A push that D100 was actively refusing therefore read as plain silence.
A diagnostic that is not plugged in is not a diagnostic. When a peer looks silent, check the log wiring before believing the wire.
3. What the machine says now (this is the open item)¶
With the node's log connected, the push ladder runs a long way and then fails LOUDLY:
[push] SendConnectLetter / ReserveFileEntry / SendShortAck / OpenFile / SetBlockSize / WriteFile
[push] SendData: 4 frame(s) sent
[net] node 100 rejected our frame at Flags1 0x0017 with XENSE (our sequence is AHEAD ...) FFDE
[net] node 100 rejected our frame at Flags1 0x0018 with XENSE ...
Everything up to 0x0016 was acknowledged. The rejections begin at the FIRST multi-frame data burst and continue in pairs from there.
MEASURED, not assumed:
- the FA ladder itself is accepted: connect letter, ReserveFileEntry, our ShortAck, OpenFile,
SetBlockSize and WriteFile all drew replies;
- our file server answered D100's own LIST-FILES over the same link at the same time, correctly,
with the two conversations sharing one Flags 1 counter - which is what the law says;
- the failure is XENSE (-34, 0xFFDE) = our number is AHEAD of what D100 expects. So D100
did not receive one of our datagrams, or did not accept it.
NOT established: whether the missing datagram left our side at all. A link-layer ACK proves the frame arrived; it does NOT prove the XMSG layer above accepted the datagram. Splitting those two needs the wire or D100's own frame counters, not more reasoning.
3a. SOLVED - and section 3b below drew the WRONG conclusion from the same evidence¶
The push completes. 20400 bytes, all ten blocks, written to a file created by a quoted new name, with no XENSE anywhere in the run:
Do not copy this command line. It carries
--announce-restart, which was in every recipe at the time and is now known to make the peer refuse the conversation (XRDDF, "Another port already has this name"). The run below is kept as the record of what was actually run. Today the same run is made WITHOUT that flag.
Xmsg.Live.Runner --config topology-d19999.json --announce-restart --resync-hard \
--push bigpush.txt --push-as SYNCTS2:TXT --push-to 100 127.0.0.1 10364 19999 150
...
[push] finished: 20400 bytes written to "SYNCTS4:TXT"
D100's OWN file server then reported the file back to us as 20400 bytes when we opened it for reading, which is confirmation from the machine rather than from our log.
What actually cured it: --resync-hard. It sends the reachability request (Flags 1 0xFFFF)
AND zeroes our own stored counter, and the next frame goes out at 0x0000.
That answers a question open since 2026-08-08: D100 DOES reset its expected-from-us when it
receives our reachability request. D103 over HDLC did not - that measurement stands and is why
AnnounceRestart still keeps our counter by default. The two machines behave differently, so
this must stay an explicit choice and not become automatic on the strength of one peer.
Section 3b is wrong and is kept only so the mistake is not made again. It concluded that D100
refuses a datagram sent while the previous is still unaccepted - a pacing limit. The successful
run disproves that: [push] SendData: 4 frame(s) sent appears TEN times, four datagrams at a
time, every one accepted. The real fault was that we were AHEAD from the first frame; the
rejections that looked like a window were simply the drift being reported once per frame.
Still open, and NOT to be explained by the dead pacing theory: a later pull run drifted ahead
again mid-conversation, at 0x003F, while our file server was answering D100's directory walk
over the same link. Whether two active halves can outrun the peer is UNMEASURED - the one thing
now known is that four datagrams in a row do not.
3b. WITHDRAWN - what the evidence looked like before the successful run¶
The runs above all started with a carried-over counter, which muddies the reading. A later run
began with a real reset - [seq] node 100: RESET to 0x0000 (was 0x0021) - so both sides were in
step at zero and every number below is fresh, not inherited drift.
18.102 ORIGINATE 0x0002 |
18.119 ORIGINATE 0x0003 | all three ACKed at 18.186 / 18.204 / 18.223
18.171 ORIGINATE 0x0004 |
18.277 ORIGINATE 0x0005 push short-ack ACKed, but not until 18.429
18.316 ORIGINATE 0x0006 our server's short-ack XENSE at 18.362
18.332 ORIGINATE 0x0007 our server's reply XENSE at 18.398
18.389 ORIGINATE 0x0008 XENSE at 18.452
0x0005 was ACCEPTED, and its acknowledgement arrived at 18.429 - after 0x0006 had already
been refused at 18.362. So when D100 saw 0x0006 it had not yet taken 0x0005, and called the
next number ahead.
The rejected values track OUR originations one for one, including which half of our node sent
them - 0x0006 is our file server's short acknowledgement and 0x0007 its reply, 16 ms apart.
That is what identifies the Flags 1 in a subtype-0x07 frame as the REFUSED datagram's number
rather than D100's own counter; our own CreateNetworkError documents it the same way. Worth
stating because a symmetric exchange makes two counters look like one, and that trap has already
cost this project two wrong turns.
So the failure is a PACING failure, not a numbering one. Our two halves - the file server answering D100, and the push client driving D100 - each originate independently, and together they emit datagrams faster than D100 accepts them.
NOT established: how many datagrams D100 will take before it must accept one. It is not a
strict one-at-a-time rule - 0x0002, 0x0003 and 0x0004 went out as a burst in the same run
and were all accepted. Something about rate or about the two halves interleaving decides it, and
guessing a window size here would be exactly the mistake the send-window number already caused
three times. Measure it before coding a limit.
The repair to try is to serialise our originations per peer: hold the next datagram for a node until the previous one has been acknowledged. That is testable against this same run.
4. A defect this exposed, and it is ours¶
A frame rejected with XENSE still consumes and persists its Flags 1. Nothing steps back, so every rejection pushes us one further ahead and the next attempt starts worse. Three runs drifted 0x003B -> 0x0040 that way with no traffic in between succeeding.
There is already a step-down recovery - XmsgServerHost.ResyncAcceptDown - but it only covers an
un-confirmed TAD accept. The general case (any originated frame refused with XENSE) has none.
Generalising it is the obvious repair and it is testable live: the XENSE numbers should walk down
and the conversation should start.
5. Recovering a diverged pair, until then¶
Restart XMSG on D100 (tools/restart-xmsg-cosmos.ps1 -Port 9010 -WithEthernet). It zeroes
D100's expectation and makes it send a ReachabilityRequest, which zeroes our store in step.
Verified twice today - the store went to 0x0000 and the numbers climbed together afterwards.
--announce-restart does NOT cure this direction: it tells the peer to reset while deliberately
keeping our own counter (see the memory announce-must-not-reset-our-own-sequence), which makes
an already-high counter worse, not better.
6. Two console traps worth writing down¶
LIST-FILES D19999(SYSTEM)."",""- the quoted""is the OUTPUT FILE parameter and SINTRAN reads the quotes as "create this file". The first run creates an empty-named file and every later run answersFILE ALREADY EXISTSlocally, before any traffic leaves the machine. Use the prompt form:LIST-FILESthenD19999(SYSTEM).then blank.FILE-ACCESS INTERNAL ERROR; CALL NOT VALID IN CURRENT STATEcan be stuck on ONE terminal of the pool rather than on the machine. Opening a fresh connection got a clean listing out of a machine that had answered that error four times in a row on the old line.
7. The sync daemon runs, and what actually stops it running unattended¶
The folder-watch daemon (--sync <folder> --sync-user <user> --sync-to <node>) was built and run
against D100 the same day. It scans, holds back a file still being written, plans, and carries one
transfer at a time.
It works with nobody at a console. With --request-link it learned the peer in 0.0 seconds
and drove a full push ladder by itself - connect letter, ReserveFileEntry, OpenFile, SetBlockSize,
WriteFile, SendData - before dying on the ahead-drift that --resync-hard cures.
Three defects were found by RUNNING it, none by reading it:
- One file was queued on every scan. The ledger is written only when a transfer succeeds, so between queueing a file and finishing it nothing stopped the next scan queueing it again.
- A bad file name took the whole node down. The plan names files as a person would type them,
D100(SYSTEM)."WATCH1:TXT"; the wire carries neither machine nor user, and the open request refused 27 characters against a fifteen-byte field. The exception escaped the loop tick and stopped the runner - file server, TAD sessions and all, for one bad name. - Our own resync dropped the in-memory link, leaving us unable to address a peer we had been
talking to a moment earlier.
ResetSequencedrops it correctly when the PEER announces; our own announce must keep it.
The remaining blocker is not a bug, it is a gap in what we know. CanReach is false until an
XMSG datagram arrives FROM the peer, because the layer learns its seed from an inbound datagram
and never derives it. So the daemon cannot open a conversation with a machine that has not spoken
since we started. --request-link brings up the ND LINK layer only: once that was enough to draw
a real conversation out of D100, and another time D100 answered it with a disconnect (frame kind
0x6F) and stayed silent.
A daemon that needs somebody to type LIST-FILES on the far machine is not a daemon. Closing this
is a PROTOCOL question - how a real COSMOS node opens a conversation with a peer that has not
spoken - so it wants a capture of a real machine doing it, not an invented seed.