Rullst Studio: local development control room
Rullst Studio is a developer-facing Axum dashboard. It can run as a standalone
server bound to 127.0.0.1 (port 5555 by default) or its router can be mounted
explicitly by an application.
Studio’s built-in boundary is deliberately limited to debug builds and verified loopback peers. It is not a shared-environment authentication system; do not expose raw subrouters publicly without application-level authentication, authorization, TLS, and network policy.
Projects generated from the Blank, API and SaaS starters (and the LMS, ERP,
Portfolio and Blog examples in examples/) start the standalone server only in
debug builds and link to http://127.0.0.1:5555.
Release builds do not start it; runtime RULLST_ENV or legacy APP_ENV values
cannot override that compile-time boundary.
To watch a deployed application, mount the read-only
production monitoring endpoint instead (new in
v13): it reports request rates, latency, errors, queues and N+1
findings behind a token or the application’s own sign-in. The standalone
rullst-studio binary
(cargo install rullst-studio --version =13.0.0-alpha.4 --features remote --locked,
then rullst-studio or cargo rullst studio --remote) reads it for several projects from your
machine, with a project switcher and encrypted local tokens; see
Watch your projects from Studio.
That remote mode has none of the local pages below.
Running Studio
In a project generated from a starter, run the application in a debug build
(cargo rullst dev) and open http://127.0.0.1:5555/studio: its main
starts the full Studio next to the server. Studio stops with the application.
cargo rullst studio is different: it runs the application with the studio
argument (cargo run -- studio), and Core’s Artisan handler then serves a
smaller local compatibility UI (database, AI, telemetry, security, capital and
span pages) on 127.0.0.1:5555 until it stops; it is not the rullst-studio
router described below. Both use port 5555, so stop cargo rullst dev first.
cargo rullst studio
In your own main, the rullst-studio library entry point starts the full
Studio:
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
rullst_studio::run_studio(5555_u16).await
}
Studio uses the process-wide ORM pool that the application initialized
(Server, Artisan or an explicit Orm::init). If none exists when a database
view is requested, Studio initializes it once from the current directory with
the resolver shared by Server and Artisan: the process DATABASE_URL, then
DATABASE_URL from ./.env (never overriding the process environment), then
[database].url parsed from Rullst.toml. Without a configured database the
views report that database tools are unavailable and nothing is created; the
former sqlite://db.sqlite fallback is gone, and errors never echo
configuration content. Applications that call Orm::init with their own URL
should do so before Studio serves requests.
Studio::new().into_router(LocalStudioAccess::loopback_only()) builds the same
debug-only router for explicit composition. The serving stack must preserve
Axum ConnectInfo<SocketAddr> or requests fail closed. An IPv4-mapped peer
(::ffff:127.0.0.1, reported by a dual-stack :: listener) is checked as the
IPv4 address it carries. Unsafe methods need a
same-origin Origin header. Studio responses carry
Referrer-Policy: same-origin, so browsers send the page’s real origin on
Studio’s own form posts and no referrer to other origins. If a host layer
replaces that policy with no-referrer, browsers send Origin: null; Studio
then accepts the request only with Sec-Fetch-Site: same-origin, which page
scripts cannot set. A bare Origin: null, or one from a same-site document
such as another local port, is rejected. Optional OpenAPI and
queue views are enabled with with_openapi and with_horizon.
with_cache opts a supported cache into metadata-only local inspection, while
with_distributed_traces supplies the bounded store shared with a separately
mounted push-only ingestion router.
Current views
/studio: data browser and dashboard shell./studio/radar: runtime probes and recorded spans. Process CPU sampling is implemented for Linux and Windows and the KPI cards refresh from/api/radarevery two seconds. The first delta-based CPU sample, unsupported platforms, and disconnected probes displayUnavailable; Studio does not synthesize a healthy value./studio/security: counters and events emitted by the in-process security store. Audit-chain integrity displaysUnavailableuntil a verifier is connected./studio/capital: the in-process revenue view; it is not an accounting ledger. Its table lists the 20 most recent webhook events and its badge counts every event the process-local manager retains (at most 100)./studio/traces: local spans plus authenticated attribute-free distributed records and explicit slow/repeated SQL-label heuristics./studio/cache: metadata-only view of an explicitly supplied Memory/Redis cache, using opaque identifiers and one-entry invalidation./studio/migrations,/studio/ai,/studio/env,/studio/features, and/studio/er: development tools for their corresponding subsystems./studio/requests: the request log, streamed over SSE from/studio/requests/stream./studio/jobsand/studio/api: the queue view and the API reference, mounted only whenwith_horizonorwith_openapisupplies their source./studio/apirenders the supplied document’s operations, parameters, request bodies and responses on the server (no script or remote asset); the document itself is served at/studio/api/openapi.json, and/studio/api/redirects to the page.
The radar page polls the /api/radar JSON endpoint; /api/revenue and
/studio/security/stats also return JSON, and /metrics serves the Prometheus
text format. The request logger uses SSE. The current crate does not promise a
separate WebSocket telemetry transport or zero runtime overhead.
Tooling boundaries
- The data browser reads, searches, and paginates allowlisted SQLx identifiers.
A page shows at most 25 rows, ordered by the complete primary key (otherwise
by every selected column); the database cuts each cell’s text to 256
characters before it reaches Studio (key columns keep up to 16 KiB for row
actions, and a longer key makes its row read-only), and search terms are
limited to 256 bytes. Search matches the displayed columns (at most 256),
and the record count uses the same predicate. A SQL NULL shows an italic
NULLmarker; a present value Studio cannot decode as text, a number or a Boolean (for example a BLOB that is not UTF-8) shows an amberunreadablemarker instead, and a stored textNULLis shown as an ordinary value. Inside the verified debug-loopback/same-origin boundary, it may edit one primitive non-key value or delete one complete-primary-key-selected row. Inputs are bounded and parameterized; exact deletion confirmation is required and backend-specific types, including MySQL/MariaDB unsigned or zero-filled integers, remain read-only. A row offers only the actions whose form (for Edit, with a maximum-size value) fits the 64 KiB request limit, and an empty text key is addressable. A table is read-only when a primary-key column falls outside the ASCII identifier boundary or the 256-column cap or uses a floating-point type (its rendered text is rounded), and rows whose key isNULL, is not decodable as text or contains a line break or NUL offer no actions. Each write runs in a transaction that commits only when exactly one row changed; any other count is rolled back and fails (non-transactional engines such as MySQL MyISAM cannot roll back). SQLite, PostgreSQL, MySQL, and MariaDB run separate executable contracts. With the defaultsqlx::Anybuild, Studio renumbers bind markers to$nfor PostgreSQL and reads information-schema identifiers asVARCHAR, so the table view, search, row actions and ER diagram also work there; the PostgreSQL contract runs under that build and understrict-postgres. PostgreSQL browsing covers thepublicschema, and every data statement names that schema so thatsearch_pathcannot select a same-named table elsewhere. This does not supply application tenant/RBAC, audit history, undo, or a shared-production database administrator. - The API reference appears only when the application supplies its
OpenApidocument withStudio::with_openapi; Studio does not reverse-engineer arbitrary Axum routes. It is read-only: it shows at most 500 operations and 512 characters of each text field, and it does not send requests to the application (use the JSON document with an external client for that). - The request SSE records method, URI, status and latency. It deliberately does not capture bodies or headers, which commonly contain credentials and PII.
- The jobs view lists the 50 most recent records exposed by a supplied queue;
its processing/failed/completed counts describe only that window, while the
pending count and the purge actions cover the whole queue. Studio previews at
most 256 payload and 512 error characters. It reads records through
Queue::list_job_previews(new in v13), which cuts each payload and error to 2 KiB: the SQLite and Redis drivers cut them inside the store, while other drivers inherit a default that still loads complete records first. SQLite deletes successful rows by default; an application can explicitly selectQueue::sqlite_with_completed_historyfor bounded, transactionally pruned completion history and can purge that history from Studio. Retained payloads require host-controlled access and retention policy. Other drivers expose only the inspection/history contract they implement. Retry and purge require the verified local marker, so the rawjobs_monitor::routerreturns403for them when mounted outsideStudio::into_router. - The ER view inspects SQLite, PostgreSQL, MySQL, or MariaDB metadata with bound lookup values and normalizes Mermaid identifiers. Each column is listed once, and composite foreign keys pair their columns by key position. An unconfigured or unsupported source remains visibly unavailable.
- The feature-flags page changes the database table used by
DbFeatureDriver. Toggles require the verified local marker; the rawfeature_flags::routerreturns403for them. Viewing the page never changes the schema: a missingrullst_feature_flagstable is reported with the schema to add in an application migration, while connection, permission or column errors are reported as query failures without that guidance. A successful toggle invalidates already-warm drivers in the same process; other processes and direct writers converge by TTL unless the host distributes an invalidation signal. - The environment page redacts values by default, also redacts an allowlisted
value that carries URL user information, a bearer token or a secret-named
assignment (such as
?access_token=), and adds only a safe projection of process-globalRullstConfig; URLs, filesystem paths, secrets, cookies and credentials are omitted. - Cache inspection returns at most 100 UI rows containing an opaque keyed identifier, value byte length and remaining TTL. Values and exact logical keys are never rendered, bulk flush is absent, and individual invalidation requires the verified local mutation marker. Custom cache drivers remain unavailable until they implement Core’s bounded metadata contract.
Telemetry contract
Studio reads runtime state exposed by RadarSnapshot, SpanCollector, the
security store, queues, and configured database connections. A counter means only
that the corresponding instrumentation path emitted it; it is not proof that all
traffic passed through that control. Missing sources must remain visibly
unavailable.
Remote producers do not connect to the viewer. The application separately
mounts TraceIngestor::router, distributes a 32–128-byte
TraceIngestionKey, and uses TraceBatchSigner for the exact body and four
headers. Each ingestor binds one exact producer name to one key; multiple
producers use separate endpoints/keys over the same store. The route accepts
1–128 closed v1 spans under 128 KiB, verifies
HMAC-SHA256 plus a 60-second timestamp and one-time nonce, then commits to a
bounded process-local store idempotently. It contains no Studio read or admin
route. TLS/network policy, key custody and rotation, producer authorization,
clock synchronization, label redaction, durable storage and OTLP integration
remain deployment work.
Production boundary
Studio is an optional crate. Its supported run_studio and Studio::into_router
paths reject credential-free use in release builds, but consumers should still
exclude it from production features unless they are implementing and testing a
separate authenticated administrator boundary. Built-in shared production
access remains roadmap work, not a password environment-variable promise.
v12.1.0 browser composition fix
This section is for applications still on 12.0.x; 12.1.0 and every 13.0 prerelease include the fix.
The Portfolio issue report
identified two omissions in the published 12.0.0 raw data-browser router:
its layout requested /studio/assets/studio.css, but only the full local
Studio builder mounted the assets, and its Cache link had no handler.
Applications nesting data_browser::router() under /studio therefore saw
an unstyled page and a Cache 404. The stock Portfolio generator starts a
separate debug-only Studio; the Azure showcase added its own embedded router.
The 12.1.0 fix serves the embedded CSS/client and an honest, unconnected Cache
page through both the raw and nested browser compositions. It does not enable
public Studio administration, connect an application cache automatically, or
weaken the local access checks. The full builder still requires
LocalStudioAccess and an explicitly supplied Studio::with_cache for real
cache inspection. The browser’s unconnected page displays Unavailable, not
zero entries or a fabricated hit rate, and exposes no cache mutation endpoint.
Migrating an existing showcase
- Update the application’s Rullst dependencies and lockfile together to 12.1.0 or later. Installing a newer CLI does not rewrite previously generated application files or redeploy containers.
- Remove the temporary
.route(...)registrations for/assets/studio.css,/studio/assets/studio.css,/assets/logger.js,/studio/assets/logger.js,/cache, and/studio/cacheadded directly todata_browser::router(). They would conflict with the framework’s new routes. Remove the now-unused copied assets and temporary handlers only after checking their other uses. - Preserve the application’s authentication/administrator-authorization layer, trusted TLS boundary, and network restrictions around the entire router. The routing correction does not validate that custom production policy.
- Rebuild and verify that an authorized
/studiopage loads its same-origin stylesheet with HTTP 200 andtext/css, and that/studio/cachereturns a styled Unavailable page unless a supported cache is explicitly wired. Unauthorized requests must still be denied. Check full visits and any application-managed HTMX partial requests, then redeploy deliberately.
The showcase’s temporary Cache page contains hard-coded counters (including
100.0% hit rate); those are not framework telemetry. Remove that workaround
instead of treating its numbers as production measurements. Existing projects
do not gain this source correction solely from a dependency update.