/**
 * kestrel.markets/protocol — the OVERSIGHT contract (protocol v0.3).
 *
 * The cross-repo seam between the CLI (OSS `kestrel` — `ascii`/Ink Rendering) and
 * the Web dashboard (platform `kestrel.markets` — `html` Rendering). ADR-0035: the
 * human never places a ticket — the human mode is PM/Pod OVERSIGHT (positions,
 * plans, P&L, risk envelope) plus chat. "The agent's ASCII screen and the human's
 * HTML chart are two Renderings of one Frame" (CONTEXT.md, *Rendering*) — so if the
 * Web dashboard can show a number the CLI cannot, that number was fetched OUTSIDE
 * this contract, and the protocol is broken. This module owns the shared model;
 * both Renderings consume it, neither invents a value.
 *
 * Normative source: docs/design/oversight-protocol-contract.md §3–§5 (owner-approved
 * 2026-07-14). The model has exactly EIGHT parts and nothing else belongs in it.
 *
 * HARD CONSTRAINTS (identical to src/protocol/index.ts — see its header):
 *  - ZERO RUNTIME dependencies. The ONLY imports are intra-protocol type-imports
 *    (`./session.ts`, `./index.ts` — both dependency-free); this module pulls in NO
 *    engine / session / blotter / fill / lang / grade / frame / render / ledger /
 *    cli code and must typecheck with `chdb` uninstalled. The import-graph boundary
 *    is an automated CI invariant (tests/protocol.boundary.test.ts). Hence the
 *    acting Kernel is a STRUCTURAL MIRROR of `src/frame/types.ts` `Kernel` — never
 *    an import of it (§3.2). CONFORMANCE, HONESTLY: today this leaf is a
 *    HAND-MAINTAINED parallel of `src/frame/types.ts` with NO compiler link — its
 *    field-for-field correspondence is verified by audit, not yet by a witness. The
 *    total projector `src/oversight/project.ts` (bead kestrel-telx.2, NOT YET
 *    LANDED) is the PLANNED conformance point: it will carry a `Record<keyof Kernel,
 *    true>` key-witness on the `src/frame` side so that a section added to `Kernel`
 *    breaks the build until this mirror handles it. Until it lands, adding a field
 *    to `src/frame/types.ts` `Kernel*` and forgetting it here does NOT auto-red — so
 *    any `Kernel*` edit must be reconciled against this leaf by hand.
 *  - SHAPES ONLY. No signing keys, no Stripe/commerce logic. Every signed,
 *    replayable artifact carries an opaque `*Ref` HANDLE, never a key or a signing
 *    routine (`OversightAct` 'fund' carries `authorizationRef`, never a credential).
 *  - GENERIC INSTRUMENTS ONLY. No founding-app tickers or strategy names in types,
 *    comments, or examples. Illustrative fixtures use SPX/SPY/QQQ (ARCHITECTURE §7);
 *    product symbols ride INSIDE opaque `symbol` fields at runtime.
 *
 * Version: `major.minor`. This leaf first landed at PROTOCOL_VERSION 0.3 (bumped 0.2 → 0.3, purely
 * additive). It now bumps 0.3 → 0.4 for the `ActReceipt` `state` field (kestrel-ysqj): `submitAct`'s
 * receipt gains an explicit queued-vs-bound stamp so 'admitted' and 'in force' are distinguishable to a
 * remote caller — a shape change to an existing type, hence a minor bump (index.ts owns the constant;
 * this note records why it moved). The schema tag `kestrel.session/<PROTOCOL_VERSION>` and the
 * content-addressed view/rendering identities track it; the graded-bus conformance root does NOT (it is
 * decoupled, ENGINE_VERSION-gated), so only the catalog-records bake re-pins, never a conformance root.
 *
 * NAMING: index.ts re-exports leaves with `export *` and already exports
 * `Position`/`Instrument`/`Order`/`Fill`/`Side`, so every oversight type here is
 * PREFIXED (`KernelPosition`, `KernelRestingOrder`, `KernelFill`, …). A collision is
 * a compile error, not a style question.
 *
 * CLOSED-VOCABULARY IDIOM (mandatory): every union ships its runtime tuple + the
 * both-ways exhaustiveness guard (`SESSION_DIAGNOSTICS` / `_SessionDiagnosticsExhaustive`
 * in session.ts), so a member added to (or removed from) a union without editing its
 * tuple BREAKS THE BUILD. That is what makes "an event outside the enum is a protocol
 * violation" enforceable rather than aspirational.
 */

import type { OperationId, SessionDiagnostic, SessionId, TurnEntry } from "./session.ts";
import type { Scope } from "./index.ts";

/* ── 1. Caller / session identity (ADR-0035 §a, §b) ─────────────────────────── */

export type CallerKind = "agent" | "human";
export const CALLER_KINDS = ["agent", "human"] as const satisfies readonly CallerKind[];
type _CallerKindsExhaustive<_Missing extends never = Exclude<CallerKind, (typeof CALLER_KINDS)[number]>> = true;

/** How the Caller was decided. Auditable — never a guess we hide. */
export type CallerDetection = "flag" | "env" | "ci" | "tty";
export const CALLER_DETECTIONS = ["flag", "env", "ci", "tty"] as const satisfies readonly CallerDetection[];
type _CallerDetectionsExhaustive<_Missing extends never = Exclude<CallerDetection, (typeof CALLER_DETECTIONS)[number]>> = true;

/**
 * Resolved ONCE, env-first / TTY-second (an agent can hold a PTY). The SINGLE source read by both
 * the render-mode resolver and platform-bound telemetry, so rendering and analytics can never
 * disagree about who is calling (ADR-0035 §a).
 *
 * FAIL-CLOSED: under ANY ambiguity `interactive` is `false`. A wrongly-launched TUI hangs an agent
 * forever — the worst failure; guessing "agent" wrong merely gives a human plain text.
 */
export interface Caller {
  readonly kind: CallerKind;
  /** The detected harness, when known (a coding-agent CLI, CI). `null` ⇒ unknown, never invented. */
  readonly harness: string | null;
  readonly detectedBy: CallerDetection;
  /** Interactive session eligible: a CONFIDENT human only — both TTYs, no agent env, no CI. */
  readonly interactive: boolean;
}

/** What an order MEANS when it fires (CONTEXT: Mode). Orthogonal to Caller and to authentication. */
export type Mode = "sim" | "paper" | "live";
export const MODES = ["sim", "paper", "live"] as const satisfies readonly Mode[];
type _ModesExhaustive<_Missing extends never = Exclude<Mode, (typeof MODES)[number]>> = true;

/**
 * Who is watching what, under which authority. The deterministic `sessionId` (a genesis hash — the
 * same on a self-hosted Kestrel and on the managed backend) and the control-plane `operation`
 * (platform-minted, NOT content-derived) are BRANDED distinctly in `./session.ts` and are never
 * substituted for one another. With no account, a session opens on a local paper Pod: `mode:"paper"`,
 * `operation` absent (ADR-0035 §b) — no broker, no signup.
 */
export interface OversightIdentity {
  readonly caller: Caller;
  readonly sessionId: SessionId;
  readonly operation?: OperationId;
  readonly mode: Mode;
  readonly scopes: readonly Scope[];
}

/* ── 2. The acting Kernel (structural mirror of src/frame/types.ts `Kernel`) ── */
// The protocol imports NOTHING, so this is a structural mirror, not an import. It is a
// HAND-MAINTAINED parallel today: there is NO compiler link between these shapes and
// `src/frame/types.ts`, so a field added there does NOT auto-red here. The PLANNED conformance
// point is the total projector `src/oversight/project.ts` (bead kestrel-telx.2, not yet landed),
// whose `Record<keyof Kernel, true>` key-witness will break the build when `Kernel` grows a
// section. Until it lands, keep this mirror faithful BY HAND — audit every `Kernel*` shape below
// field-for-field against its `src/frame/types.ts` counterpart, and record any DELIBERATE
// exclusion at the type (see `KernelFill` / `RiskEnvelope` below) so a drop is never silent.

export type Attribution = "OBS" | "CALC" | "MODEL";
export const ATTRIBUTIONS = ["OBS", "CALC", "MODEL"] as const satisfies readonly Attribution[];
type _AttributionsExhaustive<_Missing extends never = Exclude<Attribution, (typeof ATTRIBUTIONS)[number]>> = true;

/** Every rendered number is a Field, carried with its provenance so the Rendering invents nothing.
 *  `asOfSeq` is an ORDINAL, never a wall clock. */
export interface KernelField<T = number> {
  readonly value: T;
  readonly attribution: Attribution;
  /** Required for MODEL, absent for OBS/CALC. */
  readonly source?: string;
  readonly modelVer?: string;
  readonly confidence?: number;
  readonly asOfSeq?: number;
}

/** A DATE-BLIND leg. `strike`/`right` are BOTH present for an option and BOTH absent for a
 *  spot/equity leg (ADR-0017 — a fictional strike is never written). `symbol` is opaque: no
 *  application ticker ever appears in this contract. */
export interface KernelLeg {
  readonly symbol: string;
  readonly strike?: number;
  readonly right?: "C" | "P";
}

export interface KernelPosition {
  readonly leg: KernelLeg;
  readonly qty: number;              // signed
  readonly basis: number;            // average cost per contract
  readonly fair: number | null;      // null ⇒ UNKNOWN, rendered as `—`, never blank
  /** Running UNREALIZED P&L in DOLLARS (mirror of `src/frame/types.ts` `Position.unrealUsd`,
   *  kestrel-c11): `qty × (mark − basis) × multiplier`, marked to spot (equity/spot) or intrinsic
   *  (option) with the SAME dollar scaling the fill engine's `pnl` applies. It rides the contract so
   *  the human READS its P&L (`-$25.97`) instead of re-deriving it and mis-scaling cents-for-dollars
   *  (the 100× abandon bug) — a Rendering invents no value (CONTEXT.md *Rendering*). `null` ⇒ the
   *  mark is UNKNOWN (no spot) ⇒ rendered `—`, never a fabricated `0`. Required here (absent-not-
   *  hidden): the projector emits explicit `null`, never a dropped key. */
  readonly unrealUsd: number | null;
  /** The plan that OPENED the leg (provenance). */
  readonly plan: string | null;
  /** The plan that OWNS this inventory claim — exactly one plan may claim a line; owner and engine
   *  see the same claim (mutual visibility). Distinct from `plan`. */
  readonly claimOwner: string | null;
  readonly structure: string | null; // e.g. a vertical, a barbell
}

/** `live` and `clamped` are INDEPENDENT flags: a working order whose price was clamped is BOTH.
 *  They are never collapsed into one label — a `live` badge may never mask a clamped price
 *  (RUNTIME §4: a silent price is forbidden). */
export interface KernelRestingOrder {
  readonly ref: string;
  readonly leg: KernelLeg;
  readonly side: "buy" | "sell";
  readonly qty: number;
  readonly px: number;
  readonly live: boolean;
  readonly clamped: boolean;
  /** Price-resolution annotation (e.g. `fair=fallback(mid)`, `cap fair,0.73`). */
  readonly note: string | null;
  readonly plan: string | null;
}

/** A fill since the last vantage. DELIBERATE MIRROR EXCLUSION vs `src/frame/types.ts` `FillRecord`:
 *  the frame's `clock` (the HH:MM ET the fill printed at) is DROPPED. The oversight contract permits
 *  a wall clock in EXACTLY ONE place — `SpectatorFrame.asof` (§4, invariant 4) — and fill ORDER is
 *  carried by the Bus `seq`, never by a clock token on the deterministic path. Excluded on purpose,
 *  recorded here so the drop is not silent (not an oversight of the mirror). */
export interface KernelFill {
  readonly leg: KernelLeg;
  readonly side: "buy" | "sell";
  readonly qty: number;
  readonly px: number;
  readonly plan: string | null;
}

export type PlanLifecycle = "authored" | "armed" | "fired" | "managing" | "done";
export const PLAN_LIFECYCLES = [
  "authored", "armed", "fired", "managing", "done",
] as const satisfies readonly PlanLifecycle[];
type _PlanLifecyclesExhaustive<_Missing extends never = Exclude<PlanLifecycle, (typeof PLAN_LIFECYCLES)[number]>> = true;

export type PlanOutcome = "filled" | "expired" | "invalidated";
export const PLAN_OUTCOMES = ["filled", "expired", "invalidated"] as const satisfies readonly PlanOutcome[];
type _PlanOutcomesExhaustive<_Missing extends never = Exclude<PlanOutcome, (typeof PLAN_OUTCOMES)[number]>> = true;

export interface KernelPlanState {
  readonly name: string;
  readonly state: PlanLifecycle;
  readonly outcome: PlanOutcome | null;
  /** A logged reason (a de-arm/invalidation reason, a wake note). Never blank-on-unknown. */
  readonly note: string | null;
  /** The ARM-TIME gate-block reason when this plan is stuck `authored` on an unsatisfiable regime
   *  gate (mirror of `src/frame/types.ts` `PlanStateEntry.blockedReason`, kestrel-50w). Present ⇒
   *  render `authored (blocked: <reason>)`, so a bare `authored` (a live plan awaiting its WHEN) is
   *  DISTINGUISHABLE from one that can NEVER arm — the phantom-position trap (an overseer misreads
   *  `authored` as armed-and-live and believes in a position that never existed). `null` ⇒ not
   *  blocked. Fail-closed distinguishability, required here (absent-not-hidden). */
  readonly blockedReason: string | null;
}

/** The cockpit risk envelope: remaining-R plus the three nested envelopes the fire-time router
 *  lives inside — **plan ⊆ book ⊆ owner**. The OWNER envelope IS the funded balance (ADR-0035 §g):
 *  you cannot lose more than you funded.
 *
 *  DELIBERATE MIRROR EXCLUSION vs `src/frame/types.ts` `BudgetEnvelope`: the frame's `sizing`
 *  (`SizingHeadroom` — the max fillable size the remaining-R budget admits, kestrel-m9i.32) is
 *  DROPPED. It is an AGENT-authoring aid (so a model sizes WITHIN the envelope rather than hitting a
 *  silent fire-time clamp); a PM never authors tickets (ADR-0035, CONTEXT.md *PM*), so it is not PM
 *  oversight state. The bounded-risk fact the cockpit renders is the funded `ownerEnvelope`, carried
 *  below. Excluded on purpose, recorded here so the drop is not silent. */
export interface RiskEnvelope {
  readonly remainingR: number;
  readonly planEnvelope: number;
  readonly bookEnvelope: number;
  readonly ownerEnvelope: number;
}

/** The plan-lifecycle usage view (distinct from the cockpit `RiskEnvelope`). */
export interface KernelBudget {
  readonly used: number | null;
  readonly remaining: number | null;
  readonly total: number | null;
  readonly maxConcurrentR: number | null;
}

export type WakeSeverity = "routine" | "elevated" | "urgent";
export const WAKE_SEVERITIES = ["routine", "elevated", "urgent"] as const satisfies readonly WakeSeverity[];
type _WakeSeveritiesExhaustive<_Missing extends never = Exclude<WakeSeverity, (typeof WAKE_SEVERITIES)[number]>> = true;

/** Why the author is looking now + the RELATIVE deadline. `deadlineMin` is minutes-to-close —
 *  never an absolute time, never a date (determinism). `null` ⇒ `T-— to close`. */
export interface KernelWake {
  readonly reason: string;
  readonly severity: WakeSeverity;
  readonly deadlineMin: number | null;
}

/** Per-vehicle book health (the routing gate). A `dark` vehicle is NAMED, never hidden, and taints
 *  its dependents (they render UNKNOWN). */
export interface KernelVehicleHealth {
  readonly symbol: string;
  readonly bidPresentRate: number;   // 0..1 — the market-maker-pull fingerprint
  readonly twoSided: boolean;
  readonly staleS: number;
  readonly dark: boolean;
}

export type EngineActionKind = "fired" | "cancelled" | "rejected" | "clamped";
export const ENGINE_ACTION_KINDS = [
  "fired", "cancelled", "rejected", "clamped",
] as const satisfies readonly EngineActionKind[];
type _EngineActionKindsExhaustive<_Missing extends never = Exclude<EngineActionKind, (typeof ENGINE_ACTION_KINDS)[number]>> = true;

/** One L0/L1 engine action since the last vantage. `asofSeq` is an ordinal, never a wall clock.
 *  `reason` carries WHY a `rejected` action was refused (e.g. "uncovered sell refused: never
 *  naked") — the fail-closed refusal is DATA the overseer can read, not a swallowed error. */
export interface KernelEngineAction {
  readonly id: string;
  readonly kind: EngineActionKind;
  readonly asofSeq: number;
  readonly reason: string | null;
}

/** An owner act as the kernel already records it (id + kind + ordinal). The TYPED act vocabulary
 *  that PRODUCES these is `OversightAct` (§3.6); this is the recorded projection. */
export interface KernelOwnerAct {
  readonly id: string;
  readonly kind: string;
  readonly asofSeq: number;
}

export type ClaimType = "predictor" | "regime";
export const CLAIM_TYPES = ["predictor", "regime"] as const satisfies readonly ClaimType[];
type _ClaimTypesExhaustive<_Missing extends never = Exclude<ClaimType, (typeof CLAIM_TYPES)[number]>> = true;

/** A predictor/regime claim MUST be an honest MODEL Field (source + modelVer + confidence). An
 *  OBS/CALC "claim" is REFUSED at construction — a dishonest claim never renders as if honest. */
export interface KernelClaim {
  readonly field: KernelField;
  readonly claimType: ClaimType;
}

/** HARD, machine-checkable, narrowing-only — the ONLY channel that feeds admission (ADR-0026). */
export interface KernelMandate {
  readonly objective: string;
  readonly rUsd: number;             // 1R = $X — the R-definition the grade uses
  readonly successCriterion: string;
  readonly riskRule: string;
}

/** SOFT, directional English — content-hashed, bound into grade provenance (`brief_hash`).
 *  HARD GUARD: the Brief can NEVER enter admission/narrowing. It directs perception and authoring;
 *  it authorizes nothing. */
export interface KernelBrief {
  readonly text: string;
  readonly hash: string;
  readonly version: string | null;
}

/**
 * The acting kernel, as the seam carries it. EVERY section is present (absent-not-hidden): an
 * absent source section projects to an explicit `null`/`[]`, never to a dropped key.
 */
export interface OversightKernel {
  readonly mandate: KernelMandate | null;
  readonly brief: KernelBrief | null;
  readonly wake: KernelWake | null;
  readonly dataHealth: readonly KernelVehicleHealth[];
  readonly unavailable: readonly string[];      // named unavailable capabilities — listed, never hidden
  readonly budgetEnvelope: RiskEnvelope | null;
  readonly ownerActs: readonly KernelOwnerAct[];
  readonly engineLog: readonly KernelEngineAction[];
  readonly claims: readonly KernelClaim[];
  readonly positions: readonly KernelPosition[];
  readonly resting: readonly KernelRestingOrder[];
  readonly fillsSinceLast: readonly KernelFill[];
  readonly budget: KernelBudget | null;
  readonly plans: readonly KernelPlanState[];
}

/* ── 3. The org: Book leaf, Pod node (CONTEXT: Pod / Book / PM / Coverage / Risk) ── */

/** The two model tiers (ADR-0032). TIER (clock/price) is ORTHOGONAL to ORG (allocation). */
export type AgentTier = "strategist" | "watcher";
export const AGENT_TIERS = ["strategist", "watcher"] as const satisfies readonly AgentTier[];
type _AgentTiersExhaustive<_Missing extends never = Exclude<AgentTier, (typeof AGENT_TIERS)[number]>> = true;

/** The four escalation triggers (ADR-0032 §4) under the owner-approved HYBRID policy:
 *  - `mandate-edge`  AUTOMATIC, fail-closed — the cheap tier never pushes the risk boundary on its
 *                    own judgment. It escalates rather than attempt-and-get-refused.
 *  - `brief-flag`    ALWAYS-escalate event classes the Brief pre-declares (e.g. a regime break).
 *  - `regime`        a structural regime break / SHOCK keyframe — definitionally the strategist's job.
 *  - `uncertainty`   the watcher's OWN forced-comment certainty below threshold ("I am out of my
 *                    depth; call the PM"). This is the only trigger that is the watcher's judgment. */
export type EscalationReason = "mandate-edge" | "brief-flag" | "regime" | "uncertainty";
export const ESCALATION_REASONS = [
  "mandate-edge", "brief-flag", "regime", "uncertainty",
] as const satisfies readonly EscalationReason[];
type _EscalationReasonsExhaustive<_Missing extends never = Exclude<EscalationReason, (typeof ESCALATION_REASONS)[number]>> = true;

/**
 * WHO decided. The four rungs of the attribution ladder (ADR-0032 Resolved 3, extended):
 * deterministic runtime → watcher → strategist → human. A superset of `AgentTier`, because the
 * two rungs that are not models still author actions the cockpit must attribute.
 */
export type AuthorTier = "runtime" | "watcher" | "strategist" | "human";
export const AUTHOR_TIERS = [
  "runtime", "watcher", "strategist", "human",
] as const satisfies readonly AuthorTier[];
type _AuthorTiersExhaustive<_Missing extends never = Exclude<AuthorTier, (typeof AUTHOR_TIERS)[number]>> = true;

/**
 * THE TIER STAMP — carried by EVERY action, from day one (owner decision, 2026-07-14).
 *
 * TIERS ARE EXPOSED, NOT HIDDEN, and the reason is principled: **Kestrel already refuses
 * unattributed judgment.** A `MODEL` Field is refused at construction without its receipt
 * (source + modelVer + confidence — CONTEXT.md *Attribution*: "nothing above CALC goes
 * unattributed"). An action AUTHORED BY a model deserves the same rule; hiding which tier decided
 * would be honest about where a NUMBER came from and cagey about where a DECISION came from.
 *
 * A Rendering may FOLD the stamp ("just show me the book") — it may never HIDE it. Same discipline
 * as absent-not-hidden. Because the stamp is in the contract from day one, NO RENDERING NEEDS
 * REWORK when the second tier lights up.
 */
export interface Authorship {
  readonly tier: AuthorTier;
  /** The deciding model. `null` for `runtime` and `human` — ABSENT, never invented. */
  readonly model: string | null;
  /** The deciding actor's version: the model version for a model tier, the runtime version for
   *  `runtime`, `null` for `human`. Same receipt discipline as a MODEL Field's `modelVer`. */
  readonly version: string | null;
}

/** The cascade, rendered per Book. Positions in time are ORDINALS (`seq`), never clocks. */
export interface AgentTierStatus {
  /** Rare, frontier: the standing thesis + its last re-frame. The strategist authors the watcher's
   *  View and Brief (owner-approved); a re-brief is a NORMAL supersede. */
  readonly strategist: {
    readonly model: string | null;
    readonly thesis: string | null;
    readonly briefHash: string | null;
    readonly lastReframeSeq: number | null;
  };
  /** Fast, cheap, in-loop: the wake-cadence actor. Its tactical authority is: manage armed Plans,
   *  reload/exit/adjust, size-within-budget, reschedule its own Wake, request a View, stand down —
   *  AND (owner-approved) arm a NEW BOUNDED PLAN within the existing Coverage + Mandate. New
   *  Coverage / thesis / allocation stays STRATEGIST-ONLY. The Gate admits every action regardless. */
  readonly watcher: {
    readonly model: string | null;
    readonly lastActionSeq: number | null;
  };
  /** A pending escalation: the watcher woke the PM and is waiting for a re-brief. The per-event
   *  record is the first-class `oversight.escalation` event (§3.5.1); this is the standing status. */
  readonly escalation: {
    readonly pending: boolean;
    readonly reason: EscalationReason | null;
    readonly atSeq: number | null;
  };
  /** The content-hashed System Profile (ADR-0013) this cascade runs under — the ConfigId whose
   *  cadence axis (strategist calls/day vs watcher calls) is SWEPT on `ev_per_ktoken`, not fixed
   *  (owner-approved). The oversight stream NAMES it; it never computes a grade. */
  readonly configId: string | null;
}

/** A Book = the org LEAF: the only place positions and orders live. */
export interface BookView {
  readonly bookId: string;
  /** Coverage = instruments + THE THESIS FOR WHY. Instruments alone are not coverage. */
  readonly coverage: { readonly symbols: readonly string[]; readonly thesis: string };
  readonly kernel: OversightKernel;          // book-state for a Trader
  readonly tiers: AgentTierStatus;
  /** The derived status pill — projection only, no new values: severity + relative deadline +
   *  attention. `coalesced` = wakes folded into this one (attention spent, not risk). */
  readonly status: {
    readonly severity: WakeSeverity;
    readonly deadlineMin: number | null;
    readonly wakesRemaining: number | null;
    readonly coalesced: number;
  };
}

/**
 * A Pod = the recursive node: allocating role + envelope + children. It holds NO POSITIONS and has
 * NO KERNEL. Its `aggregate` is exactly the child-published org-facts fold the engine already
 * models (`children(any).<fact>`): a total map of child id → the facts THAT CHILD published. A fact
 * no child published is ABSENT — and absent is UNKNOWN, which de-arms a PM wake with a logged
 * reason; it is never a silent `0`.
 */
export interface PodView {
  readonly podId: string;
  readonly envelope: RiskEnvelope;            // budgets nest: child ⊆ parent
  readonly aggregate: Readonly<Record<string, Readonly<Record<string, number>>>>;
  readonly children: readonly OrgNode[];
}

/** Discriminated so a Rendering can walk the tree without guessing. */
export type OrgNode =
  | { readonly node: "book"; readonly book: BookView }
  | { readonly node: "pod"; readonly pod: PodView };

/* ── 4. The contract object ───────────────────────────────────────────────── */

/** A rectangular pane of scalar cells — so BOTH Renderings can draw it without inventing a value.
 *  `null` is UNKNOWN and renders as such. NOT pre-rendered ascii. */
export interface SpectatorPane {
  readonly paneId: string;
  readonly columns: readonly string[];
  readonly rows: readonly (readonly (string | number | null)[])[];
}

/** Spectator context: NOTHING AT STAKE (the bare-invocation orientation, ADR-0035 §c). Off the
 *  deterministic path — so live data and a WALL CLOCK are fine HERE, and nowhere else. */
export interface SpectatorFrame {
  readonly watchlist: readonly string[];
  readonly asof: string;                       // ISO — the ONE permitted wall clock in this contract
  readonly panes: readonly SpectatorPane[];
}

/**
 * THE contract object. CLI renders it `ascii`/Ink; Web renders it `html`. Same values on both.
 */
export interface OversightFrame {
  readonly identity: OversightIdentity;
  /** Ordinal position on the Bus — NOT a wall clock (determinism). Every `seq` below is ≤ this. */
  readonly asofSeq: number;
  /** The acting view: the PM's Pod. Phase 0 = a degenerate one-Book Pod. `null` on a bare
   *  spectator orientation (nothing at stake, nothing attached). */
  readonly pod: PodView | null;
  /** The spectator view: present on the bare orientation; `null` inside an acting session. */
  readonly spectator: SpectatorFrame | null;
  /** The slot awaiting an answer, if a Book is mid-turn (§3.5). `null` when nothing is pending. */
  readonly pending: OversightDelivery | null;
  /** The recorded conversation up to `asofSeq` — a projection of Bus message events (§3.7), so a
   *  cold Web client re-baselines its chat history from the snapshot, not from a side-channel. */
  readonly messages: readonly OversightMessage[];
}

/* ── 5. The turn-stream ───────────────────────────────────────────────────── */

/**
 * A Frame delivery awaiting an answer — the FOUR pentad legs (`sessionId`, `ordinal`, `parentHash`,
 * `frameRoot`) plus the kernel PROJECTION of what was delivered. The fifth leg — the exact authored
 * bytes — rides the turn.
 *
 * HARD RULE: `frameRoot` addresses the RUNTIME's canonical Frame. The `kernel` here is a VIEW. A
 * Rendering must never re-hash the projection into `frameRoot`, and the projection must never be
 * substituted for the delivered Frame. (Mirror of `session/controller-types.ts` `Delivery`, with the
 * frame body replaced by its Rendering-neutral projection.)
 */
export interface OversightDelivery {
  readonly sessionId: SessionId;
  readonly ordinal: number;                 // OPEN_ORDINAL (-1) at OPEN; 0,1,… per Wake
  readonly parentHash: string;
  readonly frameRoot: string;
  readonly bookId: string;
  /** WHICH TIER owns this slot — the cascade is one Session, one Bus, two adapters interleaved at
   *  their wake ordinals by a SINGLE driver (owner-approved). `recordedAgent` replays the whole
   *  cascade byte-identically because both tiers are already above-the-line `Agent`s. */
  readonly tier: AgentTier;
  readonly kernel: OversightKernel;
}

/** What a committed turn DID to the standing document (mirror of `TurnDisposition`). */
export type TurnDisposition = "armed" | "revised" | "pass" | "stood-down" | "failure";
export const TURN_DISPOSITIONS = [
  "armed", "revised", "pass", "stood-down", "failure",
] as const satisfies readonly TurnDisposition[];
type _TurnDispositionsExhaustive<_Missing extends never = Exclude<TurnDisposition, (typeof TURN_DISPOSITIONS)[number]>> = true;

/** A committed turn as the cockpit sees it: the content-addressed `TurnEntry` (the full pentad,
 *  from `./session.ts` — imported, NOT re-declared) + its disposition + WHO AUTHORED IT. */
export interface OversightTurn {
  readonly entry: TurnEntry;
  readonly disposition: TurnDisposition;
  /** The tier stamp — `{ tier, model, version }`, present from day one on every action. Replaces a
   *  bare `tier` field: the cockpit must be able to say not just WHICH TIER decided but WHICH MODEL,
   *  at WHICH VERSION (§3.3 `Authorship`). */
  readonly authoredBy: Authorship;
  /** The escalation this turn RAISED, when the watcher woke the PM. `null` otherwise. The CANONICAL
   *  record is the first-class `oversight.escalation` event (§3.5.1) — this is the back-reference. */
  readonly escalated: EscalationReason | null;
}

/**
 * The watcher woke the PM. Spends ATTENTION, never risk (ADR-0001's Wake invariant), so it is a Bus
 * fact with a `seq` and never an authored action.
 */
export interface OversightEscalation {
  readonly seq: number;
  readonly bookId: string;
  /** WHO called — the full stamp, so "which watcher, which version escalated" is answerable. */
  readonly from: Authorship;
  /** WHICH TIER was woken. `"strategist"` today; the ladder allows `"human"`. */
  readonly to: AuthorTier;
  /** WHY (ADR-0032 §4, hybrid policy). `mandate-edge` and `brief-flag` are AUTOMATIC; `uncertainty`
   *  is the watcher's own call, read off its forced-comment certainty. */
  readonly reason: EscalationReason;
  /** The watcher's forced-comment rationale for the call — the legible trace of a fast judgment
   *  (ADR-0032 §8.5). `null` ⇒ none authored; never invented. */
  readonly note: string | null;
  /** The re-brief that ANSWERED it, once it lands: the strategist's superseding turn. `null` while
   *  the escalation is still pending — absent, not hidden. */
  readonly answeredBySeq: number | null;
}

/* ── 6. Oversight is TYPED ACTS, not tickets (ADR-0035 §f, §g) ────────────── */

/**
 * Every structured human effect is a PM/owner act that lands on the Bus as a seq-ordered,
 * replayable event — exactly like a wake. There is NO fourth mutation path.
 *
 * Approval gates CAPITAL, not trades: `fund` widens the owner envelope (human-signed); inside the
 * funded envelope agents trade freely at machine speed with NO per-trade approval. Every de-risking
 * act is ALWAYS FREE — authority only narrows.
 */
export type OversightAct =
  | { readonly act: "allocate"; readonly target: string; readonly envelopeR: number }
  | { readonly act: "arm"; readonly target: string }
  | { readonly act: "de-arm"; readonly target: string; readonly reason: string }      // free
  | { readonly act: "coverage"; readonly bookId: string;
      readonly symbols: readonly string[]; readonly thesis: string }                  // thesis REQUIRED
  | { readonly act: "fund"; readonly ownerEnvelope: number;
      /** Human-signed capital authorization. Broker credentials (CLI-direct BYO broker) or a
       *  platform OAuth approval (managed) — the OSS side holds an OPAQUE HANDLE, never a key. */
      readonly authorizationRef: string }
  | { readonly act: "de-fund"; readonly ownerEnvelope: number }                       // free
  | { readonly act: "pause"; readonly target: string }                                // free
  | { readonly act: "veto"; readonly target: string; readonly reason: string };       // free

export type OversightActKind = OversightAct["act"];
export const OVERSIGHT_ACT_KINDS = [
  "allocate", "arm", "de-arm", "coverage", "fund", "de-fund", "pause", "veto",
] as const satisfies readonly OversightActKind[];
type _OversightActKindsExhaustive<_Missing extends never = Exclude<OversightActKind, (typeof OVERSIGHT_ACT_KINDS)[number]>> = true;

/** The acts that only NARROW authority. These are ALWAYS admitted — de-risking is never gated,
 *  never queued behind an approval, never refused for want of capital. */
export const NARROWING_ACTS = [
  "de-arm", "de-fund", "pause", "veto",
] as const satisfies readonly OversightActKind[];

/**
 * The lifecycle stamp on an ADMITTED act's receipt — is it merely QUEUED (admitted, awaiting a slot to
 * bind to) or BOUND (in force NOW)? Without it, `ok:true` collapses 'admitted' and 'in force' into one
 * signal, and for a de-risking veto that gap is a SAFETY LIE: an owner reading `ok:true` reasonably
 * believes the risk is removed NOW, when the act is in fact pending against a FUTURE slot (kestrel-ysqj).
 */
export type ActState = "queued" | "bound";
export const ACT_STATES = ["queued", "bound"] as const satisfies readonly ActState[];
type _ActStatesExhaustive<_Missing extends never = Exclude<ActState, (typeof ACT_STATES)[number]>> = true;

/**
 * A submitted act's outcome. On `ok`, `seq` carries the Bus `seq` it landed at (the act IS an event) and
 * `state` separates 'admitted' and 'in force' so the two are never collapsed into a bare `ok:true`:
 *  - `"queued"` — ADMITTED but NOT YET IN FORCE: a narrowing act ({@link NARROWING_ACTS}) that arrived with
 *    no open slot, waiting to bind the NEXT answered slot. There is no slot yet, so `slot` is absent
 *    (absent-not-hidden — the queued member simply has no `slot` key to invent a value for).
 *  - `"bound"` — IN FORCE NOW: a narrowing act that stood the pending slot down (`slot` = the ordinal of
 *    the delivered slot it bound to), or an envelope act (`fund`/`de-fund`) that took effect immediately
 *    (`slot` is `null` — it binds NO delivered slot, and `null` states that honestly rather than dropping
 *    the key).
 */
export type ActReceipt =
  | { readonly ok: true; readonly seq: number; readonly actId: string; readonly state: "queued" }
  | { readonly ok: true; readonly seq: number; readonly actId: string; readonly state: "bound";
      readonly slot: number | null }
  | { readonly ok: false; readonly reason: string };

/**
 * An act AS IT LANDS ON THE BUS — the act plus WHO authored it. A human owner act stamps
 * `authoredBy.tier = "human"` (model and version `null`): the fourth rung of the attribution ladder,
 * and precisely the event whose ABLATION REPLAY yields `human_alpha` (ADR-0032 Resolved 3, extended).
 * An act a *strategist* authors (an allocation) stamps its tier and model the same way. Every action
 * in this contract carries the stamp — no action is unattributed.
 */
export interface OversightActRecord {
  readonly seq: number;
  readonly actId: string;
  readonly act: OversightAct;
  readonly authoredBy: Authorship;
}

/* ── 7. The human-message channel ─────────────────────────────────────────── */

/** Who spoke. `owner` = the human above the root pod (the `human` rung of the ladder — §3.3).
 *  `pm`/`trader` = an agent replying in the conversation (its reasoning already rides the Bus as
 *  JOURNAL); they carry the full `Authorship` stamp on the events they author. */
export type MessageAuthor = "owner" | "pm" | "trader";
export const MESSAGE_AUTHORS = ["owner", "pm", "trader"] as const satisfies readonly MessageAuthor[];
type _MessageAuthorsExhaustive<_Missing extends never = Exclude<MessageAuthor, (typeof MESSAGE_AUTHORS)[number]>> = true;

/**
 * A chat message as a Bus fact. Content-addressed (`messageId = sha256(canonical(author,text,to))`)
 * so a re-send at the same slot is an idempotent duplicate and never a second event.
 *
 * CARRIES NO AUTHORITY. A message can never arm, size, fund, or place. Anything that commits risk is
 * an `OversightAct` (§3.6) the human explicitly confirms. Chat is sugar over authoring + owner acts.
 */
export interface OversightMessage {
  readonly seq: number;                 // its ordinal on the Bus — the replay key
  readonly messageId: string;           // content address
  readonly author: MessageAuthor;
  /** The Book this message is addressed to; `null` = the Pod / the desk. */
  readonly to: string | null;
  readonly text: string;
}

/* ── 8. The ONE input box (ADR-0035 §e, §f) ───────────────────────────────── */

/**
 * What the one input box resolved to. PURE and TOTAL — it never throws, never performs I/O, and
 * never has an authority side effect. Resolution order is fixed:
 *   1. full Kestrel grammar        → `grammar`      (executes deterministically)
 *   2. a bare instrument/expiry    → `shortcut`     (SHOWS a View — adds NO authority)
 *   3. a typed act                 → `act`          (the ONLY structured path to authority)
 *   4. grammar-SHAPED but invalid  → `parse-error`  (SURFACES; never silently becomes chat)
 *   5. clear prose                 → `chat`
 */
export type InputResolution =
  | { readonly kind: "grammar"; readonly document: string }
  | { readonly kind: "shortcut"; readonly view: string; readonly symbols: readonly string[] }
  | { readonly kind: "act"; readonly act: OversightAct }
  | { readonly kind: "chat"; readonly text: string; readonly to: string | null }
  | { readonly kind: "parse-error"; readonly diagnostics: readonly string[] };

export type InputResolutionKind = InputResolution["kind"];
export const INPUT_RESOLUTION_KINDS = [
  "grammar", "shortcut", "act", "chat", "parse-error",
] as const satisfies readonly InputResolution["kind"][];
type _InputResolutionKindsExhaustive<_Missing extends never = Exclude<InputResolution["kind"], (typeof INPUT_RESOLUTION_KINDS)[number]>> = true;

/* ── 9. The stream event enum (§4.2 — closed, exhaustive-guarded) ─────────── */

export type OversightEventType =
  | "oversight.snapshot" | "oversight.delivery" | "oversight.turn" | "oversight.act"
  | "oversight.escalation" | "oversight.message" | "oversight.journal" | "oversight.diagnostic"
  | "oversight.finalized" | "oversight.failed";

export const OVERSIGHT_EVENT_TYPES = [
  "oversight.snapshot", "oversight.delivery", "oversight.turn", "oversight.act",
  "oversight.escalation", "oversight.message", "oversight.journal", "oversight.diagnostic",
  "oversight.finalized", "oversight.failed",
] as const satisfies readonly OversightEventType[];
type _OversightEventTypesExhaustive<
  _M extends never = Exclude<OversightEventType, (typeof OVERSIGHT_EVENT_TYPES)[number]>
> = true;

/** The two TERMINAL event types that end the stream (mirror of the `operation.completed` /
 *  `operation.failed` pair). `oversight.finalized` ends it cleanly; `oversight.failed` throws. */
export const OVERSIGHT_TERMINAL_TYPES = [
  "oversight.finalized", "oversight.failed",
] as const satisfies readonly OversightEventType[];

/** True iff `t` is a contract event type. Anything else is a protocol violation the client MUST
 *  fail closed on (mirror of `isSseEventType`). */
export function isOversightEventType(t: string): t is OversightEventType {
  return (OVERSIGHT_EVENT_TYPES as readonly string[]).includes(t);
}

/** The decoded stream event — a discriminated union on `type`. `seq` is present exactly on the
 *  BUS-BACKED members: those events ARE Bus facts and are replayable by seq. */
export type OversightEvent =
  | { readonly type: "oversight.snapshot"; readonly frame: OversightFrame }
  | { readonly type: "oversight.delivery"; readonly seq: number; readonly delivery: OversightDelivery }
  | { readonly type: "oversight.turn"; readonly seq: number; readonly turn: OversightTurn }
  | { readonly type: "oversight.act"; readonly record: OversightActRecord }        // seq rides the record
  | { readonly type: "oversight.escalation"; readonly escalation: OversightEscalation } // seq rides it
  | { readonly type: "oversight.message"; readonly message: OversightMessage }   // seq rides the message
  | { readonly type: "oversight.journal"; readonly seq: number; readonly bookId: string;
      readonly authoredBy: Authorship; readonly text: string }
  | { readonly type: "oversight.diagnostic"; readonly diagnostic: SessionDiagnostic; readonly ordinal: number }
  | { readonly type: "oversight.finalized"; readonly seq: number; readonly sessionId: SessionId;
      readonly tipHash: string; readonly artifacts: readonly string[] }
  | { readonly type: "oversight.failed"; readonly reason: string };

/**
 * The DECODED-union ↔ enum guard. `_OversightEventTypesExhaustive` (above) pins the string union
 * `OversightEventType` ↔ the tuple `OVERSIGHT_EVENT_TYPES` in both directions. This one closes the
 * remaining link: the discriminants of the DECODED object union `OversightEvent` ↔ `OversightEventType`,
 * in BOTH directions. Together they make the whole chain — tuple ↔ string union ↔ decoded union — a
 * closed loop: a member added to (or removed from) `OversightEvent` without touching the enum
 * (`_ExtraInDecoded`), or an enum member with no decoded case (`_MissingFromDecoded`), makes a default
 * non-`never` and FAILS THE BUILD here. This is the one-way drift a self-referential runtime check
 * (a tuple compared to a variable typed FROM that tuple) structurally cannot catch.
 */
type _OversightEventDiscriminantsExhaustive<
  _MissingFromDecoded extends never = Exclude<OversightEventType, OversightEvent["type"]>,
  _ExtraInDecoded extends never = Exclude<OversightEvent["type"], OversightEventType>,
> = true;

/* ── 10. The backend seam (§5.1) ──────────────────────────────────────────── */

/** Implemented LOCALLY (OSS: in-process controller + local BYOK agent + BYO broker) and REMOTELY
 *  (platform: hosted OAuth, managed keys, funded accounts). The CLI (Ink) and the Web dashboard are
 *  two CLIENTS OF THIS — that is the whole cross-repo seam. */
export interface OversightBackend {
  readonly kind: "local" | "remote";

  identity(): Promise<OversightIdentity>;

  /** The current view — a PURE PROJECTION of harness state. */
  frame(): Promise<OversightFrame>;

  /** The live stream. `after` is the OPAQUE cursor; absent ⇒ from the current snapshot. */
  stream(after?: string): AsyncIterable<OversightEvent>;

  /** Parse-first resolution of the one input box. PURE, TOTAL, fail-closed (§3.8). */
  resolveInput(text: string): InputResolution;

  /** A typed act → a Bus event. Chat can NEVER reach this; only an `act` resolution can. */
  submitAct(act: OversightAct): Promise<ActReceipt>;

  /** Post an owner message → a Bus event (§3.7). Carries NO authority. */
  say(text: string, to: string | null): Promise<{ readonly seq: number; readonly messageId: string }>;

  /** Answer a delivered slot — the ONE thing that crosses into the graded Bus as an authored action.
   *  The response vocabulary is the EXISTING `AuthoredResponse`; no fourth kind is added. */
  respond(
    r: { readonly kind: "authored"; readonly document: string }
     | { readonly kind: "pass" }
     | { readonly kind: "stand-down"; readonly reason: string },
  ): Promise<{ readonly ok: true; readonly turn: OversightTurn }
           | { readonly ok: false; readonly diagnostic: SessionDiagnostic }>;
}
