Skip to content

Services

The Services view lists every Wolverine service that has registered with CritterWatch. From here you drill into a single service's detail page, which is the operational hub for one logical Wolverine application — its nodes, its agents, its message handlers, its endpoints, its tenants, its DDL, and the rest.

CritterWatch Services view — a card grid of every registered Wolverine service with health, version, node count, throughput, DLQ depth, and capability badges

Services List

The list page renders both as a card grid (default) and as a sortable table. Use the toggle in the toolbar to switch.

Each card / row shows:

FieldMeaning
NameWolverine ServiceName — unique within the cluster
LabelHuman-readable display name; defaults to the service name and is editable on the service's Overview tab
VersionWolverine version reported on the most recent VersionDetected snapshot
StatusAggregate health roll-up — Healthy / Warning / Critical / Unknown
NodesActive process instances heartbeating in the cluster
Msgs/hrRecent throughput across all handlers and endpoints
DLQUnresolved dead letters in this service's stores
AlertsOpen alerts (raised + elevated) attributed to this service

Clicking a service opens its detail page. The detail page binds the active tab to the URL via ?tab=<name> so deep links and back-button navigation always land on the right pane.

Service Detail

The detail page is organised into five always-on tabs and three conditional tabs. The conditional tabs are only rendered when the service surfaces the relevant capability.

Overview · Event Store · Durability · Cluster · Health & Metrics · [HTTP] · [Tenants] · [Sagas]
  • HTTP appears when the service exposes an ASP.NET / gRPC surface.
  • Tenants appears for any multi-tenancy mode.
  • Sagas appears when the service hosts at least one saga.

Where did the old tabs go? The per-service Messaging and Documents tabs were retired — messaging now lives in the fleet-wide Messaging Explorer and documents in the Document Store Explorer, both reachable from the Overview capability cards (below). The old Storage tab was renamed Event Store, its message-store table moved to Durability, and brokers moved to the Overview Brokers card. The standalone Metrics tab merged into Health & Metrics (alerts first, then the metrics table).

URL backward compatibility

The reorganisations keep old URLs working so saved bookmarks don't 404:

Old ?tab=Lands on
?tab=messages / ?tab=messagingOverview (the Messaging tab is retired)
?tab=documentsOverview (the Documents tab is retired)
?tab=nodesCluster tab, Nodes view
?tab=agentsCluster tab, Agents view

If a service has any critical alerts and no explicit ?tab= is set, the detail page opens on Health & Metrics (whose first section is Alerts) so the operator sees the active incident first.


Overview Tab

The Overview pane is the at-a-glance identity card for the service. It is the only tab that always opens, and it is the home for editable service-level configuration.

Mini metrics dashboard

Five compact cards across the top:

CardSource
Msgs/hrTotal messages handled per hour (sent + received)
Avg Exec TimeAverage handler execution time, milliseconds (excludes middleware)
Avg Effective TimeAverage end-to-end processing time including middleware, serialization, and transport
DLQ/hrMessages moved to the dead-letter queue per hour
Exceptions/hrHandler exceptions per hour (includes retried + dead-lettered)

Identity

A two-column descriptions table with:

  • Service ID — Wolverine ServiceName

  • Label — editable inline via the pencil-affordance EditableLabelCell. Click the label, type a new value, press Return or click away to commit. The change emits UpdateLabelName and is reflected immediately across every page that shows this service.

  • Version — Wolverine version

  • Communication URI — how CritterWatch talks back to the service (typically wolverine.signalr:// for the BFF transport)

  • TenancyNone, Single, ConjoinedTenancy, DefaultTenant, or ExtendedDynamic. The Tenants tab is shown for any tenancy mode beyond Single / None; the per-row actions (Add / Disable / Enable / Remove / Hard delete) are only enabled when at least one event-store is DynamicMultiple.

  • Tenant ID Style — surfaces Marten's StoreOptions.TenantIdStyle when the service has any tenancy. One of:

    • CaseSensitive — tenant IDs stored exactly as supplied; case must match at session-open time.
    • ForceLowerCase — tenant IDs silently lowercased on every reference.
    • ForceUpperCase — tenant IDs silently uppercased on every reference.

    When multiple document stores disagree the tag shows the first value with a (mixed) suffix and a warning tooltip. Hidden when the service has no tenancy.

Health checks

A card on the Overview tab confirms whether this service is wired up to ASP.NET Core health checks. Two states:

  • Registered (green) — the service is exposing its Wolverine state to anything that asks via the standard /healthz endpoint, with the registered check names and tags listed as chips. This is what Kubernetes liveness and readiness probes consume.
  • Not registered (amber) — the service has no Wolverine-aware health check registered. Without one, k8s and load balancers can't tell whether the runtime is up or jammed.

The card is hidden entirely when the monitored service is running an older Wolverine.CritterWatch build that doesn't ship the registration descriptor.

For the why and how, see Health Checks. The "Operator docs" link in the card goes there directly.

Trace provider binding

A card on the Overview tab lets the operator pick which configured OTel store CritterWatch queries when looking up traces for this service's saga instances and handler runs. The dropdown lists every provider configured globally (Settings → Trace providers); leaving it empty falls back to the default. The "Manage providers" link opens Settings.

Capabilities at a glance

Below the identity table, a Capabilities panel renders one card per capability this service exposes — the launch pad from the service into the fleet-wide explorers, scoped to this service:

  • HTTP / gRPC → the HTTP / gRPC explorer (?service=)
  • Event store → the Event Store Explorer
  • Documents → the Document Store Explorer (/documents?service=) — this is where the retired per-service Documents tab now lives
  • Messaging → the Messaging Explorer (/messaging?service=) — the retired Messaging tab's replacement
  • Brokers → the Messaging Explorer's Brokers view (/messaging?service=&tab=brokers) — where the old Storage-tab Brokers section moved

On-page capabilities (EF Core, Durability, Cluster, Tenants, Sagas, Health & Metrics) switch the active tab instead of navigating away.

Evicting a service

The page header carries a danger-styled Evict button. Use it to remove a service that has been decommissioned (or is stuck registered) from CritterWatch — it clears the service's summary, alerts, metrics, and timeline and drops you back to the Dashboard. Eviction is "clear until it speaks again": if the service is in fact still alive, it re-registers on its next heartbeat, so this is safe to use on a service you think is gone. The button confirms via dialog and is disabled (with a tooltip) without an operations license.


Event Store Tab

The Event Store tab (its URL is still ?tab=storage) is the service-scoped event-sourcing summary. It holds the Event Model swim-lane (see Event Modeling) with an "Open in Event Store Explorer →" jump, the Event Stores cards, and the DbContexts section. Heavy stream/projection browsing lives in the global Event Store Explorer; document stores have their own Document Store Explorer.

Event Stores

Per-store cards listing registered projections and DDL. See DdlBlock for the syntax-highlighted DDL panel.

Moved (#641): the Message Stores persistence-count table and the Wolverine Configuration snapshot now live on the Durability tab (below). The standalone per-service Document Stores section was retired — reach document mappings through the Documents capability card → Document Store Explorer. Brokers moved to the Overview Brokers capability card → Messaging Explorer.

DbContexts

One card per EF Core DbContext registered in the service. Picked up automatically: contexts wired through AddDbContextWithWolverineIntegration get the full Wolverine-integrated picture, and any plain AddDbContext<>() registration is detected by the implicit-discovery hook in UseEntityFrameworkCoreTransactions, so untracked contexts still show up — just with the integration badges off.

Each card's title row carries a strip of badges. Read them in order:

  • Wolverine — green when the model has been mapped through MapWolverineEnvelopeStorage. This is the single most important badge to triage missing-publish issues. When green, your domain events publish atomically with SaveChangesAsync because the envelope tables share the context's connection. When grey ("Plain"), Wolverine isn't driving this context's transaction at all — any messaging happens on a separate connection, which is correct but won't give you transactional outbox guarantees.
  • TenancySingle for a single-database context (info color), ConnectionString or DbDataSource for multi-tenant (warning color). Multi-tenant cards expand to show the per-tenant database list (server + database + tenantId only — never the raw connection string).
  • TxEager (green) when transactional middleware opens an explicit database transaction up front; Lightweight (amber) when atomicity is delegated to SaveChangesAsync. Absent when Wolverine isn't wrapping this handler in transactional middleware.
  • Domain eventsOutgoingDomainEvents (the per-handler OutgoingDomainEvents collection is the source) or PerEntityType (one or more entity-level domain-event scrapers are registered). When PerEntityType, expand the card to see exactly which entity publishes which event type.
  • OutboxMapped (green-path) means the envelope tables share this context's connection. ExternalConnection means Wolverine's outbox factory is registered but the envelope tables live on a different connection — correct, but slower under load. Absent for plain contexts.
  • Wolverine-managed — present when the application is using UseEntityFrameworkCoreWolverineManagedMigrations. Wolverine's resource-startup pipeline will create or migrate this context's database on its own. Without the badge, schema management is your responsibility (dotnet ef database update or equivalent).

Below the badges, the Pending migrations field starts at "—" until you click Check pending migrations. The action is on-demand — clicking it runs IMigrator.GetPendingMigrationsAsync on the target service, opening a connection to the database and reading the __EFMigrationsHistory table. That's a synchronous round-trip per click, which is why we don't bake it into the regular capabilities snapshot. Inline error text appears under the field if the probe couldn't reach the database (transient connection failure, missing permissions, or a provider that doesn't support migrations).

Cards expand to a per-entity table — table name, primary-key columns, index count, foreign-key count, plus chips for sagas, owned types, and view-mapped entities. Saga rows cross-link to the Sagas tab so you can jump straight from "what does this context manage" to "what does this saga handle". Drill into an entity row for its full index and foreign-key detail.

Brokers

Moved (#641). Brokers are no longer on the service detail page. Reach them from the Overview Brokers capability card, which deep-links to the Messaging Explorer → Brokers view (/messaging?service=&tab=brokers). The broker-health columns described below now render there, with the same meaning.

Configured messaging brokers (RabbitMQ, Kafka, Azure Service Bus, etc.) appear with each broker's declared queue / topic surface area.

Broker health columns

Each broker row carries a small health strip that surfaces the connection's actual condition rather than the configuration alone.

  • Status — a traffic-light dot. Green when the connection is steady, orange when it has recently flapped (lost-and-recovered inside the last few minutes), red when it is currently down. Hover the dot for the probe's last description — typically the close-reason for an unhealthy connection or the recovery timestamp for a degraded one.
  • Last successful — relative time since the most recent good probe (2m ago, 1h ago). Drift on this column is the leading indicator that something started failing in the background; if the row says "Healthy" but Last successful is climbing past the probe cadence (60 s by default), the broker is flapping faster than the probe can resolve it.
  • Reconnects — running counter of times this transport has lost and re-established the broker connection since the host started. A single bump is harmless transient weather; sustained climbing is the signal something is actually broken — typically a misbehaving network path, an undersized broker, or expired credentials retrying. The counter renders bold-amber once it leaves zero.
  • Cert expiry — the TLS certificate's expiry date when TLS is configured. The chip switches to amber within 14 days of expiry; the action is to rotate the cert before the broker starts refusing new connections.

A status of Unknown (grey dot, "—" for the timestamp) means the transport hasn't connected yet. That is normal during cold starts — the first probe pass runs ~60 s after the host comes up. If a row stays Unknown past the warm-up window, the transport is misconfigured or disabled; check the host's startup logs.

The same data is surfaced on the per-broker detail page (click the broker name) with extra room for the probe description and the reporting node number — useful when multiple nodes probe the same broker and you need to spot which one is seeing the issue.


Durability Tab

Live operator surface for the inbox/outbox tables backing this service. The tab was renamed from ReliabilityDurability (#641) and is now the home of the Message Stores table and the Wolverine Configuration snapshot that used to sit on the old Storage tab.

The Message Stores table carries a row per store with live persistence counts:

ColumnMeaning
IconPostgres / SqlServer / RabbitMQ / etc.
URIDatabase / store URI — click to drill in (see below)
RoleMain (durability tables) or Ancillary (DLQ-only or per-tenant overflow)
IncomingPersisted incoming envelopes (inbox)
OutgoingPersisted outgoing envelopes (outbox)
ScheduledPersisted scheduled-send envelopes awaiting their delivery time
HandledSuccessfully handled envelopes retained for idempotency (KeepAfterMessageHandling)
Dead LetterDLQ count for this store. Click a non-zero count to drill into Dead Letters, pre-filtered to this store + service.

The URI cell drills into a dedicated Per-message-store detail page at /service/:id/message-store/:storeUri — recent inbox/outbox samples, DLQ counts, scheduled-job queue, and the DDL for that store's durability tables.

The Wolverine Configuration snapshot below the table is reference data captured on each VersionDetected (once per deploy): Service Settings, Durability — Operational, Durability — Polling & Cadence, and Durability — Retention. Each field carries a tooltip pulled from the upstream Wolverine XML docs.

Use this tab when:

  • A persistence count looks wrong and you need a per-store drill-in
  • You want to see the actual DDL Wolverine has installed
  • You need the per-store DLQ count without leaving the service page

The row backgrounds shade gold when an ?focus=<storeUri> query parameter is present — used by deep links from the alerts feed when a DLQ-rate alert fires.


Cluster Tab

The Cluster tab collapses the node-instance and agent-assignment lenses into a single tab with a segmented view-mode toggle. Both views are full operational surfaces — switching used to lose scroll, filter, and selection state, so they now sit side-by-side under one parent.

Cluster view toggle

A radio group at the top of the tab switches between:

  • Nodes — process-instance lens
  • Agents — agent-assignment lens

The active view is URL-bound via ?view=nodes|agents so deep links and operator bookmarks survive remounts.

Nodes view

Cluster-wide actions

ActionEffect
Trigger ElectionForce a new leader election. Disabled while OperationsEnabled is false. Confirmation popconfirm.
Clear Node History (overflow menu)Trim the node-history table; retains the 10 most recent records

The destructive Trigger Election is the primary action; Clear Node History is demoted to an overflow kebab so an operator can't fat-finger it next to the election button.

Node view-mode toggle

A second toggle inside the Nodes view switches between:

  • Cards — one el-card per node with a Leader pill, alert badge, last health check, version, agents-assigned count, and Eject button
  • List — dense table with the same surface area for big clusters

Heuristic auto-flips to List at 6+ nodes; an explicit operator override is URL-bound via ?nodeView=cards|list for share-and-restore.

Per-node columns / fields

FieldMeaning
Heartbeat dotLiveness pill — green / amber / red / grey. See Heartbeat dot below
Node #Wolverine node number; clickable — drills into the per-node detail page
RoleLeader pill if this node holds the leadership lease
AlertsPer-node active-alert badge — click to focus the alerts list filtered to that node
VersionWolverine version on this node
UptimeTime since started; tooltip shows the wall-clock start time
Last health checkRelative-time short-form; tooltip shows the absolute timestamp
AgentsCount of agents currently assigned to this node
Actions → EjectEjectNode(nodeNumber) — danger popconfirm, redistributes assigned agents

Heartbeat dot

Each running service sends a quick liveness ping to CritterWatch every 30 seconds. The dot beside every node number turns the dot through four states based on the gap since the last ping:

ColourStateMeaningWhat to do
GreenLiveHeartbeat received within the last 60 s.Nothing — the node is healthy.
AmberStaleAt least one heartbeat missed (60–150 s old).Watch for a few more seconds — a single missed beat usually means a slow GC or a brief network blip.
RedSilentFive or more heartbeats missed (older than 150 s, or never received).Open the per-node detail page. The node has likely hung, lost its broker connection, or been killed without graceful shutdown. Check that the service process is up, the broker is reachable, and the network between them isn't partitioned.
GreyUnknownThe node has only just started and no heartbeat has arrived yet, or the service hasn't been integrated with heartbeats.Usually fine — older deployments that haven't been rebuilt against a heartbeat-aware Wolverine still show grey. If the node has been around for several minutes and stays grey, the service may need to be redeployed against the current Wolverine.CritterWatch package.

Hovering the dot reveals the exact Last heartbeat: <timestamp> line plus a one-sentence description of the colour, so operators don't have to remember the rules.

The thresholds are sized against the default 30 s cadence (two missed beats turns the dot amber; five missed beats turns it red). Services that ship a slower or faster cadence — supplied via the heartbeatInterval parameter on AddCritterWatchMonitoring — still use the same wall-clock thresholds today; we'll revisit per-service scaling when a real deployment asks for it.

Agents view

Agent-assignment lens — every registered agent (projection shards, subscriptions, leadership, durability, scheduled-message agents, etc.) with its current node and status:

ColumnMeaning
Agent URIUnique identifier (e.g. wolverine://leader/, marten://projection/TripSummary:All)
StatusHealthy / Degraded / Offline
Assigned NodeWhich node currently owns this agent
Last SeenTime of most recent health report

Per-row actions:

ActionEffect
PinPinAgentToNode — keep this agent on a chosen node across rebalances
UnpinUnpinAgent — drop the assignment override

If a deep link supplies ?scheme=<scheme> (e.g. from the Topology page), the agent table is pre-filtered to that scheme regardless of which view the URL landed on.


Health & Metrics Tab

The Health & Metrics tab (URL ?tab=metrics) carries the service's alert and metric story, and its label shows a critical/warning badge when alerts are active. Two sections:

  1. Alerts (first) — the active-alert timeline for this service with remediation actions, plus a focus chip when you arrive from an alert deep link.
  2. Metrics — the per-message-type metrics table (throughput, average execution + effective time, DLQ/hr, exceptions/hr), sortable for finding the hot path.

Retired tabs (#640). The per-service Messaging tab (the handler / message-type catalog) and the Endpoints tab (the listener + sender lifecycle grid) were removed from this page. Both now live in the fleet-wide Messaging Explorer, reachable from the Overview Messaging capability card:


Tenants Tab (conditional)

Visible whenever the service has any tenancy mode beyond Single / None. The tab renders one of two layouts depending on cardinality:

Dynamic case (DynamicMultiple)

Editable list — operators add / disable / enable / remove / hard-delete tenants at runtime via the relay commands. Top toolbar:

  • Refresh — re-issues RequestTenantList to the service
  • Add Tenant — opens the tenant-creation drawer (AddTenant command)

Per-tenant columns:

ColumnMeaning
Tenant IDTenant identifier as the application sees it
Database URIPer-tenant database connection string
Executions (1h)How many handler executions this tenant generated in the last hour. Quick read on traffic volume per tenant.
Failures (1h)How many handler failures (transient exceptions) this tenant hit in the last hour. Renders red when non-zero.
DLQ depthHow deep this tenant's dead-letter queue is right now. Renders red when non-zero.
StatusActive / Disabled
ActionsDisable / Enable (mutually exclusive) · Remove · Hard delete

The three traffic columns only appear once at least one tenant is publishing per-tenant metrics. If you don't see them, your monitored service is either single-tenant or its Prometheus scrape isn't emitting tenant_id labels yet (turn that on in the service's Wolverine + OpenTelemetry config). The columns disappear again the moment no tenant has data — they don't sit there showing dashes.

When to drill in

Click any tenant row to expand it. The expand panel shows two cards:

  • Top message types — the top five message types this tenant is processing, ranked by execution count, with per-type failure and DLQ counts. Use this to answer "which handler is the noisy tenant hammering?" before paging the team that owns it.
  • Durable counters — the full breakdown of this tenant's durable-store depth: incoming, scheduled, outgoing, handled, and dead-letter counts. Reach for this when the service-level Durability tab numbers look high and you want to know which tenant is parked in the inbox.

What "normal" looks like:

  • Failures (1h) > 0 — single failures from a transient outage are normal; sustained non-zero across refreshes means the tenant is hitting a real bug or running with bad config.
  • DLQ depth > 0 — anything above zero means messages are stuck. A tenant whose DLQ depth is climbing while the rest are flat is a classic noisy-neighbour signal.
  • Executions (1h) far above the rest — usually fine on its own, but combine it with rising failures and you have a tenant that's about to start affecting the rest of the system.

Lifecycle

The full lifecycle a tenant goes through, from creation through permanent destruction:

  1. Register — the monitored service starts up with MultiTenantedDatabasesWithMasterDatabaseTable (Marten) configured. CritterWatch sees the DynamicMultiple cardinality and renders the Tenants tab.
  2. Add — operator clicks Add Tenant, supplies a tenant id + connection string. CritterWatch dispatches AddTenant; the service writes a master-table row and (best-effort) creates the per-tenant database if it doesn't already exist.
  3. Use — application code opens sessions against the new tenant id and reads/writes per-tenant data.
  4. Disable / Enable — soft toggles via DisableTenant and EnableTenant. Disabled tenants stay in the master table but session-open against them throws UnknownTenantIdException until re-enabled. Useful for incident-time isolation without data loss.
  5. Remove (RemoveTenant) — drops only the master-table row. The per-tenant database is left intact for forensic recovery / backup. Reversible by re-running AddTenant with the same id + connection string.
  6. Hard delete (HardDeleteTenant) — drops the per-tenant database and removes the master row. Permanent; not reversible.

The end-to-end lifecycle is pinned by the multi_tenancy_lifecycle_end_to_end integration test so any UX change is forced to keep the underlying behaviour stable.

Tenant-id normalization (TenantIdStyle)

When the host sets StoreOptions.TenantIdStyle to ForceLowerCase or ForceUpperCase, both AddTenant and session-open run the supplied tenant id through MaybeCorrectTenantId before doing anything with it. Concrete consequence:

ConfiguredAddTenant("Acme-Corp", …) writesmartenStore.LightweightSession("Acme-Corp") resolves to
CaseSensitive (default)Acme-Corpthe Acme-Corp tenant DB (or fails if you only added acme-corp)
ForceLowerCaseacme-corpthe acme-corp tenant DB
ForceUpperCaseACME-CORPthe ACME-CORP tenant DB

The normalization is silent — the operator's typed id is rewritten without a warning. The UI uses this contract to inline-warn before the command leaves the browser. Under ForceLowerCase / ForceUpperCase, you cannot have two tenants whose ids differ only in case (the second AddTenant collides with the first).

Actions

ActionEffect
DisableDisableTenant(tenantId) — soft-disables; messages routed to the tenant queue without dropping data
EnableEnableTenant(tenantId) — re-enables a disabled tenant
RemoveRemoveTenant(tenantId) — drops the master-table record only; the per-tenant database itself is not deleted
Hard deleteHardDeleteTenant(tenantId) — drops the tenant database and removes the master record. Permanent.

See Wolverine.CritterWatch / Inbound commands for the full command reference.

Static / sharded case (StaticMultiple) — read-only

Tenant lists configured at startup show as a read-only table. The Add Tenant button is absent and per-row actions render as a "Read-only" hint with the tooltip "This service uses static tenancy; tenants are configured at startup, not at runtime." Source: eventStores[*].database.databases[*].tenantIds, flattened and de-duplicated by tenant id; each row carries the DatabaseDescriptor.identifier (or databaseName) so operators see which DB the tenant routes to.

Add Tenant dialog — TenantIdStyle validation

The Tenant ID input runs through the resolved TenantIdStyle policy client-side (mirroring JasperFx.MultiTenancy.TenantIdStyleExtensions.MaybeCorrectTenantId):

  • Empty / whitespace-only input is rejected (the Add button stays disabled).
  • When the input would be silently rewritten under ForceLowerCase / ForceUpperCase (e.g. Acme-Corpacme-corp under ForceLowerCase), an inline warning appears under the input. The Add button stays enabled — the operator can knowingly proceed; the warning surfaces the policy rather than enforcing a different one.
  • Under CaseSensitive no warning is shown; the id is stored exactly as supplied.

Hard-delete typed-id confirmation

Hard delete is gated behind a typed-tenant-id confirmation modal:

The standard "are you sure?" double-confirm is not enough friction for a database-drop. The operator must type the exact tenant id; only then does the dialog's primary action button enable. Audit log records every hard delete with the operator's identity (see Audit Log).


HTTP Tab (conditional)

Only rendered when the service surfaces any HTTP endpoint — either a Wolverine HTTP graph (Wolverine.HTTP is referenced), or non-Wolverine ASP.NET Core endpoints when the host opts in via services.AddCritterWatchHttp().

The tab is one merged surface — Wolverine HTTP chains and non-Wolverine endpoints share the same table with a Source chip per row. A filter strip above the table lets you narrow by source.

All Endpoints

ColumnMeaning
SourceWolverine / MinimalApi / Mvc / RazorPages / SignalR / Other — color-coded chip
MethodGET / POST / PUT / DELETE / PATCH / OPTIONS — color-coded tag
RouteRoute pattern. Multi-version Wolverine chains show one row with a chip per declared version (v1 / v2 / 2024-01-01)
HandlerGenerated handler endpoint or display name (monospace)

Click any row to drill into the per-endpoint detail page. Wolverine chains route to the HTTP chain detail page; non-Wolverine endpoints route to the ASP.NET endpoint detail page.

Above the table, source chips work as a multi-select filter; toggle the ones you care about. The free-text input on the right matches anywhere in the route, method set, or handler name.

A configuration block follows with the Wolverine HTTP graph's settings — global route prefix, antiforgery defaults, applied policies, middleware types, tenant-detection strategies. Tooltip per field, sourced from XML doc comments.

Coming soon: when a route fires a downstream message, you'll see chips on the row pointing at the Messaging-tab page for that message type. The Wolverine HTTP graph surfaces cascading messages on the chain detail page today; the row-level chip is the next step.


Detail-page drill-ins

Several tabs feed into dedicated detail pages with their own routes. Each is breadcrumb-anchored back to the parent service.

Per-node detail page

Route: /service/:id/node/:nodeNumber

Drilled into from the Cluster tab → Nodes view (clicking the node number cell in either Cards or List view). Surfaces:

  • Node identity: number, version, started time, last health-check timestamp
  • Heartbeat history graph
  • Agents currently assigned to this node, with un-assign / pin controls
  • Per-node alert list filtered to this node
  • Eject control

Per-message-store detail page

Route: /service/:id/message-store/:storeUri

Drilled into from the Durability tab → message-store row click. Surfaces:

  • Persistence counts (incoming, outgoing, scheduled, handled, DLQ)
  • Recent inbox / outbox samples
  • The actual installed DDL for that store's durability tables (rendered through DdlBlock)
  • DLQ drill-in pre-filtered to this store

Endpoint detail page

Route: /service/:id/endpoint/:endpointUri

Drilled into from the Endpoints tab. Surfaces:

  • Endpoint identity (URI, mode, transport)
  • Live metrics (sent / received / failed / DLQ)
  • Circuit-breaker + buffering settings (read-only — edit happens on the parent tab)
  • Recent message samples
  • The handler source code if the endpoint terminates in a handler

Handler chain detail page

Route: /service/:id/handler/:messageType

Drilled into from the Messaging tab. Surfaces:

  • Message type identity (full type name, namespace)
  • Middleware chain (in order)
  • Generated handler source code with syntax highlighting
  • Per-endpoint sticky-routing metadata
  • Recent metric trend

Saga detail page

Route: /service/:id/saga/:stateType

For sagas declared in the service. Surfaces saga state shape, configured timeouts, retry policy, and a link to Saga Instances for active state rows.

Saga instances page

Route: /service/:id/saga/:stateType/instances

Live list of in-flight saga instances. Per-row: instance id, state, last update, message log.

Subscription / Projection / Broker detail pages

Subscription detail: /service/:id/subscription/:name Projection detail: /service/:id/projection/:shardName Broker detail: /service/:id/broker/:broker

Each surfaces the same shape — identity, config snapshot, recent activity, lifecycle controls (rewind, rebuild, restart). See the linked tabs for full coverage:

  • Projections — Projections
  • Subscriptions — Cluster tab → Agents view
  • Brokers — Overview Brokers card → Messaging Explorer

DDL viewer

A few tabs render PostgreSQL DDL through the shared DdlBlock component. It uses highlight.js with the SQL grammar, light + dark themes, and a copy-to-clipboard affordance. Used on the Event Store tab (Event Stores) and the per-message-store detail page.


Operations gating

Every action on the detail page that mutates state — Evict service, Pin / Unpin, Eject Node, Add / Remove / Hard-delete Tenant, Trigger Election, projection rewind / rebuild / restart, and edits — is gated by the global "Operations enabled" flag. (Listener Pause / Drain / Restart moved to the Endpoints grid in the Messaging Explorer and is gated there.) When disabled, the buttons render disabled with a hover tooltip explaining why. This is set in Settings → Connection settings, and is the recommended way to switch CritterWatch into a read-only mode for production.

Free for read-only monitoring. A commercial license is required for administrative actions and the MCP server.