/**
 * # adapters/broker — the venue-agnostic broker/feed adapter SEAM (kestrel-7o2.4, increment 1)
 *
 * The charter claim this module proves: **`sim | paper | live` is ONE Session path where only the
 * gate behind the seam differs** (CONTEXT: Mode — "one engine path; only the gate differs"; sim.ts
 * §"One code path, one gate"). This file names the two seams that make that true and the mode-keyed
 * factory that selects a gate — WITHOUT any IBKR code, any network, any npm dependency, or any
 * real-money path (that is a later increment).
 *
 * ## The execution seam is ALREADY the Gate (engine/plans.ts)
 * The engine hands the gate an {@link OrderIntent} that is **fully resolved before it arrives**: a
 * numeric `px` (a silent mid is forbidden, RUNTIME §4), never-naked enforced, the price-resolution
 * receipt (`sourceAnnotation`) attached, the directional-guard evidence (`moneyness`/`covered`)
 * threaded. So a broker adapter is a **TRANSMITTER, never a re-pricer** — it routes the intent to a
 * venue and reports back what the venue did. A {@link BrokerAdapter} IS a {@link Gate}.
 *
 * ## How the reference gate (SimGate, session/sim.ts) surfaces ORDER events — the pattern to MIRROR
 * `SimGate.submit` is SYNCHRONOUS: it rests the intent in the {@link SimFillEngine} (the judge) and
 * returns a `string` ref. The fill engine APPENDS its typed ORDER events (`place | fill | reject |
 * cancel`) to a cumulative `events` buffer; an injected `drain()` closure slices the fresh ones onto
 * the emitted bus (stamping `seq`). The engine learns of fills ONLY by OBSERVING `ORDER fill` bus
 * events — `SessionCore.step` feeds each drained fill back via `engine.onEvent(fillEvent)` (the
 * fills-before-sweep ordering, RUNTIME §7). The engine never simulates a fill itself.
 *
 * A {@link BrokerAdapter} mirrors this EXACTLY (it does not invent a divergent event path): it holds
 * an `events` buffer of seq-less {@link NewBusEvent}s, its `submit`/`cancel` append `place | fill |
 * reject | cancel` records to it, and the SAME injected `drain()` surfaces them + feeds fills back to
 * the engine. The {@link OrderAction} set is CLOSED (`place | cancel | fill | reject`, bus/types.ts,
 * guarded by `isValidStreamType`) — an adapter stays within it.
 *
 * ## The FEED seam is the bus itself
 * There is NO abstract feed interface in the runtime today: `SessionCore.step(ev: BusEvent)` consumes
 * the {@link BusEvent} union directly (a `META` header + `TICK/BOOK` carrying the real
 * `OptionQuote[]` legs + `underlier_px` + `TICK/SPOT` carrying `px`/`bid`/`ask`). A {@link FeedSource}
 * is therefore exactly a **producer of that SAME union** — the mock replays a deterministic in-memory
 * sequence; a live feed would translate a venue's market-data stream into the identical shapes. No new
 * event type is introduced, so `SessionCore.step` folds a FeedSource's output with zero changes.
 *
 * ## Mode is first-class, and LIVE fails closed
 * `Mode` is `sim | paper | live` (bus/types.ts); the record layer already branches on it (`fidelityOf`,
 * `instanceIdentityOf`). The protocol authorization scopes DELIBERATELY EXCLUDE `broker`/`live` from
 * {@link WALLET_SIGNABLE_SCOPES} (protocol/index.ts) — **live authority requires a human signature, never
 * a wallet/config alone**. {@link makeGate} encodes exactly that boundary: `sim` returns a SimGate
 * (byte-identical to today), `paper` returns a {@link BrokerAdapter}-backed gate, and `live` FAILS CLOSED
 * with a typed {@link LiveGateRefused} UNLESS an explicit, human-authorized {@link LiveArm} is present.
 * **This increment provides NEITHER a live arm NOR live routing** — so the only correct outcome for `live`
 * here is the typed refusal.
 *
 * @remarks Increment 1 is the SEAM + the fail-closed factory. The green implementation (the reference
 * paper adapter's fill model, the SimGate construction, the live arm's signature verification) lands
 * behind these shapes; the stubs below throw {@link NotImplemented} until then.
 */

import type { Gate, OrderIntent } from "../engine/index.ts";
import type { BusEvent, Mode, NewBusEvent, OrderAction, Right } from "../bus/index.ts";
import type { SimFillEngine, SpotFillEngine } from "../fill/index.ts";
// The reference `sim` gate. It IS the venue face for `sim` mode (a Gate), so it lives behind the same
// factory as the paper adapter — `makeGate("sim", …)` returns THIS exact class, byte-identical to the
// hardwired `new SimGate(...)` SessionCore used before (a pure refactor). Value import (constructed
// below); the sim.ts↔adapters edge is a runtime-only cycle (each side names the other solely inside a
// function body / a constructor, never at module-init), so ESM resolves it with live bindings.
import { SimGate } from "../session/sim.ts";

/** A seq-less ORDER event — the exact `NewBusEvent` variant {@link SimFillEngine} appends to its
 * cumulative log; the owner (the emitted stream via the injected `drain`) stamps `seq`. */
type NewOrderEvent = Extract<NewBusEvent, { stream: "ORDER" }>;

// ─────────────────────────────────────────────────────────────────────────────
// (1) BrokerAdapter — a Gate-shaped venue face
// ─────────────────────────────────────────────────────────────────────────────

/**
 * A venue-agnostic broker adapter (kestrel-7o2.4). A BrokerAdapter **IS a {@link Gate}** — the one
 * execution seam (engine/plans.ts): `submit` transmits an ALREADY-RESOLVED {@link OrderIntent} to the
 * venue and returns the ref the engine correlates later `ORDER` events against; `cancel` pulls a
 * resting order. Because the intent arrives fully priced + never-naked-checked + receipt-bearing, the
 * adapter is a **transmitter, never a re-pricer** (it must not touch `intent.px`).
 *
 * ORDER events (`place | fill | reject | cancel`) it produces are surfaced back to the engine EXACTLY
 * as {@link SimFillEngine} surfaces the sim judge's: appended to {@link events} (seq-less
 * {@link NewBusEvent}s), then an injected `drain()` closure stamps them onto the emitted bus and feeds
 * `fill`s back via `engine.onEvent(fillEvent)`. The adapter never invents a divergent event path, and
 * every event it emits inhabits the CLOSED `OrderAction` vocabulary.
 */
export interface BrokerAdapter extends Gate {
  /** The injected clock (epoch ms) the driver pins before each event — the gate seam is `now`-less
   * (RUNTIME §0); the driver owns the clock and pins it, exactly as `SessionCore` pins `SimGate.now`.
   * Stamped onto the ORDER events this adapter emits. */
  now: number;
  /** Transmit a resolved intent to the venue; return the venue ref (the engine correlates fills
   * against it). MUST NOT re-price — `intent.px` is the resolved limit. */
  submit(intent: OrderIntent): string;
  /** Pull a resting order by its ref. A no-op on an unknown/already-terminal ref (fail-closed, never
   * a crash). */
  cancel(ref: string): void;
  /** The cumulative typed ORDER events this adapter has produced, in order — seq-less
   * {@link NewBusEvent}s mirroring {@link SimFillEngine.events}. The owner (the emitted stream via the
   * injected `drain`) stamps `seq`; a `fill` here is what the engine OBSERVES through
   * `engine.onEvent`. Read-only. */
  readonly events: readonly NewBusEvent[];
  /** The venue's AUTHORITATIVE position PULL (kestrel-7o2.9) — a queryable snapshot of the broker's
   * own reported net position per {@link PositionKey}, the source of truth the reconciliation-trip
   * compares the engine's EXPECTED positions against (ADR-0034 §4: "reconciliation must anchor to the
   * broker's AUTHORITATIVE PULL, not only push"). OPTIONAL: the reference sim/paper mock exposes it
   * (an in-memory net-of-fills query); a push-only transport that cannot pull is fail-closed at
   * reconcile time. Never re-prices — a read-only report. */
  positions?(): PositionSnapshot;
  /** The venue's TRUE per-intent contract multiplier — dollars per point per contract (kestrel-7o2.8,
   * defense-in-depth). OPTIONAL: a venue that knows each contract's multiplier (the IBKR paper face
   * resolves it from its {@link BrokerAdapter} ContractBook — an option `100`, an equity `1`) exposes
   * it so the seam's {@link makeLiveClamp L0 clamp} computes `notional = px·qty·multiplier` on the REAL
   * per-contract notional, NOT a flat `1×` that is 100× too loose for a 100× option. `makeGate("paper")`
   * reads it STRUCTURALLY (the seam never imports a venue transport) and threads it into the clamp. A
   * venue that cannot resolve a multiplier omits this; the clamp then keeps its configured flat one. */
  multiplierOf?(intent: OrderIntent): number;
}

/** Construction deps for the reference **mock/paper** {@link BrokerAdapter} (kestrel-7o2.4). Mirrors
 * how {@link SimFillEngine} + `SessionCore.#drain` are wired: `drain` surfaces this adapter's fresh
 * {@link BrokerAdapter.events} onto the emitted stream (and feeds `fill`s back to the engine) after
 * every `submit`/`cancel`, exactly as the SimGate closure does. Zero network. */
export interface PaperBrokerDeps {
  /** Surface this adapter's fresh ORDER events — the SimGate `() => void drain()` analogue. */
  readonly drain: () => void;
  /** Contract multiplier (dollars per point per contract). Carried for parity with the fill engine;
   * the paper venue's deterministic fill model uses the intent's own `px` as the transmitted limit. */
  readonly multiplier?: number;
}

/** Build one seq-less ORDER event from a resolved intent, mirroring {@link SimFillEngine}'s private
 * `#emit` field-for-field (`order_id`, optional `plan`/`plan_instance`, `instrument`, `side`, `qty`,
 * `strike`/`right` when present, then `px` + optional `reason`). The adapter NEVER invents a divergent
 * event path — every event inhabits the CLOSED {@link OrderAction} vocabulary and reads back through
 * the same projector the sim ORDER events do. */
function orderEvent(
  action: OrderAction,
  ts: number,
  intent: OrderIntent,
  extra: { readonly px: number; readonly reason?: string },
): NewOrderEvent {
  return {
    ts,
    stream: "ORDER",
    type: action,
    order_id: intent.ref,
    ...(intent.plan !== undefined ? { plan: intent.plan } : {}),
    ...(intent.plan_instance !== undefined ? { plan_instance: intent.plan_instance } : {}),
    instrument: intent.instrument,
    side: intent.side,
    qty: intent.qty,
    ...(intent.strike !== undefined ? { strike: intent.strike } : {}),
    ...(intent.right !== undefined ? { right: intent.right } : {}),
    px: extra.px,
    ...(extra.reason !== undefined ? { reason: extra.reason } : {}),
  };
}

/**
 * The reference **mock/paper** broker adapter (kestrel-7o2.4): a deterministic, in-memory,
 * ZERO-NETWORK venue that transmits an intent and reports a deterministic paper fill back as an
 * `ORDER fill` event — the full paper loop with no IBKR code, no wall clock, no RNG.
 *
 * It mirrors {@link SimFillEngine}'s ORDER-event flow EXACTLY: `submit` appends a `place` then a `fill`
 * (both at the intent's ALREADY-RESOLVED `px` — a TRANSMITTER, never a re-pricer) to its cumulative
 * {@link BrokerAdapter.events} buffer, then calls the injected {@link PaperBrokerDeps.drain} — the SAME
 * closure {@link SimGate} calls — which surfaces the fresh events onto the emitted bus and feeds the
 * `fill` back to the engine via `engine.onEvent`. Because the paper venue fills on submit, no order ever
 * rests, so `cancel` is a fail-closed no-op (never a crash).
 */
class PaperBroker implements BrokerAdapter {
  now = 0;
  readonly #events: NewOrderEvent[] = [];
  readonly #drain: () => void;

  constructor(deps: PaperBrokerDeps) {
    // `multiplier` is carried for parity with the fill engine; the paper venue's deterministic fill
    // model uses the intent's own `px` as the transmitted limit, so it never enters the price path.
    void deps.multiplier;
    this.#drain = deps.drain;
  }

  /** The cumulative typed ORDER events this adapter has produced, in order (read-only). A `fill` here
   * is what the engine OBSERVES through `engine.onEvent`; the injected `drain` stamps `seq`. */
  get events(): readonly NewBusEvent[] {
    return this.#events;
  }

  submit(intent: OrderIntent): string {
    // TRANSMITTER (never-naked upstream, RUNTIME §4): transmit the resolved intent as a `place`, then
    // report the deterministic paper fill — both at `intent.px`, never a re-priced/mid anchor. Append
    // to the buffer FIRST, then drain, exactly as SimGate rests-then-drains.
    this.#events.push(orderEvent("place", this.now, intent, { px: intent.px }));
    this.#events.push(orderEvent("fill", this.now, intent, { px: intent.px, reason: "paper" }));
    this.#drain();
    return intent.ref; // the ref the engine correlates the fill against (mirrors SimGate echoing the ref)
  }

  cancel(_ref: string): void {
    // The paper venue fills on submit, so nothing rests — a cancel is a fail-closed no-op. Still drains
    // (zero fresh events ⇒ harmless) to keep the rest-then-drain shape identical to SimGate.cancel.
    this.#drain();
  }

  /** The venue's AUTHORITATIVE position PULL (kestrel-7o2.9): net-of-fills, signed (buy `+`, sell `−`)
   * per {@link PositionKey}, folded from this adapter's own `ORDER fill` records. Deterministic (no
   * clock/RNG) and read-only — the reconciliation-trip anchors to THIS, the broker's own truth, not the
   * engine's push stream (ADR-0034 §4). A push-only live transport that cannot pull would simply omit
   * this method; the paper double is fully queryable. */
  positions(): PositionSnapshot {
    const snap: Record<PositionKey, number> = {};
    for (const ev of this.#events) {
      if (ev.type !== "fill") continue;
      const key = positionKeyOf(ev);
      snap[key] = (snap[key] ?? 0) + (ev.side === "buy" ? ev.qty : -ev.qty);
    }
    return snap;
  }
}

/**
 * The reference **mock/paper** broker adapter (kestrel-7o2.4): a deterministic, in-memory,
 * ZERO-NETWORK venue that transmits an intent and reports a deterministic paper fill back as an
 * `ORDER fill` event — the full paper loop with no IBKR code.
 */
export function makePaperBroker(deps: PaperBrokerDeps): BrokerAdapter {
  return new PaperBroker(deps);
}

// ─────────────────────────────────────────────────────────────────────────────
// (2) FeedSource — a producer of the existing BusEvent union
// ─────────────────────────────────────────────────────────────────────────────

/**
 * A venue-agnostic **feed** (kestrel-7o2.4). The runtime has no abstract feed interface: `SessionCore`
 * folds the {@link BusEvent} union directly. A FeedSource is exactly a producer of that SAME union — a
 * `META` header, `TICK/BOOK` with real `OptionQuote` legs, `TICK/SPOT` with `px`/`bid`/`ask` — so
 * `SessionCore.step` consumes it unchanged. The mock replays a deterministic in-memory sequence; a live
 * feed would translate a venue's market-data stream into the identical shapes.
 */
export interface FeedSource {
  /** The bus events this source produces, in order — the SAME union `SessionCore.step` consumes. */
  events(): Iterable<BusEvent>;
}

/** The reference **mock** feed (kestrel-7o2.4): replays a fixed in-memory {@link BusEvent} sequence —
 * the SAME union `SessionCore.step` already folds, so it consumes the feed with zero changes and no new
 * event type. Deterministic (no clock/RNG): re-iterable, byte-stable across calls. A live feed is a
 * drop-in that yields the identical union off a venue's market-data stream. */
class ReplayFeed implements FeedSource {
  readonly #events: readonly BusEvent[];
  constructor(events: readonly BusEvent[]) {
    this.#events = events;
  }
  events(): Iterable<BusEvent> {
    return this.#events;
  }
}

export function replayFeed(events: readonly BusEvent[]): FeedSource {
  return new ReplayFeed(events);
}

// ─────────────────────────────────────────────────────────────────────────────
// (3) The mode-keyed gate factory — one Session path, only the gate differs
// ─────────────────────────────────────────────────────────────────────────────

/** The gate mode (kestrel-7o2.4) — the same closed {@link Mode} vocabulary the bus/record layer keys
 * on (`sim | paper | live`). */
export type GateMode = Mode;

/**
 * The **risk limits** one {@link LiveArm} authorizes (kestrel-7o2.9) — a plain typed config carried ON
 * the arm, checked by the {@link makeLiveClamp L0 pre-transmit clamp} BEFORE any order reaches the wire
 * (ADR-0034 §4). All three are hard ceilings; over ANY of them ⇒ a typed {@link ClampRefused}, never a
 * transmit (fail-closed / bounded-risk, RUNTIME §8). The broker's own margin check is NOT the risk
 * boundary — L0 sits ABOVE the adapter (ADR-0034 §7).
 */
export interface RiskLimits {
  /** Max single-order size (contracts/shares) — `intent.qty` may not exceed it. */
  readonly maxOrderQty: number;
  /** Max ABSOLUTE net position per {@link PositionKey} AFTER the order (given current positions). */
  readonly maxPositionQty: number;
  /** Max notional per order in dollars: `px × qty × multiplier` may not exceed it. */
  readonly maxNotionalUsd: number;
}

/** The UNBOUNDED paper-clamp limits (kestrel-7o2.21) — used when `makeGate("paper", …)` is called with
 * NO {@link PaperGateDeps.limits} config. Every ceiling is `+Infinity`, so no order is ever refused for
 * size/position/notional, yet the paper gate is STILL a composed {@link LiveClamp} (the kill-switch and
 * reconciliation-trip remain reachable) rather than the bare adapter. This keeps the pre-clamp paper
 * path's emitted bytes unchanged while making the L0 envelope a real, reachable call site. */
export const UNBOUNDED_PAPER_LIMITS: RiskLimits = {
  maxOrderQty: Number.POSITIVE_INFINITY,
  maxPositionQty: Number.POSITIVE_INFINITY,
  maxNotionalUsd: Number.POSITIVE_INFINITY,
};

/**
 * A **human-signed grant** of live authority over a specific {@link RiskLimits} (kestrel-7o2.9). Live
 * is NOT wallet-signable — the protocol excludes `broker`/`live` from {@link WALLET_SIGNABLE_SCOPES}
 * (`src/protocol/index.ts`), so a wallet/config alone can never arm live; a human signature is
 * required. This is the RAW claim a caller presents; {@link armLive} verifies its signature through an
 * {@link AuthoritySigner} and only then mints the unforgeable {@link LiveArm}. A grant is a plain
 * object — it is NOT itself authority; presenting one proves nothing until the signature verifies.
 */
export interface LiveAuthorityGrant {
  /** Must be the non-wallet-signable `"live"` scope (protocol/index.ts). */
  readonly scope: "live";
  /** The risk limits this human signature authorizes — the signature is OVER these limits, so the
   * limits cannot be widened after signing without invalidating the signature. */
  readonly limits: RiskLimits;
  /** Opaque handle to the human signature over `limits` (never a key/routine — mirrors the protocol
   * `CertifiedReceipt.signatureRef` open/closed seam). */
  readonly signatureRef: string;
}

/**
 * Verifies the human signature on a {@link LiveAuthorityGrant} (kestrel-7o2.9) — the open/closed seam
 * that keeps the live-authority proof out of this module. **Production** wires a real signature
 * verifier (the human wallet/HSM signature over the canonical limits); **tests** wire a mock signer
 * over a deterministic {@link sha256} of the limits. {@link armLive} calls `verify` and fails closed if
 * it returns `false` — a grant whose signature does not verify can NEVER mint an arm.
 */
export interface AuthoritySigner {
  verify(grant: LiveAuthorityGrant): boolean;
}

/**
 * The explicit, human-authorized Kestrel-level **LIVE arm** (kestrel-7o2.4 declared it; kestrel-7o2.9
 * makes it REAL and UNFORGEABLE). It is an OPAQUE, BRANDED token — the exported name is a type alias to
 * a class ({@link LiveArmToken}) with a private `#` brand, and the class value is NOT exported. Two
 * walls make it unforgeable:
 *
 *  1. **Nominal brand (compile-time).** The private `#brand` field makes a plain object literal NOT
 *     structurally assignable to `LiveArm` — a forger must reach for `as unknown as LiveArm`, which the
 *     runtime wall then catches.
 *  2. **Construction guard (run-time).** The only mint is {@link armLive}, which verifies a human
 *     signature ({@link AuthoritySigner}) before `new`-ing the token. Nothing else can construct it, so
 *     {@link isLiveArm} (a `#brand in x` check) rejects every fabricated value.
 *
 * The arm CARRIES the {@link RiskLimits} it authorizes (read via {@link armLimits}); those limits are
 * what the human signed over, so a caller cannot self-grant wider limits by hand-rolling an arm. Live
 * authority is NOT wallet-signable ({@link WALLET_SIGNABLE_SCOPES} excludes `broker`/`live`).
 */
export type LiveArm = LiveArmToken;

/** The unforgeable {@link LiveArm} token (kestrel-7o2.9). NOT exported — the private `#brand` field
 * blocks structural forgery AND the class value is unreachable outside this module, so {@link armLive}
 * (signature-verified) is the sole mint. */
class LiveArmToken {
  /** Nominal, private brand — no object literal can carry it, so `#brand in x` is a forgery-proof
   * runtime check and the type is not structurally assignable from a plain object. */
  readonly #brand = true;
  constructor(
    /** The risk limits the human signature authorized. */
    readonly limits: RiskLimits,
    /** The verified grant this arm was minted from (its `signatureRef` is the authority receipt). */
    readonly grant: LiveAuthorityGrant,
  ) {
    void this.#brand;
  }
  /** The forgery-proof runtime brand check — true iff `x` was minted by {@link armLive}. */
  static has(x: unknown): x is LiveArmToken {
    return typeof x === "object" && x !== null && #brand in x;
  }
}

/** The sim-gate ingredients {@link makeGate} needs to build the reference SimGate for `sim` mode —
 * the SAME pieces `SessionCore` hands `new SimGate(...)` today, so the `sim` path stays byte-identical.
 * (`spotAllowed` = only the strict-cross family has a spot judge; `drain` surfaces fills, RUNTIME §7.) */
export interface SimGateDeps {
  readonly fill: SimFillEngine;
  readonly spotFill: SpotFillEngine;
  readonly spotAllowed: boolean;
  readonly drain: () => void;
}

/**
 * The paper-mode L0 clamp config (kestrel-7o2.21) — the carrier for the {@link makeGate}(`"paper"`)
 * safety envelope. It supplies the {@link RiskLimits} (and multiplier/tolerance/kill-switch/position
 * readers) the paper clamp enforces, sourced from CONFIG rather than a human signature — paper is
 * deliberately NOT human-signable. `makeGate` mints a {@link PaperArm} from these limits (or uses the
 * supplied {@link arm}) and wraps `deps.broker` in a {@link makeLiveClamp L0 clamp}. Absent ⇒ the paper
 * gate is still CLAMPED (never the bare adapter) but with UNBOUNDED limits, so its bytes are unchanged
 * from the pre-clamp paper path while the kill-switch/reconciliation-trip remain reachable.
 */
export interface PaperGateDeps {
  /** Config-supplied risk ceilings the paper clamp enforces. Absent ⇒ unbounded (no size/position/
   * notional refusal), but the clamp is still composed (kill-switch + reconciliation reachable). */
  readonly limits?: RiskLimits;
  /** A pre-minted {@link PaperArm} (from {@link makePaperArm}); overrides {@link limits} if present.
   * Deliberately NOT a {@link LiveArm} — a paper gate can never carry live authority. */
  readonly arm?: PaperArm;
  /** Contract multiplier for `notional = px·qty·multiplier`. Absent ⇒ `1`. */
  readonly multiplier?: number;
  /** Reconciliation tolerance (absolute qty per key). Absent ⇒ `0`. */
  readonly tolerance?: number;
  /** The kill-switch to consult/trip. Absent ⇒ the clamp mints a fresh one. */
  readonly killSwitch?: KillSwitch;
  /** Engine/Blotter EXPECTED positions for the max-position check + reconcile. Absent ⇒ the broker's
   * own PULL ({@link BrokerAdapter.positions}) if it exposes one, else empty. */
  readonly positions?: () => PositionSnapshot;
  /** The broker's AUTHORITATIVE position PULL for the reconciliation-trip. Absent ⇒ the broker's own
   * {@link BrokerAdapter.positions} if it exposes one, else empty. */
  readonly brokerPositions?: () => PositionSnapshot;
}

/** Everything the mode-keyed factory may need. Exactly one branch is exercised per mode; the others
 * are absent (a construction-order convenience, like `ComposedProvider`). */
export interface GateDeps {
  /** The SimGate ingredients — required for `sim`. */
  readonly sim?: SimGateDeps;
  /** The venue adapter — required for `paper` (and, in a later increment, `live`). */
  readonly broker?: BrokerAdapter;
  /** The paper L0 clamp config (kestrel-7o2.21) — the {@link RiskLimits}/multiplier/tolerance the
   * `paper` gate's {@link makeLiveClamp clamp} enforces. Absent ⇒ an unbounded-but-still-composed clamp
   * (the paper gate is NEVER the bare adapter). NOT human-signable — paper authority is config, not a
   * signature. */
  readonly paper?: PaperGateDeps;
  /** The explicit human-authorized live arm — the ONLY thing that could unlock `live`. Absent in this
   * increment ⇒ `makeGate("live", …)` fails closed with {@link LiveGateRefused}. Accepts an EXPLICIT
   * `undefined` (not just omission): "no arm" is a first-class fail-closed input a caller may state
   * outright, and it must refuse identically to omitting it (exactOptionalPropertyTypes). */
  readonly liveArm?: LiveArm | undefined;
  /** The PAPER VENUE to serve this gate from (kestrel-7o2.8) — the name a venue adapter registered
   * itself under via {@link registerPaperVenue} (e.g. `"ibkr"`). An alternative to handing
   * {@link broker} directly: it lets `makeGate("paper", …)` reach a venue face WITHOUT this module
   * importing that venue's transport (so no npm/socket dependency ever lands on the `sim` path). An
   * UNREGISTERED name is refused, never a silent fallthrough. */
  readonly venue?: PaperVenue | undefined;
}

// ── the paper-venue registry (kestrel-7o2.8) ─────────────────────────────────

/** The name a paper venue adapter registers itself under (e.g. `"ibkr"`). */
export type PaperVenue = string;

/** How {@link makeGate} reaches a registered paper venue — a THUNK, so the venue's adapter (and its
 * socket/npm dependencies) is only touched when a `paper` gate actually asks for it. */
export type PaperVenueFactory = () => BrokerAdapter;

const PAPER_VENUES = new Map<PaperVenue, PaperVenueFactory>();

/**
 * Register a venue adapter as the {@link BrokerAdapter} `makeGate("paper", { venue })` serves
 * (kestrel-7o2.8). The venue's own module calls this at its composition root, so THIS module never
 * imports a venue transport — `sim` keeps its zero-dependency path, byte-identically. Registration is
 * PAPER-ONLY by construction: nothing here can unlock `live`, which still requires the human-signed
 * {@link LiveArm} and refuses with {@link LiveGateRefused} regardless of what is registered.
 */
export function registerPaperVenue(venue: PaperVenue, make: PaperVenueFactory): void {
  PAPER_VENUES.set(venue, make);
}

/** The paper venues registered so far, sorted (a stable, loggable list — never an RNG/insertion-order
 * leak into a diagnostic). */
export function registeredPaperVenues(): readonly PaperVenue[] {
  return [...PAPER_VENUES.keys()].sort();
}

/** Resolve a registered paper venue, or refuse. An UNREGISTERED name is a LOUD error, never a silent
 * fallthrough to some other gate (fail-closed). Absent name ⇒ `undefined` (the caller then requires
 * an explicit {@link GateDeps.broker}). */
function resolvePaperVenue(venue: PaperVenue | undefined): BrokerAdapter | undefined {
  if (venue === undefined) return undefined;
  const make = PAPER_VENUES.get(venue);
  if (make === undefined) {
    const known = registeredPaperVenues();
    throw new Error(
      `makeGate: mode 'paper' names the UNREGISTERED venue ${JSON.stringify(venue)} (registered: ${known.length === 0 ? "none" : known.join(", ")}) — fail-closed, never a silent fallthrough`,
    );
  }
  return make();
}

// ── ONE shared kill-switch + TWO independently-proven ceilings (kestrel-7o2.8, defense-in-depth) ────
//
// A venue adapter MAY already carry its own {@link KillSwitch}, {@link RiskLimits}, and per-contract
// multiplier (the IBKR paper face does — its inbound pump trips a switch the moment the venue's own
// report proves a never-naked break, and it resolves each contract's true multiplier). The design here
// is deliberately defense-in-depth, and the two axes are NOT symmetric:
//
//   • KILL-SWITCH — exactly ONE, SHARED. `makeGate("paper", …)` reads the venue's own switch
//     (structurally — the seam never imports a venue module) and threads it INTO the clamp, so a trip
//     in EITHER layer (an operator STAND_DOWN on the seam, or the adapter's at-observation never-naked
//     trip) halts the WHOLE path. A second independent switch would leave a trip unable to reach the
//     other layer — the hole this closes.
//   • RISK CEILINGS — TWO layers, each INDEPENDENTLY LOAD-BEARING (ADR-0034 §4 defense-in-depth): the
//     SEAM clamp (this makeLiveClamp, L0) and the adapter's own venue-boundary WALL 5. The seam reads
//     the venue's SAME numeric limits + true per-contract multiplier and threads them in, so the two
//     never drift to divergent numbers — but neither is a mere backstop to a single "canonical owner":
//     a bare adapter (no seam) must still refuse, and a bare seam over a WALL-5-less adapter must still
//     refuse. Each is proven load-bearing by a mutation test that guts it and watches a test go RED.

/** Structural {@link KillSwitch} probe — true iff `x` presents the switch's read-and-trip surface. */
function isKillSwitch(x: unknown): x is KillSwitch {
  return (
    typeof x === "object" &&
    x !== null &&
    typeof (x as { tripped?: unknown }).tripped === "boolean" &&
    typeof (x as { trip?: unknown }).trip === "function"
  );
}

/** Structural {@link RiskLimits} probe — true iff `x` carries all three numeric L0 ceilings. */
function isRiskLimits(x: unknown): x is RiskLimits {
  return (
    typeof x === "object" &&
    x !== null &&
    typeof (x as { maxOrderQty?: unknown }).maxOrderQty === "number" &&
    typeof (x as { maxPositionQty?: unknown }).maxPositionQty === "number" &&
    typeof (x as { maxNotionalUsd?: unknown }).maxNotionalUsd === "number"
  );
}

/** The venue adapter's OWN kill-switch, if it self-guards (e.g. the IBKR paper face) — else `undefined`
 * (the reference mock/paper broker has none, so the clamp mints a fresh one, unchanged). Read
 * structurally so the seam never imports a venue transport. */
function venueKillSwitchOf(broker: BrokerAdapter): KillSwitch | undefined {
  const k = (broker as { killSwitch?: unknown }).killSwitch;
  return isKillSwitch(k) ? k : undefined;
}

/** The venue adapter's OWN configured L0 ceilings, if it exposes them — so the seam clamp enforces the
 * SAME limits (canonical, above the adapter) instead of an UNBOUNDED no-op. `undefined` for a venue
 * that carries none. Read structurally so the seam never imports a venue transport. */
function venueLimitsOf(broker: BrokerAdapter): RiskLimits | undefined {
  const l = (broker as { limits?: unknown }).limits;
  return isRiskLimits(l) ? l : undefined;
}

/** The venue adapter's OWN per-intent contract multiplier resolver, if it exposes one (kestrel-7o2.8) —
 * the IBKR paper face resolves it from its ContractBook (an option `100`, an equity `1`), so the seam
 * clamp computes `notional = px·qty·multiplier` on the venue's TRUE per-contract multiplier rather than
 * a flat `1×` that is 100× too loose for a 100× option. `undefined` for a venue that carries none (the
 * reference mock/paper broker), so the clamp keeps its configured flat multiplier. Read structurally so
 * the seam never imports a venue transport; the returned closure re-binds to the broker on each call. */
function venueMultiplierOf(broker: BrokerAdapter): ((intent: OrderIntent) => number) | undefined {
  const m = (broker as { multiplierOf?: unknown }).multiplierOf;
  if (typeof m !== "function") return undefined;
  return (intent: OrderIntent): number => (broker as Required<Pick<BrokerAdapter, "multiplierOf">>).multiplierOf(intent);
}

/**
 * The typed, fail-closed refusal {@link makeGate} raises for a `live` gate requested without an explicit
 * {@link LiveArm} (kestrel-7o2.4). A DISTINCT error class (not a bare `Error`) so a caller can catch the
 * live-authority refusal specifically and never mistake it for an unrelated failure — and so a `live`
 * request can NEVER silently fall through to a routing gate. Mirrors the protocol boundary: `live` is not
 * in {@link WALLET_SIGNABLE_SCOPES}; live authority requires a human signature (RUNTIME §8, fail-closed).
 */
export class LiveGateRefused extends Error {
  readonly mode = "live" as const;
  constructor(reason: string) {
    super(reason);
    this.name = "LiveGateRefused";
  }
}

/** Thrown by the reference stubs until the green implementation lands (kestrel-7o2.4 increment 1 is the
 * SEAM + the fail-closed factory; the behavior behind the shapes is a later increment). Distinct from
 * {@link LiveGateRefused} so a RED test can tell "not yet built" from "live refused by design". */
export class NotImplemented extends Error {
  constructor(what: string) {
    super(`kestrel-7o2.4: ${what} is not implemented in this increment (SEAM + RED phase)`);
    this.name = "NotImplemented";
  }
}

/**
 * Select the execution gate for a mode (kestrel-7o2.4) — the ONE line the hardwired
 * `this.gate = new SimGate(...)` (session/sim.ts) becomes, proving `sim | paper | live` is one Session
 * path where only the gate differs:
 *
 *  - `sim`   → the reference SimGate over {@link GateDeps.sim} — BYTE-IDENTICAL to today.
 *  - `paper` → the {@link BrokerAdapter} in {@link GateDeps.broker} (a BrokerAdapter IS a Gate).
 *  - `live`  → FAILS CLOSED with {@link LiveGateRefused} unless {@link GateDeps.liveArm} is present.
 *              This increment provides no arm and no routing, so `live` ALWAYS refuses here.
 *
 * The `sim` overload returns the concrete {@link SimGate} (its `now` is pinned by the driver each event),
 * so SessionCore keeps its `readonly gate: SimGate` field and this stays a pure refactor.
 *
 * ## The `paper` gate's L0 GUARANTEE — self-sufficient, never delegated (ADR-0034 §4, kestrel-ct9m)
 *
 * `makeGate("paper", …)` NEVER returns the bare adapter: it returns the {@link makeLiveClamp L0 clamp}
 * wrapped around it. The clamp's contract holds over a transport with NO walls of its own (the reference
 * mock today; a future live transport with no venue-side validation tomorrow) — "a bare seam over an
 * adapter with no WALL 5 still refuses". Concretely, BEFORE anything is transmitted, `submit` refuses with
 * a typed {@link ClampRefused} when ANY of these holds:
 *
 *  - the {@link KillSwitch} is tripped                                        ⇒ `"killed"`
 *  - `qty` or `px` is NON-FINITE (NaN/±Infinity)                              ⇒ `"not-a-number"`
 *  - `qty` is not a POSITIVE INTEGER, or `px` is not POSITIVE                 ⇒ `"malformed-intent"`
 *  - the contract multiplier is non-finite or non-positive                    ⇒ `"multiplier"`
 *  - `qty` exceeds `maxOrderQty`                                              ⇒ `"order-size"`
 *  - `|projected net position|` exceeds `maxPositionQty`                      ⇒ `"position"`
 *  - `px·qty·multiplier` exceeds `maxNotionalUsd`                             ⇒ `"notional"`
 *
 * The WELL-FORMEDNESS rows are load-bearing, not hygiene: every ceiling above them is a `>` comparison,
 * and a `>` comparison against a NaN, a negative, or a zero is `false` — i.e. it PASSES. Without those
 * rows a `qty = -1_000_000` sell clears the size ceiling, drives the notional negative past
 * `maxNotionalUsd`, and sign-inverts the projected position. Validity is checked HERE and not deferred to
 * the venue face's own WALL 1, because on a bare seam there is no WALL 1 to defer to.
 */
export function makeGate(mode: "sim", deps: GateDeps): SimGate;
export function makeGate(mode: GateMode, deps: GateDeps): Gate;
export function makeGate(mode: GateMode, deps: GateDeps): Gate {
  switch (mode) {
    case "sim": {
      // BYTE-IDENTICAL to the hardwired `new SimGate(this.fill, this.spotFill, …, () => void this.#drain())`
      // — the same class over the same ingredients, so the sim path's emitted bytes never move.
      const sim = deps.sim;
      if (sim === undefined) {
        throw new Error("makeGate: mode 'sim' requires deps.sim (the SimFillEngine ingredients) — none present (fail-closed)");
      }
      return new SimGate(sim.fill, sim.spotFill, sim.spotAllowed, sim.drain);
    }
    case "paper": {
      // The broker is resolved EITHER handed in directly (`deps.broker`, the reference mock/paper
      // double) OR through the venue REGISTRY (`deps.venue`, kestrel-7o2.8 — e.g. the IBKR paper face,
      // which registers itself so this module never imports its socket transport). An unregistered
      // venue is refused inside `resolvePaperVenue`; neither door can reach `live`.
      // The resolved broker is then CLAMPED (kestrel-7o2.21): the L0 pre-transmit clamp wraps the paper
      // broker so an over-limit / killed order is REFUSED before it reaches the venue. NEVER the bare
      // adapter. Authority is a PaperArm (config limits, not a human signature): paper carries no
      // real-money risk and is not human-signable, yet the clamp still records `provenance: "paper"` so
      // this gate can never be reached as a live gate.
      const broker = deps.broker ?? resolvePaperVenue(deps.venue);
      if (broker === undefined) {
        throw new Error(
          `makeGate: mode 'paper' requires deps.broker (a BrokerAdapter) or a registered deps.venue (registered: ${registeredPaperVenues().join(", ") || "none"}) — none present (fail-closed)`,
        );
      }
      const cfg = deps.paper;
      // SINGLE KILL-SWITCH (kestrel-7o2.8). If the resolved venue adapter self-guards (the IBKR paper
      // face trips a switch from INSIDE its inbound pump on an observed never-naked break), that ONE
      // switch must ALSO be the switch this seam clamp consults — otherwise the clamp mints a second,
      // independent one and a trip in either layer can never reach the other (the double-kill-switch
      // hole). Thread the venue's own switch into makeLiveClamp so a trip in EITHER layer halts the
      // WHOLE path. An explicit `cfg.killSwitch` still wins (a caller wiring one shared switch by hand).
      const killSwitch = cfg?.killSwitch ?? venueKillSwitchOf(broker);
      // TWO INDEPENDENTLY-PROVEN L0 CEILINGS, DEFENSE-IN-DEPTH (ADR-0034 §4). This is NOT one canonical
      // owner with a subordinate backstop — it is a SEAM ceiling (this makeLiveClamp, L0) AND an adapter
      // venue-boundary ceiling (the IBKR face's WALL 5), each proven load-bearing on its own (a bare
      // adapter with no seam still refuses; a bare seam over an adapter with no WALL 5 still refuses).
      // Prefer the venue adapter's OWN configured ceilings so the SEAM clamp enforces the SAME numeric
      // limits — read from the venue so the two layers can never drift to divergent numbers. Absent both
      // ⇒ UNBOUNDED (the reference mock/paper broker), so that path's bytes stay unchanged. This retires
      // the "benign UNBOUNDED no-op that masks the seam": the seam ceiling is now real, not a pass-through.
      const limits = cfg?.limits ?? venueLimitsOf(broker) ?? UNBOUNDED_PAPER_LIMITS;
      const arm = cfg?.arm ?? makePaperArm(limits);
      const brokerPull = (): PositionSnapshot => broker.positions?.() ?? {};
      // TRUE PER-CONTRACT MULTIPLIER (kestrel-7o2.8, the notional-100x fix). The seam clamp bounds
      // `notional = px·qty·multiplier`; on the registry path `deps.paper` is undefined, so before this
      // fix the multiplier defaulted to `1` and the seam's maxNotionalUsd ceiling was 100× too loose for
      // a 100× option — the adapter's WALL 5 was then the ONLY correct notional bound. Now the seam READS
      // the venue's own per-intent multiplier (option 100, equity 1) and threads it in, so the seam L0
      // ceiling is computed on REAL notional. An explicit `cfg.multiplier` still wins; a venue that
      // exposes none (the reference mock/paper broker) falls back to `1`, unchanged.
      return makeLiveClamp({
        underlying: broker,
        arm,
        multiplier: cfg?.multiplier ?? venueMultiplierOf(broker) ?? 1,
        positions: cfg?.positions ?? brokerPull,
        brokerPositions: cfg?.brokerPositions ?? brokerPull,
        ...(killSwitch !== undefined ? { killSwitch } : {}),
        ...(cfg?.tolerance !== undefined ? { tolerance: cfg.tolerance } : {}),
      });
    }
    case "live": {
      // FAIL CLOSED. Live authority is NOT wallet-signable (the protocol excludes `broker`/`live` from
      // WALLET_SIGNABLE_SCOPES) — a human signature is required. Without an explicit arm the ONLY correct
      // outcome is the typed refusal; the `live` request can NEVER silently fall through to a routing gate.
      const arm = deps.liveArm;
      if (arm === undefined) {
        throw new LiveGateRefused(
          "live authority requires an explicit, human-signed Kestrel arm — none present (fail-closed; live is not wallet-signable, protocol/index.ts, RUNTIME §8)",
        );
      }
      // MODE WALL (kestrel-7o2.21): the arm must be a signature-verified LiveArm. A PaperArm (config
      // authority) or any forgery is NOT a LiveArm, so it can NEVER unlock live — refuse fail-closed. This
      // is what makes a PaperArm paper-only: it authorizes the paper clamp, never a live gate.
      if (!isLiveArm(arm)) {
        throw new LiveGateRefused(
          "live authority requires a human-signed LiveArm minted by armLive — the supplied arm was not (a PaperArm or forgery can never authorize live, fail-closed)",
        );
      }
      // A genuine LiveArm was supplied, but THIS increment ships no live routing (the seam is declared,
      // not wired). Honestly distinct from the by-design refusal above, and still never a live gate.
      throw new NotImplemented("live gate routing");
    }
    default: {
      // Exhaustive over the closed Mode vocabulary — an unknown mode is refused, never a silent default.
      const never: never = mode;
      throw new Error(`makeGate: unknown mode ${JSON.stringify(never)} (fail-closed)`);
    }
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// (4) The SAFETY ENVELOPE (kestrel-7o2.9) — arm mint + L0 clamp + kill-switch + reconciliation-trip
//
// This is the per-process safety envelope that MUST land BEFORE any live-capable order-routing code
// (ADR-0034 §4). It ships NO live routing / no venue transport — it is exercised in full against the
// reference mock/paper {@link BrokerAdapter} (an in-memory broker double). The cross-process singleton
// (the control-plane lease, ADR-0034 §7 / kestrel-7o2.2) is a SEPARATE bead — NOT built here.
//
// Increment status: SEAM + RED. The shapes below are declared; the stubs throw {@link NotImplemented}
// until the green implementation lands. (Pure helpers — {@link positionKeyOf}, {@link isLiveArm},
// {@link armLimits} — are real, since there is no behavior to defer in a key/brand read.)
// ─────────────────────────────────────────────────────────────────────────────

/** A net-position key (kestrel-7o2.9): `` `${instrument}|${strike}|${right}` `` for an option leg,
 * bare `instrument` for a spot/equity leg (ADR-0017 — no fictional strike). The unit the L0
 * max-position check and the reconciliation-trip both key on. */
export type PositionKey = string;

/** A snapshot of SIGNED net position per {@link PositionKey} (kestrel-7o2.9) — buy `+`, sell `−`. Two
 * readings are compared at reconcile: the engine/Blotter EXPECTED snapshot vs the broker's
 * AUTHORITATIVE PULL ({@link BrokerAdapter.positions}). */
export interface PositionSnapshot {
  readonly [key: PositionKey]: number;
}

/** The {@link PositionKey} for an order/leg (kestrel-7o2.9) — pure, so the clamp, the engine-expected
 * snapshot, and the broker PULL all key identically (no skew). */
export function positionKeyOf(o: { readonly instrument: string; readonly strike?: number; readonly right?: Right }): PositionKey {
  return o.strike !== undefined && o.right !== undefined ? `${o.instrument}|${o.strike}|${o.right}` : o.instrument;
}

/**
 * Mint an unforgeable {@link LiveArm} from a human-signed {@link LiveAuthorityGrant} (kestrel-7o2.9) —
 * the SOLE construction path. Verifies the grant's signature through the injected {@link AuthoritySigner}
 * and, only if it verifies, `new`s the branded token carrying the signed {@link RiskLimits}. FAIL-CLOSED:
 * a grant whose signature does not verify (a wallet/config forgery, a widened-after-signing limit set)
 * throws {@link LiveGateRefused} — it can NEVER mint an arm. Live is not wallet-signable (protocol
 * excludes `broker`/`live` from {@link WALLET_SIGNABLE_SCOPES}); this is the human-signature wall.
 */
export function armLive(grant: LiveAuthorityGrant, signer: AuthoritySigner): LiveArm {
  // Wall 1 — the scope must be the non-wallet-signable `"live"` (protocol excludes it from
  // WALLET_SIGNABLE_SCOPES); a grant claiming any other scope can never be a live arm.
  if (grant.scope !== "live") {
    throw new LiveGateRefused(
      `armLive: a live arm requires scope "live" (got ${JSON.stringify(grant.scope)}) — fail-closed (live is not wallet-signable, protocol/index.ts)`,
    );
  }
  // Wall 2 — the human signature over the EXACT limits must verify. A wallet/config forgery, or a limit
  // set widened after signing, fails here and can NEVER mint an arm (fail-closed, RUNTIME §8).
  if (!signer.verify(grant)) {
    throw new LiveGateRefused(
      "armLive: the human signature over the risk limits did not verify — a wallet/config forgery or a widened-after-signing limit set cannot mint a live arm (fail-closed)",
    );
  }
  // Verified — mint the branded token carrying exactly the limits the human signed over. This is the
  // SOLE construction site (the class value is not exported), so `isLiveArm` is a total forgery check.
  return new LiveArmToken(grant.limits, grant);
}

/** The forgery-proof runtime brand check (kestrel-7o2.9) — true iff `arm` was minted by {@link armLive}.
 * A plain object literal (even one shaped like a grant, even cast `as unknown as LiveArm`) returns
 * `false`: the private `#brand` is unreachable except through the sole mint. REAL (not a stub) — a
 * brand read has no behavior to defer, and the clamp/factory rely on it to reject fabricated arms. */
export function isLiveArm(arm: unknown): arm is LiveArm {
  return LiveArmToken.has(arm);
}

/** The {@link RiskLimits} an {@link LiveArm} authorizes (kestrel-7o2.9) — the exact limits the human
 * signed over, read off the opaque token. REAL (not a stub). */
export function armLimits(arm: LiveArm): RiskLimits {
  return arm.limits;
}

/**
 * A **PAPER arm** (kestrel-7o2.21) — a distinct, BRANDED authority that carries config-supplied
 * {@link RiskLimits} for the paper venue WITHOUT any human signature. It is the paper-mode sibling of
 * {@link LiveArm}: same "an unforgeable token that carries limits" shape, but minted by a plain config
 * mint ({@link makePaperArm}) rather than the signature-verified {@link armLive}. This is what lets the
 * {@link makeLiveClamp L0 clamp} wrap the PAPER broker — the clamp becomes a REAL call site of the L0
 * envelope instead of dead code — WITHOUT fabricating live authority.
 *
 * The wall that keeps this safe: a PaperArm is NOT a {@link LiveArm} ({@link isLiveArm} returns `false`
 * for it, its `#paperBrand` is a different private field than {@link LiveArmToken}'s `#brand`), so it can
 * NEVER authorize a live gate — `makeGate("live", …)` rejects it fail-closed, and a clamp built from a
 * PaperArm records `provenance: "paper"`. Paper is deliberately not human-signable; a PaperArm proves
 * only paper authority, never live. {@link armLive} remains the SOLE mint of {@link LiveArm}.
 */
export type PaperArm = PaperArmToken;

/** The branded {@link PaperArm} token (kestrel-7o2.21). NOT exported — the private `#paperBrand` field
 * (distinct from {@link LiveArmToken}'s `#brand`) blocks structural forgery AND makes it un-confusable
 * with a LiveArm, so {@link makePaperArm} is the sole mint and {@link isPaperArm} is a total check. */
class PaperArmToken {
  /** Nominal, private brand — distinct from {@link LiveArmToken.prototype} so no PaperArm is ever an
   * accidental LiveArm; `#paperBrand in x` is a forgery-proof runtime check. */
  readonly #paperBrand = true;
  constructor(
    /** The config-supplied risk limits this paper arm authorizes (NOT a human signature). */
    readonly limits: RiskLimits,
  ) {
    void this.#paperBrand;
  }
  /** True iff `x` was minted by {@link makePaperArm}. */
  static has(x: unknown): x is PaperArmToken {
    return typeof x === "object" && x !== null && #paperBrand in x;
  }
}

/**
 * Mint a {@link PaperArm} from plain config {@link RiskLimits} (kestrel-7o2.21) — the paper-mode
 * counterpart to {@link armLive}, but with NO signature verification, because paper carries no
 * real-money risk and is deliberately NOT human-signable. The minted arm is a branded token, so a
 * forged/literal object still cannot pass as authority (the {@link makeLiveClamp} construction guard
 * rejects anything neither {@link isLiveArm} nor {@link isPaperArm}). It authorizes ONLY paper: it is
 * not a {@link LiveArm} and can never unlock `makeGate("live", …)`.
 */
export function makePaperArm(limits: RiskLimits): PaperArm {
  return new PaperArmToken(limits);
}

/** The forgery-proof runtime brand check for a {@link PaperArm} (kestrel-7o2.21) — true iff `arm` was
 * minted by {@link makePaperArm}. A plain literal (even cast `as unknown as PaperArm`) returns `false`,
 * and a genuine {@link LiveArm} returns `false` too (distinct brand): paper and live authority never
 * cross-authorize. REAL (not a stub). */
export function isPaperArm(arm: unknown): arm is PaperArm {
  return PaperArmToken.has(arm);
}

/** The authority a {@link makeLiveClamp L0 clamp} may be built on (kestrel-7o2.21): a signature-verified
 * {@link LiveArm} OR a config-minted {@link PaperArm}. Both are BRANDED tokens — a raw/forged literal is
 * neither, and the clamp constructor rejects it fail-closed. The clamp records which one built it
 * ({@link LiveClamp.provenance}) so a PaperArm-built clamp is paper-only. */
export type ClampArm = LiveArm | PaperArm;

/** Which L0 boundary a {@link ClampRefused} tripped (kestrel-7o2.9) — a closed, typed reason so a
 * caller can tell an over-limit refusal from a killed one and log the exact wall.
 *
 * `"not-a-number"` and `"multiplier"` (kestrel-7o2.10, A3) are the WELL-FORMEDNESS boundaries that must
 * clear BEFORE any ceiling comparison, because a ceiling comparison on a corrupt number FAILS OPEN
 * rather than closed:
 *  - `"not-a-number"` — a non-finite `qty`/`px`. `NaN > ceiling` is `false` for EVERY ceiling, so a NaN
 *    silently satisfies all three limits and transmits. Fail-closed demands the inverse: an unorderable
 *    number is REFUSED, never waved through.
 *  - `"multiplier"` — a non-finite or non-positive contract multiplier. `notional = px·qty·multiplier`,
 *    so a `0` multiplier computes EVERY notional as `0` and the `maxNotionalUsd` ceiling becomes inert
 *    (a `NaN`/negative one corrupts it just as silently). The ceiling is only as real as its inputs.
 *  - `"malformed-intent"` (kestrel-ct9m) — a FINITE but unorderable `qty`/`px`: a qty that is not a
 *    POSITIVE INTEGER (negative, zero, fractional) or a px that is not POSITIVE. Same fail-open shape as
 *    the NaN one, one step removed: `-1_000_000 > maxOrderQty` is `false`, `px · -1_000_000 · mult >
 *    maxNotionalUsd` is `false` (the notional goes NEGATIVE), and `projected = current + (buy ? qty :
 *    -qty)` SIGN-INVERTS, so a million-lot sell clears every ceiling and transmits. A `0`/negative px
 *    collapses or inverts the notional the same way. The L0 seam may not delegate its own validity to a
 *    venue face's WALL 1 (ADR-0034 §4: a bare seam over a WALL-less adapter still refuses). */
export type ClampLimit =
  | "order-size"
  | "position"
  | "notional"
  | "killed"
  | "not-a-number"
  | "multiplier"
  | "malformed-intent";

/**
 * The typed, fail-closed refusal the {@link makeLiveClamp L0 clamp} raises when an order is over a
 * {@link RiskLimits} ceiling OR the {@link KillSwitch} is tripped (kestrel-7o2.9). A DISTINCT error
 * class (not a bare `Error`, distinct from {@link LiveGateRefused}) so a caller catches an L0 refusal
 * specifically. The order is NEVER transmitted — the clamp throws BEFORE it calls the underlying
 * adapter's `submit` (ADR-0034 §4/§7, bounded-risk / never-naked). Carries the tripped {@link ClampLimit}
 * and the refused order's `ref` for the logged reason.
 */
export class ClampRefused extends Error {
  readonly limit: ClampLimit;
  readonly ref: string;
  constructor(limit: ClampLimit, ref: string, reason: string) {
    super(reason);
    this.name = "ClampRefused";
    this.limit = limit;
    this.ref = ref;
  }
}

/**
 * The **kill-switch** (kestrel-7o2.9, ADR-0034 §4) — a stateful, per-process halt the {@link makeLiveClamp
 * clamp} consults before EVERY transmit. A single operator action (or an adapter-detected degradation:
 * dead feed, lost heartbeat, reconciliation break) `trip`s it; once tripped ALL live transmission is
 * refused ({@link ClampRefused}, `"killed"`), fail-closed, with the logged reason. STAND_DOWN is always
 * reachable. It only ever latches ON — there is no un-trip on the seam (re-arming is a fresh human act,
 * out of band). Shared by injection so a reconciliation-trip and an operator halt hit the same switch.
 */
export interface KillSwitch {
  /** Has the switch been tripped? Once `true`, stays `true` (latch). */
  readonly tripped: boolean;
  /** The reason logged at the trip, or `null` while un-tripped. */
  readonly reason: string | null;
  /** Trip the switch (idempotent — first reason wins). All subsequent transmits refuse. */
  trip(reason: string): void;
}

/** Build a fresh, un-tripped {@link KillSwitch} (kestrel-7o2.9). Latches ON — `trip` is idempotent
 * (first reason wins) and there is NO un-trip on the seam; re-arming is a fresh human act, out of band. */
export function makeKillSwitch(): KillSwitch {
  let tripped = false;
  let reason: string | null = null;
  return {
    get tripped(): boolean {
      return tripped;
    },
    get reason(): string | null {
      return reason;
    },
    trip(r: string): void {
      // Idempotent latch: only the FIRST trip records its reason; subsequent trips are no-ops (the switch
      // never un-trips). STAND_DOWN is always reachable; once tripped, every consulting transmit refuses.
      if (!tripped) {
        tripped = true;
        reason = r;
      }
    },
  };
}

/**
 * The **L0 live clamp** (kestrel-7o2.9) — a {@link BrokerAdapter} wrapper at the live-gate boundary that
 * the underlying adapter CANNOT bypass. Its `submit`, BEFORE delegating to the underlying adapter,
 * checks the resolved {@link OrderIntent} against the {@link LiveArm}'s {@link RiskLimits} (max order
 * size, max position given current positions, max notional `= px·qty·multiplier`) AND the
 * {@link KillSwitch}. Over ANY limit, OR killed ⇒ a typed {@link ClampRefused} and the order is NEVER
 * transmitted (fail-closed). It also exposes {@link reconcile}, the reconciliation-trip.
 */
export interface LiveClamp extends BrokerAdapter {
  /** The kill-switch this clamp consults (the injected one, or a fresh one) — trip it to halt all
   * transmission; read {@link KillSwitch.tripped} to observe a reconciliation-trip. */
  readonly killSwitch: KillSwitch;
  /** Which authority built this clamp (kestrel-7o2.21): `"live"` iff a signature-verified
   * {@link LiveArm}, `"paper"` iff a config-minted {@link PaperArm}. Recorded so a PaperArm-built clamp
   * is PAPER-ONLY — it can never be presented as a live gate (a paper clamp never wraps a live
   * transport; `makeGate("live", …)` refuses a PaperArm at the factory, before any clamp is built). */
  readonly provenance: "live" | "paper";
  /**
   * The **reconciliation-trip** (kestrel-7o2.9, ADR-0034 §4). Pull the broker's AUTHORITATIVE position
   * snapshot and compare it, per {@link PositionKey}, against the engine/Blotter EXPECTED snapshot. Any
   * divergence beyond `tolerance` (a broker fill the engine did not originate, or an engine order with
   * no broker terminal state) ⇒ `trip` the {@link KillSwitch} with a logged reason, halting all further
   * transmission. Never a silent divergence. Anchors to the broker's PULL, not the push stream.
   */
  reconcile(): void;
}

/** Construction deps for {@link makeLiveClamp} (kestrel-7o2.9). */
export interface LiveClampDeps {
  /** The venue adapter the clamp wraps — the reference mock/paper {@link BrokerAdapter} in tests; a live
   * transport in production (later beads, behind this envelope). */
  readonly underlying: BrokerAdapter;
  /** The authority the clamp is built on (kestrel-7o2.21): a signature-verified {@link LiveArm} OR a
   * config-minted {@link PaperArm} — both carry the authorized {@link RiskLimits}. A FORGED/literal arm
   * (neither {@link isLiveArm} nor {@link isPaperArm}) is REJECTED at construction with a
   * {@link LiveGateRefused} — the clamp can never be built on fabricated authority. The clamp records
   * which one built it in {@link LiveClamp.provenance}; a PaperArm makes the clamp paper-only. */
  readonly arm: ClampArm;
  /** The kill-switch to consult/trip. Absent ⇒ the clamp mints a fresh one ({@link LiveClamp.killSwitch}). */
  readonly killSwitch?: KillSwitch;
  /** The engine/Blotter EXPECTED positions (signed net per {@link PositionKey}) — read for the
   * max-position check and the reconciliation-trip's expected side. */
  readonly positions: () => PositionSnapshot;
  /** The broker's AUTHORITATIVE position PULL for the reconciliation-trip (ADR-0034 §4). In production
   * this wires to `underlying.positions()`; injected here so the pull point is explicit and testable. */
  readonly brokerPositions: () => PositionSnapshot;
  /** Contract multiplier for `notional = px·qty·multiplier` (kestrel-7o2.8). Either a flat `number`
   * (one instrument class) OR a per-intent resolver `(intent) => number` — the shape `makeGate("paper")`
   * threads for a venue that knows each contract's TRUE multiplier (the IBKR face: option `100`, equity
   * `1`), so the notional ceiling is computed on real per-contract notional, never a flat `1×` that is
   * 100× too loose for a 100× option. */
  readonly multiplier: number | ((intent: OrderIntent) => number);
  /** Reconciliation tolerance (absolute qty per key). Absent ⇒ `0` (exact match required). */
  readonly tolerance?: number;
}

/**
 * Build the {@link LiveClamp L0 pre-transmit risk clamp} over an underlying {@link BrokerAdapter}
 * (kestrel-7o2.9). FAIL-CLOSED at construction: a forged/literal {@link LiveArm} (one {@link isLiveArm}
 * rejects) throws {@link LiveGateRefused} — the envelope can never be armed on fabricated authority.
 * The returned clamp IS a BrokerAdapter (a Gate), so it drops into the same execution seam; its `submit`
 * enforces the arm's {@link RiskLimits} + the {@link KillSwitch} above the underlying adapter, and
 * {@link LiveClamp.reconcile} trips the switch on a broker-vs-engine position break.
 */
export function makeLiveClamp(deps: LiveClampDeps): LiveClamp {
  return new LiveClampImpl(deps);
}

/**
 * The concrete {@link LiveClamp} — the L0 pre-transmit risk clamp (kestrel-7o2.9). It IS a
 * {@link BrokerAdapter} (a {@link Gate}), so it drops into the ONE execution seam and the underlying
 * adapter cannot be reached without passing through it. Every guard runs BEFORE the delegated
 * `underlying.submit`, so an over-limit / killed order is NEVER transmitted (the underlying's `.events`
 * stays untouched) — fail-closed, bounded-risk above the adapter (ADR-0034 §4/§7). It is a TRANSMITTER,
 * never a re-pricer: it either forwards `intent` byte-for-byte or REFUSES; it never touches `intent.px`.
 */
class LiveClampImpl implements LiveClamp {
  readonly #underlying: BrokerAdapter;
  readonly #limits: RiskLimits;
  readonly provenance: "live" | "paper";
  readonly #positions: () => PositionSnapshot;
  readonly #brokerPositions: () => PositionSnapshot;
  readonly #multiplier: number | ((intent: OrderIntent) => number);
  readonly #tolerance: number;
  readonly killSwitch: KillSwitch;

  constructor(deps: LiveClampDeps) {
    // FAIL-CLOSED at construction: the authority must be a BRANDED token — a signature-verified LiveArm
    // (minted by `armLive`) or a config-minted PaperArm (minted by `makePaperArm`). A forged/literal arm
    // is NEITHER (no private brand), so it is rejected here — the envelope can never be armed on
    // fabricated authority. The clamp RECORDS which authority built it: a PaperArm makes it paper-only
    // (it can never be reached as a live gate; `makeGate("live")` refuses a PaperArm at the factory).
    if (isLiveArm(deps.arm)) {
      this.provenance = "live";
    } else if (isPaperArm(deps.arm)) {
      this.provenance = "paper";
    } else {
      throw new LiveGateRefused(
        "makeLiveClamp: the supplied arm was minted by neither armLive (a verified human signature ⇒ LiveArm) nor makePaperArm (config ⇒ PaperArm) — the L0 clamp can never be built on fabricated authority (fail-closed)",
      );
    }
    this.#underlying = deps.underlying;
    this.#limits = deps.arm.limits;
    this.#positions = deps.positions;
    this.#brokerPositions = deps.brokerPositions;
    this.#multiplier = deps.multiplier;
    this.#tolerance = deps.tolerance ?? 0;
    this.killSwitch = deps.killSwitch ?? makeKillSwitch();
  }

  /** The driver pins the clock on the clamp; it flows straight through to the underlying transmitter so
   * the emitted ORDER events carry the same `now` (the gate seam is `now`-less, RUNTIME §0). */
  get now(): number {
    return this.#underlying.now;
  }
  set now(v: number) {
    this.#underlying.now = v;
  }

  /** The underlying transmitter's ORDER events — the clamp adds no event path of its own (it either
   * forwards to `underlying.submit` or refuses before any event is produced). */
  get events(): readonly NewBusEvent[] {
    return this.#underlying.events;
  }

  submit(intent: OrderIntent): string {
    // (0) Kill-switch FIRST — once tripped (operator STAND_DOWN or a reconciliation break) nothing
    // transmits. Consulted before the limits so a halted process refuses uniformly.
    if (this.killSwitch.tripped) {
      throw new ClampRefused(
        "killed",
        intent.ref,
        `live transmission halted — kill-switch tripped: ${this.killSwitch.reason ?? "(no reason)"} (fail-closed)`,
      );
    }
    const limits = this.#limits;
    // (i) WELL-FORMEDNESS FIRST — a ceiling comparison on a corrupt number FAILS OPEN (kestrel-7o2.10,
    // A3). `NaN > maxOrderQty`, `NaN > maxPositionQty` and `NaN > maxNotionalUsd` are ALL `false`, so a
    // NaN qty/px satisfies every ceiling below and TRANSMITS — the exact inversion of fail-closed. The
    // check must precede the ceilings, not sit beside them. This is the SEAM's own wall: the venue face's
    // WALL 1 refuses a malformed intent too, but each L0 layer is load-bearing ALONE (ADR-0034 §4) — the
    // clamp is the only ceiling over a WALL-less/bare adapter and may not delegate its own validity.
    if (!Number.isFinite(intent.qty)) {
      throw new ClampRefused(
        "not-a-number",
        intent.ref,
        `order qty ${intent.qty} is not a finite number — every ceiling comparison against it is false, so it would satisfy maxOrderQty/maxPositionQty/maxNotionalUsd and transmit (fail-closed, bounded-risk)`,
      );
    }
    if (!Number.isFinite(intent.px)) {
      throw new ClampRefused(
        "not-a-number",
        intent.ref,
        `order px ${intent.px} is not a finite number — notional = px·qty·multiplier would be NaN and clear maxNotionalUsd silently (fail-closed, bounded-risk)`,
      );
    }
    // (i-b) ORDERABILITY (kestrel-ct9m) — finite is NOT enough. A FINITE but unorderable qty/px fails
    // every ceiling open in exactly the same direction as the NaN above:
    //   `-1_000_000 > maxOrderQty`                 ⇒ false — the size ceiling passes
    //   `px · -1_000_000 · mult > maxNotionalUsd`  ⇒ false — the notional is NEGATIVE, the ceiling passes
    //   `projected = current + (buy ? qty : -qty)` ⇒ SIGN-INVERTED — a sell ADDS to the position
    // so a million-lot sell cleared all three L0 walls and transmitted. A `0`/negative px collapses or
    // inverts the notional the same way, and a fractional qty is not an orderable lot at any venue.
    // The seam refuses on its OWN account: IBKR's venue face (WALL 1) rejects these too, but ADR-0034 §4
    // makes each L0 layer load-bearing ALONE — a bare seam over a transport with no WALL 1 (the reference
    // mock today, a future live transport tomorrow) is then the only wall, and it may not delegate its
    // own validity. Fixture: tests/adapters.clamp-malformed-intent.test.ts (bare transport, both drivers).
    if (!Number.isInteger(intent.qty) || intent.qty <= 0) {
      throw new ClampRefused(
        "malformed-intent",
        intent.ref,
        `order qty ${intent.qty} is not a positive integer — a negative/zero/fractional qty satisfies maxOrderQty and maxNotionalUsd by comparison (both are \`>\` tests a non-positive value passes) and sign-inverts the projected position, so it would transmit unbounded (fail-closed, bounded-risk)`,
      );
    }
    if (intent.px <= 0) {
      throw new ClampRefused(
        "malformed-intent",
        intent.ref,
        `order px ${intent.px} is not a positive number — notional = px·qty·multiplier would be zero or negative and the maxNotionalUsd ceiling ${limits.maxNotionalUsd} would enforce nothing (fail-closed, bounded-risk)`,
      );
    }
    // (ii) Max single-order size.
    if (intent.qty > limits.maxOrderQty) {
      throw new ClampRefused(
        "order-size",
        intent.ref,
        `order qty ${intent.qty} exceeds maxOrderQty ${limits.maxOrderQty} (fail-closed, bounded-risk)`,
      );
    }
    // (iii) Max ABSOLUTE net position per key, given the engine-EXPECTED current positions.
    const key = positionKeyOf(intent);
    const current = this.#positions()[key] ?? 0;
    const projected = current + (intent.side === "buy" ? intent.qty : -intent.qty);
    if (Math.abs(projected) > limits.maxPositionQty) {
      throw new ClampRefused(
        "position",
        intent.ref,
        `projected net position ${projected} at ${key} exceeds maxPositionQty ${limits.maxPositionQty} (fail-closed)`,
      );
    }
    // (iv) THE MULTIPLIER ITSELF, before it is trusted to compute a ceiling (kestrel-7o2.10, A3). The
    // notional ceiling is only as real as its inputs: a `0` multiplier computes EVERY notional as `0`, so
    // `0 > maxNotionalUsd` is false and the ceiling silently enforces NOTHING; a NaN/negative/absent one
    // corrupts it the same way. This is REACHABLE, not theoretical — `makeGate("paper")` threads the
    // VENUE's own `multiplierOf`, and the IBKR face returns `contract.multiplier ?? CONSERVATIVE`, where
    // `??` does NOT catch a `0` a malformed contractDetails resolved. A multiplier that cannot bound a
    // notional is refused rather than believed.
    const multiplier = typeof this.#multiplier === "function" ? this.#multiplier(intent) : this.#multiplier;
    if (!Number.isFinite(multiplier) || multiplier <= 0) {
      throw new ClampRefused(
        "multiplier",
        intent.ref,
        `contract multiplier ${multiplier} is not a finite positive number — notional = px·qty·multiplier would be meaningless and the maxNotionalUsd ceiling ${limits.maxNotionalUsd} would enforce nothing (fail-closed, bounded-risk)`,
      );
    }
    // (v) Max notional per order = px · qty · multiplier (never re-priced — `intent.px` is the resolved
    // limit). The multiplier is the venue's TRUE per-contract one when a resolver was threaded in
    // (kestrel-7o2.8: option 100, equity 1), so a 100× option's notional is not computed 100× too loose.
    const notional = intent.px * intent.qty * multiplier;
    if (notional > limits.maxNotionalUsd) {
      throw new ClampRefused(
        "notional",
        intent.ref,
        `order notional ${notional} exceeds maxNotionalUsd ${limits.maxNotionalUsd} (fail-closed)`,
      );
    }
    // Every ceiling clears and the switch is live — transmit the resolved intent UNCHANGED (transmitter).
    return this.#underlying.submit(intent);
  }

  /** Cancels are risk-REDUCING — always forwarded, even while halted (pulling resting orders is part of
   * STAND_DOWN). A fail-closed no-op downstream on an unknown/terminal ref. */
  cancel(ref: string): void {
    this.#underlying.cancel(ref);
  }

  /** The clamp's authoritative position PULL is the broker's own — the reconciliation anchor it was
   * wired with (ADR-0034 §4). Read-only; never re-prices. */
  positions(): PositionSnapshot {
    return this.#brokerPositions();
  }

  reconcile(): void {
    const expected = this.#positions();
    const actual = this.#brokerPositions();
    // Compare per key across BOTH snapshots' union: a broker fill the engine never originated (expected
    // 0 / actual N) AND an engine order with no broker terminal state (expected N / actual 0) both break.
    for (const key of new Set<PositionKey>([...Object.keys(expected), ...Object.keys(actual)])) {
      const e = expected[key] ?? 0;
      const a = actual[key] ?? 0;
      if (Math.abs(a - e) > this.#tolerance) {
        this.killSwitch.trip(
          `reconciliation break at ${key}: engine expected ${e}, broker PULL reported ${a} (Δ ${a - e}, tolerance ${this.#tolerance}) — halting live transmission (ADR-0034 §4, fail-closed)`,
        );
        return; // first break latches the switch; every subsequent submit refuses "killed"
      }
    }
  }
}
