# How the Cordis Shadow Configuration System Works: Complete Technical Guide

> Discover how the Cordis shadow configuration system works. Learn about its symbol-based shadow mechanism, transparent proxies, and isolated service state for preserved caller metadata. A complete technical guide.

- Repository: [Cordiverse/cordis](https://github.com/cordiverse/cordis)
- Tags: deep-dive
- Published: 2026-08-23

---

**The Cordis shadow configuration system tracks caller context through a symbol-based shadow mechanism that wraps service access in transparent proxies, enabling isolated service state with preserved caller metadata.**

Cordis implements a sophisticated **shadow configuration system** to maintain caller context isolation while allowing services to trace their origin. This architecture, found in the `cordiverse/cordis` repository, solves the fundamental problem of context leakage in dependency injection frameworks. Unlike simple context passing, Cordis uses **symbol-based shadows** and **traceable proxies** to ensure that services always know who created them without exposing their internal state.

## What Is the Cordis Shadow System?

The shadow system consists of two interconnected mechanisms working together in [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts). First, the `shadow` symbol acts as a hidden property key for storing caller references. Second, the `createTraceable` function builds proxies that temporarily inject this shadow during property access.

The core symbol is defined as a shared symbol to ensure cross-realm compatibility:

```typescript
// packages/core/src/utils.ts
export const symbols = {
  shadow: Symbol.for('cordis.shadow'),
  // ... other symbols
}

```

This design choice means `Symbol.for('cordis.shadow')` returns identical values even across different module boundaries, critical for plugin ecosystems.

## How Shadows Store Caller Metadata

When a service instantiates, its constructor receives a caller context. Cordis captures this relationship by storing the caller as a shadow on the service's own context object. The `createShadow` function performs this operation:

```typescript
// packages/core/src/utils.ts#L41-L46
export function createShadow<T extends object>(origin: T, ctx: Context) {
  return ctx.extend({
    [symbols.shadow]: origin,
  }) as T
}

```

The `createShadow` function extends the provided context with the `symbols.shadow` property pointing to the original caller. This allows services to access their creator through `this.ctx[symbols.shadow]` while keeping their own context tree separate.

## Transparent Proxying with createTraceable

Property access on traceable objects triggers temporary shadow creation. The `createTraceable` proxy handler in `packages/core/src/utils.ts#L57-L90` intercepts gets and sets to inject the correct caller context:

```typescript
// Simplified flow from createTraceable implementation
const handler = {
  get(target, prop, receiver) {
    // Check if property has its own tracker
    const value = Reflect.get(target, prop, receiver)
    
    // Create temporary shadow with caller context preserved
    const shadow = createShadow(ctx[symbols.shadow] ?? ctx, ctx)
    
    // Return wrapped value that uses shadow for nested calls
    return wrapWithShadow(value, shadow)
  }
}

```

This proxy ensures that when you access `service.someMethod()`, the `this` context inside `someMethod` sees the original caller, not the service's internal shadow. Functions receive special treatment through `createShadowMethod`, which binds `this` to the shadow object.

## Context Extension Preserves Shadows

The `Context.extend` method in `packages/core/src/context.ts#L55-L63` maintains shadow continuity when creating derived contexts:

```typescript
// From context.ts extend implementation
extend(meta: Context.Meta) {
  const self = getTraceable(this, this)
  const shadow = this[symbols.shadow]
  
  const ctx = Object.assign(Object.create(self), {
    [symbols.shadow]: shadow,
    // ... other meta
  })
  
  return ctx
}

```

This preservation is essential for plugin composition. When a plugin extends a context, any existing shadow from upstream callers travels with it, maintaining the full ancestry chain.

## Practical Service Implementation

A standard service automatically participates in the shadow system:

```typescript
import { Context, Service, symbols } from '@cordis/core'

class Greeter extends Service {
  constructor(ctx: Context) {
    super(ctx, 'greeter')
  }
  
  hello() {
    // Access the caller context through the shadow symbol
    const caller = this.ctx[symbols.shadow] as Context
    return `Hello from ${caller.config.name}`
  }
}

// Usage: caller context is automatically preserved
const root = new Context({ name: 'main-app' })
const greeter = root.provide('greeter', Greeter)
greeter.hello() // "Hello from main-app"

```

The `Service` base class handles shadow registration during construction, making the pattern transparent to implementers.

## Opting Out with noShadow Services

Services can disable shadow tracking by setting `noShadow: true` in their tracker configuration. This is useful for lightweight utilities that don't need caller tracing:

```typescript
import { Context, Service, symbols } from '@cordis/core'

class Simple extends Service {
  static inject = []
  
  [Service.tracker] = {
    property: 'ctx',
    noShadow: true  // Disables shadow creation
  }
  
  constructor(public ctx: Context) {}
  
  checkShadow() {
    // this.ctx[symbols.shadow] is undefined
    return this.ctx[symbols.shadow]
  }
}

```

The test suite in `packages/core/tests/shadow.spec.ts#L54-L71` validates this behavior, confirming that `noShadow` services bypass the wrapping logic entirely.

## Fiber Resolution Uses Shadows

The reflective fiber system in `packages/core/src/reflect.ts#L82-L84` leverages shadows to determine execution context:

```typescript
// From reflect.ts fiber resolution
const fiber = ctx[symbols.shadow] ?? ctx

```

This fallback pattern—shadow first, then direct context—ensures that fiber-bound operations always associate with the caller's runtime state, not the service's internal fiber tree. The shadow becomes the bridge between static service definition and dynamic execution context.

## Shadow System Architecture Summary

| Component | Location | Responsibility |
|-----------|----------|--------------|
| `symbols.shadow` | `utils.ts#L49-L50` | Global symbol for shadow property key |
| `createShadow` | `utils.ts#L41-L46` | Builds shadow-wrapped contexts |
| `createTraceable` | `utils.ts#L57-L90` | Proxy factory for transparent shadow injection |
| `Context.extend` | `context.ts#L55-L63` | Preserves shadows during context derivation |
| Fiber resolution | `reflect.ts#L82-L84` | Uses shadow for runtime context selection |

## Summary

- **Cordis shadow configuration** uses `Symbol.for('cordis.shadow')` to store caller references without polluting the public context API.

- **The `createShadow` function** in [`packages/core/src/utils.ts`](https://github.com/cordiverse/cordis/blob/main/packages/core/src/utils.ts) wraps contexts with caller metadata, enabling services to trace their origin.

- **`createTraceable` proxies** inject temporary shadows during property access, ensuring nested calls maintain correct caller context.

- **`noShadow: true`** in `Service.tracker` configuration opts out of shadow wrapping for lightweight services.

- **Fiber resolution** prefers shadows over direct contexts, binding runtime operations to caller state rather than service internals.

## Frequently Asked Questions

### What problem does the Cordis shadow system solve?

The shadow system prevents **context leakage** in dependency injection frameworks. Without shadows, services would expose their internal context tree to consumers, making it impossible to distinguish between the caller's state and the service's own configuration. Shadows maintain this boundary while still allowing services to access caller metadata when needed.

### How do I access the caller context in my service?

Access `this.ctx[symbols.shadow]` where `symbols` is imported from `@cordis/core`. The shadow contains the context that originally instantiated or injected your service. Note that this returns `undefined` for services with `noShadow: true` or when accessed from root-level contexts without parents.

### Can I disable shadow tracking for performance?

Yes. Set `[Service.tracker] = { property: 'ctx', noShadow: true }` in your service class. This bypasses `createShadow` and `createTraceable` wrapping, returning raw values directly. Use this only for services that never need caller introspection, as it breaks the `symbols.shadow` access pattern.