jspurefix 5.3.0 → 5.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/BACKPORT_PLAN.md +120 -10
  2. package/DEMO_PORT_PLAN.md +286 -0
  3. package/dist/dictionary/parser/quickfix/dictionary-validator.d.ts +39 -0
  4. package/dist/dictionary/parser/quickfix/dictionary-validator.js +321 -0
  5. package/dist/dictionary/parser/quickfix/dictionary-validator.js.map +1 -0
  6. package/dist/dictionary/parser/quickfix/index-visitor.d.ts +10 -0
  7. package/dist/dictionary/parser/quickfix/index-visitor.js +68 -0
  8. package/dist/dictionary/parser/quickfix/index-visitor.js.map +1 -0
  9. package/dist/dictionary/parser/quickfix/index.d.ts +7 -0
  10. package/dist/dictionary/parser/quickfix/index.js +7 -0
  11. package/dist/dictionary/parser/quickfix/index.js.map +1 -1
  12. package/dist/dictionary/parser/quickfix/quick-fix-graph-file-parser.d.ts +13 -0
  13. package/dist/dictionary/parser/quickfix/quick-fix-graph-file-parser.js +65 -0
  14. package/dist/dictionary/parser/quickfix/quick-fix-graph-file-parser.js.map +1 -0
  15. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.d.ts +73 -0
  16. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.js +363 -0
  17. package/dist/dictionary/parser/quickfix/quick-fix-graph-parser.js.map +1 -0
  18. package/dist/dictionary/parser/quickfix/sax-tree-builder.d.ts +5 -0
  19. package/dist/dictionary/parser/quickfix/sax-tree-builder.js +103 -0
  20. package/dist/dictionary/parser/quickfix/sax-tree-builder.js.map +1 -0
  21. package/dist/dictionary/parser/quickfix/validation-error.d.ts +17 -0
  22. package/dist/dictionary/parser/quickfix/validation-error.js +32 -0
  23. package/dist/dictionary/parser/quickfix/validation-error.js.map +1 -0
  24. package/dist/dictionary/parser/quickfix/x-element.d.ts +26 -0
  25. package/dist/dictionary/parser/quickfix/x-element.js +82 -0
  26. package/dist/dictionary/parser/quickfix/x-element.js.map +1 -0
  27. package/dist/store/fix-msg-ascii-store-resend.js +6 -0
  28. package/dist/store/fix-msg-ascii-store-resend.js.map +1 -1
  29. package/dist/store/store-config.d.ts +1 -0
  30. package/dist/store/store-config.js.map +1 -1
  31. package/dist/transport/ascii/ascii-session.js +3 -0
  32. package/dist/transport/ascii/ascii-session.js.map +1 -1
  33. package/dist/util/definition-factory.js +1 -1
  34. package/dist/util/definition-factory.js.map +1 -1
  35. package/jsfix.test_client.txt +67 -67
  36. package/jsfix.test_server.txt +64 -64
  37. package/package.json +6 -6
  38. package/src/dictionary/parser/quickfix/dictionary-validator.ts +473 -0
  39. package/src/dictionary/parser/quickfix/index-visitor.ts +100 -0
  40. package/src/dictionary/parser/quickfix/index.ts +7 -0
  41. package/src/dictionary/parser/quickfix/quick-fix-graph-file-parser.ts +63 -0
  42. package/src/dictionary/parser/quickfix/quick-fix-graph-parser.ts +450 -0
  43. package/src/dictionary/parser/quickfix/sax-tree-builder.ts +112 -0
  44. package/src/dictionary/parser/quickfix/validation-error.ts +34 -0
  45. package/src/dictionary/parser/quickfix/x-element.ts +115 -0
  46. package/src/store/fix-msg-ascii-store-resend.ts +8 -0
  47. package/src/store/store-config.ts +1 -0
  48. package/src/transport/ascii/ascii-session.ts +4 -0
  49. package/src/util/definition-factory.ts +2 -2
package/BACKPORT_PLAN.md CHANGED
@@ -215,11 +215,9 @@ Storm protection was largely wired during PR 3C/3D. Remaining gaps completed:
215
215
 
216
216
  | File | Action |
217
217
  |------|--------|
218
- | `src/config/js-fix-config.ts` | Add optional `resendGapFillOnly?: boolean` |
219
- | `src/store/fix-msg-ascii-store-resend.ts` | Early return path when enabled — always GapFill instead of replaying |
220
- | New: `src/test/store/resend-gap-fill-only.test.ts` | ~3 tests |
218
+ ### Status: **DONE**
221
219
 
222
- Can be done at **any time** independent of all other PRs.
220
+ Added `resendGapFillOnly` option to `StoreConfig`. When enabled, `FixMsgAsciiStoreResend` always returns a single GapFill instead of replaying stored messages — prevents accidental duplicate order execution for client/initiator sessions. 5 tests added.
223
221
 
224
222
  ---
225
223
 
@@ -250,17 +248,129 @@ PR 5B (ResendGapFillOnly) ──── independent, can be done anytime
250
248
  | 4C | Low | New file, tested with mocks — **DONE** (PR #120) |
251
249
  | 4D | Medium | Changes send path, store errors must not block sends — **DONE** |
252
250
  | 5A | Low | Wiring only, coordinator makes decisions — **DONE** |
253
- | 5B | None | Additive config option |
251
+ | 5B | None | Additive config option — **DONE** |
254
252
 
255
253
  ---
256
254
 
257
- ## Phase 6: Message Generator / XML Parser (Future)
255
+ ## Phase 6: QuickFix XML Parser Rework
256
+
257
+ **Priority: Medium | Risk: HIGH | Scope: Large (multi-session)**
258
+
259
+ ### Background
260
+
261
+ The C# QuickFix XML parser (`QuickFixXmlFileParser.cs`) is architecturally superior:
262
+ - **DOM-based**: parses XML once into `XDocument`, then walks the tree
263
+ - **Graph-based resolution**: nodes + edges + work queue, deterministic single logical pass
264
+ - **Post-processor**: `IndexVisitor` + `ContainedFieldCollector` with memoisation ensures every `ContainedFieldSet` knows all tags below it
265
+ - **Pre-parse validation**: `DictionaryValidator` catches missing fields, duplicates, undefined references with Levenshtein typo suggestions
266
+
267
+ The TS parser uses SAX streaming with iterative N-pass (up to 5x) forward reference resolution. This works but is fragile, hard to reason about, and diverges from C#.
268
+
269
+ **Scope**: Only the QuickFix parser chain needs rework. FIXML (XSD, already has include graph) and Repository (sequential file parsing) are fine as-is.
270
+
271
+ ### Strategy: SAX → In-Memory Tree → Graph Resolution
272
+
273
+ Rather than replacing SAX with a new XML library, wrap SAX to build a lightweight in-memory element tree on a single pass. This tree then provides DOM-like random access for the graph resolver, without introducing new dependencies.
274
+
275
+ ```typescript
276
+ interface XElement {
277
+ name: string
278
+ attributes: Record<string, string>
279
+ children: XElement[]
280
+ line?: number
281
+ }
282
+ ```
283
+
284
+ ### Delivery: 6 PRs
285
+
286
+ #### PR 6A: XElement tree builder (new files only, zero risk)
287
+
288
+ ### Status: **DONE** (PR #124)
289
+
290
+ `XElement` interface + `XDocument`/`XNode` query wrappers + `SaxTreeBuilder` (single SAX pass → in-memory tree). 51 tests across all FIX dictionaries.
291
+
292
+ #### PR 6B: DictionaryValidator (new files only, zero risk)
293
+
294
+ ### Status: **DONE**
295
+
296
+ Three-pass validator ported from C#: collect definitions, validate references (with Levenshtein "did you mean" suggestions), check unused definitions. 40 tests including validation against all real FIX dictionaries.
297
+
298
+ #### PR 6C: Graph-based parser + IndexVisitor (medium risk)
299
+
300
+ ### Status: **DONE**
301
+
302
+ Verbatim port of C# `QuickFixXmlFileParser`: `GraphNode`/`Edge`/`NodeElementType` + work queue + field/component/group/message resolution. New parser sits alongside legacy parser for safe comparison testing.
303
+
304
+ Includes `IndexVisitor` post-processor (originally PR 6D, merged into 6C because the parser doesn't work without it). Walks every message in post-order, clears aggregated tag indices on each set, and re-adds direct fields via `ContainedSetBuilder` so parents correctly know all descendant tags.
305
+
306
+ **Comparison test results:** Graph parser produces a **superset** of legacy parser output for FIX50SP2 — correctly resolves deeply nested forward references (e.g., DividendFXTriggerDateBusinessCenter chain) that the legacy 5-pass iterative parser truncates. This is a correctness improvement.
307
+
308
+ 41 new tests including comparison against legacy parser for FIX 4.2, 4.3, 4.4, and 5.0SP2.
309
+
310
+ #### PR 6E: Switch default parser (HIGH risk)
311
+
312
+ ### Status: **DONE**
313
+
314
+ Added `QuickFixGraphFileParser` adapter that extends `FixParser` and matches the legacy parser's `(MakeDuplex, GetJsFixLogger)` constructor signature. `DefinitionFactory.getParser()` now instantiates the graph parser for QuickFix XML — all dictionary loading goes through the graph parser by default.
315
+
316
+ The legacy `QuickFixXmlFileParser` remains exported for backward compatibility (anyone importing it directly still gets the old behaviour), but no production code in the project uses it.
317
+
318
+ **Test fallout fixed:**
319
+ - `src/test/env/data/fix5-mod.xml` had a redundant 215-line "admin fields" section that duplicated 62 fields already present in the canonical fields section. Plus the `HopGrp` and `MsgTypeGrp` components were defined twice. The legacy parser silently swallowed both classes of duplicate; the graph parser's validator correctly flags them as errors. Removed the redundant admin section (kept the unique `OTP` custom tag) and the duplicate component definitions.
320
+ - `src/test/ascii/qf-50sp2-dict.test.ts` had two assertions where `Instrument` and `TrdCapRptSideGrp` were expected as `required=false` for TradeCaptureReport. The dictionary clearly says `required='Y'` for both. The legacy parser was producing `required=false` (a real bug); the graph parser correctly returns `true`. Updated the assertions.
321
+ - Updated `getTrimDefinitions` in the same file to use `QuickFixGraphFileParser` instead of the legacy parser for consistency.
322
+
323
+ All 533 tests pass with the graph parser as the default.
324
+
325
+ Note: `parse-progress.ts`, `parse-state.ts`, and the legacy parser itself are NOT yet deleted — that's deferred to a follow-up cleanup PR after a release cycle to give downstream consumers time to migrate.
326
+
327
+ #### PR 6F: Fix Trim function (medium risk)
328
+
329
+ ### Status: **DONE — TS trim was already correct**
330
+
331
+ After investigation: the TS trim function (`QuickFixXmlFileBuilder`) is correct. The "bug" we suspected was actually introduced during the C# port — see C# commit `2ee4620c`. The original C# `WriteComponents` used a `foreach` over a snapshot of `_seenComponents`, dropping any components discovered during iteration. The C# fix added `ProcessComponentFields` for eager recursive collection.
332
+
333
+ The TS version uses `while (components.length > 0) { components.pop() }` — a proper iterative discovery loop where newly-pushed components are processed in subsequent iterations. This pattern doesn't have the bug.
334
+
335
+ **Added** comprehensive round-trip tests in `src/test/dictionary/trim-round-trip.test.ts`:
336
+ - Trim → reparse with strict graph parser validation
337
+ - Compare flattened tag sets at the message level
338
+ - Deep nested-set comparison (every component/group in every message)
339
+ - Worst case: trim ALL 116 FIX50SP2 messages and deep-compare every one
340
+
341
+ All 9 tests pass. No code changes needed to `quick-fix-xml-file-builder.ts`.
342
+
343
+ ### Dependency Graph
344
+
345
+ ```
346
+ PR 6A (XElement tree) ──────────┐
347
+ ├──→ PR 6C (Graph parser) ──→ PR 6E (Switch default)
348
+ PR 6B (DictionaryValidator) ───┘ │
349
+
350
+ PR 6D (IndexVisitor) ──→ PR 6E
351
+
352
+ PR 6F (Fix Trim) ──── after 6C is stable
353
+ ```
354
+
355
+ ### Risk Summary
356
+
357
+ | PR | Risk | Reason |
358
+ |----|------|--------|
359
+ | 6A | None | New files only, SAX wrapper — **DONE** (PR #124) |
360
+ | 6B | None | New files only, validation — **DONE** |
361
+ | 6C | Medium | Graph parser + IndexVisitor — sits alongside legacy parser — **DONE** |
362
+ | 6D | (merged into 6C) | IndexVisitor was needed for 6C to work correctly |
363
+ | 6E | HIGH | Switches default parser — exposed 2 legacy parser bugs (now fixed) — **DONE** |
364
+ | 6F | None | TS trim already correct — round-trip tests added — **DONE** |
365
+
366
+ ### Test Strategy
258
367
 
259
- **Priority: Low | Risk: Medium | Scope: Large**
368
+ The parser is the foundation of the entire system. Test strategy must be comprehensive:
260
369
 
261
- - C# uses nested types for groups (sub-types) in generated code
262
- - QuickFix XML parser has improved design with better error tracking
263
- - Nice to align for maintenance but not critical
370
+ 1. **Comparison tests**: parse every FIX XML dictionary (4.2, 4.3, 4.4, 5.0SP2) with both old and new parser, diff the resulting `FixDefinitions`
371
+ 2. **Round-trip tests**: parse trim reparse, verify definitions match
372
+ 3. **Micro-dictionary tests** (future hardening): use Trim to create single-message dictionaries, then mutate them (remove fields, duplicate tags, etc.) to test validator edge cases
373
+ 4. **Regression anchor**: snapshot the `FixDefinitions` output for each FIX version before any changes — tests assert against snapshots
264
374
 
265
375
  ---
266
376
 
@@ -0,0 +1,286 @@
1
+ # Demo Port Plan: cspurefix standalone-demo → jspf-demo
2
+
3
+ ## Background
4
+
5
+ With Phase 6 of the cspurefix backport complete (jspurefix 5.5.0), jspurefix is at functional parity with cspurefix. The next major track is bringing `~/dev/ts/jspf-demo` up to the level of `~/dev/cs/purefix-standalone-demo` — the C# reference application that has been built up incrementally over months of soak testing.
6
+
7
+ The C# standalone demo is the **golden source for "what production-grade jspurefix usage looks like"**, including all the bugs that surfaced during a 17-day continuous soak test (Jan 28–Feb 13, 2026: ~92 hours runtime, 12 transport disruptions, 100% recovery, zero manual intervention).
8
+
9
+ This work follows the same incremental approach as the backport: small PRs, each independently mergeable, each adding a coherent feature and not trying to do everything at once.
10
+
11
+ ---
12
+
13
+ ## Current state of jspf-demo
14
+
15
+ **What exists** (4 TypeScript files in `src/trade_capture/`):
16
+ - `app.ts` — `AppLauncher extends SessionLauncher` boots client + server in same process
17
+ - `trade-capture-client.ts` — initiator that sends `TradeCaptureReportRequest`, receives reports, hard-coded `done()` after 32s
18
+ - `trade-capture-server.ts` — acceptor that responds with 5 trades + a `setInterval` for live trades
19
+ - `trade-factory.ts` — synthetic trade generator (Gold, Silver, Platinum, Magnesium, Steel)
20
+
21
+ **What's missing** (in rough order of importance):
22
+ 1. State reset on reconnect (timers, counters, caches all leak)
23
+ 2. Timer cleanup tracking (the "timer keeps running after disconnect" bug)
24
+ 3. CLI options (--client / --server / --skeleton / --store / --disconnect-after)
25
+ 4. Config-based scenarios (reset / recovery / broker-reset session configs)
26
+ 5. File store wiring and persistent sequence tests
27
+ 6. Multi-client support with session registry
28
+ 7. Sequence mismatch test scenarios
29
+ 8. Skeleton mode and soak-test scripts
30
+ 9. README + docs (current README is 3 lines of CI badges)
31
+ 10. Trade capture flow alignment with C# (sec def request → 5 securities → trade request → trades)
32
+
33
+ ---
34
+
35
+ ## Scope decisions
36
+
37
+ **In scope**: everything that demonstrates jspurefix resilience and recovery features, plus the test scenario scripts that validate them. The goal is a reference app that shows how to use jspurefix correctly in production, with smoke tests that exercise the hard cases.
38
+
39
+ **Out of scope (for now)**:
40
+ - TLS support — nice-to-have, defer to a follow-up if there's demand
41
+ - GC monitoring — .NET-specific; Node has different profiling tools (`--inspect`, V8 heap snapshots) that don't fit the same API
42
+ - `--clients 1..5` multi-initiator support — soak-test scaffolding, not core feature; defer
43
+ - Generated FIX types — `jspurefix` already has runtime parsing; the demo currently uses `ILooseObject`-style access which is fine for a demo
44
+
45
+ These can become follow-up phases if/when needed.
46
+
47
+ ---
48
+
49
+ ## Lessons learned from C# git history
50
+
51
+ These are real bugs that surfaced during soak testing in the C# demo. **Each one is a hazard for the TypeScript port.** The plan below explicitly addresses each.
52
+
53
+ | Bug | Fix | Phase |
54
+ |-----|-----|-------|
55
+ | Unsolicited trade timer kept running after session disconnect — eventually multiple timers running concurrently | Track timer with cancellation token / clearable handle stored as instance field; cancel on `onStopped()` | **D2** |
56
+ | Application state (counters, flags, caches) leaked across reconnects | Explicitly reset all state in `onReady()` | **D2** |
57
+ | Duplicate trade requests sent on reconnect | Add guard flag (`hasSentTradeRequest`) and reset it in `onReady()` | **D2** |
58
+ | Old transport handle still alive when client reconnected fast → multiple sessions for same CompID, each with its own timer | Session registry on acceptor: stop the old session when a new logon arrives with the same CompID | **D6** |
59
+ | Shared parser instance across multiple concurrent sessions caused state corruption | Per-session parser instance via session factory | **D6** |
60
+ | Wildcard `TargetCompID="*"` for multi-client acceptor was overwritten on first logon, breaking subsequent sessions | Store original CompID at factory init; restore on each clone | **D6** |
61
+ | Buffer corruption when OS wakes from sleep mid-read | Already fixed in jspurefix Phase 2 (parser reset on disconnect) ✓ | done |
62
+ | Sequence mismatch loops on reconnect | Already fixed in jspurefix Phase 3 (coordinator) ✓ | done |
63
+ | Hard to debug multi-client logs without session tagging | Tag logs with SenderCompID | **D6** |
64
+
65
+ The good news: many of the underlying engine bugs are already fixed in jspurefix. The demo-level bugs are application-layer hazards we need to avoid.
66
+
67
+ ---
68
+
69
+ ## Delivery: 9 PRs (D1 through D9)
70
+
71
+ ### PR D1: Hygiene + jspurefix bump (zero risk)
72
+
73
+ **Goal**: Get the demo onto jspurefix 5.5.0 and modernise the project skeleton.
74
+
75
+ | File | Action |
76
+ |------|--------|
77
+ | `package.json` | Bump `jspurefix` dependency to `^5.5.0`. Remove unused deps (`request`, `typings`). Add a real `test` script. |
78
+ | `README.md` | Replace 3-line badge-only README with a basic intro that says what the demo does. Add "build / run" section. |
79
+ | `.travis.yml` | Remove (Travis is gone, AppVeyor + GitHub Actions cover us). Keep `appveyor.yml`. |
80
+ | `tslint.json` | Remove (eslint is the canonical linter now). |
81
+
82
+ **Risk**: None. New deps and docs only.
83
+
84
+ ---
85
+
86
+ ### PR D2: Resilience fixes for the existing flow (low risk, high value)
87
+
88
+ **Goal**: Fix the hard-won bugs from C# soak testing in the existing TS code, before we add anything new. These are real bugs that exist in the current `trade-capture-client.ts` and `trade-capture-server.ts`.
89
+
90
+ | File | Action |
91
+ |------|--------|
92
+ | `src/trade_capture/trade-capture-server.ts` | Track the `setInterval` handle as a class field. Clear it in `onStopped()` AND at the start of any new request handler (defensive). Currently it's already cleared in `onStopped()` per the agent's read — verify and harden. |
93
+ | `src/trade_capture/trade-capture-client.ts` | Add `onReady()` state reset: clear received trades map, reset any guard flags. Add a `hasSentTradeRequest` guard so reconnect doesn't double-send. The current `done()`-after-32s scheduler should also be guarded against firing twice on reconnect. |
94
+ | `src/trade_capture/trade-capture-server.ts` | Same `onReady()` reset for any server-side state (next trade ID counter is OK to keep monotonic across reconnects but verify). |
95
+ | New: `src/test/trade-capture-resilience.test.ts` | Smoke test: start server, connect client, force disconnect, reconnect, verify exactly one set of trades is exchanged (not two). |
96
+
97
+ **Risk**: Low. We're hardening existing code, not changing observable behaviour in the happy path.
98
+
99
+ **Critical**: This PR fixes the "timer keeps running" and "duplicate sends after reconnect" classes of bugs at the demo level. Even before we add CLI options or new features, the existing demo will be more correct.
100
+
101
+ ---
102
+
103
+ ### PR D3: Match the C# trade capture flow (low risk)
104
+
105
+ **Goal**: Make the message flow match the C# reference: client requests securities first, then requests trades after receiving 5 security definitions.
106
+
107
+ **Why**: Two reasons:
108
+ 1. It exercises more of the FIX surface (SecurityDefinition messages)
109
+ 2. It's the canonical flow the C# demo uses, which means test scripts written against C# can be ported with minimal changes
110
+
111
+ | File | Action |
112
+ |------|--------|
113
+ | `src/trade_capture/trade-capture-client.ts` | In `onReady()`: send `SecurityDefinitionRequest` for `MarketID="20"` instead of going straight to `TradeCaptureReportRequest`. On receiving each `SecurityDefinition`, increment counter; once 5 received, send `TradeCaptureReportRequest`. |
114
+ | `src/trade_capture/trade-capture-server.ts` | Add handler for `SecurityDefinitionRequest` that responds with 5 `SecurityDefinition` messages (Gold/Silver/Platinum/Copper/Steel — match C# names). Existing `TradeCaptureReportRequest` handler unchanged. |
115
+ | `src/trade_capture/trade-factory.ts` | Add helper for `SecurityDefinition` message construction. |
116
+
117
+ **Risk**: Low. New message handlers added, existing ones unchanged.
118
+
119
+ ---
120
+
121
+ ### PR D4: CLI options + mode switching (medium risk)
122
+
123
+ **Goal**: Replace the hard-coded "boot client and server in same process" launcher with a real CLI that can run client-only, server-only, or both.
124
+
125
+ | File | Action |
126
+ |------|--------|
127
+ | New: `src/cli/cli-options.ts` | Define CLI shape using `commander` or `yargs` (we should agree which — leaning `commander` for simplicity). Options: `--client`, `--server`, `--config <path>`, `--store <dir>`, `--disconnect-after <secs>`, `--timeout <secs>`, `--skeleton`, `--log`. |
128
+ | New: `src/cli/cli-options-binder.ts` | Validate flags (mutually exclusive flags, range checks). |
129
+ | `src/trade_capture/app.ts` | Refactor `AppLauncher` to accept `CliOptions`. Dispatch to `runClient()`, `runServer()`, or `runBoth()`. Preserve "run both" as the default for backward compat. |
130
+ | New: `src/cli/index.ts` | Entry point that parses argv and calls the launcher. |
131
+ | `package.json` | Update bin/scripts so `npm run tcp-tc` still works but new CLI is also available. |
132
+
133
+ **Risk**: Medium. Touches the entry point and could break the existing `npm run tcp-tc` workflow if not done carefully. Mitigation: preserve the existing behaviour as the default mode.
134
+
135
+ ---
136
+
137
+ ### PR D5: File store + persistent scenario configs (medium risk)
138
+
139
+ **Goal**: Wire up the `IFixSessionStore` (added in jspurefix Phase 4) to the demo so we can run reset / recovery / broker-reset scenarios. This is where persistence comes online.
140
+
141
+ | File | Action |
142
+ |------|--------|
143
+ | New: `data/session/recovery-initiator.json` | File store, ResetSeqNumFlag=N, port 2345, store dir `store/initiator`. Mirrors C# `recovery-initiator.json`. |
144
+ | New: `data/session/recovery-acceptor.json` | File store, ResetSeqNumFlag=N, port 2345, store dir `store/acceptor`. |
145
+ | New: `data/session/broker-reset-initiator.json` | File store, client wants resume (reset=N), reconnectSeconds=10, store dir `store/broker-initiator`. |
146
+ | New: `data/session/broker-reset-acceptor.json` | File store, server forces reset (reset=Y), store dir `store/broker-acceptor`. |
147
+ | `src/trade_capture/app.ts` | Pick up `--store <dir>` from CLI to override store factory at runtime (matches C# behaviour). |
148
+ | `data/session/test-initiator.json` / `test-acceptor.json` | Document them as the "reset mode (default)" configs. Keep their behaviour unchanged. |
149
+ | New: `src/test/file-store-roundtrip.test.ts` | Smoke test: run a session with file store, kill it, restart, verify sequences resumed. |
150
+
151
+ **Risk**: Medium. The file store API was added recently — needs careful testing in the demo context.
152
+
153
+ ---
154
+
155
+ ### PR D6: Multi-client + session registry (medium-high risk)
156
+
157
+ **Goal**: Support multiple concurrent clients to the same acceptor without state corruption. This is where the hardest soak-test bugs lived in C#.
158
+
159
+ **Note**: the C# `SessionRegistry` is itself work-in-progress (lost when leaving the hedge fund where the original was written). The broader vision is loading ~20 broker dictionaries and using first-message metadata to dispatch — that's a future expansion. For D6 we're building the **minimum viable registry** to fix the stale-transport bug.
160
+
161
+ | File | Action |
162
+ |------|--------|
163
+ | New: `src/trade_capture/session-registry.ts` | A map from `(SenderCompID, TargetCompID)` to active session. On new logon, stop the old session for the same key before accepting. Designed so the broker-multiplex use case can be layered on later. |
164
+ | `src/trade_capture/trade-capture-server.ts` | Use the registry to detect and stop stale sessions. |
165
+ | `data/session/multi-client-acceptor.json` | New config with `TargetCompID="*"` (wildcard mode). |
166
+ | `src/trade_capture/app.ts` | If `TargetCompID === "*"`, clone the description per session so each session has its own actual TargetCompID after logon. Store the original `*` value at factory init. |
167
+ | Logging | Tag every log line with the session's SenderCompID so multi-client logs can be traced. |
168
+ | New: `src/test/multi-client.test.ts` | Two clients connecting concurrently, both receive trades, no cross-talk, no duplicate timers. |
169
+
170
+ **Risk**: Medium-high. This is where most of the C# soak-test bugs lived. The session registry is new code; the wildcard CompID handling is tricky.
171
+
172
+ **Future expansion** (post-D6, not in scope here):
173
+ - Load multiple broker dictionaries at startup
174
+ - Inspect first message of incoming FIX file/connection to pick the right dictionary
175
+ - Expose registry state to a React frontend (or any HTTP/SSE consumer)
176
+ - This was the original use case at the hedge fund — capture it as a follow-up phase once the basic registry is solid.
177
+
178
+ ---
179
+
180
+ ### PR D7: Scenario test scripts (low risk, high value)
181
+
182
+ **Goal**: Port the C# `test-scenarios.sh` to TypeScript-runnable scenarios. These are the smoke/soak tests that drove all the bug fixes.
183
+
184
+ | File | Action |
185
+ |------|--------|
186
+ | New: `scripts/test-scenarios/seq-mismatch.ts` | Truncate client sequence, restart, verify mismatch recovery loop completes. |
187
+ | New: `scripts/test-scenarios/server-bounce.ts` | Server timeout, restart, client should resume. |
188
+ | New: `scripts/test-scenarios/client-bounce.ts` | Client timeout, restart, server should accept resumed client. |
189
+ | New: `scripts/test-scenarios/broker-reset.ts` | First session establishes sequences, second session has server send `ResetSeqNumFlag=Y`, verify reset to 1. |
190
+ | New: `scripts/test-scenarios/run-all.sh` | Wrapper that runs all scenarios in sequence, reports pass/fail. |
191
+ | `package.json` | Add scripts: `npm run scenario:seq-mismatch`, `npm run scenario:all`, etc. |
192
+
193
+ **Risk**: Low. These are test scripts that exercise the demo, not changes to production code. If they break, they break in obvious ways.
194
+
195
+ **Implementation note**: prefer Node scripts over bash where possible — easier for cross-platform (Windows/macOS/Linux) and avoids the bash compatibility issues the C# demo has between `.sh` and `.ps1`.
196
+
197
+ ---
198
+
199
+ ### PR D8: Skeleton mode + long-running smoke test (low risk)
200
+
201
+ **Goal**: Add skeleton mode (heartbeat-only handler) to enable long-running stability tests without trade traffic noise.
202
+
203
+ | File | Action |
204
+ |------|--------|
205
+ | New: `src/trade_capture/skeleton-handler.ts` | Bare-bones session that accepts logon and exchanges heartbeats only. Does not subscribe to trades, does not handle app messages beyond logging. |
206
+ | `src/trade_capture/app.ts` | Add `--skeleton` CLI flag that swaps in `SkeletonHandler` instead of full client/server. |
207
+ | New: `scripts/soak-test.sh` | Mirror C# `soak-test.sh`: kill any running, start skeleton server + client, wait. |
208
+ | New: `docs/soak-testing.md` | How to run the soak test, what to look for in heap snapshots, expected memory profile. |
209
+
210
+ **Risk**: Low. New mode, doesn't touch existing code paths.
211
+
212
+ ---
213
+
214
+ ### PR D9: Documentation (zero risk)
215
+
216
+ **Goal**: Real README + scenario docs so a new user can understand what the demo does and how to extend it.
217
+
218
+ | File | Action |
219
+ |------|--------|
220
+ | `README.md` | Real intro: what the demo is, what it demonstrates, quickstart, CLI reference, scenario list. |
221
+ | New: `docs/architecture.md` | Diagram of client/server flow, session lifecycle, state reset on reconnect. |
222
+ | New: `docs/scenarios.md` | Each scenario script: what it tests, what it proves, how to run it, what success looks like. |
223
+ | New: `docs/extending.md` | How to add new message handlers, how to wire your own application logic. |
224
+
225
+ **Risk**: None. Docs only.
226
+
227
+ ---
228
+
229
+ ## Dependency graph
230
+
231
+ ```
232
+ D1 (hygiene) ──→ D2 (resilience fixes) ──→ D3 (sec def flow) ──→ D4 (CLI)
233
+
234
+
235
+ D5 (file store) ──→ D6 (multi-client)
236
+
237
+
238
+ D7 (scenarios)
239
+
240
+
241
+ D8 (skeleton/soak)
242
+
243
+
244
+ D9 (docs)
245
+ ```
246
+
247
+ Most PRs are sequential because they build on each other (CLI → store → multi-client → scenarios). D1 and D9 can happen out of order if useful.
248
+
249
+ ---
250
+
251
+ ## Risk summary
252
+
253
+ | PR | Risk | Reason |
254
+ |----|------|--------|
255
+ | D1 | None | Deps + docs |
256
+ | D2 | Low | Hardens existing code, no new flows |
257
+ | D3 | Low | New message handlers, existing ones unchanged |
258
+ | D4 | Medium | Refactors entry point |
259
+ | D5 | Medium | New persistence, depends on Phase 4 store API |
260
+ | D6 | Medium-high | Concurrency, hard bugs lived here in C# |
261
+ | D7 | Low | Test scripts only |
262
+ | D8 | Low | Additive new mode |
263
+ | D9 | None | Docs |
264
+
265
+ ---
266
+
267
+ ## General principles (reminder, same as backport plan)
268
+
269
+ 1. **Incremental PRs** — one PR per phase, smallest safe changes
270
+ 2. **Tests first** — write the smoke test before the fix
271
+ 3. **Maintain backward compat** — `npm run tcp-tc` should keep working through every PR
272
+ 4. **Each PR is independently mergeable and useful** — no half-finished features
273
+ 5. **Reference the C# demo when in doubt** — it's the golden source
274
+ 6. **Move slowly** — this is a lot of work and will span multiple sessions
275
+
276
+ ---
277
+
278
+ ## Decisions
279
+
280
+ 1. **CLI library**: either `commander` or `yargs` is fine — both are mature and well-supported. Pick whichever feels cleaner when we get to D4 (no strong preference).
281
+
282
+ 2. **Session registry**: this is application-level code, not in jspurefix itself. The concept comes from real-world hedge fund work where one server hosts ~20 broker dictionaries simultaneously and uses metadata from the first FIX message to pick the right one. The registry there fed into a React frontend for selecting brokers. **The C# `SessionRegistry` is itself a work-in-progress** — work that was lost when leaving the fund and needs re-adding. So in D6 we're not just porting an existing piece, we're collaboratively designing it. Keep it minimal in D6 (just enough to fix the multi-client stale-transport bug); the broader broker-multiplex use case is a future expansion.
283
+
284
+ 3. **TypeScript modernisation in jspf-demo**: defer. The existing TS in the demo is old, but stack-wide modernisation can wait until D1–D9 are done. We don't want to mix two refactors.
285
+
286
+ 4. **Test framework**: **jest**, for consistency with jspurefix and the user's other projects.
@@ -0,0 +1,39 @@
1
+ import { XDocument } from './x-element';
2
+ import { ValidationError } from './validation-error';
3
+ export declare class DictionaryValidator {
4
+ private readonly _errors;
5
+ private readonly fieldsByName;
6
+ private readonly fieldsByTag;
7
+ private readonly componentsByName;
8
+ private readonly messagesByName;
9
+ private readonly messagesByMsgType;
10
+ private readonly fieldNamesCaseInsensitive;
11
+ private readonly componentNamesCaseInsensitive;
12
+ private readonly referencedFields;
13
+ private readonly referencedComponents;
14
+ private readonly allFieldNames;
15
+ private readonly allComponentNames;
16
+ get errors(): ReadonlyArray<ValidationError>;
17
+ get hasErrors(): boolean;
18
+ get hasWarnings(): boolean;
19
+ validate(doc: XDocument): void;
20
+ throwIfErrors(): void;
21
+ private collectFieldDefinitions;
22
+ private validateFieldDefinition;
23
+ private validateFieldEnums;
24
+ private collectComponentDefinitions;
25
+ private validateComponentDefinition;
26
+ private collectMessageDefinitions;
27
+ private validateMessageDefinition;
28
+ private validateHeader;
29
+ private validateTrailer;
30
+ private validateComponentReferences;
31
+ private validateMessageReferences;
32
+ private validateFieldReferences;
33
+ private validateComponentReference;
34
+ private checkUnusedDefinitions;
35
+ private addError;
36
+ private addWarning;
37
+ static findSimilar(input: string, candidates: string[]): string | null;
38
+ static levenshteinDistance(s1: string, s2: string): number;
39
+ }