All checks were successful
deploy / deploy (push) Successful in 48s
Instead of fetching up to 50k inventory snapshots (~442MB), scope the query to only the time range of the current submission batch with a 15-minute buffer. For a typical 1-hour batch this drops from 50k rows to ~300 rows, well within the 1280Mi pod memory limit. The coalesced_at_idx on intent_inventory_snapshots covers the BETWEEN clause so this remains efficient. Proof: history-writer OOM kills from refreshQuoteOutcomes inventory fetch Assumptions: 15min buffer covers the attribution window for all submissions Still fake: heuristic gap outcomes may attribute trades imprecisely
196 lines
11 KiB
Markdown
196 lines
11 KiB
Markdown
# Implementation Proof: operator dashboard foundation, funds desk, and operator controls
|
|
|
|
Status: open
|
|
Opened: 2026-04-04
|
|
|
|
## Target outcome
|
|
The next turn is complete only when operators can answer from one real dashboard:
|
|
- are we making money
|
|
- are we trading
|
|
- can I safely control the system
|
|
- what exactly is broken if the answer is no
|
|
|
|
That answer must come from the live BTC/EURe system itself rather than from stitched-together `curl`, `kubectl`, and SQL by hand.
|
|
|
|
This is an operator-surface turn built on top of the already-proven data and execution loop. It should make the live system easier to supervise without loosening spendable truth or silently widening the trade hot path.
|
|
|
|
## Hypothesis
|
|
`unrip` becomes materially easier and safer to run once it has a dedicated dashboard service that:
|
|
|
|
1. reads PostgreSQL for durable history and current portfolio metrics
|
|
2. reads service `/state` and `/healthz` for live freshness and controls
|
|
3. shows operators the current EUR portfolio value, current profit view, current funds position, current alerts, and current control state in one place
|
|
4. exposes the routine non-destructive control actions that already exist
|
|
5. makes stale or unhealthy state obvious without requiring log-diving
|
|
|
|
If operators still have to reconstruct balances, alerts, arming state, and recent activity from multiple terminals, the system remains too operationally expensive for routine funded use.
|
|
|
|
## Scope
|
|
- [I010] Operator dashboard foundation: dedicated dashboard pod plus backend API that reads PostgreSQL and service `/state`, with a global live status bar, arm/disarm controls, stale-state display, and `Funds` as the default home page.
|
|
- [I011] Funds desk: current balances, EUR-equivalent value, deposit-handle reveal with retrieval timestamps, emergency stop plus withdraw-all control path, deposit/withdraw history, trade-driven asset-change history, and flexible withdrawal forms by asset and EUR equivalent.
|
|
- [I014] Strategy and system operator pages: active strategy settings, skip reasons, alert state, service health, topic lag, persistence health, and cluster-operator stats.
|
|
|
|
## Turn-shaping rule
|
|
This turn is about truthful operator surfaces, not new trading logic.
|
|
|
|
- The browser must read one dashboard backend, not Kafka, not PostgreSQL directly, and not a fan-out of service control APIs.
|
|
- The dashboard backend may read PostgreSQL, service HTTP state, Kafka, and narrow read-only cluster metadata.
|
|
- If the dashboard shows a control, that control must either be real and wired or clearly absent. No fake buttons.
|
|
- Frontend polling is prohibited. Live dashboard updates must use WebSockets.
|
|
|
|
## Non-goals
|
|
- No new strategy logic, dynamic fee logic, or benchmark-aware thresholding.
|
|
- No full quote analytics workbench or hindsight trade analytics page beyond the narrow recent-quotes and successful-trades views named in this turn.
|
|
- No browser-direct Kafka, Redpanda, or PostgreSQL access.
|
|
- No public internet ingress, auth stack, or multi-user permission system in this turn.
|
|
- No live-funds-moving withdrawal submit path without explicit user approval during implementation.
|
|
- No dashboard widgets backed only by invented or hand-maintained data.
|
|
- No polling-based live refresh loops.
|
|
|
|
## Required runtime behavior
|
|
|
|
### Dashboard topology
|
|
- A dedicated `operator-dashboard` service must exist in cluster runtime.
|
|
- The dashboard frontend must be served by the dashboard service itself or by a tightly coupled companion in the same deployment shape.
|
|
- The dashboard backend must be the only browser-facing integration point.
|
|
- The browser must use one authenticated WebSocket connection to the dashboard backend for live updates.
|
|
|
|
### Authentication skeleton
|
|
The dashboard backend must have authentication and authorization hooks in place from the first turn even if the initial implementation is permissive.
|
|
|
|
Required behavior:
|
|
- REST API routes must pass through backend auth middleware.
|
|
- WebSocket connection establishment must also pass through backend auth checks.
|
|
- The current implementation may resolve to an always-true local operator identity, but the code path must already return a structured auth context rather than bypass auth entirely.
|
|
- The frontend must learn auth state from the backend, not hard-code a local assumption.
|
|
|
|
### Profitability-first operator view
|
|
The dashboard must revolve around the question `are we making money`.
|
|
|
|
At minimum the default view must make it obvious:
|
|
- current total portfolio value in EUR terms
|
|
- profit or loss versus the deposit-time baseline
|
|
- profit or loss versus simply holding the original deposits
|
|
- a compact attribution split between market move and trading contribution using the truthful data available in this turn
|
|
- whether trading is currently active, including recent trade count and last successful trade time
|
|
|
|
If some part of the answer is still provisional because the system does not yet track every fee or per-trade realized settlement delta, the dashboard must say that explicitly rather than implying complete precision.
|
|
|
|
### Global status bar
|
|
The dashboard must show a stable top-level status strip with at least:
|
|
- active pair
|
|
- latest reference price and freshness
|
|
- latest inventory snapshot and freshness
|
|
- active alert count and highest visible severity
|
|
- strategy armed state
|
|
- executor armed state
|
|
- current total portfolio value in EUR terms
|
|
|
|
If one of these inputs is stale or unavailable, the bar must show that explicitly rather than quietly rendering old values.
|
|
The status bar should update from the backend WebSocket feed rather than browser polling.
|
|
|
|
### Funds home page
|
|
`Funds` must be the default landing page.
|
|
|
|
Its top section must answer `are we making money` before it answers lower-level operator questions.
|
|
|
|
It must show at least:
|
|
- current total portfolio value in EUR terms
|
|
- profit or loss versus the deposit-time baseline
|
|
- profit or loss versus simple hold of the original deposits
|
|
- current spendable balances by asset
|
|
- current EUR-equivalent value by asset and in total
|
|
- latest credited deposit inventory
|
|
- pre-credit funding visibility and current funding handles
|
|
- recent deposit and withdrawal records
|
|
- recent trade-driven asset changes
|
|
- the timestamps that tell the operator how fresh each section is
|
|
|
|
The dashboard may show funding observations, but it must not present pre-credit observations as spendable.
|
|
If the profit view excludes some fee classes or cannot yet provide per-trade realized net profit, that limitation must be visible in the page copy.
|
|
|
|
### Quote and trade activity
|
|
The dashboard must include a narrow operator-facing activity surface that is intentionally smaller than the future analytics workbench.
|
|
|
|
At minimum it must show:
|
|
- the 10 most recent incoming quotes
|
|
- live updates for that recent-quote list through the backend WebSocket feed
|
|
- a list of all successful trades with pagination
|
|
|
|
The recent-quote list may be minimal as long as it is real, current, and useful. For example:
|
|
- quote time
|
|
- pair or asset direction
|
|
- request kind
|
|
- input and output amounts when available
|
|
|
|
The successful-trades list must be durable-history-backed rather than in-memory-only.
|
|
|
|
### Strategy and operator surfaces
|
|
The dashboard must show:
|
|
- current strategy threshold and notional settings
|
|
- recent decisions and skip reasons
|
|
- current alert state and recent alert transitions
|
|
- service-by-service health and pause state
|
|
- writer offsets, persistence health, and relevant freshness timestamps
|
|
- read-only cluster/operator facts needed to understand whether the app pods are healthy
|
|
|
|
### Controls
|
|
The dashboard must expose routine controls through the backend, not by requiring the operator to hand-craft HTTP calls.
|
|
|
|
At minimum this turn must support some real control actions end to end, such as:
|
|
- service refresh where available
|
|
- pause or resume on safe services
|
|
- funding observer pause or resume
|
|
- withdrawals freeze or unfreeze
|
|
- withdrawal estimate requests
|
|
|
|
If arm/disarm or drain controls are shown for strategy or executor, they must be explicit about their risk class and confirmation requirements.
|
|
|
|
### Risk boundary for treasury actions
|
|
This turn may prepare operator-visible withdrawal estimation and confirmation surfaces, but it must not silently add a one-click live-funds path.
|
|
|
|
Any control that can move funds or materially alter live execution risk must remain:
|
|
- explicitly identified as risky
|
|
- confirmation-gated
|
|
- subject to separate user approval before live use
|
|
|
|
## Required durable and live sources
|
|
The dashboard backend must use:
|
|
- PostgreSQL for durable history and portfolio metrics
|
|
- service `/state` or `/healthz` for live state and freshness
|
|
- Kafka on the backend side for live event fan-out to WebSocket clients where appropriate
|
|
- optionally a narrow read-only cluster source for pod or deployment readiness
|
|
|
|
Kafka remains the event backbone underneath the system, but the browser must not depend on Kafka directly.
|
|
The backend may consume Kafka specifically to avoid polling and to drive live quote, price, inventory, alert, and trade updates into the dashboard WebSocket stream.
|
|
|
|
## Definition of done
|
|
- The live cluster still runs the previously proven BTC/EURe loop with unchanged spendable truth.
|
|
- A real dashboard service is deployed and reachable by port-forward or equivalent operator access.
|
|
- `Funds` is the default page and makes the current profit view explicit, including current EUR value, deposit-baseline comparison, hold comparison, and current balances from real sources.
|
|
- Strategy or system views show live operator state from real sources, including alerts and service health.
|
|
- The dashboard exposes a recent-quotes section that updates live over WebSockets with no polling.
|
|
- The dashboard exposes a durable successful-trades list with pagination.
|
|
- Backend auth is present for both REST and WebSocket paths, even if the initial auth provider is permissive.
|
|
- At least one safe control action is executed through the dashboard backend and reflected in the underlying service state.
|
|
- A stale or paused condition induced safely is visible in the dashboard without manual SQL or log parsing.
|
|
- The dashboard clearly distinguishes durable history from live current state.
|
|
- Tests cover backend aggregation and control routing logic.
|
|
- Remaining risky or intentionally deferred controls are named plainly.
|
|
|
|
## Failure conditions
|
|
- The dashboard is only a static shell over mocked or copied data.
|
|
- The browser has to call five different service APIs directly to work.
|
|
- Balances or alerts shown in the dashboard disagree with PostgreSQL or live service state.
|
|
- Freshness is hidden, forcing the operator to guess whether a value is current.
|
|
- Live updates depend on frontend polling instead of WebSockets.
|
|
- The dashboard has no backend auth path and would need auth bolted on later.
|
|
- The turn adds risky treasury controls without explicit approval and confirmation design.
|
|
- Profitability remains buried in generic metric cards or requires the operator to reconstruct the answer manually.
|
|
- The dashboard ships without enough runtime truth to replace routine manual `curl` and SQL work.
|
|
|
|
## Current real
|
|
- The first funded BTC/EURe live loop is already proven and archived.
|
|
- Pre-credit BTC funding visibility, durable alert records, and rollout-safe armed-state persistence are now live.
|
|
- PostgreSQL contains the durable trade, inventory, funding, alert, and portfolio-metric history needed for a dashboard backend.
|
|
- Operators still depend on manual terminal workflows to inspect that truth.
|