piker/plans/opencode/live-backend-chart-e2e-revi...

349 lines
15 KiB
Markdown
Raw Normal View History

# Live-backend chart E2E plan review
## Decision
Reorient the human-facing test effort around the production Qt chart
running against real public backends. Put replay implementation,
scenario design, provider failure injection, and replay-backed CI on
hold until a real application or backend failure demonstrates a need
for controlled data.
The first valuable target is an in-process application journey using
pytest-qt, Trio guest mode, an isolated Piker actor tree, and public
Kraken data. Installed-process and paper-order journeys follow after
that path can start, report readiness, and stop without process-wide
signals.
Entry-point backend discovery remains independently useful, but it is
not a prerequisite for chart E2E. Treat it as a short standalone patch
or defer it with replay.
## Review of the original plan
The original plan correctly identifies the end goal: user-facing QtBot
journeys paired with installed-process tests. It also correctly requires
real Tractor, IPC, SHM, feed, search, and EMS boundaries.
Its central ordering assumption is now rejected. It makes a deterministic
offline provider the organizing dependency for chart boot, search, paper
orders, public APIs, process tests, and failure handling. It schedules
that provider and a general subprocess harness before the production
Qt/Trio lifecycle seam.
That ordering optimizes for deterministic default CI. It does not optimize
for finding failures users currently encounter with real venues, real
history payloads, real quote timing, real symbology, and the composed chart.
The replay phase is not technically required by the later lifecycle work:
1. Qt/Trio startup and shutdown are provider-independent.
2. A chart can use Kraken's public history and quote APIs without credentials.
3. Search can use the real Kraken symbol service.
4. Paper EMS can consume a real public feed without spawning live brokerd.
5. Installed chart startup can use an isolated registry and real provider.
6. Guest errors, child failures, SIGINT behavior, and repeated-session leaks
do not require synthetic provider failures.
Replay remains potentially useful for forcing exact races, reproducing a
captured venue payload, denying network in required CI, or proving a fix for
a specific data-dependent regression. Those are follow-up uses, not current
prerequisites.
## What replay would and would not audit
A replay-backed chart journey would prove that the composed application can
consume one protocol-conforming source, render known bars, process known
quotes, and shut down. It would be useful interface and lifecycle coverage.
It would not prove the behavior most likely to drift outside the repository:
- exchange REST and WebSocket availability;
- TLS, DNS, geo-routing, and endpoint changes;
- authentication behavior accidentally applied to public requests;
- real symbology and market metadata changes;
- incomplete, duplicated, delayed, or out-of-order history;
- backend normalization against current venue payloads;
- reconnect and rate-limit behavior;
- the timing and volume of actual chart updates.
The current priority is the second list. Therefore real-provider application
qualification should precede further replay work.
## Current practical blockers
### Blocking application host
`run_qtractor()` currently creates or reuses `QApplication`, constructs the
production `MainWindow` and `GodWidget`, starts Trio guest mode, shows the
window, and then blocks in `app.exec_()`.
This prevents a synchronous pytest-qt test from receiving a session handle
while Qt owns the host loop.
### Unobservable Trio completion
The Trio guest `done_callback` prints unexpected errors and unconditionally
calls `app.quit()`. It does not retain the `outcome.Outcome`, expose a Qt
completion signal, or let pytest fail on the original exception.
Calling `app.quit()` also violates pytest-qt ownership of its session-scoped
application.
### Process-wide chart shutdown
`MainWindow.closeEvent()` saves geometry and sends SIGINT to the entire test
process. A QtBot-owned window cannot exercise normal close behavior without
interrupting pytest.
### No application readiness contract
`_async_main()` has a meaningful readiness point after the initial chart,
paper order mode, search handlers, and status cleanup are active, but it does
not publish that state outside its Trio task.
### Incomplete child-resource accounting
The test harness isolates XDG paths, config paths, registry addresses, and
current-process SHM. A chart application test must additionally record the
specific child actor IDs and SHM names created by datad so teardown can prove
those exact resources disappeared without broad cleanup.
## Existing foundation to keep
The completed harness foundation already provides:
- pytest-qt ownership of one `QApplication`;
- import-time PyQt6 and offscreen selection with `--headless`;
- process-level XDG isolation before Piker and Qt imports;
- function-scoped restoration of Piker, Qt, QSettings, and PyQtGraph state;
- real QtBot key delivery and widget registration;
- unique Tractor registry addresses from the Tractor pytest plugin;
- test-owned config directories propagated to child actors through
`piker_test_dir` runtime variables;
- exact current-process SHM ownership checks.
This is sufficient foundation for the lifecycle extraction. Replay is not
needed to begin it.
## Revised test model
Keep independent dimensions instead of defining application coverage by its
data source:
| Dimension | Initial selection | Later selections |
|---|---|---|
| Boundary | In-process production application | Installed chart process |
| Data source | Live Kraken public data | Binance, other qualified backends |
| Display | Qt offscreen through pytest-qt | Real Wayland/X11 compositor |
| Clearing | Real paper EMS | Credentialed live broker qualification |
| Cadence | Explicit local/CI qualification | Repeated soak and release gate |
The first test is therefore a headless, live-provider, in-process application
journey. It is not a deterministic default unit test, and it should not be
presented as one.
## Isolation contract
Real provider access does not require attaching to a developer's production
Piker runtime. Each application test must:
1. Use the Tractor pytest plugin's unique registry address.
2. Pass that address explicitly through `run_qtractor()` into
`maybe_open_pikerd()`; never allow fallback to the default registry.
3. Propagate the test-owned config directory through
`tractor_runtime_overrides['piker_test_dir']`.
4. Start a fresh local `pikerd`, `datad.kraken`, and `samplerd` under the
test's registry.
5. Use public Kraken data only; do not load user credentials.
6. Record every actor identity and each chart feed's exact SHM names.
7. Request structured shutdown through the application session.
8. Assert those actors, streams, registry connections, and SHM names are gone.
9. Never use process-name matching, broad SHM scans, `pkill`, or global ports.
10. Treat the external network as intentionally shared qualification input.
An explicitly selected live test must fail on connection or provider errors,
not silently skip after startup. Tests skip only when live qualification was
not requested.
## Immediate vertical slice
### Production lifecycle seam
Extract a nonblocking `start_qtractor()` from `run_qtractor()`.
It should return a `QtractorSession` containing:
- the production `MainWindow` and `GodWidget`;
- a Trio token and owned cancellation scope;
- an application-ready Qt signal;
- a completion Qt signal;
- the final `outcome.Outcome`;
- a structured `request_shutdown()` method;
- whether the adapter or caller owns `QApplication.exec()`.
`start_qtractor()` must configure and show the production objects but must not
call `app.exec()` or `app.quit()`. `run_qtractor()` remains the blocking CLI
adapter: it calls `start_qtractor()`, owns `app.exec()`, and unwraps the final
outcome into a process result.
Change `MainWindow.closeEvent()` to request session shutdown through an
injected callback. Keep geometry persistence. Remove process-wide SIGINT from
normal window closure; terminal SIGINT remains an outer CLI concern.
Publish application readiness from `_async_main()` only after the initial
feed, charts, paper order mode, search handlers, and startup status have all
entered their live scopes.
### First live chart journey
Add an explicitly selected test such as:
`tests/app/test_live_chart.py::test_kraken_chart_boots_and_closes`
Use `xbtusd.spot.kraken`, because Kraken public REST/WebSocket data is
credential-free and the pair is continuously active.
The test should:
1. Start `QtractorSession` with pytest-qt's `QApplication`.
2. Pass the unique test registry and child config root.
3. Register `session.window` with `qtbot` immediately.
4. Wait for the application-ready signal with a bounded timeout.
5. Assert the displayed FQME and production window title.
6. Assert both historical and real-time chart visualizations have nonempty
data from their real SHM arrays.
7. Observe at least one post-start quote/display update from Kraken.
8. Drive Ctrl-L with QtBot and assert the real search bar receives focus.
9. Drive the existing search-dismiss interaction and assert chart focus.
10. Request structured close and wait for the completion signal.
11. Unwrap a successful Trio outcome.
12. Prove exact actor and SHM teardown.
Do not assert pixels, exact prices, exact bar counts, result ordering, actor
PIDs, generated SHM names, or quote arrival within an unrealistically short
interval. Those values vary while the semantic behavior remains correct.
## Live-provider selection
Use Kraken as the initial baseline:
- public market metadata, OHLC, and WebSocket data need no credentials;
- `xbtusd.spot.kraken` and `ethusdt.spot.kraken` already appear in live feed
tests;
- the existing suite allows Kraken feed coverage in CI;
- crypto trading is continuous outside venue maintenance and outages.
Use Binance spot as a second qualification only where network and geographic
access are known. Keep Kucoin informational until its public configuration and
startup behavior receive direct repair. Exclude IB, Questrade, and current
Deribit from credential-free chart qualification.
Add one explicit selection mechanism, preferably `--live-provider=kraken`.
Without it, live application tests skip. With it, backend startup failure is a
test failure and retains the original Trio/Tractor exception and diagnostics.
Do not add a blanket retry. Record cold-start duration and failure signatures.
A separate repeated-run or soak command can measure operational reliability
after the first journey is stable.
## Reordered implementation phases
### Phase A: Qt/Trio lifecycle
Implement `QtractorSession`, nonblocking startup, structured close, observable
outcome, and repeated-session cleanup. Verify first with a provider-independent
guest coroutine so failures are localized to lifecycle ownership.
### Phase B: real Kraken chart
Boot the production `_async_main()` against `xbtusd.spot.kraken` using the
isolated runtime contract. Prove chart readiness, live data, one QtBot focus
journey, and complete teardown.
### Phase C: real chart behavior
Add A-to-B-to-A Kraken symbol switching, chart navigation, timeframe changes,
and paper order open/cancel behavior. Prefer stable semantic assertions and
record real failures before extracting narrow regression tests.
### Phase D: installed-process coverage
Run the installed `piker chart` command with the same isolated registry/config
contract. Prove cold startup, readiness, graceful close, exit status, and exact
descendant cleanup. Add standalone `pikerd` attachment only after cold start
works.
### Phase E: qualification breadth
Add Binance where available, real compositor runs, repeated cold starts, and
bounded soak sessions. Keep provider outages visible as qualification results
rather than rewriting them into synthetic success.
### Deferred work
Defer replay scenarios, provider-conformance tests, deterministic quote
sequencing, synthetic provider failures, and replay-backed required CI.
Reconsider replay only when one of these concrete needs appears:
- a real payload must be preserved as a regression fixture;
- an ordering race cannot be reproduced reliably against a live venue;
- required network-free CI needs a minimal known-good chart source;
- a reconnect or malformed-data defect needs deterministic fault control.
Backend entry-point discovery may land separately if its implementation stays
small and useful to external providers. Do not make it block Phase A or B, and
do not use plugin support as a reason to continue replay work.
## Replay resumption record
If replay work resumes, first move its implementation out of the production
backend namespace and use it to introduce general external backend discovery:
1. Move the implementation to `piker.testing.brokers.replay`.
2. Add a `piker.brokers` Python entry-point group which resolves installed
external backend modules after conventional built-in imports.
3. Register replay through that entry point in Piker's package metadata.
4. Preserve a backend's actual module path when constructing datad and brokerd
`enable_modules` lists so Tractor authorizes the external endpoint module.
5. Reject duplicate names, built-in shadowing, non-module entry points, name
mismatches, and unsupported backend API versions.
6. Preserve dependency import failures from built-in backends instead of
misclassifying them as absent modules.
7. Cover resolver behavior with focused unit tests and prove one installed
entry-point backend through a spawned datad actor.
Do not use pytest-only `sys.modules` or package-attribute monkeypatching: those
registrations do not reliably cross spawn or forkserver actor boundaries.
External backends under active development can use editable installation in
the same Python environment as Piker.
## Practical sequence from the current worktree
The replay worktree contains the completed replay experiment. The unfinished
entry-point resolver experiment was deliberately excluded. Preserve this
branch as parked work rather than mixing it into the live chart patch.
Create the live-chart work on a fresh branch/worktree from the completed
Phase 1 head. The first implementation boundary should touch only the Qt/Trio
lifecycle and its provider-independent lifecycle tests. The second boundary
should add the opt-in real Kraken chart journey and any defects that journey
demonstrates.
If entry-point discovery is finished first, cap it at one independent resolver
commit with focused unit tests. Do not move or expand replay as part of that
commit.
## Effort estimate
- Entry-point resolver and unit tests: roughly half a day if kept independent.
- Qt/Trio lifecycle extraction and tests: one to two focused days.
- First isolated real Kraken chart journey: one to two days, depending on
defects revealed during startup and teardown.
- First QtBot search/focus behavior journey: roughly one additional day after
lifecycle stability.
- Installed-process chart coverage: one to two days after the in-process path.
The first real chart should therefore be reachable in roughly two to four
focused days without further replay work.