Branded Types for ID Semantic Attribution in the Magnitude Codebase

Magnitude leverages Effect-TS branded types to enforce type-safe ID boundaries, intersecting primitive strings with Brand.Brand<"…"> to create nominal types that cannot be accidentally mixed across domains.

The magnitudedev/magnitude repository implements a defensive type-safety strategy using branded types to distinguish semantically different identifiers. By combining primitive strings with unique brand markers from the Effect-TS library, the codebase prevents subtle runtime bugs caused by passing the wrong ID to the wrong function. This pattern creates opaque, nominal types that exist only at compile time while remaining plain strings at runtime, ensuring zero performance overhead with maximum type safety.

Effect-TS Branding Pattern Implementation

The codebase constructs branded types by intersecting the base string type with Brand.Brand<"Label">, then exposing a nominal constructor through Brand.nominal<T>(). This approach treats structurally identical types as distinct, preventing accidental cross-contamination between different ID domains.

The Nominal Type Constructor

In packages/launcher/src/cli-binary-resolver.ts, the pattern appears as:

import { Brand } from "effect"

export type ExecutablePath = string & Brand.Brand<"ExecutablePath">
export const ExecutablePath = Brand.nominal<ExecutablePath>()

The Brand.nominal<T>() function generates a companion object (here also named ExecutablePath) containing the .of() method. This method narrows a plain string into the branded type at compile time without adding runtime wrapper objects, preserving the string primitive for serialization and storage.

Core Branded ID Types by Domain

Magnitude defines specific branded types across its monorepo packages to enforce semantic boundaries between unrelated identifier concepts.

ExecutablePath for Launcher Binaries

Located in packages/launcher/src/cli-binary-resolver.ts, the ExecutablePath type represents absolute file system paths to launcher binaries:

export type ExecutablePath = string & Brand.Brand<"ExecutablePath">

This prevents accidental passing of relative paths or arbitrary strings to functions expecting resolved, absolute binary locations.

Mutation State Identifiers in Effect-Query

The packages/effect-query/src/Model.ts file defines two complementary branded types for cache management:

  • MutationStateId: string & Brand.Brand<"MutationStateId"> — Uniquely identifies a persisted mutation state in the query-layer cache.
  • MutationScope: string & Brand.Brand<"MutationScope"> — Scopes mutation IDs to specific logical domains (e.g., user sessions or project contexts).

These types ensure that cache keys cannot be confused with scope identifiers, preventing invalidation logic errors.

Agent Runtime Event IDs

In packages/agent/src/events.ts, the UserBashCommandId type tracks user-issued shell commands:

export type UserBashCommandId = string & Brand.Brand<'UserBashCommandId'>

This separates bash command event identifiers from other event types in the agent's event bus system.

ACN Protocol Recovery Tags

The packages/acn-protocol/src/transport/recovery.ts defines RecoveryDeclared using a slightly different pattern:

export type RecoveryDeclared = Brand.Brand<"RpcRecoveryDeclared">

Unlike the ID types, this acts as a structural tag rather than a string intersection, allowing the ACN protocol to distinguish recovery declarations from ordinary RPCs at the type level.

Practical Usage Examples

Constructing branded type instances requires using the .of() method provided by the nominal brand constructor. The following patterns appear throughout the codebase:

Creating an ExecutablePath:

import { ExecutablePath } from "@magnitudedev/launcher"

const path: ExecutablePath = ExecutablePath.of("/usr/local/bin/magnitude")

Using MutationStateId in cache operations:

import { MutationStateId } from "@magnitudedev/effect-query"

const stateId: MutationStateId = MutationStateId.of("state-123")

Emitting events with UserBashCommandId:

import { UserBashCommandId } from "@magnitudedev/agent"

const cmdId: UserBashCommandId = UserBashCommandId.of("cmd-42")
eventBus.emit({ type: "UserBashCommand", id: cmdId, command: "ls -la" })

At runtime, all branded IDs remain plain JavaScript strings, ensuring compatibility with JSON serialization and external APIs while maintaining strict compile-time contracts.

Summary

Frequently Asked Questions

What are branded types in TypeScript?

Branded types are a compile-time pattern that creates distinct types from identical underlying structures, typically by intersecting a primitive with a unique marker type. In Magnitude, this allows two strings representing different ID types (like MutationStateId and MutationScope) to be treated as incompatible by the TypeScript compiler, preventing accidental assignment between them despite both being strings at runtime.

Why does Magnitude use Effect-TS Brand instead of nominal typing libraries?

Magnitude uses effect/Brand because it provides a standardized, well-tested implementation of nominal typing specifically designed for functional TypeScript patterns. The Brand.nominal<T>() constructor automatically generates the .of() factory method and type guards, reducing boilerplate while integrating seamlessly with Effect's ecosystem of type-safe utilities used throughout the magnitudedev/magnitude codebase.

How do you convert a plain string to a branded ID at runtime?

You must use the branded type's .of() method, which is exposed by the Brand.nominal() constructor. For example, ExecutablePath.of("/path/to/binary") narrows a string to ExecutablePath. Direct casting or type assertions bypass the brand's safety guarantees and should be avoided in production code.

Can branded types prevent ID confusion across network boundaries?

Branded types only enforce safety at compile time within the TypeScript codebase. Once serialized to JSON for network transmission, the brand information is lost. Magnitude relies on domain-specific parsing layers at API boundaries to reconstruct branded types using the same .of() constructors, ensuring that external string inputs are validated and properly branded before entering internal type-safe flows.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →