/**
 * # frame/types — the plain, typed inputs a Frame renderer consumes (ADR-0008/0009)
 *
 * The renderer is a **pure function of these objects** — it invents no value (ADR-0008
 * lineage): every number it prints is a field on the input, and an absent/UNKNOWN field
 * renders as an explicit `—` (never a guessed or defaulted number). The stepped day runner
 * populates these from the canonical state (`spot`/`hod`/`lod`/`vwap`/opening range, RUNTIME
 * §2), the book reduction ({@link ../bus/types.ts BookState}), and the fill/plan engines
 * (positions, resting orders, fills, budget, plan lifecycle — RUNTIME §5–6).
 *
 * ## Date-blind by construction (EVALUATION.md)
 * There is **no date field anywhere** in these inputs. Time is carried three ways, all
 * relative or clock-only: minutes-to-close (`T-92m to close`), a wake ordinal + minutes since
 * the last vantage (`wake 3, 41m since last`), and an **HH:MM ET clock** string on tape rows
 * and headers (`13:24`). `session_date`, day-of-week, and epoch-millisecond values are
 * **forbidden** — the runner must not put them here, and the renderer never emits them. A CI
 * grep over rendered output enforces this.
 *
 * ## Unknowns are modelled explicitly
 * A value that is genuinely unavailable is `null` (or an omitted optional) — the renderer
 * turns it into `—`. This is the fail-closed discipline of RUNTIME §8 carried into the screen:
 * an unknown is shown as unknown, never filled in.
 *
 * ## The Frame of Fields + the cockpit lead block (this milestone)
 * Two additions land here on top of the plain inputs above:
 *  - {@link Field} (4gl.1) — the one **watermarked, attributed** value type: every number the
 *    author reads is a `Field { value, attribution, source, modelVer, confidence, asOfSeq }`, so a
 *    number carries how it was derived (`OBS`/`CALC`/`MODEL`) and, for a `MODEL` value, its receipt
 *    (source + modelVer) and confidence. {@link makeField} is the construct-time honesty guard: a
 *    `MODEL` field that omits its receipt or confidence is REFUSED (fail-closed).
 *  - The extended {@link Kernel} — the acting **cockpit lead block** (4gl.3): wake, per-vehicle
 *    data-health + named unavailable capabilities, positions/inventory-claims, resting orders,
 *    the risk-envelope budget, owner acts, the engine log, and predictor/regime claims — the
 *    superset of the plan-lifecycle section. Every section is always present (absent renders as an
 *    explicit `none`/`UNKNOWN`, never dropped), the block LEADS every frame, and it is
 *    non-configurable (built in code and prepended outside any pane selection — see
 *    {@link KERNEL_SECTION_LABELS} / {@link RESERVED_PANE_IDS}).
 */

import type { OptionsAnalytics } from "./options-analytics.ts";
import type { SessionScheme } from "./session-scheme.ts";

/** Session-calendar phase (mirrors {@link ../bus/types.ts SessionPhase}); a label, never a date. */
export type FramePhase = "pre" | "open" | "regular" | "close" | "post";

/** Option right — the two contract kinds (shared vocabulary with the language `Leg`). */
export type FrameRight = "C" | "P";

/** One instrument the session carries — its spec, as the briefing lists it. */
export interface InstrumentSpec {
  readonly symbol: string;
  readonly assetClass: "equity" | "index" | "future" | "option-underlier";
  /** Signal-space vs execution-space role, when the session pins one (ARCHITECTURE §2). */
  readonly role?: "signal" | "exec";
  /** Contract multiplier (per-contract $ per point), when known. `null`/absent ⇒ `—`. */
  readonly multiplier?: number | null;
  /** Minimum price increment, when known. `null`/absent ⇒ `—`. */
  readonly tick?: number | null;
  /**
   * The instrument's SessionScheme (ADR-0041 §1/§3, kestrel-wa0j.44) — a series-registry attribute
   * declaring its monotone boundary stream (which `open`/`close`/session-split boundaries exist), the
   * stream `SessionOrdinal`'s `d-N` indexes. OPTIONAL: absent ⇒ {@link ../frame/session-scheme.ts
   * DEFAULT_SESSION_SCHEME} (`equity-rth`, which carries a close), so every existing frame input is
   * byte-identical (no-churn). A scheme with NO close boundary (a perp's rolling UTC day) makes the
   * `prior-context` pane's close address DEFECTIVE — the pane renders the declared rolling extreme,
   * never an invented midnight close ({@link ../frame/paradigm-ledger.ts}, ADR-0041 §3). One of the
   * closed {@link ../frame/session-scheme.ts SESSION_SCHEMES} roster. */
  readonly sessionScheme?: SessionScheme;
}

/** The canonical levels of one signal instrument (RUNTIME §2). Any level may be `null` (UNKNOWN
 * before warm — e.g. `priorClose` is a cross-session inject that may be absent; the opening
 * range reads UNKNOWN until the first tick). `null`/absent ⇒ `—`. */
export interface LevelSet {
  readonly spot: number | null;
  readonly priorClose?: number | null;
  readonly hod?: number | null;
  readonly lod?: number | null;
  readonly vwap?: number | null;
  readonly orHigh?: number | null;
  readonly orLow?: number | null;
  /**
   * The declared ROLLING EXTREME (ADR-0041 §3, kestrel-wa0j.44) — the anchor a no-close
   * {@link ../frame/session-scheme.ts SessionScheme} (a perp's rolling UTC day) uses IN PLACE of a
   * prior close: the rolling-window high/low the frame carries (e.g. the rolling 24h extreme). The
   * `prior-context` pane renders THESE for a no-close scheme (the sanctioned periphrasis), never an
   * invented midnight close. OPTIONAL: absent on every equity frame (byte-identical — a close-bearing
   * scheme never reads them); `null`/absent under a no-close scheme ⇒ the pane fails closed to the
   * DEFECTIVE refusal (no honest anchor to render), never a fabricated value.
   */
  readonly rollingHigh?: number | null;
  /** The rolling-window LOW companion to {@link rollingHigh} (the declared rolling extreme's low). */
  readonly rollingLow?: number | null;
  /**
   * Whole seconds since the SPOT tick that established {@link spot} last printed, at this frame's
   * cutoff (kestrel-rs4). A dead feed and a frozen market produce the SAME number for `spot`; only
   * this age tells them apart, so once it passes the staleness backstop
   * ({@link ../frame/options-analytics.ts SPOT_STALE_AFTER_MS}) the pane tags the rendered value
   * `[STALE <age>]` rather than present a price the feed stopped supporting with the confidence of
   * a fresh tick. Mirrors {@link ../frame/options-analytics.ts OptionsAnalytics.spotStaleSeconds}
   * (the same age, the same backstop — the two surfaces can never call one spot fresh and dead at
   * once). `null`/absent ⇒ the caller stated no age; the value renders bare (byte-identical to a
   * pre-rs4 frame) — an unaged spot is NOT a claim of freshness.
   */
  readonly spotStaleSeconds?: number | null;
}

/**
 * One tape bucket, in the incumbent rotated-candlestick geometry (rendering-variants.md
 * candidate 2). Carries the bucket's OHLC and the HH:MM **ET clock** it closed at — the clock
 * is the only time token on a row (date-blind). The renderer maps price → column position; the
 * numbers here are the truth, the column is layout.
 */
export interface TapeRow {
  /** HH:MM ET clock label for this bucket (e.g. `13:24`). Date-blind: no date component. */
  readonly clock: string;
  readonly open: number;
  readonly high: number;
  readonly low: number;
  readonly close: number;
  /** Bucket volume, when the tape carries it (v1 SPOT tape does not). `null`/absent ⇒ omitted. */
  readonly volume?: number | null;
}

/** One near-money chain leg summary for the chain pane. A `null` bid/ask side is **dark** (the
 * MM-pull fingerprint, ARCHITECTURE §4) — rendered as a flag, never as a price. `fair` carries
 * its receipt annotation (`fairNote`, e.g. `b76 nLiq=5` or `fallback(mid)`). */
export interface ChainRow {
  readonly strike: number;
  readonly right: FrameRight;
  /** Top-of-book bid; `null` ⇒ bid dark. */
  readonly bid: number | null;
  /** Top-of-book ask; `null` ⇒ ask dark. */
  readonly ask: number | null;
  /** ExecutionFair for the leg; `null`/absent ⇒ unbuildable this moment (`—`). */
  readonly fair?: number | null;
  /** The fair receipt annotation carried onto the value (RUNTIME §4). */
  readonly fairNote?: string;
  /** Last printed trade for the leg, when one has printed. */
  readonly last?: number | null;
}

/** The market pane for one instrument: its levels, its recent tape, its near-money chain. For a
 * briefing the tape is the long-horizon orientation window; for a wake delta it is only the
 * buckets **since the last vantage**. */
export interface MarketPane {
  readonly instrument: string;
  readonly levels: LevelSet;
  /** Recent tape buckets, oldest first, newest last (append-only, ADR-0008). Keep ≤ ~20. */
  readonly tape: readonly TapeRow[];
  /** Bucket width in minutes (default 5; finer near the close). */
  readonly tapeBucketMin: number;
  /** Near-money chain legs (bid/ask/fair + receipt + dark flags). Empty for equity-only. */
  readonly chain: readonly ChainRow[];
  /**
   * RELATIVE days-to-expiry of the near-money chain (`0` for a 0dte) — the day driver's machine
   * channel already carries it (`AuthorFrame.chain.dte`); threading it here restores the dte the
   * chain-pane header lost in the one-renderer swap (kestrel-wa0j.19 §4). Date-blind by
   * construction: a relative offset, never a date. `null`/absent ⇒ omitted from the header
   * (the {@link TapeRow.volume} idiom — the header is byte-identical to today).
   */
  readonly chainDte?: number | null;
  /**
   * The **options-analytics projection** (pane-library-spec §5) — the full-chain per-strike surface
   * (NBBO/mid OBS, IV/greeks MODEL, OI OBS) the GEX/IV panes read. Optional: absent on the
   * SPOT-only / near-money-chain-only constructions (the pane fails closed to UNKNOWN when it is
   * absent), populated only for a View that opts into the options-analytics panes. See
   * {@link ./options-analytics.ts}.
   */
  readonly options?: OptionsAnalytics;
}

/** A held position in the acting book — basis + current fair (RUNTIME §6). `qty` is signed
 * (long positive). `fair` `null`/absent ⇒ `—`. */
export interface Position {
  readonly instrument: string;
  /** Strike — an OPTION leg carries a finite strike; an EQUITY/SPOT leg carries NEITHER strike NOR
   * right (ADR-0017: a spot instrument has neither, and a fictional strike is never written). Both
   * absent ⇒ the percept renders `<instrument> shares` with no option chrome ({@link isSpotLeg}). */
  readonly strike?: number;
  /** Right — present (with {@link strike}) for an OPTION leg; absent for an EQUITY/SPOT leg (ADR-0017). */
  readonly right?: FrameRight;
  readonly qty: number;
  /** Average cost per contract (the position's basis). */
  readonly basis: number;
  /** Current ExecutionFair per contract, when buildable. `null`/absent ⇒ `—`. */
  readonly fair?: number | null;
  /**
   * The position's running **unrealized P&L in DOLLARS** (kestrel-c11) — `qty × (mark − basis) ×
   * multiplier`, marked to the current spot (equity/spot) or intrinsic (option), with the SAME
   * dollar scaling the fill engine's `pnl` applies (`src/engine/orgfacts.ts`). Computed UPSTREAM in
   * the org-facts layer ({@link ../session/simulate.ts kernelOf}) and PRINTED here, so the agent
   * READS its P&L (`-$25.97`) rather than deriving it and mis-scaling cents-for-dollars (the 100×
   * abandon bug). `null` ⇒ the mark is UNKNOWN (no spot) ⇒ renders `—` (fail-closed, never a
   * fabricated 0). Absent ⇒ the field is not rendered at all (purely additive; pre-c11 constructions
   * are byte-identical).
   */
  readonly unrealUsd?: number | null;
  /** The plan that opened the position (provenance), when it came from one. */
  readonly plan?: string;
  /**
   * The plan that **owns this inventory claim** — the cockpit's inventory-claim ownership: exactly
   * one plan may claim a given inventory line, and the owner and the engine see the same claim
   * (mutual visibility). Distinct from {@link plan}: `plan` records which plan *opened* the leg;
   * `claimOwner` records the live ownership the cockpit renders. Absent on the pre-cockpit,
   * summary-only construction.
   */
  readonly claimOwner?: string;
  /** The structure this leg belongs to (e.g. a vertical, a barbell) — the cockpit position label. */
  readonly structure?: string;
}

/** A resting order working in the book — its resolved price + the price-resolution annotation
 * it carries (RUNTIME §4: a silent mid is forbidden, so the annotation rides here). */
export interface RestingOrder {
  readonly ref: string;
  readonly side: "buy" | "sell";
  readonly instrument: string;
  /** Strike/right — BOTH present for an OPTION leg, BOTH absent for an EQUITY/SPOT leg (ADR-0017; a
   * spot order has neither). Both absent ⇒ the percept renders `<instrument> shares` ({@link isSpotLeg}). */
  readonly strike?: number;
  readonly right?: FrameRight;
  readonly qty: number;
  readonly px: number;
  /** Price-resolution annotation (e.g. `fair=fallback(mid)`, `cap fair,0.73`). */
  readonly note?: string;
  readonly plan?: string;
  /**
   * Working in the book right now. An **independent** flag from {@link clamped}: the cockpit
   * surfaces both, so a `live` label can never mask a clamped price (fail-closed honesty,
   * RUNTIME §4 — a silent price is forbidden). Absent on the pre-cockpit construction.
   */
  readonly live?: boolean;
  /**
   * Price was clamped to the premium band. **Independent** of {@link live}: a working order whose
   * price was clamped is `live` AND `clamped` — both render, never collapsed into one label.
   */
  readonly clamped?: boolean;
}

/** A fill that happened since the last vantage (RUNTIME §6). `clock` is the HH:MM ET it filled
 * at, when known (date-blind). */
export interface FillRecord {
  readonly side: "buy" | "sell";
  readonly instrument: string;
  /** Strike/right — BOTH present for an OPTION fill, BOTH absent for an EQUITY/SPOT fill (ADR-0017; a
   * spot fill has neither). Both absent ⇒ the percept renders `<instrument> shares` ({@link isSpotLeg}). */
  readonly strike?: number;
  readonly right?: FrameRight;
  readonly qty: number;
  readonly px: number;
  readonly clock?: string;
  readonly plan?: string;
}

/** The book's risk envelope usage (RUNTIME §5). Any of these may be `null`/absent ⇒ `—`. */
export interface Budget {
  readonly used: number | null;
  readonly remaining: number | null;
  readonly total?: number | null;
  /** Max concurrent R the envelope permits, when the book pins one. */
  readonly maxConcurrentR?: number | null;
}

/** One plan's lifecycle state (RUNTIME §5). */
export interface PlanStateEntry {
  readonly name: string;
  readonly state: "authored" | "armed" | "fired" | "managing" | "done";
  readonly outcome?: "filled" | "expired" | "invalidated";
  /** A logged reason (a de-arm/invalidation reason, a wake note). */
  readonly note?: string;
  /** kestrel-50w: the ARM-TIME gate-block reason when this plan is stuck `authored` on an unsatisfiable
   * regime gate (the gated tag is UNKNOWN — no feed — or holds a different value). Present ⇒ the plan is
   * rendered `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 agent misread `authored`
   * as armed-and-live and traded a position that never existed). Reuses the engine's own reason string. */
  readonly blockedReason?: string;
}

// ─────────────────────────────────────────────────────────────────────────────
// The Frame of Fields (4gl.1) — a watermarked, attributed value
// ─────────────────────────────────────────────────────────────────────────────

/**
 * Provenance class of a rendered number (CONTEXT: Field). How the value was derived:
 *  - `OBS`   — an observed market datum (a quote, a trade).
 *  - `CALC`  — a deterministic transform of `OBS` (VWAP, range, %chg) — carries no receipt.
 *  - `MODEL` — a model output (a fair, a predictor/regime claim) — MUST carry a receipt
 *    (`source` + `modelVer`) and a `confidence`; an unattributed model claim is dishonest.
 */
export type Attribution = "OBS" | "CALC" | "MODEL";

/**
 * The one value type: **every rendered number is a Field**, carried with its provenance so the
 * renderer invents nothing. A `MODEL` value is honest only if it carries its receipt (`source`
 * carrying the model, `modelVer`) and its `confidence`; `OBS`/`CALC` carry neither. `asOfSeq` is
 * the engine's monotonic sequence at derivation — an **ordinal, never a wall-clock time** (the
 * determinism / replay-stability key; there is no date anywhere on a Field, RUNTIME §8).
 *
 * Construct a Field through {@link makeField}, which refuses a dishonest `MODEL` at construct time.
 */
export interface Field<T = number> {
  /** The value itself — the number (or label) that reaches the screen. */
  readonly value: T;
  /** How the value was derived (OBS / CALC / MODEL). */
  readonly attribution: Attribution;
  /** The source watermark carrying the model — required for `MODEL`; absent for `OBS`/`CALC`. */
  readonly source?: string;
  /** The model version receipt — required for `MODEL`; absent for `OBS`/`CALC`. */
  readonly modelVer?: string;
  /** Model confidence in `[0,1]` — required for `MODEL`; absent for `OBS`/`CALC`. */
  readonly confidence?: number;
  /** Engine monotonic sequence at derivation (an ordinal, NEVER a wall-clock/date). */
  readonly asOfSeq?: number;
}

/**
 * Raised by the honesty guards when a claim/field would misrepresent its provenance. Throwing is
 * the fail-closed response: a dishonest `MODEL` claim is REFUSED, never rendered as if honest.
 */
export class KernelHonestyError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "KernelHonestyError";
  }
}

/**
 * The `[0,1]` range contract {@link Field.confidence} pins, as an executable predicate (4gl.19).
 * Merely FINITE is not honest: a `confidence` of `1.7` or `-0.5` is a number the contract cannot
 * express, so admitting it would let a guard report "honest MODEL claim" while the frame renders
 * `conf=1.70` — a silent-false. Bounds are CLOSED (`0` and `1` are honest: no confidence, and
 * certainty). `NaN`/`±Infinity` fail both comparisons, so they are refused here too.
 */
function isHonestConfidence(confidence: number): boolean {
  return confidence >= 0 && confidence <= 1;
}

/**
 * Construct a {@link Field}, refusing a dishonest one at construct time (the 4gl.1 honesty guard):
 *  - a `MODEL` field MUST carry `source` + `modelVer` + a `confidence` in `[0,1]` — a `MODEL` claim
 *    missing any of them, or carrying an out-of-range confidence, is REFUSED (fail-closed);
 *  - an `OBS`/`CALC` field MUST carry neither a `modelVer` receipt nor a `confidence` — a
 *    mis-attributed field that smuggles a model receipt in as `OBS`/`CALC` is REFUSED.
 * Never returns a Field that violates the attribution contract.
 */
export function makeField<T>(spec: {
  readonly value: T;
  readonly attribution: Attribution;
  readonly source?: string;
  readonly modelVer?: string;
  readonly confidence?: number;
  readonly asOfSeq?: number;
}): Field<T> {
  const { attribution } = spec;
  if (attribution === "MODEL") {
    if (spec.source === undefined || spec.source === "") {
      throw new KernelHonestyError(
        "MODEL Field carries no source watermark — a MODEL value MUST carry source(modelVer) + confidence",
      );
    }
    if (spec.modelVer === undefined || spec.modelVer === "") {
      throw new KernelHonestyError(
        "MODEL Field carries no modelVer receipt — a MODEL value MUST carry source(modelVer) + confidence",
      );
    }
    if (spec.confidence === undefined) {
      throw new KernelHonestyError(
        "MODEL Field carries no confidence — a MODEL value MUST carry source(modelVer) + confidence",
      );
    }
    if (!isHonestConfidence(spec.confidence)) {
      throw new KernelHonestyError(
        `MODEL Field carries an out-of-range confidence ${spec.confidence} — MODEL confidence MUST be in [0,1]`,
      );
    }
  } else {
    if (spec.confidence !== undefined) {
      throw new KernelHonestyError(`${attribution} Field must carry no confidence`);
    }
    if (spec.modelVer !== undefined) {
      throw new KernelHonestyError(`${attribution} Field must carry no modelVer receipt`);
    }
  }
  // Build without ever assigning `undefined` to an optional key (exactOptionalPropertyTypes).
  const out: {
    value: T;
    attribution: Attribution;
    source?: string;
    modelVer?: string;
    confidence?: number;
    asOfSeq?: number;
  } = { value: spec.value, attribution };
  if (spec.source !== undefined) out.source = spec.source;
  if (spec.modelVer !== undefined) out.modelVer = spec.modelVer;
  if (spec.confidence !== undefined) out.confidence = spec.confidence;
  if (spec.asOfSeq !== undefined) out.asOfSeq = spec.asOfSeq;
  return out;
}

// ─────────────────────────────────────────────────────────────────────────────
// The extended cockpit lead block (4gl.3) — the sections the kernel always leads with
// ─────────────────────────────────────────────────────────────────────────────

/** Wake urgency — a named severity (not a raw magnitude). */
export type WakeSeverity = "routine" | "elevated" | "urgent";

/**
 * Why the author is looking now, and the deadline it must act within. `deadline` is
 * **minutes-to-close — a RELATIVE duration** (rendered `T-Nm`), `null` ⇒ `UNKNOWN`; it is NEVER a
 * wall-clock time or a date (date-blind by construction, EVALUATION.md).
 */
export interface WakeRef {
  readonly reason: string;
  readonly severity: WakeSeverity;
  /** Minutes to close (relative duration), or `null` when unknown. Never an absolute time/date. */
  readonly deadline: number | null;
}

/**
 * Per-vehicle book health (the routing gate). `bidPresentRate` (0..1) is the market-maker-pull
 * fingerprint; `dark` means the book pulled / went one-sided. A dark vehicle is NAMED, never
 * hidden or blank (absent-not-hidden), and taints its dependents (they render UNKNOWN, RUNTIME §8).
 */
export interface VehicleHealth {
  readonly instrument: string;
  readonly bidPresentRate: number;
  readonly twoSided: boolean;
  readonly staleS: number;
  readonly dark: boolean;
}

/** The four engine-log buckets (fixed order in the rendered section). */
export type EngineActionKind = "fired" | "cancelled" | "rejected" | "clamped";

/** One L0/L1 engine action since the last vantage. `asofSeq` is an ordinal, never a wall-clock. */
export interface EngineAction {
  readonly id: string;
  readonly kind: EngineActionKind;
  readonly asofSeq: number;
  /** The reason a `rejected` action was refused (e.g. `"uncovered sell refused: never naked"`, a
   * cancelOrder no-such-ref refuse) or a `clamped` action was reduced (e.g. `"exceeds plan budget"`).
   * Carried on the kernel data so the acting agent can see WHY its order was refused/clamped
   * (kestrel-7kt / kestrel-75n); the text renderer surfaces it after the `id@seq` as `(<reason>)` when
   * present, byte-stable when absent. Absent for non-refusal/non-clamp actions. */
  readonly reason?: string;
}

/** One owner act (a standing-directive change the owner made). `asofSeq` is an ordinal. */
export interface OwnerAct {
  readonly id: string;
  readonly kind: string;
  readonly asofSeq: number;
}

/** The two claim kinds the kernel carries. */
export type ClaimType = "predictor" | "regime";

/**
 * A predictor/regime claim — an **honest `MODEL` {@link Field}** carrying its receipt and
 * confidence. Validate with {@link assertClaimHonest}: an `OBS`/`CALC` claim, or a `MODEL` claim
 * missing its receipt/confidence, is REFUSED (fail-closed).
 */
export interface Claim {
  readonly field: Field;
  readonly claimType: ClaimType;
}

/**
 * The **sizing headroom** (kestrel-m9i.32) — the MAX fillable size the remaining-R budget admits for
 * the exec instrument, so a model can size WITHIN the bounded-risk envelope instead of discovering it
 * by a SILENT fire-time clamp. Bounded risk is the FULL COST BASIS (ADR-0017): a long equity lot
 * charges `qty × entry_px × mult` (mult 1), a long option `qty × premium × mult` — so the admissible
 * size is `floor(remainingUsd / basisPerUnit)`. PURE: a derivation of the injected budget + price, no
 * wall clock / RNG. Absent-not-hidden: an unbuildable basis (dark/absent price, or an option whose
 * per-leg premium isn't cleanly surfaced) reads `maxUnits: null` and carries a {@link note}, never a
 * fabricated cap.
 */
export interface SizingHeadroom {
  /** The exec instrument the headroom is computed for. */
  readonly instrument: string;
  /** Order unit — an equity/spot leg sizes in `shares`, an option leg in `contracts` (ADR-0017). */
  readonly unit: "shares" | "contracts";
  /** The cost basis the bounded-risk clamp charges PER UNIT (ADR-0017): equity = `spot × mult`
   * (mult 1), option = `premium × mult`. `null` when the basis price is unbuildable this moment (a
   * dark/absent spot, or an option premium not cleanly surfaced) — never a guessed value. */
  readonly basisPerUnit: number | null;
  /** MAX fillable size the remaining-R budget admits: `floor(remainingUsd / basisPerUnit)`. `null`
   * when {@link basisPerUnit} or {@link remainingUsd} is unbuildable (shown as UNKNOWN, not faked). */
  readonly maxUnits: number | null;
  /** The remaining-R budget in $ (the numerator; `null` ⇒ no budget state). */
  readonly remainingUsd: number | null;
  /** A basis-rule note when the per-unit basis can't be surfaced cleanly (e.g. an option whose
   * per-leg premium isn't available — the model still gets the remaining $ + the cost-basis rule). */
  readonly note?: string;
}

/**
 * The cockpit's risk-envelope budget — the remaining risk plus the three nested envelopes the
 * fire-time router lives inside (plan ⊆ book ⊆ owner). This is the cockpit "budget / remaining-R"
 * + "owner envelope" view; the plan-lifecycle usage view is the separate {@link Budget}.
 */
export interface BudgetEnvelope {
  readonly remainingR: number;
  readonly planEnvelope: number;
  readonly bookEnvelope: number;
  readonly ownerEnvelope: number;
  /** The MAX fillable size the remaining-R budget admits for the exec instrument (kestrel-m9i.32) —
   * so a model sizes WITHIN the bounded-risk envelope, not into a SILENT fire-time clamp. Absent on
   * the pre-cockpit / degenerate construction (renders as `sizing: UNKNOWN`, absent-not-hidden). */
  readonly sizing?: SizingHeadroom | null;
}

/**
 * The honesty guard, as a validator (4gl.3): a predictor/regime claim MUST be a `MODEL` {@link
 * Field} with a source watermark, a `modelVer` receipt, and a `confidence` in `[0,1]`. An
 * `OBS`/`CALC` "claim", or a `MODEL` claim missing its receipt/confidence or carrying an
 * out-of-range confidence (4gl.19), is REFUSED — throwing a {@link KernelHonestyError}
 * (fail-closed; a dishonest claim never renders as if honest).
 */
export function assertClaimHonest(claim: Claim): void {
  const label = claim.claimType;
  const f: Field | undefined = claim.field;
  if (f === undefined || f === null) {
    throw new KernelHonestyError(`claim ${label} carries no Field`);
  }
  if (f.attribution !== "MODEL") {
    throw new KernelHonestyError(
      `claim ${label} is attributed ${f.attribution}, not MODEL — a predictor/regime claim MUST be ` +
        "a MODEL Field with source + confidence (honesty guard)",
    );
  }
  if (f.source === undefined || f.source === "") {
    throw new KernelHonestyError(`MODEL claim ${label} carries no source watermark`);
  }
  if (f.modelVer === undefined || f.modelVer === "") {
    throw new KernelHonestyError(`MODEL claim ${label} carries no modelVer receipt`);
  }
  if (f.confidence === undefined) {
    throw new KernelHonestyError(`MODEL claim ${label} carries no confidence`);
  }
  if (!isHonestConfidence(f.confidence)) {
    throw new KernelHonestyError(
      `MODEL claim ${label} carries an out-of-range confidence ${f.confidence} — MODEL confidence MUST be in [0,1]`,
    );
  }
}

// ── The non-configurable / fail-closed contract of the cockpit lead block ────────
// The kernel LEADS every frame and is built in code, prepended OUTSIDE any pane selection; it
// cannot be dropped or reordered by configuration. These constants are the canonical contract a
// renderer consumes: the lead sentinel, the 8 fixed-order section labels (every section always
// present — absent-not-hidden), and the reserved pane ids a configured pane may never claim.

/** The line the cockpit lead block begins with — the kernel-leads sentinel a frame checks for. */
export const KERNEL_LEAD_SENTINEL =
  "==== SAFETY / CONTROL KERNEL (non-configurable; leads every frame) ====";

/**
 * The line a **delta-encoded** kernel block begins with (kestrel-312, the KV-cache-efficiency half
 * of the v5 percept). In a cache-monotone stream (the `conversation` / `conversation-cached`
 * policies, where the reader provably holds the prior full kernel in cached context) a WAKE frame's
 * kernel is transmitted as ONLY the fields that MOVED since the prior frame — the byte-stable
 * skeleton (the {@link KERNEL_LEAD_SENTINEL} banner, the 8 section labels, and every unchanged field
 * value) lives in the reader's cached prefix and is NOT re-emitted. This sentinel LEADS such a block
 * so the lead guard ({@link ./render.ts assertKernelLeads}) still fires, and it tells the reader the
 * complete current kernel is `cached skeleton + these moved fields`.
 *
 * ## The reinterpreted lead invariant (fail-closed, honesty-preserving)
 * Every frame makes the COMPLETE current kernel available to the reader — as a full block in a
 * KEYFRAME, or as `cached-skeleton + delta` in a cache-monotone stream — and a fail-closed
 * composed-completeness guard ({@link ./render.ts composeKernelDelta}) verifies the composition
 * reconstructs the full kernel BYTE-IDENTICALLY (a delta that would compose to an INCOMPLETE kernel
 * throws, exactly as the lead guard does). Absent-not-hidden is preserved: a field that MOVED to
 * UNKNOWN is present-and-marked in the delta, never silently omitted — omitted-because-unchanged
 * (not in the moved-set, held in cache) is never confused with omitted-because-unknown. A KEYFRAME
 * (the OPEN briefing, and every `stateless-redraw` frame where the reader holds NO prior context)
 * ALWAYS carries the COMPLETE {@link KERNEL_LEAD_SENTINEL} block — self-complete frames stay
 * self-complete.
 */
export const KERNEL_DELTA_SENTINEL = "==== KERNEL DELTA (unlisted fields unchanged) ====";

/** The 8 fixed, always-present section labels — in fixed order; none may be dropped or reordered. */
export const KERNEL_SECTION_LABELS = [
  "-- WAKE --",
  "-- DATA-HEALTH --",
  "-- POSITIONS / INVENTORY-CLAIMS --",
  "-- RESTING ORDERS --",
  "-- BUDGET / REMAINING-R --",
  "-- OWNER ENVELOPE + ACTS --",
  "-- L0/L1 ENGINE LOG --",
  "-- PREDICTOR / REGIME CLAIMS --",
] as const;

/** Pane ids reserved for the non-configurable kernel — a configured pane may never claim them. */
export const RESERVED_PANE_IDS = ["kernel", "cockpit"] as const;

/**
 * Fail-closed guard for pane configuration: a configured pane may not claim a reserved id (the
 * kernel is built in code and cannot be impersonated or displaced by a pane). Throws a
 * {@link KernelHonestyError} on a reserved id.
 */
export function assertPaneIdAllowed(paneId: string): void {
  const id = paneId.trim().toLowerCase();
  if ((RESERVED_PANE_IDS as readonly string[]).includes(id)) {
    throw new KernelHonestyError(
      `pane id "${paneId}" is reserved for the non-configurable cockpit kernel and cannot be ` +
        "claimed by a configured pane (fail-closed)",
    );
  }
}

/**
 * The **MANDATE** (ADR-0026) — the HARD, machine-checkable, narrowing-ONLY channel: what the agent
 * may NOT do + what it is graded against. It is the ONLY thing that feeds admission/narrowing (its
 * risk envelope is the {@link BudgetEnvelope} the kernel already carries; this makes the objective +
 * R-definition + success criterion EXPLICIT and labeled). Sourced from the SESSION/CELL — different
 * cells declare different objectives. A GRADED run MUST declare one ({@link assertGradedMandate}).
 */
export interface Mandate {
  /** The objective in one line, e.g. `session day-trade, flat-by-close`. */
  readonly objective: string;
  /** The dollar value of one risk unit, `1R = $X` (the R-definition the grade uses). */
  readonly rUsd: number;
  /** The success criterion, e.g. `maximize risk-adjusted return vs the null baseline`. */
  readonly successCriterion: string;
  /** The bounded-risk rule in one line, e.g. `bounded-risk; never exceed your envelope`. */
  readonly riskRule: string;
}

/**
 * The **BRIEF** (ADR-0026) — the SOFT, directional English channel: what the agent SHOULD go look
 * for (goal + philosophy/approach + persona). Versioned + content-hashed so language becomes
 * attributable ("this performance came from this thesis"); the hash binds into grade provenance
 * (`brief_hash`, sibling to `promptHash`). **HARD GUARD**: the Brief is unvalidated free text that
 * can NEVER enter admission/narrowing/bounded-risk — only the {@link Mandate}/envelope do. The OSS
 * side merely RENDERS a Brief supplied by the cell/config and binds its hash to provenance; the
 * full Brief/Journal/Thesis lifecycle is a platform (kestrel.markets) artifact.
 */
export interface Brief {
  /** The directional English (goal + approach + persona). Unvalidated — never an admission input. */
  readonly text: string;
  /** The content hash of {@link text} (`sha256:<hex>` or a bare hex), bound into grade provenance. */
  readonly hash: string;
  /** An optional version label for the Brief (e.g. `contrarian-v2`). */
  readonly version?: string;
}

/**
 * Fail-closed guard for a GRADED run: a benchmark cell MUST state what it is optimizing. Throws a
 * {@link KernelHonestyError} when there is no {@link Mandate} (or a malformed one — a blank objective,
 * a non-positive/absent `rUsd`, a blank success/risk rule). Takes the {@link Mandate} DIRECTLY (not a
 * Kernel) so it wires at the point a graded run COMMITS its mandate — the plan-fixture freeze, where
 * the mandate binds into `plan_fixture_sha` grade provenance (ADR-0032 §7,
 * {@link ../session/harness/plan-fixture.ts capturePlanFixture}). The deterministic grade driver never
 * renders, so the honesty is enforced at that commit, not behind a render flag no production path sets.
 * The renderer itself stays tolerant — an ungraded/degenerate frame renders without a mandate section.
 */
export function assertGradedMandate(m: Mandate | null | undefined): void {
  if (m === undefined || m === null) {
    throw new KernelHonestyError(
      "graded run declares no MANDATE — a benchmark cell MUST state what it is optimizing (objective, " +
        "1R = $X, success criterion, bounded-risk rule). Fail-closed: the cell must supply a mandate.",
    );
  }
  const bad: string[] = [];
  if (typeof m.objective !== "string" || m.objective.trim() === "") bad.push("objective");
  if (typeof m.rUsd !== "number" || !Number.isFinite(m.rUsd) || m.rUsd <= 0) bad.push("rUsd (1R = $X, > 0)");
  if (typeof m.successCriterion !== "string" || m.successCriterion.trim() === "") bad.push("successCriterion");
  if (typeof m.riskRule !== "string" || m.riskRule.trim() === "") bad.push("riskRule");
  if (bad.length > 0) {
    throw new KernelHonestyError(
      `graded run declares an INCOMPLETE mandate — missing/blank: ${bad.join(", ")} (fail-closed; a graded ` +
        "run must state a complete objective + R-definition + success criterion + bounded-risk rule).",
    );
  }
}

/**
 * The acting **kernel** (CONTEXT: Kernel) — bound to the session, not the View, and always present
 * in an acting Frame. It is the **cockpit lead block** (4gl.3): a superset that carries both the
 * extended cockpit sections and the original plan-lifecycle section.
 *
 * ## Absent-not-hidden, and non-configurable
 * Every cockpit section is OPTIONAL on this type so the pre-cockpit acting-kernel construction
 * stays valid (a degenerate kernel is constructible) — but a renderer treats **every** section as
 * always present: an absent/empty section renders as an explicit `none`/`UNKNOWN`, never dropped.
 * The block LEADS every frame and is built in code and prepended outside any pane selection; it
 * cannot be dropped or reordered by configuration ({@link KERNEL_SECTION_LABELS} /
 * {@link RESERVED_PANE_IDS}). A spectator Frame would carry a provenance kernel instead.
 */
export interface Kernel {
  // ── The MANDATE + BRIEF standing context (ADR-0026 — kept as two DISTINCT channels) ──────────
  /** The **MANDATE** (hard, machine-checkable, narrowing-ONLY): the objective + R-definition +
   * success criterion + bounded-risk rule the agent is graded against. It is the ONLY channel that
   * feeds admission/narrowing (the risk envelope below is its machine half). Sourced from the
   * SESSION/CELL. Optional on this type so a degenerate kernel stays constructible; a GRADED cell
   * MUST declare one ({@link assertGradedMandate}). */
  readonly mandate?: Mandate | null;
  /** The **BRIEF** (soft, directional English — ADR-0026): what the agent SHOULD go look for (goal
   * + philosophy/approach + persona). Versioned + content-hashed; the hash binds into grade
   * provenance (`brief_hash`). HARD GUARD: unvalidated free text that can NEVER enter
   * admission/narrowing — only the {@link mandate}/envelope do. Sourced from the SESSION/CELL. */
  readonly brief?: Brief | null;

  // ── The extended cockpit lead block (4gl.3) ──────────────────────────────────
  /** Why the author is looking now + the RELATIVE deadline (`T-Nm`). Absent ⇒ `UNKNOWN`. */
  readonly wake?: WakeRef | null;
  /** Per-vehicle book health (routing gate). Empty ⇒ `UNKNOWN`; a dark vehicle is named. */
  readonly dataHealth?: readonly VehicleHealth[];
  /** Named unavailable capabilities — listed explicitly, never hidden. Empty ⇒ `none`. */
  readonly unavailable?: readonly string[];
  /** The cockpit risk-envelope budget (remaining-R + plan/book/owner envelopes). */
  readonly budgetEnvelope?: BudgetEnvelope | null;
  /** Owner acts this session (standing-directive changes). Empty ⇒ `none`. */
  readonly ownerActs?: readonly OwnerAct[];
  /** The L0/L1 engine log (fired / cancelled / rejected / clamped). Empty ⇒ `none`. */
  readonly engineLog?: readonly EngineAction[];
  /** Predictor/regime claims — each an honest `MODEL` Field. The render seam refuses a dishonest one
   * per-claim as it materializes the section ({@link assertClaimHonest}, wired in {@link ./render.ts}). */
  readonly claims?: readonly Claim[];

  // ── The existing plan-lifecycle / acting section (kestrel superset) ───────────
  readonly positions: readonly Position[];
  readonly resting: readonly RestingOrder[];
  readonly fillsSinceLast: readonly FillRecord[];
  /** The plan-lifecycle risk-usage view (used / remaining). The cockpit view is {@link budgetEnvelope}. */
  readonly budget?: Budget | null;
  readonly plans: readonly PlanStateEntry[];
}

/**
 * The OPEN keyframe (full briefing) input. Macro/overnight context is intentionally **absent**
 * in v1 — the renderer emits the pane header with `macro: unavailable (v1 harness)`
 * (absent-with-reason, per EVALUATION.md), never an invented value.
 */
export interface BriefingInput {
  /** Minutes to the session close; `null` ⇒ `T-— to close`. */
  readonly timeToCloseMin: number | null;
  readonly phase?: FramePhase | null;
  /** Current HH:MM ET clock, when the runner supplies one (date-blind). */
  readonly clockET?: string;
  readonly instruments: readonly InstrumentSpec[];
  readonly market: MarketPane;
  readonly kernel: Kernel;
  /** The PRIOR sessions' tapes (kestrel-wa0j.20) — each ordinal's rows serve its `tape d-N` address as
   * its own single-session block. Typically ABSENT in the v1 single-day sim (honestly so); absent ⇒
   * `tape d-N` renders the Train 1B absence line, byte-identical. Additive + optional-honest. */
  readonly priorSessions?: readonly PriorSessionTape[];
  /** The ARMED plans' enforced terms (kestrel-wa0j.29) — threaded so the `armed-plan` pane renders the
   * WHEN/entries/exits/invalidation/size-envelope the watcher enforces. Typically ABSENT at OPEN (no
   * plan has armed yet — authoring happens AT the open); absent ⇒ the field is unserialized and the
   * pane fails closed to absent-with-reason. Additive + optional-honest (byte-identical without it). */
  readonly armedPlan?: ArmedPlanTerms;
}

/**
 * The prior author vantage's **served values** (kestrel-wa0j.48) — the typed snapshot the driver
 * SERVED at the author's PREVIOUS vantage, threaded into THIS wake's frame input so the `delta`
 * pane can state WHAT MOVED under `stateless-redraw` (where the prior percept is NOT in context and
 * the change information exists nowhere else in the frame). This is the frame-carried prior the
 * renderer reads: a pane invents no value (ADR-0041 §1), so the prior-vantage data must enter
 * THROUGH THE FRAME INPUT, never be reconstructed by the renderer.
 *
 * Every field is **optional-honest**: it carries ONLY a value the prior frame actually served — an
 * absent field is a value the prior vantage did not carry (never a fabricated 0). The driver
 * captures the TYPED values it served (spot/hod/lod/vwap off the served levels, the ATM straddle
 * extrinsic when computable), NEVER the rendered bytes. DATE-BLIND: `baseSeq` is a relative ordinal
 * and `baseClockET` is the HH:MM ET clock — no date component.
 */
export interface PriorVantage {
  /** The prior vantage's percept ordinal / base seq the kernel already knows (relative, date-blind).
   * `null`/absent ⇒ the base is named by its clock alone. */
  readonly baseSeq?: number | null;
  /** The prior vantage's HH:MM ET clock (date-blind) — the `delta since <baseClockET>` anchor. */
  readonly baseClockET?: string;
  /** Spot the prior vantage served; `null`/absent ⇒ the prior vantage did not carry a spot. */
  readonly spot?: number | null;
  readonly hod?: number | null;
  readonly lod?: number | null;
  readonly vwap?: number | null;
  /** The prior vantage's ATM straddle EXTRINSIC (time value remaining, `gross book-mid − intrinsic`),
   * when it was computable at that vantage; `null`/absent otherwise (a dark/one-sided/absent book). */
  readonly atmExtrinsic?: number | null;
}

/**
 * One PRIOR session's tape (kestrel-wa0j.20 — the DATA half of Train 1B's `tape d-N` ADDRESS). A
 * SessionOrdinal address `tape d-N` (N ≥ 1) names a prior session; Train 1B (kestrel-wa0j.24) landed
 * the address (it binds at `mat`, renders honest absence at `renderable`), and THIS is the typed data
 * that turns that absence into the prior session's OWN bytes. Rendered as its OWN complete
 * single-session tape block through the EXISTING single-session renderer (own axis from its own
 * extremes) — sessions are NEVER folded/aggregated across the overnight gap (that is wa0j.20b, behind
 * the render-unification PR #194).
 *
 * DATE-BLIND (ADR-0041 §1): `ordinal` is a RELATIVE session index (1 = the immediately prior session,
 * 2 = the one before, …) — the same date-blind class as wake ordinals / `T-Nm`, NEVER a date; each
 * row's `clock` is HH:MM-only; `sessionLabel` (when present) is a relative descriptor, never a date.
 */
export interface PriorSessionTape {
  /** Relative session ordinal — `1` = the immediately prior session, `2` = the one before, … Matches
   * the `d-N` address the author spells. NEVER a date (date-blind, ADR-0041 §1). */
  readonly ordinal: number;
  /** That session's OHLC tape rows, in its own time order (each row's `clock` is HH:MM-only). */
  readonly rows: readonly TapeRow[];
  /** An optional date-blind label for the session (a relative descriptor, e.g. `prior RTH`) — NEVER a
   * date. Absent ⇒ the block header names the ordinal alone. */
  readonly sessionLabel?: string;
}

/**
 * ONE armed plan's ENFORCED terms — the typed snapshot the WATCHER reads (kestrel-wa0j.29 / ADR-0041).
 *
 * The watcher is asked to judge a plan's PREMISE ("the plan armed on a thesis — is it still true?")
 * but the armed document's WHEN/DO/TP/EXIT/INVALIDATE clauses live in the supersede action on the bus
 * and are NEVER rendered into any frame: the kernel carries only the plan's lifecycle NAME + STATE. The
 * `armed-plan` pane closes that gap — and, exactly as the `delta`/`prior-session` panes do, its data
 * enters THROUGH THE FRAME INPUT (a pane invents no value, ADR-0041 §1): the sim driver captures the
 * plan's TYPED clauses from the armed document it already holds and prints each through the canonical
 * clause printer ({@link ../lang/print.ts planClause}) — a typed value → its byte-stable text, NEVER the
 * rendered pane bytes, never a hand-rolled restatement.
 *
 * Every field is **optional-honest**: it carries ONLY a term the plan actually authored — an absent
 * field is a clause the plan does not have (never a fabricated one). The clause texts are the canonical
 * printed forms grouped by role (a variadic run when the plan authored several of that kind).
 */
export interface ArmedPlanTermsEntry {
  /** The plan's lineage NAME (the same name the kernel's PlanStateEntry carries). */
  readonly name: string;
  /** The arming PREMISE — the plan's top-level `WHEN` trigger, canonical text. Absent ⇒ the plan armed
   * with no top-level entry gate (a pure inventory adopter / an unconditional arm). */
  readonly when?: string;
  /** The ENTRY tickets (`DO` / `ALSO` / `RELOAD`), each as canonical clause text — what the plan
   * acquires and reloads. Absent/empty ⇒ the plan authored none (a pure management adopter). */
  readonly entries?: readonly string[];
  /** The EXIT surface (`TP` take-profits + `EXIT` thesis-break stops), each as canonical clause text —
   * the terms the watcher enforces to get OUT. Absent/empty ⇒ the plan authored no exit surface. */
  readonly exits?: readonly string[];
  /** The INVALIDATION surface (`INVALIDATE` thesis-dead + `CANCEL-IF` resting cancels), each as
   * canonical clause text — what kills the thesis. Absent/empty ⇒ the plan authored none. */
  readonly invalidations?: readonly string[];
  /** The SIZE ENVELOPE — the plan's authored risk `budget` (e.g. `0.5R`), canonical text. Absent ⇒ the
   * plan declared no budget (it nests under an ancestor Book/Pod envelope). */
  readonly sizeEnvelope?: string;
}

/**
 * The ARMED plans' enforced terms threaded onto a frame (kestrel-wa0j.29) — the DATA the `armed-plan`
 * pane renders. Present only on a frame the sim driver threaded armed-plan terms into (a wake with at
 * least one plan in an enforced lifecycle state — `armed`/`fired`/`managing`); ABSENT when nothing is
 * armed (the pane then fails closed to exactly ONE absent-with-reason line). Additive + optional-honest:
 * a frame without it is byte-identical to today. `plans` is non-empty by construction (an empty armed
 * set is threaded as ABSENT, not an empty list — absent-not-hidden).
 */
export interface ArmedPlanTerms {
  readonly plans: readonly ArmedPlanTermsEntry[];
}

/**
 * The WAKE delta-frame input — everything **since the last vantage** (EVALUATION.md §Part 1.3):
 * the tape buckets since last, the current levels/kernel, and the wake's own identity (ordinal
 * + minutes since last + reason). Append-only by construction (ADR-0008): a delta frame is a
 * suffix stamped with its own tape anchor.
 */
export interface WakeDeltaInput {
  /** The wake ordinal (`wake 3`). */
  readonly wakeIndex: number;
  /** Minutes since the author's last vantage; `null` ⇒ `—`. */
  readonly minutesSinceLast: number | null;
  readonly timeToCloseMin: number | null;
  readonly phase?: FramePhase | null;
  readonly clockET?: string;
  /** Why this wake fired (the trigger idiom or the structural-cadence reason). */
  readonly wakeReason?: string;
  /** The market pane; its `tape` carries only the buckets since the last vantage. */
  readonly market: MarketPane;
  /** The acting kernel; its `fillsSinceLast` are the fills since the last vantage. */
  readonly kernel: Kernel;
  /** The prior author vantage's served values (kestrel-wa0j.48) — present on a WAKE frame the
   * driver threaded a prior vantage into (every wake AFTER the first). Absent on the first wake and
   * on any frame with no prior vantage; the `delta` pane fails closed to absent-with-reason then.
   * Additive: a frame without it is byte-identical (the delta pane is catalog-only, never a default). */
  readonly priorVantage?: PriorVantage;
  /** The PRIOR sessions' tapes (kestrel-wa0j.20) — each ordinal's rows serve its `tape d-N` address as
   * its own single-session block. Present only on a frame the driver threaded a multiday tape into
   * (typically ABSENT in the v1 single-day sim — honestly so); absent ⇒ `tape d-N` renders the Train 1B
   * absence line, byte-identical. Additive + optional-honest (a frame without it is byte-identical). */
  readonly priorSessions?: readonly PriorSessionTape[];
  /** The ARMED plans' enforced terms (kestrel-wa0j.29) — threaded so the `armed-plan` pane renders the
   * WHEN/entries/exits/invalidation/size-envelope the watcher enforces (the watcher's role-keyed
   * percept: it manages plans whose document text it otherwise never sees). Present only on a wake with
   * at least one plan in an enforced state (`armed`/`fired`/`managing`); absent ⇒ the field is
   * unserialized and the pane fails closed to absent-with-reason. Additive + optional-honest. */
  readonly armedPlan?: ArmedPlanTerms;
}
