/**
 * # engine/disarm — one home for the de-arm RULE (kestrel-z473.5)
 *
 * The disarm EVENT (engine → Bus CONTROL `disarm` / WAKE inform → Blotter {@link
 * ../blotter/types.ts DisarmRecord}) is **intrinsic** and lives on the Bus — it stays exactly where it
 * is. This module owns only the RULE around it: **what COUNTS as a disarm**, and **how a de-arm reason
 * is WORDED**. Both were previously restated across `engine/orgfacts.ts`, `engine/plans.ts`,
 * `session/agent.ts`, `session/authoring-loop.ts`, `session/harness/cascade.ts`, and
 * `blotter/project.ts`; here they are stated ONCE so every surface reads the same source of truth.
 *
 * ## Fail-closed doctrine
 * A de-arm is never silent (AGENTS: "de-arm with a logged reason, never a silent default"). Every reason
 * a de-arm carries is a **byte-stable** string minted through the builders below — a re-homing, not a
 * re-wording, so goldens and e2e fixtures that pin these substrings pass unchanged.
 *
 * ## Layering
 * `engine` sits BELOW `session`, so this module NEVER imports a session type. The stand-down
 * predicates/factory are typed **structurally** (the action discriminant alone), which every
 * `Action`/`AgentTurn` from `session/agent.ts` satisfies. Session imports FROM here — the sanctioned
 * `session → engine` direction.
 */

// ── the disarm EVENT discriminant (Bus CONTROL vocabulary) ──────────────────

/** The CONTROL-stream `type` a de-arm event carries (`src/bus/types.ts`, open control vocabulary). The
 * Blotter projects a {@link ../blotter/types.ts DisarmRecord} from exactly this discriminant, so the
 * projector and the driver's emit sites agree by construction. */
export const DISARM_CONTROL_TYPE = "disarm";

// ── what COUNTS as a disarm (the agent-path predicate) ──────────────────────

/** The action-kind discriminant of the fail-closed de-arm turn — the ONLY de-arm an agent can author
 * (CONTEXT / `session/agent.ts`: "the agent-path stand-down is the only de-arm"). A `standDown` de-arms
 * clean; inventory rides its own TP/EXIT (it never liquidates). */
export const STAND_DOWN = "standDown" as const;

/** Structural shape of an authored action — its discriminant alone. Kept minimal so this engine module
 * never imports the `Action` union from `session/agent.ts`; every concrete `Action` satisfies it. */
interface ActionKind {
  readonly kind: string;
}

/** Is this action the fail-closed de-arm (`standDown`)? */
export function isStandDown(action: ActionKind): boolean {
  return action.kind === STAND_DOWN;
}

/** Does this terminal turn DISARM the standing book? A `standDown` de-arms the armed Plan; an empty PASS
 * (or a manage/order action) leaves it armed. The cascade frozen-fan uses this to detect a frozen
 * strategist's fail-closed re-invoke and HOLD the plan armed instead of tearing it down
 * (`session/harness/cascade.ts`). */
export function disarmsPlan(turn: { readonly actions: readonly ActionKind[] }): boolean {
  return turn.actions.some(isStandDown);
}

/** The fail-closed de-arm turn: stand down clean with a logged reason (inventory rides its TP/EXIT —
 * never liquidates; RUNTIME §8). The recorded/fixed adapters and the bounded authoring loop return this
 * rather than crash. Byte-stable — the only per-turn material is the `reason`. Typed structurally (the
 * returned shape IS a `StandDownAction` turn) so `session` can assign it straight to an `AgentTurn`. */
export function standDownTurn(
  reason: string,
): { readonly actions: readonly [{ readonly kind: typeof STAND_DOWN; readonly reason: string }] } {
  return { actions: [{ kind: STAND_DOWN, reason }] };
}

// ── de-arm reason WORDING (the engine mints through these) ───────────────────

/** The loud fail-closed reason an ARMED plan whose WHEN (entry OR manage) names a genuinely unresolvable
 * ORG path de-arms with. `engine/orgfacts.ts` mints it (the org read view names the path); `engine/plans.ts`
 * relays it to a WAKE inform, edge-latched per plan (RUNTIME §3/§8, CONTEXT: "de-arms cleanly with a
 * logged reason"). `pathKey` is the already-formatted org path (`children(any).drawdown`, `fills.count`, …). */
export function orgPathUnresolvableReason(pathKey: string): string {
  return `org path "${pathKey}" is unresolvable — no such org fact in this session; de-arm (fail-closed, RUNTIME §8)`;
}

/** The loud fail-closed reason a `WHEN <event> leg <n>` per-leg fill qualifier de-arms with: no
 * fill-telemetry provider can scope a single leg today, so honouring the qualifier would fire on ANY
 * session fill (cross-plan bleed). `engine/plans.ts` `ComposedProvider.fillEvent` mints it; the real
 * capability (per-plan/per-leg scoping) lands with kestrel-qbwo.2 (kestrel-s3h8). */
export function perLegFillQualifierReason(event: string, leg: number): string {
  return (
    `fill trigger \`${event} leg ${leg}\` names a per-leg qualifier no fill-telemetry provider can scope today — ` +
    `the available telemetry is session-scoped, so honouring it would fire on ANY session fill (cross-plan bleed). ` +
    `UNKNOWN, de-armed (fail closed; per-plan/per-leg scoping is kestrel-qbwo.2)`
  );
}
