Files
Ken Johnson 8d0a27b82e FREQ + LST + the wind-down fixes that fell out of writing them
cm.events grows TCometFreqMatch / TCometFreqMatches plus
hook signatures: TCometOnFreqRequest (now returns matches
instead of per-file accept), TCometOnFreqNak,
TCometOnLstRequest, TCometOnLstItem, TCometOnLstEnd.
TCometSessionConfig wires them up.

cm.xfer handles all five wire packets:
  NPKT_FREQ      (in: HandleFreq -- pattern fed to consumer
                  hook, returned matches queued into its
                  own FFreqQueue, drained ahead of provider
                  files via StartFreqOutbound)
  NPKT_FREQNAK   (in: HandleFreqNak fires consumer hook +
                  decrements awaiting-responses counter)
  NPKT_LSTREQ    (in: HandleLstReq -- emits one
                  NPKT_LSTITEM per match + NPKT_LSTEND)
  NPKT_LSTITEM   (in: parses name/size/time/desc, fires
                  Cfg.OnLstItem)
  NPKT_LSTEND    (in: fires Cfg.OnLstEnd, decrements
                  awaiting-responses)

Consumer-side request API (RequestFiles, RequestListing)
queues patterns; xfer drains them as the FIRST action in
Step (DrainPendingRequests) so they reach the peer before
either side decides "nothing to send -> end-of-batch".
DeferEndOfBatch / CompleteBatch added too for the late-
request scenario.

Awaiting-responses tracking via FAwaitingResponses (separate
from FRxEndSeen so a late request emission can't clobber a
previously-set peer-end-of-batch state).

Three wind-down robustness fixes that surfaced while writing
the LST test:

  1) ReadOneFrame on Recv = -1 used to FailSession + Exit
     immediately, dropping any frames already buffered in
     FRecvBuf.  Now parses buffered bytes first; only fails
     if no parseable frame remains AND the phase isn't a
     tolerated wind-down.

  2) cmpShutdown used to Pump exactly once before
     transitioning to cmpDone.  When peer queued multiple
     trailing frames before its ShutdownWrite (LSTITEMs +
     LSTEND + NPKT_END all in flight), the trailing ones
     were dropped.  Now while-Pump until both send queue
     drained AND no more parseable frames.

  3) NPKT_END was sent via raw CMWritePacket which bypasses
     EmitFrame's encryption layer.  On a CRYPT-active
     session that meant NPKT_END went out plaintext, the
     receiver tried to decrypt it as ciphertext, and got a
     CRC garbage frame.  Now NPKT_END uses EmitFrame so it
     gets encrypted alongside everything else.

Spec maintenance: removed the "Hydra-style" comparative
language from FSP-COMET-001 section 12; the spec now
describes Comet on its own terms.

12 tests pass (added test_freq_lst); all 7 cross-targets
clean.

That closes the v0.1.0 spec-parity gap:
  CRAM-MD5, ED25519, KEYEX/CRYPT, RPOS, ZLIB, FREQ, LST
  -- all done.
2026-04-22 14:09:46 -07:00
..