Guide 1 showed what the suite is. This one shows what actually runs, who owns each artifact, and how data crosses a process boundary — because most of the expensive bugs in this project were boundary bugs: a seam orphaned by an F5, a probe reading a rotation generation that did not exist, a second writer on a one-writer file. Companion page: ← Architecture Map
NinjaTrader.exeEverything in this diagram shares one AppDomain and one compiled assembly
(NinjaTrader.Custom.dll). That is why one broken .cs blocks the whole suite, and why an F5
that swaps the assembly can leave a chart's bar type publishing into an orphaned static seam store.
w=0 auditioning, sensor
replacement and standalone grading possible without touching fusion.Beacon/BeaconForeign, so a
consumer can say DECOUPLED — restart NT rather than “absent”. A chart reload does not fix it. Restart
NinjaTrader.
The canon pipeline is tools → JSONL → ingester → DB → analyzer. Everything downstream of NT is plain Python over plain files; there is no service mesh and no message broker, which is deliberate — a bake node that loses power resumes from a checkpoint file.
legacy-node_probe.py writes into the same
sentinel.db as the local probes — that is the only thing crossing back from the remote box.One row per thing that is separately startable, killable, or crashable.
| Process | Where | Responsibility |
|---|---|---|
| NinjaTrader.exe | NT | Hosts everything NinjaScript: charts, bar clocks, sensors, Council, order sources, AddOn services. One AppDomain, one assembly — so one bad file is a total outage, and an F5 is a code-swap event with consequences. |
| Chart pipeline | NT | Per scope: build bars → publish sensor seams → fuse a verdict → optionally place and record an order. The unit of everything; a scope is one chart's worth of context. |
| AddOn services | NT | Process-wide, chart-independent: safety gate, governor, kill switch, alerts, news, fleet identity, config IO, theming, copier, replay transport. |
| NT8BridgeServer | NT | Third-party AddOn (cli-nt-bridge) exposing NT's own compiler headlessly: real Roslyn errors in ~15 s, plus deploy which recompiles and hot-reloads under whatever is running. |
| Conductor | NT | Replay transport — seek, queue, per-session checkpoint, crash-resume, autorun. Holds no account reference and calls no order method, by design. |
ingest.py --watch | daemon | Tails the JSONL corpus into SQLite. The only writer of corpus rows to the DB; folds the council vote-vector in. |
probe.py --watch | daemon :8502 | Samples NT + suite health (incl. swallowed Lab faults) into the DB every cycle. Writes a heartbeat row so 0 means “measured zero just now”, never “dead probe”. |
corpus_probe.py | daemon | Audits whether the recorded corpus is trustworthy — completeness, schema, vote coverage — as opposed to merely present. |
docs\audit.py | daemon :8505 | Docs drift: broken links, stale HTML siblings, contract-version drift, dangling {{tokens}}, orphans. Gates commits via a pre-commit hook. |
legacy-node_probe.py | daemon :8504 | One read-only PowerShell metrics snapshot per cycle over SSH. Never touches NT on the remote box. |
| Streamlit | server :8501 | The research surface — Observatory (honesty ladder), Explorer, Tick-Paths, Council-Paths. Pages import the CLIs so numbers cannot drift between the two. |
| Grafana | server :3000 | Ops boards over the SQLite datasource. Dashboard JSON is generated by build_*_dashboard.py, never hand-edited. |
snapshot.py | task | Tiered WORM corpus snapshots, validate-before-destruct. Two scheduled tasks. Guarantees integrity — not correctness. |
replay_sync.py | tool | Classifies the bake set by rule (so new Sentinel files are auto-included), tars, applies, compiles on the remote node. |
| Harness CLIs | tool | Bar types + sensors + fusion outside NT from raw CSV. Trusted only behind equivalence, which itself is trusted only behind selftest_equivalence. |
Two loadable recorders once meant a workspace load could silently start a second writer emitting contaminated rows into the training corpus. Ownership is therefore explicit, and “harmlessly present in the tree” is not a category that exists here.
| Artifact | Writer (exactly one) | Readers | Format / note |
|---|---|---|---|
| Seam stores | each sensor / bar clock, for its own seam | Council · Cockpit · Bridge · Helm | In-memory statics. Scope-keyed, auto-expiring, no history. |
Excursions\council\ | ExcursionRecorder v2.3.0 | ingest.py · pathlab · train.py | Schema 1.5. ⚠ 1.3/1.4 are contaminated — never train on them. |
Excursions\ticks\ | the recorders | pathlab · tickpaths · limitlab | ctick.3 — self-describing header, so context needs no join. |
Excursions\candidates\ | CandidateRecorder v1.1.0 | pathlab --kind candidates | Joins at 97% on (inst,bartype,dir,fireTime). |
Ledger\ | SentinelCore Foundation | Lens · Dashboard · diagnostics export | Orders/actions/fills + episode & instance context. |
sentinel.log | SentinelCore.Log | humans · probes · forensics | 6 generations. Rotation is deliberately silent — logging it would recurse. |
Roster.conf | Cockpit ⑤ / System Builder via RosterIO | Council via the same RosterIO | One reader/writer type ⇒ format cannot drift. |
Lanes.conf | operator | LaneIO → Council | Wins over the F6 setting, and the override is announced in the log. |
sentinel.db | ingest.py + the probes (own tables) | Grafana · Streamlit · analysis CLIs | WAL + 30 s busy timeout, because concurrent writers were real. |
catalog.db | quartermaster\catalog.py | Quartermaster CLI · bake planning | Dates live in the filename ⇒ a 100 GB scan never opens a .nrd. |
run-log.jsonl | Conductor | Conductor resume | Session-boundary checkpoints. ⚠ a polluted log misdirects resume. |
cards.off / theme.txt | operator (file presence) | SentinelSkin, polled ≤2 s | Presence = render gate off. Seams still publish; only drawing no-ops. |
| Rule | Why it exists |
|---|---|
| UI thread only | Enumerating ChartControl.Indicators or touching a window throws off the data thread. NT is multi-UI-threaded — each window has its own dispatcher, so cross-window property access throws every time and needs Win32 HWND calls instead. |
| Data thread only | Reading ind.Values[i][barsAgo]. Resolve the reference on the UI thread, read values on the data thread. |
Never in OnRender | Reading Value[]. Cache display values in OnBarUpdate; a render pass must not touch series state. |
| 1 s timer, not the tape | A safety readout that refreshes on ticks displays a stale account in a quiet market. The Deck's account band is timer-driven for exactly this reason. |
| F5 vs restart | F5 recompiles and reloads. It does not reload a chart's sticky bar type, and it orphans bar-type seams. Bars-type work ⇒ full NT restart. deploy hot-reloads under a running instance; a plain file write does not. |
| Version forks | Namespace + class name are an indicator's serialization identity. A new version is a different tool: remove the old instance from the chart first, or two of them collide on one scope. |
| Realtime gate | UpdatedUtc stamps wall-clock even during replay, so a freshness check cannot tell replay from live. Anything that records gates on State.Realtime. |
| :3000 · :8501 · :8502 · :8504 · :8505 | Grafana · Streamlit · health probe · legacy-node probe · docs audit. Each daemon binds its port as its own single-instance guard. |
OnStateChange simply never votes, forever, and nothing says so. That is what the
declared roster and RosterComplete exist to surface.MaxSpeedValue =
int.MaxValue — a resolved-but-implausible number that silently disabled a clamp and reported
success. Measure the outcome; do not believe the flag.nt8bridge compile → errors: []. Before any bake or
live run: F5 + full NT restart (Core v1.45.0 / Council v1.11.0), remove the old Council from each chart
before adding v1.11.0, and re-add Deck v0.2.6. Lattice (212205) and Effort (212206) compile clean but have never
been loaded — loading them also needs the restart, because bar types are sticky.