Runtime JSON
{
"components": [
{
"name": "Browser REST/WS clients",
"kind": "frontend tabs issuing REST calls and WebSocket bid connections",
"owner": "frontend",
"count_when_live": "3 viewer tabs in the requested scenario; the seller studio also issues REST start/create/activate calls when the host operates the show.",
"source": "frontend/components/studio/StudioWorkspace.tsx:231, frontend/components/studio/BidConsole.tsx:64, frontend/lib/live/backend-auction.ts:126"
},
{
"name": "ALB: encore-dev-backend-alb",
"kind": "AWS Application Load Balancer and target group",
"owner": "infra/envs/dev/app Terraform",
"count_when_live": "1 ALB and 1 target group; forwards /api/*, /shows/auctions, and /ws to backend:8080.",
"source": "infra/envs/dev/app/alb.tf:1,12,92,122"
},
{
"name": "ECS service: encore-dev-backend",
"kind": "AWS ECS Fargate service",
"owner": "infra/envs/dev/app Terraform",
"count_when_live": "1 service with desired_count from var.backend_desired_count; the activation precondition is written for count 1.",
"source": "infra/envs/dev/app/ecs.tf:105"
},
{
"name": "backend ECS task/container",
"kind": "Fargate task with one essential backend container",
"owner": "ECS service encore-dev-backend",
"count_when_live": "1 task and 1 container when desired_count=1; no per-auction ECS tasks are created.",
"source": "infra/envs/dev/app/ecs.tf:15"
},
{
"name": "Rust backend process and Axum router",
"kind": "HTTP/WebSocket server process",
"owner": "backend container",
"count_when_live": "1 process listening on 0.0.0.0:8080 with REST routes and /ws.",
"source": "backend/src/main.rs:107"
},
{
"name": "AppState",
"kind": "shared in-memory state",
"owner": "Rust backend process",
"count_when_live": "1 AppState per process; cloned into handlers and sockets.",
"source": "backend/src/main.rs:168, backend/src/state.rs:13"
},
{
"name": "Postgres durable truth",
"kind": "RDS/Postgres tables and SQL constraints",
"owner": "database layer",
"count_when_live": "1 database; for the requested live scenario it holds 2 show rows and 4 auction rows, but only active rows drive runtime workers.",
"source": "migrations/000010_shows.up.sql:4, migrations/000004_auctions.up.sql:6, migrations/000012_one_active_auction_per_show.up.sql:6"
},
{
"name": "ShowService supervisor",
"kind": "in-memory per-process supervisor",
"owner": "AppState",
"count_when_live": "1 supervisor per backend process, independent of show count.",
"source": "backend/src/state.rs:39, backend/src/show_service.rs:908"
},
{
"name": "Show room / ShowHandle",
"kind": "tokio broadcast sender plus distinct-user presence map",
"owner": "ShowService",
"count_when_live": "1 room for the live show after start_show; the scheduled show has 0 rooms unless watched. In the viewer scenario: 3 tab subscriptions, 2 distinct viewers.",
"source": "backend/src/show_service.rs:894, backend/src/show_service.rs:926, backend/src/show_service.rs:970"
},
{
"name": "AuctionRegistry",
"kind": "in-memory registry of live auction handles",
"owner": "ShowService",
"count_when_live": "1 registry; contains 1 handle while the single active auction actor is alive.",
"source": "backend/src/show_service.rs:913, backend/src/auction/auction_service/registry.rs:93"
},
{
"name": "Active auction actor",
"kind": "tokio task, one per running auction",
"owner": "AuctionRegistry",
"count_when_live": "1 actor for the active auction. The two pending auctions and one ended auction have 0 actors.",
"source": "backend/src/auction/auction_service/mod.rs:1, backend/src/auction/auction_service/registry.rs:106, backend/src/show_service.rs:470"
},
{
"name": "Auction command mailbox",
"kind": "bounded mpsc command channel with oneshot replies",
"owner": "AuctionHandle / auction actor",
"count_when_live": "1 mailbox per active actor; bid/start/close replies are ephemeral oneshot channels per command.",
"source": "backend/src/auction/auction_service/registry.rs:20, backend/src/auction/auction_service/messages.rs:33"
},
{
"name": "Auction expiry timer",
"kind": "tokio Sleep armed after activation",
"owner": "Active auction actor",
"count_when_live": "1 timer while the active auction is accepting bids.",
"source": "backend/src/auction/auction_service/actor.rs:69, backend/src/auction/auction_service/actor.rs:137"
},
{
"name": "PgBidEngine and PgAuctionStore",
"kind": "Postgres-backed bid and lifecycle adapters",
"owner": "AuctionDeps",
"count_when_live": "1 dependency set in AppState, cloned into the active actor; it performs bid, activation, and settlement SQL.",
"source": "backend/src/main.rs:139, backend/src/auction/auction_bid_engine.rs:81, backend/src/auction/auction_service/store.rs:88"
},
{
"name": "WebSocket connection loops",
"kind": "one async loop per upgraded browser tab",
"owner": "/ws route",
"count_when_live": "3 loops for the requested viewers: one viewer has 2 tabs and another has 1 tab.",
"source": "backend/src/routes/ws.rs:109, backend/src/routes/ws.rs:146"
},
{
"name": "ConnectionRegistry",
"kind": "in-memory user-to-WebSocket revocation registry",
"owner": "AppState",
"count_when_live": "1 registry with 3 live connection entries across 2 users in the requested viewer scenario.",
"source": "backend/src/state.rs:36, backend/src/auth/registry.rs:32, backend/src/auth/registry.rs:72"
},
{
"name": "show_rx broadcast receivers",
"kind": "tokio broadcast receivers held by socket loops",
"owner": "WebSocket connection loops",
"count_when_live": "3 receivers when all 3 tabs have sent watch_show for the live show.",
"source": "backend/src/routes/ws.rs:182, backend/src/routes/ws.rs:249, backend/src/routes/ws.rs:530"
},
{
"name": "Streaming deps / IVS provider",
"kind": "optional AWS IVS control-plane client",
"owner": "AppState",
"count_when_live": "0 when IVS_RECORDING_CONFIGURATION_ARN is unset; 1 StreamDeps and 1 IvsStreamProvider when configured.",
"source": "backend/src/main.rs:150, backend/src/config.rs:75, backend/src/stream_service.rs:96"
},
{
"name": "Streaming liveness poller",
"kind": "single tokio interval task",
"owner": "backend process",
"count_when_live": "0 without streaming config; 1 process-wide poller when StreamDeps exists, regardless of show count.",
"source": "backend/src/main.rs:180, backend/src/show_service.rs:1176"
},
{
"name": "show_streams row",
"kind": "durable per-show stream session row",
"owner": "Postgres",
"count_when_live": "0 in the non-streaming scenario; 1 active row for the live show when streaming is configured and DB flag streaming.enabled is true.",
"source": "migrations/000017_show_streams.up.sql:9, backend/src/show_service.rs:715, backend/src/stream_service.rs:387"
},
{
"name": "host_ivs_channels row / AWS IVS channel",
"kind": "durable host channel plus AWS IVS media-plane resource",
"owner": "Postgres and Amazon IVS",
"count_when_live": "0 in the non-streaming scenario; usually 1 persistent channel for the host when streaming has been enabled for that host.",
"source": "migrations/000018_host_ivs_channels.up.sql:13, backend/src/show_service.rs:686, backend/src/stream_service.rs:77"
}
],
"edges": [
{
"from": "Browser REST/WS clients",
"to": "ALB: encore-dev-backend-alb",
"trigger": "HTTP requests and WebSocket upgrade for /ws."
},
{
"from": "Browser REST/WS clients",
"to": "WebSocket connection loops",
"trigger": "Each opened viewer tab creates one backend /ws connection loop after ticket exchange."
},
{
"from": "ALB: encore-dev-backend-alb",
"to": "backend ECS task/container",
"trigger": "Target group forwards matching paths to container port 8080."
},
{
"from": "backend ECS task/container",
"to": "Rust backend process and Axum router",
"trigger": "Container starts the backend binary."
},
{
"from": "Rust backend process and Axum router",
"to": "AppState",
"trigger": "Router is built with shared state."
},
{
"from": "AppState",
"to": "ShowService supervisor",
"trigger": "State owns ShowService::new()."
},
{
"from": "AppState",
"to": "ConnectionRegistry",
"trigger": "State owns ConnectionRegistry::new()."
},
{
"from": "POST /api/v1/shows/{id}/start",
"to": "Show room / ShowHandle",
"trigger": "ShowService.start_show commits live status, then opens the room."
},
{
"from": "POST /api/v1/shows/{id}/auctions",
"to": "Postgres durable truth",
"trigger": "Create auction inserts item plus pending auction rows; no actor is spawned."
},
{
"from": "POST /api/v1/shows/{id}/auctions/{auction_id}/activate",
"to": "AuctionRegistry",
"trigger": "ShowService.activate_auction validates show/auction state and calls get_or_spawn."
},
{
"from": "AuctionRegistry",
"to": "Active auction actor",
"trigger": "get_or_spawn creates one actor task and handle for the auction_id."
},
{
"from": "Active auction actor",
"to": "Auction expiry timer",
"trigger": "Successful Start activates the DB row and arms Sleep until ends_at."
},
{
"from": "Active auction actor",
"to": "PgBidEngine and PgAuctionStore",
"trigger": "Start, bid, expiry, and close commands call the DB-backed adapters."
},
{
"from": "PgBidEngine and PgAuctionStore",
"to": "Postgres durable truth",
"trigger": "activation.sql, bid_lead_cte.sql, settlement.sql, and early-close settlement update rows atomically."
},
{
"from": "WebSocket connection loops",
"to": "ConnectionRegistry",
"trigger": "Each /ws connection consumes a ticket and registers one conn_id."
},
{
"from": "WebSocket connection loops",
"to": "show_rx broadcast receivers",
"trigger": "watch_show subscribes the socket to the show room."
},
{
"from": "show_rx broadcast receivers",
"to": "Show room / ShowHandle",
"trigger": "Each subscribed socket holds a broadcast receiver."
},
{
"from": "WebSocket connection loops",
"to": "Active auction actor",
"trigger": "bid messages are sent to the actor handle and answered on the same socket."
},
{
"from": "Active auction actor",
"to": "ShowService supervisor",
"trigger": "Accepted bids publish price events through publish_for_auction; close paths publish settlement events."
},
{
"from": "ShowService supervisor",
"to": "Show room / ShowHandle",
"trigger": "Show events are sent on the per-show broadcast channel."
},
{
"from": "Show room / ShowHandle",
"to": "WebSocket connection loops",
"trigger": "price, settled, stream.*, and viewer_count events fan out to all watching tabs."
},
{
"from": "Streaming deps / IVS provider",
"to": "host_ivs_channels row / AWS IVS channel",
"trigger": "start_show streaming path ensures or creates the host channel and fetches the stream key."
},
{
"from": "Streaming deps / IVS provider",
"to": "show_streams row",
"trigger": "start_show inserts a provisioning stream row when streaming.enabled is true."
},
{
"from": "Streaming liveness poller",
"to": "show_streams row",
"trigger": "Every 5s poll reads active stream rows and updates status with a monotonic CAS."
},
{
"from": "Streaming liveness poller",
"to": "host_ivs_channels row / AWS IVS channel",
"trigger": "Poller calls IVS GetStream; teardown calls StopStream."
},
{
"from": "show_streams row",
"to": "Show room / ShowHandle",
"trigger": "Liveness transitions publish stream.live or stream.degraded events."
}
],
"scenarios": {
"live_show": {
"assumptions": [
"The three viewer tabs are watching the live show.",
"The active auction was activated through the current REST path used by the studio UI, not by a legacy WS start_auction message.",
"Streaming is not configured or the DB streaming.enabled flag is false."
],
"running": {
"ecs": "1 ECS service, 1 Fargate task, 1 backend container, 1 Rust process, 1 Axum router.",
"app_state": "1 AppState, 1 ShowService, 1 AuctionRegistry, 1 ConnectionRegistry, 1 PgBidEngine/PgAuctionStore dependency set.",
"shows": "Live show: 1 in-memory ShowHandle room. Scheduled show: only a Postgres row, 0 room unless someone watches it.",
"auctions": "Active auction: 1 actor task, 1 mailbox, 1 expiry timer, 1 registry handle. Two pending auctions: Postgres rows only, 0 actors. One ended auction: Postgres row only, 0 actors.",
"viewers": "3 WebSocket socket loops, 3 ConnectionRegistry entries, 3 show_rx receivers, 2 distinct viewer IDs in the ShowHandle presence map.",
"host_notifications": "0 host-notifier channels on the REST activation path; REST activation passes notify=None.",
"durable_rows": "Postgres has 2 show rows and 4 auction rows for this scenario; the unique partial index allows only one active auction per show."
},
"not_running": [
"No per-show ECS task.",
"No per-auction ECS task or process.",
"No actor for pending auctions.",
"No actor for ended auctions.",
"No streaming liveness poller or IVS runtime path in the baseline non-streaming case."
]
},
"streaming_on": {
"assumptions": [
"IVS_RECORDING_CONFIGURATION_ARN is set in the backend environment.",
"The Postgres app_settings row streaming.enabled is true.",
"The same live show was started through ShowService.start_show with streaming enabled."
],
"running": {
"baseline": "Everything from live_show is still running.",
"stream_deps": "1 StreamDeps and 1 IvsStreamProvider in AppState.",
"liveness": "1 process-wide liveness poller task, not one per show.",
"stream_session": "1 active show_streams row for the live show. It starts as provisioning and later becomes live or degraded based on IVS GetStream.",
"host_channel": "1 durable host_ivs_channels row and 1 persistent AWS IVS channel for the host, created lazily if the host did not already have one.",
"media": "The backend never carries media bytes. If the host is broadcasting to IVS, IVS has 1 live stream on that channel; if the host has not connected OBS, the backend still only has the provisioning/degraded row plus poller.",
"credentials": "The stream key is fetched from IVS on demand and returned to the host; it is not stored in Postgres."
},
"not_running": [
"No SQS or EventBridge stream consumer.",
"No per-show stream worker task.",
"No extra auction actor for pending or ended auctions."
]
},
"after_restart": {
"assumptions": [
"The process restarts while the DB still says one show is live and one auction is active.",
"No browser tab has reconnected yet.",
"This describes the immediate post-start state before any request recreates in-memory rooms."
],
"running": {
"ecs": "1 replacement backend process in the same single-task ECS service.",
"app_state": "A fresh AppState exists with a fresh ShowService, fresh empty AuctionRegistry, fresh empty BroadcastState, fresh ConnectionRegistry, and fresh in-memory ticket store.",
"shows": "The live and scheduled shows still exist in Postgres, but there are 0 in-memory ShowHandle rooms until start_show retry or watch_show opens/subscribes one.",
"auctions": "The active, pending, and ended auctions still exist in Postgres. There are 0 auction actor tasks, 0 auction mailboxes, 0 auction expiry timers, and 0 registry handles because the code does not rehydrate actors on boot.",
"viewers": "0 WebSocket loops, 0 ConnectionRegistry entries, 0 show_rx receivers before reconnect.",
"auction_effect": "The active auction will not expire by an in-process timer until an actor exists. REST close_auction can settle directly with PgAuctionStore when no actor is found; WS bids fail with auction_unavailable because auction_handle returns None.",
"streaming_effect": "If streaming is configured, the single liveness poller starts at boot and scans active show_streams rows, but it does not recreate auction actors or show rooms."
},
"not_running": [
"No previous WebSocket connection loops survive restart.",
"No previous broadcast receivers survive restart.",
"No auction actor or expiry timer is recreated from the active auction row."
]
}
}
}
Scenario Short Answers
{
"live_show": {
"assumptions": [
"The three viewer tabs are watching the live show.",
"The active auction was activated through the current REST path used by the studio UI, not by a legacy WS start_auction message.",
"Streaming is not configured or the DB streaming.enabled flag is false."
],
"running": {
"ecs": "1 ECS service, 1 Fargate task, 1 backend container, 1 Rust process, 1 Axum router.",
"app_state": "1 AppState, 1 ShowService, 1 AuctionRegistry, 1 ConnectionRegistry, 1 PgBidEngine/PgAuctionStore dependency set.",
"shows": "Live show: 1 in-memory ShowHandle room. Scheduled show: only a Postgres row, 0 room unless someone watches it.",
"auctions": "Active auction: 1 actor task, 1 mailbox, 1 expiry timer, 1 registry handle. Two pending auctions: Postgres rows only, 0 actors. One ended auction: Postgres row only, 0 actors.",
"viewers": "3 WebSocket socket loops, 3 ConnectionRegistry entries, 3 show_rx receivers, 2 distinct viewer IDs in the ShowHandle presence map.",
"host_notifications": "0 host-notifier channels on the REST activation path; REST activation passes notify=None.",
"durable_rows": "Postgres has 2 show rows and 4 auction rows for this scenario; the unique partial index allows only one active auction per show."
},
"not_running": [
"No per-show ECS task.",
"No per-auction ECS task or process.",
"No actor for pending auctions.",
"No actor for ended auctions.",
"No streaming liveness poller or IVS runtime path in the baseline non-streaming case."
]
},
"streaming_on": {
"assumptions": [
"IVS_RECORDING_CONFIGURATION_ARN is set in the backend environment.",
"The Postgres app_settings row streaming.enabled is true.",
"The same live show was started through ShowService.start_show with streaming enabled."
],
"running": {
"baseline": "Everything from live_show is still running.",
"stream_deps": "1 StreamDeps and 1 IvsStreamProvider in AppState.",
"liveness": "1 process-wide liveness poller task, not one per show.",
"stream_session": "1 active show_streams row for the live show. It starts as provisioning and later becomes live or degraded based on IVS GetStream.",
"host_channel": "1 durable host_ivs_channels row and 1 persistent AWS IVS channel for the host, created lazily if the host did not already have one.",
"media": "The backend never carries media bytes. If the host is broadcasting to IVS, IVS has 1 live stream on that channel; if the host has not connected OBS, the backend still only has the provisioning/degraded row plus poller.",
"credentials": "The stream key is fetched from IVS on demand and returned to the host; it is not stored in Postgres."
},
"not_running": [
"No SQS or EventBridge stream consumer.",
"No per-show stream worker task.",
"No extra auction actor for pending or ended auctions."
]
},
"after_restart": {
"assumptions": [
"The process restarts while the DB still says one show is live and one auction is active.",
"No browser tab has reconnected yet.",
"This describes the immediate post-start state before any request recreates in-memory rooms."
],
"running": {
"ecs": "1 replacement backend process in the same single-task ECS service.",
"app_state": "A fresh AppState exists with a fresh ShowService, fresh empty AuctionRegistry, fresh empty BroadcastState, fresh ConnectionRegistry, and fresh in-memory ticket store.",
"shows": "The live and scheduled shows still exist in Postgres, but there are 0 in-memory ShowHandle rooms until start_show retry or watch_show opens/subscribes one.",
"auctions": "The active, pending, and ended auctions still exist in Postgres. There are 0 auction actor tasks, 0 auction mailboxes, 0 auction expiry timers, and 0 registry handles because the code does not rehydrate actors on boot.",
"viewers": "0 WebSocket loops, 0 ConnectionRegistry entries, 0 show_rx receivers before reconnect.",
"auction_effect": "The active auction will not expire by an in-process timer until an actor exists. REST close_auction can settle directly with PgAuctionStore when no actor is found; WS bids fail with auction_unavailable because auction_handle returns None.",
"streaming_effect": "If streaming is configured, the single liveness poller starts at boot and scans active show_streams rows, but it does not recreate auction actors or show rooms."
},
"not_running": [
"No previous WebSocket connection loops survive restart.",
"No previous broadcast receivers survive restart.",
"No auction actor or expiry timer is recreated from the active auction row."
]
}
}