Skip to content

Handoff, 2026-08-11

Six tasks closed against real hardware, one feature delivered and proved unattended, and a new plan opened. What is NOT done is at the bottom and is the part to read twice.


1. What was proved on the machine

Each of these was confirmed by asking D100, not by reading our own log.

Task Result
#38 pull a file 20400 bytes off D100, SHA256 identical to the original
#23 create by quoted name 20400 bytes written; D100's own file server reported the file back at that size
#33 the sync daemon a file dropped in a watched folder, nothing touched on D100, and LIST-FILES showed FILE 80 : (PACK-ONE:SYSTEM)WATCH3:TXT;1
#30 APPEND-REMOTE-BATCH D100 acknowledged the letter and answered with our serial 1B echoed, XRNCO in parameter 1
#31, #18 closed earlier, both proved live

2. The three things that unblocked everything

Two diagnostics were never connected. XmsgServerHost.Log was wired on the relay path and not on the Ethernet one; XmsgLayer gave no way to reach XmsgNode.Log at all. With them plugged in, a push that read as plain silence turned out to be D100 refusing us twice, by name. A diagnostic that is not plugged in is not a diagnostic.

--resync-hard. ~~D100 DOES reset its expected-from-us when it receives our reachability request; D103 over HDLC does NOT. Two machines, opposite behaviour, so it stays an explicit flag. This is what made the push work.~~

REFUTED 2026-08-17 - do not use this flag. D100 does NOT reset, on the announce or on a reachability request. Measured by reading D100's own table: X-C -> LIST-SYSTEMS -> XROUT system 100 -> system 19999 prints the sequence pair it holds for us, and across an --announce-restart AND a --request-link it stayed at send 64 / receive 65 while ignoring every 0x0000 frame we sent. --resync-hard zeroes OUR counter against a peer that keeps its own, which puts every later frame behind-sequence, where SINTRAN drops it without an error - and that silence is what made a second runner process look "refused" for a night.

The counter in xmsg-sequence.state is the correct value: carry on from it. The receive column of LIST-SYSTEMS equals our next Flags1 and is the oracle to check it against - confirmed twice, at 65 (0x0041) and again at 124 (0x007C). See WHAT-WE-DO-NOT-KNOW.md.

The envelope seed is a per-link constant, verified across the capture corpus, so it is now remembered in xmsg-link-seed.state. That is what lets the daemon address a machine that has not spoken since we started - and it is remembered, never invented. A node we have genuinely never heard from stays unreachable and says why.

3. A conclusion I published and then withdrew

I wrote that D100 refuses a datagram sent while the previous is still unaccepted - a pacing limit. The successful run disproves it: SendData: 4 frame(s) appears ten times and every one is accepted. The real fault was being AHEAD from the first frame, and the rejections that looked like a rate limit were one drift reported once per frame. The wrong section is kept and marked in PULL-PROVED-AND-PUSH-XENSE-BURST-2026-08-11.md rather than deleted.

4. The sync daemon, and four defects found by running it

Recipe (CORRECTED 2026-08-18 - --announce-restart REMOVED too; --resync-hard went on 2026-08-17, see section 2): --sync <folder> --sync-user <user> --sync-to <node>.

--announce-restart is not a harmless extra - it is what makes the peer refuse the conversation. On a freshly brought-up D100 a push carrying it was refused, and the peer named the reason:

[push] <- node 100 REFUSED us: XRDDF (3) - XDTYP=0x0017 XDSCR=0xFFFD

XDTYP 0x0017 is an InitializationNak; XRDDF is "Another port already has this name". Drop the flag and the identical transfer completes - and so does the next one straight after it, with no bring-up and no link cycle between. Both flags are now gone from the recipe for the same reason: they were in it because somebody once needed them, and nothing reported what they cost.

Re-proved on that recipe against D100 on 2026-08-17, all three in one sitting and with the counter simply carried on from xmsg-sequence.state:

  • pull BIGPSH3:TXT, 20400 bytes, SHA256 d523bddc...8d5aebda, identical to the pushed original;
  • push under a quoted new name, confirmed on the machine as FILE 76 : (PACK-ONE:SYSTEM)SEQFIX:TXT;1;
  • the daemon carried a watched file, confirmed as FILE 99 : (PACK-ONE:SYSTEM)SEQOK:TXT;1.

It now runs from cold too, with --originate-from-seed. Without that flag the daemon holds every transfer with "the link has not learned the peer yet" until D100 addresses us once, so a person has to touch the machine first. With it, the daemon opens the link from the seed remembered in xmsg-link-seed.state and D100 accepts:

[seq] node 100: link opened from the REMEMBERED seed 0x5B, starting Flags1 0x011E
      from the store (nothing was received first)

Nothing inbound before that line; FILE 102 : (PACK-ONE:SYSTEM)PROOF:TXT;1 appeared on the machine with nobody at the console. The starting Flags1 comes from the sequence store exactly as it does when an inbound frame opens the link, so this obeys the sequence law rather than working around it.

Left OFF by default: proved on D100 only, and D103 over HDLC differs on the related reset question, so it stays explicit until a second machine agrees.

Every one of these was found by running it against D100, none by reading it:

  • a file was re-queued on every scan (the ledger only records success, so nothing stopped it);
  • an addressed filespec D100(SYSTEM)."WATCH1:TXT" killed the whole runner - the wire carries neither machine nor user, and the exception escaped the loop tick;
  • our own resync dropped the in-memory link, stranding us from a peer we had just been talking to;
  • the seed was not remembered.

Since then the ledger survives restarts, and with no listing it answers whether the remote file exists - so a file carried before is REPLACED rather than created again. The one case that is still wrong: a file deleted on the machine behind our back. Only a listing catches that, and the daemon says so in its own startup line.

5. Chat

Server, clients, channels and aliases, plus a room a SINTRAN terminal user can join (chat join|say|who|nick|part). The rules live once in ChatRoom with no transport, and both doors carry them - ChatServer was rewritten onto it with all 26 of its tests passing unchanged.

Guide: CHAT-USER-GUIDE.md.

6. NOT DONE - read this part twice

The live two-user chat run. Nobody has sat at a real SINTRAN terminal, joined a room and talked to a second user. Until that happens the chat is a working implementation, not a proven one. This is the shortest remaining job: two terminals into our node, chat join on each, watch a line cross.

A client-side directory listing. It is the one thing that would close the daemon's last blind spot. It was deliberately NOT written: shipping an unproven walk into the planner's decision path risks reintroducing the create-refusal that was just fixed, because a wrong cursor would report "not there" and create. Write it against the machine, not against reasoning.

Why a second terminal command renders nothing under test - now understood, and it is NOT a server fault. DrainSegmentedOutput returns while OutstandingOutputCount is above zero, and a reply's final frame is tracked as outstanding like any other. Telling the server directly, NotifyAck(node, flags1), RELEASES it and the second reply goes out in full. Sending the same acknowledgement as a frame from the test client does not. So the remaining gap is in the client acknowledgement or its routing - a small, well-aimed question, and the place to start if those tests are ever unblocked. OutputWindowDiagnosticTests proves both halves.

A wrong answer I published on the way. I first reported that even NotifyAck failed to release the window. It had not failed - my diagnostic scanned the frame body for a 07 03 length BDAT header, found none, and called a reply that had been sent in full "nothing rendered". The scanner is now deliberately crude and framing-independent. A diagnostic that can only fail one way is worse than no diagnostic.

7. Next

Task #40 is the new one, and it is meant to be done together: move the chat ONTO the ND - a CHATSV RT program and a CHAT PROG client in PLANC, so two ND machines chat with the C# node switched off. Plan, with the open questions and the failure modes stated in advance: PLAN-SINTRAN-NATIVE-CHAT-RT-AND-PROG.md.

The first move there is to read, not guess: how a real RT server waits (XFTRAD, FSART) and how a real PROG waits for a keystroke and a message at once (TADADM, the spooling programs). Both are on the machine and can be read.