npm package: @statedelta-axiom/realm-system
Description: RealmSystem — composition root funcional. Cria engines, boota definitions, executa ticks via TickRunner
Author: Anderson D. Rosa
Latest version: 0.1.0

# @statedelta-axiom/realm-system

Composition root do sistema statedelta-actions. Cria engines, boota definitions, executa ticks.

O RealmSystem (RS) é o **System** — o porteiro que virtualiza o Realm. Recebe physics (leis do mundo), cria toda a infraestrutura internamente, aceita definitions no boot, e executa ticks via TickRunner. O customer não toca em engines nem handlers — só em physics, definitions e a API pública.

## Filosofia

**RS = composition root.** Não é engine, não é orquestrador. É quem monta tudo e opera. O customer diz *o que quer* (physics + definitions). O RS decide *como fazer* (engines, handlers, wiring).

**Duas fases de construção (ADR-051).** Physics define as leis do mundo (imutável). Boot define os recursos iniciais (states, actions, rules, events, views). São concerns separados — regras do jogo vs estado inicial do jogo.

**States via Store (ADR-052).** Todo state é criado via `store.create()` com factory registry. O RS nunca instancia states diretamente. O Store do apex-store coordena transações, snapshot/restore e lifecycle.

**Handlers são built-in (ADR-044).** O RS cria e registra 6 handlers internamente (dispatch, action, emit, halt, notify, request). O customer não passa handlers — são concern do RS que tem o contexto necessário pra montá-los.

**O Realm não sabe onde está.** Como o Neo na Matrix — opera com o que recebeu. Não sabe se está num RS, Sandbox, teste, ou é child de outra sandbox. O System processa e virtualiza.

**Boot com erro não transiciona.** Se o boot tiver erros, o RS emite `error` e permanece em `"created"`. O realm não bootou.

**Erro no step re-throw.** Se o TickRunner lança exceção, o RS emite `error` e re-lança. O tick não completa — transition não capturada, step:complete não emitido.

## Instalação

```bash
pnpm add @statedelta-axiom/realm-system
```

## Quick Start

```typescript
import { createRealmSystem } from "@statedelta-axiom/realm-system";

// 1. Construção — physics define as leis do mundo
const rs = createRealmSystem({
  enableViews: true,
  enableEvents: true,
  temporal: { stepSize: 4, cycleSize: 10 },
});

// 2. Boot — definitions populam o realm, env injeta birth data
const bootResult = rs.boot({
  states: [
    { id: "hp", type: "counter", data: { value: 100, min: 0, max: 100 } },
    { id: "score", type: "counter", data: { value: 0 } },
    { id: "status", type: "flags", data: { initial: ["alive"] } },
  ],
  actions: [
    {
      id: "heal",
      directives: [
        { type: "dispatch", target: "hp", op: "inc", value: 20 },
        { type: "notify", event: "healed", data: { amount: 20 } },
      ],
    },
  ],
  rules: [
    {
      id: "auto-heal",
      priority: 100,
      when: (ctx) => (ctx.get("hp") as number) < 30,
      then: [{ type: "action", id: "heal" }],
    },
    {
      id: "death-check",
      priority: 500,
      when: (ctx) => (ctx.get("hp") as number) <= 0,
      then: [
        { type: "dispatch", target: "status", op: "remove", value: "alive" },
        { type: "halt" },
      ],
    },
  ],
  events: [
    {
      id: "on-damaged",
      priority: 100,
      on: "damaged",
      then: [{ type: "dispatch", target: "score", op: "inc", value: 5 }],
    },
  ],
}, { env: { previousRealm: "forest", difficulty: "hard" } });

// 3. Ticks — executa PPP (Preparar → Processar → Propagar)
const step1 = rs.step();  // tick 0
const step2 = rs.step();  // tick 1

// 4. Leitura
rs.get("hp");        // valor atual
rs.get("hp", "max"); // field específico

// 5. Operações imperativas (entre ticks)
rs.invoke("heal");                          // invoca action diretamente
rs.dispatch("hp", "set", { value: 50 });    // muta estado diretamente
rs.emit("damaged", { amount: 10 });         // enfileira evento
rs.processEvents();                         // drena buffer de eventos

// 6. Observação
rs.on("step:complete", ({ tick, result }) => { /* ... */ });
rs.on("notify", ({ event, data }) => { /* ... */ });

// 7. Consulta — sempre disponível, sem guards
rs.getPhase();   // "created" | "booted" | "running"
rs.getTick();    // número do tick atual (-1 antes do primeiro step)
rs.isIdle();     // true entre ticks
rs.hasState("hp");
rs.hasAction("heal");
rs.hasRule("auto-heal");
rs.getView("combat");
```

## Lifecycle

```
created ──boot()──→ booted ──step()──→ running ──step()──→ running ...
                         ↑                                      ↑
                    boot com erro                         step com erro
                    permanece created                     emite error + re-throw
```

| Transição | Quando | Validação |
|-----------|--------|-----------|
| `created → booted` | `boot()` com success | Boot sem erros |
| `created → created` | `boot()` com erro | Boot com erros — não transiciona |
| `booted → running` | Primeiro `step()` ou `stepAsync()` | Phase booted ou running |
| `running → running` | Steps subsequentes | Phase booted ou running |

## Physics — Leis do Mundo

Imutável após construção. Qualquer mudança = criar outro realm.

```typescript
interface RealmPhysics {
  allowedDirectives?: DirectivePermissionEntry[];  // whitelist (exclusivo com blocked)
  blockedDirectives?: DirectivePermissionEntry[];  // blacklist
  limits?: Partial<FrameLimits>;                   // maxDepth, maxRules, maxDirectives
  maxEventPasses?: number;                         // drain loop limit (default: 10)
  async?: boolean;                                 // pausa intra-tick (default: false)
  enableViews?: boolean;                           // Pass 0 (default: false)
  enableEvents?: boolean;                          // Pass 2+ (default: true)
  temporal?: { stepSize?: number; cycleSize?: number };  // default: 1:1
  schemaValidator?: ISchemaValidator;              // validação de effects (opt-in, DI)
  imperatives?: boolean;                           // habilita invoke/dispatch/emit/processEvents (default: true)
}
```

## System Config — Tuning

Tuning de processamento e injeção de comportamento.

```typescript
interface RealmSystemConfig {
  mode?: "interpret" | "jit" | "auto";  // compilação (default: "auto")
  autoJitThreshold?: number;            // auto-promote (default: 8)
  analyzer?: boolean | AnalyzerConfig;  // ActionAnalyzer (default: false)
  debug?: boolean;                      // eventos internos (default: false)
  handlers?: Record<string, HandlerDefinition<RealmCtx>>;  // handlers customizados
}
```

### Handlers Customizados

Handlers injetados via `systemConfig.handlers` são registrados no ActionEngine
junto com os 6 built-in. O RS não sabe o que fazem — mesmo pattern de DI.

```typescript
const rs = createRealmSystem({}, {
  handlers: {
    sandbox: mySandboxHandler,
    analytics: myAnalyticsHandler,
  },
});
```

Actions podem usar diretivas dos handlers customizados normalmente:
```typescript
{ id: "create-child", directives: [{ type: "sandbox", target: "child-1" }] }
```

## Boot Definitions

Todos os campos opcionais. Tipos definidos em `@statedelta-axiom/contracts`.

```typescript
interface BootDefinitions<TCtx> {
  states?: StateDefinition[];
  actions?: ActionDefinition<TCtx>[];
  rules?: RuleDefinition<TCtx>[];
  events?: EventListenerDefinition<TCtx>[];
  eventDefinitions?: EventDefinition[];
  views?: ViewDefinition<TCtx>[];
}
```

### StateDefinition — 7 types

```typescript
{ id: "config", type: "record", data: { volume: 80, theme: "dark" } }
{ id: "hp", type: "counter", data: { value: 100, min: 0, max: 100 } }
{ id: "inv", type: "collection", data: [{ id: "sword", damage: 10 }] }
{ id: "queue", type: "list", data: ["task-1", "task-2"] }
{ id: "board", type: "matrix", data: { rows: 3, cols: 3, defaultValue: null } }
{ id: "status", type: "flags", data: { initial: ["alive"] } }
{ id: "phase", type: "statemachine", data: { initial: "menu", states: ["menu", "playing"], transitions: [{ from: "menu", to: "playing" }] } }
```

## BootResult

```typescript
result.success;              // true se zero erros
result.registered.states;    // IDs registrados
result.registered.actions;
result.registered.rules;
result.registered.events;
result.registered.views;
result.errors;               // [{ resource, id, code, message }]
```

Erros são isolados — falha em um recurso não impede os outros.

## Boot Options

Segundo argumento opcional do `boot()`. Injeta dados persistentes no `ctx.env()`.

```typescript
rs.boot(definitions, {
  env: { score: 500, previousRealm: "forest" },  // birth data
});
```

Birth data é acessível via `ctx.env("score")` em qualquer tick. Persiste
durante toda a vida do realm.

## Effects — Input Externo Passivo

Effects são o mecanismo passivo de input externo. A App deposita dados,
rules reagem no próximo tick. Key-value, um effect por tipo por tick.

```typescript
// App deposita entre ticks
rs.applyEffect("user-input", { key: "space" });
rs.applyEffect("mouse", { x: 10, y: 20 });

// Rules consultam durante o tick
ctx.run("effects")  // → { "user-input": { key: "space" }, "mouse": { x: 10, y: 20 } }

// Effects morrem no fim do tick — per-run
```

### Request — Realm Pede Input

A diretiva `{ type: "request", id: "..." }` permite que o realm solicite
input externo. O tick completa normalmente. O lock entra em vigor pro
próximo `step()` — o System trava até que a App forneça o effect solicitado.

```typescript
// Rule solicita input com JSON Schema opcional
{ type: "request", id: "user-choice", schema: {
  type: "object",
  properties: { option: { type: "string" } },
  required: ["option"],
}}

// App escuta o evento e responde
rs.on("request", ({ id }) => {
  showDialog().then(answer => rs.applyEffect(id, answer));
});

// Próximo step() trava se request não satisfeito
rs.step();  // → Error: System is locked. Pending requests: user-choice
```

`applyEffect()` satisfaz o request e deslocka. O effect fica disponível
em `ctx.run("effects")` no tick seguinte.

### Schema Validation (ADR-021)

Requests podem declarar um **JSON Schema** (Draft-07) que define o formato
esperado do payload. A validação é opt-in — só acontece quando:

1. O request declara `schema`
2. O RS recebeu `schemaValidator` na physics (DI)

Se o payload falha na validação, `applyEffect()` lança erro e o lock é
mantido — o request continua pendente.

```typescript
import type { ISchemaValidator } from "@statedelta-axiom/contracts";

// Server injeta o validator (ex: wrapper de ajv)
const rs = createRealmSystem({
  schemaValidator: myAjvValidator,  // implementa ISchemaValidator
});
```

Sem `schemaValidator`, schemas são ignorados — zero overhead.
Sem `schema` no request, o validator não é chamado — aceita qualquer payload.

## Async Mode (ADR-024)

O RS suporta execução async via `physics.async: true`. Em async mode,
o tick pode conter hooks async (directive hooks, rule hooks, event hooks)
que são aguardados via `await`.

```typescript
// Construção com async habilitado
const rs = createRealmSystem({ async: true, enableViews: true });
rs.boot(definitions);

// stepAsync() em vez de step()
const result = await rs.stepAsync();
```

### Dual-method: step() vs stepAsync()

| Método | Quando usar | O que faz |
|--------|------------|-----------|
| `step()` | Realm sync (default) | Chama `tickRunner.run()` — sync |
| `stepAsync()` | Realm async (`physics.async: true`) | Chama `await tickRunner.runAsync()` — async |

`step()` em async mode lança erro:
```
"Cannot call step() in async mode. Use stepAsync() instead."
```

`stepAsync()` funciona em ambos os modos — em realm sync, simplesmente
aguarda Promises que resolvem imediatamente.

### Validação de coerência no boot

Se hooks async são detectados mas `physics.async` não está habilitado,
o boot lança erro:
```
"Async hooks detected but physics.async is not enabled."
```

Isso previne misconfiguration silenciosa — hooks async em realm sync
causariam throw no `evaluate()` do RuleEngine/EventProcessor.

Na direção oposta, `physics.async: true` com hooks todos sync é válido —
o realm declara capacidade async sem ter hooks async no momento.

### Propagação transitiva

```
ActionEngine (directiveHooks async?)
  └── isAsync = true
        ├── RuleEngine herda
        ├── EventProcessor herda
        └── ViewEvaluator herda
              └── RS detecta no boot
```

## Snapshot / Restore

Captura e restaura estado completo do realm. Não inclui definitions —
concern da Sandbox/DSL layer.

```typescript
// Captura após ticks
rs.step();
rs.step();
const snap = rs.snapshot();

// Mais ticks...
rs.step();
rs.step();

// Restaura — volta pro ponto do snapshot
rs.restore(snap);
rs.step(); // continua do ponto restaurado
```

O snapshot inclui: Store (todos os states + $transition), tick, phase,
birth data (env), pending requests e pending effects.

## StepResult

`step()` e `stepAsync()` retornam o mesmo tipo:

```typescript
const result = rs.step();              // sync
const result = await rs.stepAsync();   // async — mesmo StepResult

result.tick;          // número do tick executado (0, 1, 2, ...)
result.runResult;     // resultado do PPP (ruleResult, eventPasses, etc)
result.halted;        // true se halt ocorreu
result.thrown;        // true se throw ocorreu
result.requests;      // requests emitidos neste tick ([{ id, schema? }])
```

## RealmCtx — 4 Canais

O contexto que rules, views e ops recebem durante o tick.

```typescript
ctx.get("hp")            // estado via Router → Store
ctx.get("hp", "max")     // field específico
ctx.get("combat:$pct")   // field de view (split por :)

ctx.run("tick")          // dados efêmeros do tick (tick, step, effects, etc)

ctx.env("score")         // birth data (imutável, setado no boot)

ctx.getDelta("hp")       // { previous: 100, changed: true }
// previous = início do tick anterior (pre-mutation)
// current = estado ao vivo (inclui mutations intra-tick)
```

Paths com `:` são splitados automaticamente: `"combat:$pct"` →
`router.get("combat", "$pct")`. Isso permite cross-view deps e
acesso a fields de qualquer state type.

## System Events

Canal de lifecycle events separado dos eventos internos do realm.
EventEmitter type-safe próprio do RS.

```typescript
// Lifecycle
rs.on("booted", ({ result }) => { /* boot completou */ });
rs.on("step:complete", ({ tick, result }) => { /* tick completou */ });
rs.on("notify", ({ event, data }) => { /* diretiva notify disparou */ });
rs.on("request", ({ id, schema }) => { /* realm pede input externo */ });
rs.on("error", ({ phase, error }) => { /* erro no boot ou step */ });

// Imperativas (ADR-043 — hooks pra registro/replay)
rs.on("imperative:invoke", ({ id, params, tick }) => { /* ... */ });
rs.on("imperative:dispatch", ({ stateId, op, params, tick }) => { /* ... */ });
rs.on("imperative:emit", ({ event, data, tick }) => { /* ... */ });
rs.on("imperative:processEvents", ({ eventsProcessed, tick }) => { /* ... */ });

// Unsubscribe
const unsub = rs.on("step:complete", listener);
unsub(); // para de ouvir
```

## Operações Imperativas (ADR-038)

Ações diretas executadas **entre ticks**, quando o System está idle.
Requerem: `phase === "running"`, `isIdle() === true`, `physics.imperatives !== false`.

```typescript
// Invoca action diretamente — eventos gerados ficam no buffer
const result = rs.invoke("heal", { target: "player" });

// Muta estado diretamente
rs.dispatch("hp", "set", { value: 50 });

// Enfileira evento — não drena imediatamente
rs.emit("damaged", { amount: 10 });

// Drena buffer de eventos (drain loop completo)
rs.processEvents();
```

Variantes async: `rs.invokeAsync()`, `rs.processEventsAsync()`.

Eventos gerados por `invoke()` ficam no buffer — drenam via `processEvents()` ou
no próximo `step()`. Cada operação emite system event (`imperative:invoke`, etc)
pra registro pela lib superior.

Desabilitação via `physics.imperatives: false` — todas as imperativas lançam erro.

## Consulta (ADR-039)

Métodos de leitura direto no RS. Sempre disponíveis, qualquer phase, sem guards.
Delegam pros engines internos — zero estado novo, zero overhead.

```typescript
// Lifecycle
rs.getPhase()          // "created" | "booted" | "running"
rs.getTick()           // -1 antes do primeiro step
rs.isIdle()            // true entre ticks
rs.isAsync()           // true se hooks async detectados
rs.getCompilationMode() // "interpret" | "jit"

// States
rs.getStateIds()       // ["hp", "config", ...]
rs.hasState("hp")      // true

// Actions
rs.getActionIds()      // ["heal", "damage", ...]
rs.hasAction("heal")   // true
rs.getActionDefinition("heal") // ActionDefinition | undefined

// Rules
rs.getRuleCount()      // 2
rs.hasRule("auto-heal") // true

// Events
rs.getEventDefinition("hit") // EventDefinition | undefined

// Views
rs.getViewCount()      // 1
rs.hasView("combat")   // true
rs.getView("combat")   // IViewRecord (dados computados, locked após step)
rs.getViewStateDeps()  // Set<string> — paths observados
```

Records de views ficam **locked** (read-only) após cada step. O evaluator
faz `unlock → compute → lock` a cada tick. Tentativa de `set()` em view
locked lança erro — protege contra mutação externa entre ticks.

## Temporal

Subdivisão temporal via physics. Default 1:1 (tick === step, sem cycle).

```typescript
{ temporal: { stepSize: 4, cycleSize: 10 } }
```

| Counter | Descrição | Acesso |
|---------|-----------|--------|
| `tick` | Global, sempre incrementa | `ctx.run("tick")` |
| `step` | Incrementa a cada `stepSize` ticks | `ctx.run("step")` |
| `tickAt` | Posição do tick no step (1..stepSize) | `ctx.run("tickAt")` |
| `cycle` | Incrementa a cada `cycleSize` steps | `ctx.run("cycle")` |
| `stepAt` | Posição do step no cycle (1..cycleSize) | `ctx.run("stepAt")` |

## Factories — Uso em Testes

As duas fases de construção são funções puras exportáveis (ADR-053).

```typescript
import {
  createSystemComponents,
  bootDefinitions,
} from "@statedelta-axiom/realm-system";

// Fase 1 — cria infraestrutura
const components = createSystemComponents({ enableViews: true });

// Fase 2 — popula realm
const result = bootDefinitions(components, {
  states: [{ id: "hp", type: "counter", data: { value: 100 } }],
  rules: [{ id: "r1", priority: 1, when: () => true, then: [...] }],
});

// Testar sem lifecycle, sem ticks
components.store.has("hp");           // true
components.ruleEngine.has("r1");      // true
components.actionEngine.invoke("heal", undefined, components.ctx);
```

## Exports

```typescript
// RS
import { createRealmSystem } from "@statedelta-axiom/realm-system";

// Factory
import {
  createSystemComponents,
  bootDefinitions,
} from "@statedelta-axiom/realm-system";
import type { SystemComponents } from "@statedelta-axiom/realm-system";

// Módulos auxiliares
import {
  createRealmCtx,
  createTransitionStore,
  createBuiltinHandlers,
  createRequestBridge,
  computeTemporalEnv,
  buildRunEnv,
  initObservedPaths,
  captureObservedValues,
  EventEmitter,
} from "@statedelta-axiom/realm-system";

// Types
import type {
  RealmPhysics,
  RealmSystemConfig,
  SystemPhase,
  StepResult,
  RequestEntry,
  RealmSnapshot,
  RealmCtx,
  DeltaInfo,
  BootOptions,
  SystemEvents,
  IRealmSystem,
  TransitionStore,
  TemporalCounters,
  IEventEmitter,
} from "@statedelta-axiom/realm-system";
```

## Arquitetura

Para internals técnicos, ver [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md).

Para decisões arquiteturais (59 ADRs), ver [`docs/DECISIONS.md`](./docs/DECISIONS.md).

## Licença

MIT