Explorer sample
This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at README.md, and llms.txt lists every page.A one-command, self-contained demo of the opt-in Orleans.Lattice.Explorer.Web
hosting library. One process on one machine, with no cloud dependency, runs a
two-region estate and the Explorer web console, so every Explorer area
has live data:
- two single-silo Orleans clusters, the
eastandwestregions, each serving every control plane the Explorer has an area for (state, auth, schema, apps, tenancy, tree administration, backup, and replication control and status) on its own h2c gRPC endpoint; - tenancy on, with two seeded tenants,
acmeandglobex; - replication between the regions over loopback gRPC, with a small background writer keeping the links busy and a switch that pauses the link;
- one backup sink both regions share, which is what lets replicated trees be backed up; and
- the Explorer console, served by
eastand connected to it.
The console is registered and mounted with the two calls a consumer makes to
embed it in their own ASP.NET app: AddLatticeExplorerWeb() registers the
Explorer with every area compiled in, and MapLatticeExplorer() maps it. Each
area probes its own facade and hides itself when the cluster does not serve it,
so there is nothing to register per area. This is the standalone web head's
code path, so a co-hosted console and the standalone head cannot drift.
Run it
dotnet run --project samples/Explorer/Explorer.csproj
Open http://localhost:5080/. Startup takes a few seconds; the console output
then lists every URL, every sample identity and everything that was seeded. The
sample runs until you press Ctrl+C.
| Switch | Effect |
|---|---|
--minimal |
One region, no tenancy and no peer: the single-cluster experience. |
--explorer-region west |
Connect the console to the west region instead of east. |
--sign-in-as <user> |
Sign the console in as another sample identity, such as acme-admin. --sign-in-as none starts signed out. |
--peer-paused |
Pause the link between the regions as soon as the seeded data has reached west. |
--port-offset <n> |
Add n to every port, when the defaults are taken (for example by another copy of the sample). |
Pass switches after --, for example
dotnet run --project samples/Explorer/Explorer.csproj -- --sign-in-as acme-admin.
The sample keeps its terminal quiet: it clears every logging provider. The one
record the console region writes there is a circuit fault, Blazor's own Error
record of an unhandled exception that ended the console's circuit, with its stack
trace, so a console that stops responding always leaves a reason behind. It logs
no user input. When the sample runs in Development (set
ASPNETCORE_ENVIRONMENT=Development), Blazor's DetailedErrors is also on, so
the browser is sent the fault's detail too.
What runs
east |
west |
|
|---|---|---|
| gRPC (facades and replication receiver) | http://localhost:5199 |
http://localhost:5198 |
| Silo / gateway ports | 11111 / 30000 | 11112 / 30001 |
| Explorer console | http://localhost:5080/ |
served by east |
Both regions run the same code, with the same identities, policy and tenants.
east also seeds the data that replication carries to west:
factory-floor(default tenant): 12 machines, replicated last-writer-wins.t/acme/ordersandt/globex/orders: five orders in each tenant.- The
task-boardapp installed and enabled in tenantacme, with three cards. Its manifest declares itstaskstree for replication, so installing it enrolledt/acme/a/task-board/tasksin replication.
Every seed is a fixed, small set. The background writer overwrites one of the
12 machines in east, and one of four west-sensor- keys in west, every
second, so the trees never grow.
Sample identities
The sample's authenticator trusts the user name and never checks the password, so any password signs in.
| User | What it is |
|---|---|
explorer-admin |
Bootstrap administrator and platform operator. The console signs in as it by default. |
acme-admin |
Tenant admin of acme. |
globex-admin |
Tenant admin of globex. |
alice |
Member of operators (may read factory-floor) and task-editors. |
bob |
Member of task-viewers. |
carol |
Member of visitors, bound to no app role. |
explorer-admin also administers both tenants, as a tenant an operator creates
without naming admins would.
The console signs in automatically, so signing out does not stick: the page
that loads next is signed in again. To see the console as another identity,
restart with --sign-in-as <user>. To switch identities within one run - the
sample keeps everything in memory, so a restart loses what you did - start with
--sign-in-as none: the console then starts signed out, its Sign in dialog
signs in as any identity, and Sign out sticks.
Walk each area
Start with dotnet run (signed in as explorer-admin, connected to east).
The console opens at /t/default, the cluster's reserved default tenant: an
operator can reach it as well as acme and globex, which it administers, and
starts there. Choose acme in the Tenant switcher in the top bar, type
t/acme in the address line and choose it, or open /t/acme, to follow the walk
below. The switcher appears only for a platform operator who can reach two or more
tenants, so acme-admin does not see it. The address is rooted at /t/{tenant} for
every tenant-scoped area. Access and Cluster are cluster-wide at /access and
/cluster; their tenant-rooted forms, such as /t/acme/access, show only that
tenant's rules and trees.
Home
The directory spine lists Data, Apps, Access, Schema, Tenancy, Replication, Backups and Cluster, and Home has a one-line status for each. Telemetry is the one area that stays hidden (see Telemetry).
Data
/t/acme/data lists acme's trees: orders and the task board's app tree
a/task-board/tasks. Open orders to browse its five entries. The default
tenant's factory-floor is not listed here, because the console is scoped to
acme; open /t/default/data to browse it, or see it in Replication and
Cluster.
Apps
/t/acme/apps. The Catalogue lists the in-image task-board app. The
task-board walkthrough covers install,
consent, role binding and opening it, and Tenants
explains the install that is already in acme.
Access
/access. Deny-by-default authorization, with one seeded grant: the
operators group may Read and RangeRead factory-floor. In Explain,
alice reading factory-floor is Allowed by the matched rule, and bob is
Denied by the default. Rules also grant each tenant admin its tenant's
orders tree, and globex-admin-read-acme-orders lets globex-admin read acme's
t/acme/orders (Read and RangeRead). A cross-tenant grant opens the boundary
between two tenants but never bypasses this policy, so that rule is what lets
globex actually read the tree acme shares with it.
Groups lists the seeded groups (operators, task-editors, task-viewers,
visitors and acme-editors). Each is a real group record with its members, not
only a membership edge, so the list shows them and New group knows their ids
are taken: typing operators there says "A group named operators already
exists." The roster group auditors is left uncreated, so New group with the
id auditors creates it; an id the roster does not list, such as nobody, is
refused when you leave the field.
Schema
/t/acme/schema. Schema enforcement and per-value versioning are on, with one
demo schema (machine-status, versions 1 and 2, and a v1 -> v2 upcaster that
adds "state": "unknown"), so the Versions page has a registry to target.
Tenancy
As the operator, /tenancy is the tenant directory: acme and globex, their
state, quota use and apps. Open a tenant for its Overview and lifecycle, and
its Members, Quota, Regions and Sharing tabs, the same tabs a
tenant admin sees:
- Quota:
acmeis capped at 500 keys andglobexat 200, each at ten trees with a 20% burst allowance. The operator sets the limits here; a tenant admin's Quota tab reads them. - Sharing:
acmehas offeredglobexRead ont/acme/orders, by its full tree id, which is what the cluster's tenant gate matches. The grant is Pending untilglobexapproves it. - Regions: both tenants may use
eastandwest, under Allowed regions (set by a platform operator), and the sample shows both residency states.acmeis resident ineastandwest, and both regions are Online: the seeder promotes them, as an operator of the hosting deployment would, so each row reads Served. That is what lets acme's task board replicate, because a region where the tenant is not Online refuses the tenant's replicated writes.globexhas no residency, so Residency (where the tenant's data is kept) reads Not set: served in every region, and each region reads No residency set and Served. Home's Tenancy line countsglobexas the one tenant with no residency set ("1 with no residency set (served in every region)"). Change a residency and the page previews what applying it does, region by region. A region added to a residency starts Provisioning, and nothing in the sample promotes it, so a change that would leave a tenant served nowhere turns Apply residency off. Remove the region the console is connected to from acme's residency and its row shows the step it has reached (Removing: Draining, Step 1 of 3); that region's own silos complete the drain, and the page follows it to Removed without a refresh. Each region keeps its own tenant registry in this sample, so a drain recorded for the other region is never seen by that region's silos and stays Draining here, which is what the page says a lasting Draining means.
For the tenant-scoped view, restart with --sign-in-as acme-admin. The
console opens at /t/acme with only Data, Apps, Tenancy, Replication and
Backups on the spine (Backups says a backup grant is needed). Tenancy is now
My tenant at /t/acme/tenancy: Members, Quota, Regions and Sharing, with no
other tenant in sight. Restart with --sign-in-as globex-admin and approve
acme's offer under /t/globex/tenancy/sharing. globex's Data directory at
/t/globex/data then lists acme's orders as a Shared tree, shared by acme
with Read only access, at /t/globex/data/t/acme/orders, and globex can browse its
five entries.
Every call the console makes asserts the tenant its address names, so
acme-admin sees acme's orders and task board under Data and Apps, and an
install at /t/{tenant}/apps lands in that tenant. The cluster checks the
assertion against the caller's own tenants, so it grants nothing by itself.
Replication
/t/acme/replication shows the estate from east: one peer region, west,
and a link per tree and direction - factory-floor both ways,
sys-replication-config (the replicated runtime configuration) and the task
board's tree - each with its backlog, errors and last contact. Enrolled
trees shows how each is enrolled: factory-floor and the app tree at runtime,
the configuration tree statically.
Press P in the console window to pause the link. Replication between the
regions is refused in both directions (the Explorer's own calls are not), so
the links age: Lagging after about 20 seconds without contact, Stalled after
a minute. Press P again to resume; the regions catch up. --peer-paused
starts in the paused state, once the seeded data has reached west, for when
the console window cannot take key presses.
Backups
/t/acme/backups. Capture a backup with Capture backup.... Replicating a
tree needs
a backup sink every region reads, and the default in-cluster sink is
per-cluster, so both regions share one in-process sink, SampleSharedBackupSink
- the stand-in for a durable off-cluster store such as the Azure Blob sink. A backup captured in one region resolves in the other. Like everything else in the sample, it lives only as long as the process.
Cluster
/cluster. The estate (cluster east, service explorer-sample), its storage,
and the region picture: east and its peer west, with the health of the links
between them. Trees administers every tree by name.
Telemetry
Hidden. The telemetry facade answers queries from a Prometheus-compatible metrics backend, and this self-contained sample runs none, so it serves no telemetry facade and the area fails closed to hidden.
Point the console at west
dotnet run --project samples/Explorer/Explorer.csproj -- --explorer-region west
The console is still served on http://localhost:5080/, but dials west's
endpoint, http://localhost:5198. The console's endpoint is set by the sample,
not in the browser: the sample leaves AllowInteractiveEndpointConfiguration
off, so the header offers no Connection settings and there is no connection
test. Restart with a different --explorer-region to change it. Replication then shows west's side of the
links, and Cluster names west as this region. Both regions seed the same
identities, policy and tenants, and the data seeded in east has replicated.
The single-cluster experience
dotnet run --project samples/Explorer/Explorer.csproj -- --minimal
One region, no tenancy, no peer and the default in-cluster backup sink.
Every area but Tenancy and Telemetry is shown; Replication and Cluster describe
a single region with nothing behind it. With no tenants, the console opens in
the default tenant, at /t/default.
How the sign-in works
- Each region registers membership and authorization (
AddLatticeMembership,AddLatticeAuth) withexplorer-adminas a bootstrap administrator, which bypasses the decision engine. The data plane is deny-by-default. - Every gRPC binding is configured with the
Basiccredential scheme, so the console'sauthorization: Basic base64(user:pass)header is understood. Transport authorization is off because the console carries no client certificate, but the cluster still authorizes every call against the resolved caller. A real deployment leaves transport authorization on. DemoBasicAuthenticatordecodes that header and returns the user name as the caller subject. A real deployment resolves the subject from a validated JWT or Entra token instead.- The console's first-run endpoint and automatic sign-in come from
SampleExplorerEnvironment, a sample-ownedIExplorerEnvironment, rather than process environment variables. The web head withholds an environment credential by default, because it signs every anonymous visitor in; the sample opts in withAllowEnvironmentCredentialSeed = true, which suits only a single-operator loopback demo. The console's persisted configuration is a sample-owned file cleared on start, so it always connects to the region asked for. - Cross-region replication runs over plaintext loopback h2c with no shared secret. That too is for this loopback demo only.
See Running the Explorer, Managing access control, Managing schema and Managing backups.
Group-merge mode
Whether locally-defined group membership affects authorization depends on the
cluster's group-merge mode. Set LATTICE_MEMBERSHIP_MERGE_MODE to Union
(default), TokenOnly or DirectoryOnly before running. Under TokenOnly,
membership comes only from the identity provider's token, so Access > Groups
turns off New group, and a group's page says so in a notice and turns off
adding and removing members while the members stay viewable; Rules and
Explain stay live. For example (PowerShell):
$env:LATTICE_MEMBERSHIP_MERGE_MODE = 'TokenOnly'
dotnet run --project samples/Explorer/Explorer.csproj
Identity directory: static (default) and Entra (opt-in)
The Access area's subject picker (a type-ahead field that searches the directory for users and groups as you type) and its validated forms run against an identity directory. When a directory is configured, entering a principal id that the directory does not know fails closed - the form refuses it, naming the directory ("... is not a group in the identity directory (static roster).") - instead of creating an unvalidated free-text id.
Static directory (default)
With no configuration, an in-memory roster backs the directory: the users in
Sample identities and the groups operators,
task-editors, task-viewers, visitors, acme-editors and auditors (the one
group the sample does not create). In a rule's subject picker, or the member
field on a group's page:
- type
al-> the picker listsalice; - choose Group as the kind and type
oper-> it listsoperators; - type
nobodyand leave the field or save -> the picker refuses it, because it is not in the roster.
Entra directory (opt-in, your tenant over Microsoft Graph)
Set all three of the following to back the picker and the validated create
with a live Microsoft Graph search over your Entra tenant
(AddEntraGraphGroupResolver, app-only). Setting only some of them stops the
sample at startup with a non-zero exit, so a half-configuration never silently
falls back to the static roster.
App registration. Create or reuse an Entra app registration and note its tenant id and client id; see the Entra ID setup guide (Steps 1-2).
Client secret. Add a client secret and record the value (shown once):
az ad app credential reset --id <client-id> --display-name lattice-explorer-graph --query password -o tsvGraph application permissions. Grant
User.Read.AllandGroup.Read.Allas application permissions, then admin-consent them:az ad app permission add --id <client-id> --api 00000003-0000-0000-c000-000000000000 ` --api-permissions df021288-bdef-4463-88db-98f22de89214=Role 5b567255-7703-4780-807c-7be8301ae99b=Role az ad app permission admin-consent --id <client-id>Export the three variables and run.
$env:LATTICE_ENTRA_TENANT_ID = '<tenant-guid>' $env:LATTICE_ENTRA_CLIENT_ID = '<app-client-id>' $env:LATTICE_ENTRA_CLIENT_SECRET = '<client-secret>' dotnet run --project samples/Explorer/Explorer.csprojThe console output then reads
Identity directory: Microsoft Entra (Graph).
The console still signs in over Basic as a sample identity in both modes; the Entra directory backs the Access area's validation and search, not the console's sign-in. See Identity directory providers.
What to look at
| File | What it shows |
|---|---|
Program.cs |
The entry point: options, start, the console output and the P key. |
ExplorerSample.cs |
The estate: both regions, the shared sink, the peer link and the writer. |
SampleRegion.cs |
One region's host: the silo, every facade and gRPC binding, the replication transport and, in east, the Explorer console. |
SampleSeeder.cs |
The seeded identities, policy, tenants, data, replication enrolment and the task-board install. |
SampleSharedBackupSink.cs |
The backup sink both regions share. |
PeerLink.cs |
The switch that pauses cross-region replication. |
ReplicationWriter.cs |
The bounded background writer. |
DemoBasicAuthenticator.cs |
The trusted-token authenticator behind the Basic sign-in. |
SampleCircuitDiagnostics.cs |
The console region's terminal log of circuit faults, and DetailedErrors in Development. |
test/ |
Explorer.Tests: option parsing and the sample's parts, plus smoke tests that start the sample in-process and check that every area but Telemetry (and, with --minimal, Tenancy) is visible to the bootstrap administrator. |
The smoke tests run in the samples CI lane:
dotnet test samples/Explorer/test/Explorer.Tests.csproj