Identity Context
The validated app/stack/network triple devstack threads through plugin context for the lifetime of a running stack.
Identity is the closed identity triple — (app, stack, network) — that devstack resolves once at
boot and threads through Effect Context for the lifetime of the stack. Plugins yield
IdentityContext inside start to read it; the values are stable for the run.
import type { AppName, StackName } from '@mysten-incubation/devstack';
interface Identity {
readonly app: AppName;
readonly stack: StackName;
readonly network: string;
}app and stack are brand-typed string aliases so they cannot be confused with each other at the
type level. network is a plain string: an opaque, substrate-blind label the caller supplies
once at boot. The substrate NEVER parses it or branches on its value — it carries no network/mode
semantics at this layer; it is threaded purely as a correlation/display label (supervisor labels,
span attributes, snapshot provenance). The CLI / runStack happen to set it to the resolved network
name ('localnet', 'testnet', 'mainnet-fork', …), but that is a producer convention, not a
substrate primitive.
Where identity comes from
app— inferred from the nearest package.jsonname(the unscoped tail) unlessdefineDevstack({ appName })or the--appCLI flag overrides it.stack—defineDevstack({ stackName })or the--stackCLI flag. Defaults to'main'.network— the resolved network name, supplied by the CLI /runStackafter thesui()plugin contributes its mode (--network,$DEVSTACK_NETWORK, or the config default). The substrate stores it verbatim and never interprets it.
app and stack are validated once before any plugin body runs. Invalid identities (empty app
name, illegal characters, conflicting --app / package.json values) surface as typed config errors
at boot, not as runtime defects mid-stack.
Yielding IdentityContext from a plugin
import { Effect } from 'effect';
import { IdentityContext, definePlugin } from '@mysten-incubation/devstack';
export const mything = () =>
definePlugin({
id: 'mything',
role: 'service',
start: () =>
Effect.gen(function* () {
const identity = yield* IdentityContext;
// Use identity.app, identity.stack (and identity.network as a
// display/correlation label) to build per-stack resource names.
return { containerName: `${identity.app}-${identity.stack}-mything` };
}),
});The supervisor provides IdentityContext via layerIdentity(identity) before any plugin's start
body runs. Inside a plugin you can rely on the values being final.
What identity threads into
- Container labels. The Docker runtime stamps
{managed:'true', app, stack, plugin, role}on every managed container, image, and network.prune/wipeenumerate by these labels. - DNS aliases. Per-stack containers register under
<app>-<stack>-<name>plus an in-network alias so siblings in the same stack can dial each other while parallel stacks coexist. - Per-stack runtime state. The id-config file and the dev-only
generated-extrastree live under.devstack/stacks/<stack>/, so two stacks of the same app keep distinct on-chain ids and dev-wallet state without collision. (The committedsrc/generatedtree is NOT keyed by identity — it is stack-invariant and id-free; see Generated Outputs.) - Snapshot metadata. Snapshots record the full identity triple;
restorerefuses identity drift before destructive changes.
Note that per-chain routing (e.g. the faucet:request:<chainId> capability key) is keyed by the
resolved on-chain chainId (the genesis digest), a sui()-plugin concern — not by
identity.network, which the substrate treats as an opaque label.
Runtime root vs identity
Identity is the symbolic triple. The on-disk runtime root (.devstack/stacks/<stack>/...) is a
separate substrate concern — plugin bodies that need a filesystem location for manifests,
projections, snapshots, or per-plugin runtime state receive it through the substrate paths service
the supervisor provides alongside IdentityContext. Most plugins yield both inside the same
Effect.gen, then derive their on-disk paths from the resolved stack root.