Table of Contents

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 east and west regions, 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, acme and globex;
  • 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 east and 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/orders and t/globex/orders: five orders in each tenant.
  • The task-board app installed and enabled in tenant acme, with three cards. Its manifest declares its tasks tree for replication, so installing it enrolled t/acme/a/task-board/tasks in 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: acme is capped at 500 keys and globex at 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: acme has offered globex Read on t/acme/orders, by its full tree id, which is what the cluster's tenant gate matches. The grant is Pending until globex approves it.
  • Regions: both tenants may use east and west, under Allowed regions (set by a platform operator), and the sample shows both residency states. acme is resident in east and west, 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. globex has 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 counts globex as 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) with explorer-admin as a bootstrap administrator, which bypasses the decision engine. The data plane is deny-by-default.
  • Every gRPC binding is configured with the Basic credential scheme, so the console's authorization: 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.
  • DemoBasicAuthenticator decodes 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-owned IExplorerEnvironment, 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 with AllowEnvironmentCredentialSeed = 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 lists alice;
  • choose Group as the kind and type oper -> it lists operators;
  • type nobody and 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.

  1. 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).

  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 tsv
    
  3. Graph application permissions. Grant User.Read.All and Group.Read.All as 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>
    
  4. 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.csproj
    

    The 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