import { IR_VERSION, IrDiscriminator, IrPath, IrResidue } from './core.js'; /** * Streaming partial kinds — the TS twin of the Python producer's contract. * * Cross-repo system-of-record (read it before changing anything here): * `common-docs/systems/architecture/content-ir/FEATURE.md`. * Python twin: `aidream/packages/matrx-graph/matrx_graph/content_ir/partial.py`. * * WHAT THIS IS * ------------ * While a structured region streams, the server announces what it thinks the * region IS and what has arrived so far, so the UI fills in progressively * instead of showing a spinner until the closing brace. Three events, a CLOSED * union on `state`, all riding `metadata.__ir_partial` on the `render_block` * events this app already receives: * * partial — repeatable. A provisional instance whose `root.value` is * VALID, CLOSED JSON (the server truncates and closes it, so * this side never repairs or guesses). `kindState` is * "speculative": it MAY still turn out to be something else. * superseded — TERMINAL. The region completed as the announced kind; drop * the provisional render, the block's own content/`__ir` is the * truth. * retracted — TERMINAL escape hatch. Detection was wrong; `becameKind` / * `becameBlockType` name what it actually is. Never a silent * swap. * * WHY IT IS NOT ON `__ir` * ----------------------- * `__ir` means "validated against the registered schema", and a valid `__ir` * is SEEDED into the fingerprint-keyed envelope memo. A provisional value * there would poison every later read of that region. The two channels never * touch: `classifyInboundEnvelopeMetadata` sees `absent` for a partial and * passes the metadata through by reference. * * Pure kernel module: types + validators only. No React, no Redux, no IO. * Lives in @ai-matrx/content-ir `wire/` so EVERY UI (Matrix, Workflow Studio, * the dashboard, the Chrome extension, the desktop app) reads the channel * through the same reader. */ /** The reserved metadata key carrying every event of this contract. */ declare const IR_PARTIAL_KEY: "__ir_partial"; type PartialKindState = "partial" | "superseded" | "retracted"; /** The provisional node. Deliberately `IrStructuredNode`-shaped so existing IR readers work. */ interface PartialKindNode { role: "structured"; /** The server's DETECTION GUESS for this region. */ kind: string; /** Always "speculative" on a partial — pre-recognition, not a resolved kind. */ kindState: "speculative"; discriminator: IrDiscriminator; path: IrPath; status: "streaming"; /** * Valid, closed JSON carrying its own `__kind`. MAY be missing required * schema fields — that is what `partial_unvalidated` in `residue.notices` * says. A renderer that throws on an absent field is not partial-ready and * must not be routed a provisional value. */ value: Record; residue: IrResidue | null; } interface PartialKindEvent { v: typeof IR_VERSION; engine: string; state: "partial"; /** Monotonic PER BLOCK — the ordering / staleness key. Keep the highest. */ seq: number; fingerprint: string; root: PartialKindNode; } interface SupersededKindEvent { v: typeof IR_VERSION; engine: string; state: "superseded"; seq: number; kind: string; } interface RetractedKindEvent { v: typeof IR_VERSION; engine: string; state: "retracted"; seq: number; /** The kind that was WRONGLY announced. */ kind: string; reason: string; /** What it actually is — null when it resolved to no registered kind. */ becameKind: string | null; /** The detector's final block type, so a consumer re-routes without guessing. */ becameBlockType: string | null; } type AnyPartialKindEvent = PartialKindEvent | SupersededKindEvent | RetractedKindEvent; /** * Read + validate a partial-channel event off a block's metadata. * * Returns null for anything malformed, foreign, or of an unknown `state`. A * malformed partial degrades to "no live rendering" — never to a wrong render, * and never to a thrown error inside a stream handler. */ declare function readPartialKindEvent(metadata: Record | null | undefined): AnyPartialKindEvent | null; /** * Ingest guard for the partial channel on a `render_block` event — the twin of * `sanitizeInboundEnvelopeMetadata` for `__ir`, and it exists for the same * reason: a malformed server event must be stripped at the wire boundary, not * carried into Redux where every later reader has to re-decide whether to * trust it. * * - no `__ir_partial` key → the SAME metadata reference back (zero-touch). * - valid event → the SAME metadata reference back (idempotence law). * - malformed → a COPY with the key stripped, plus a loud `reportMalformed`. * Dropping it degrades that block to "no live rendering" and nothing more. * * Pure: the host injects the reporter, exactly like the envelope gate, so * aidream's Workflow Studio can bind its own. */ declare function sanitizeInboundPartialKindMetadata(metadata: Record | null | undefined, context: { blockId: string; }, hooks?: { reportMalformed?: (info: { blockId: string; raw: unknown; }) => void; }): Record | undefined; /** Narrowing helper: is this the repeatable provisional event? */ declare function isProvisionalKind(event: AnyPartialKindEvent | null): event is PartialKindEvent; /** * Narrowing helper: is this a TERMINAL event? Every partial ends in exactly * one of these — that law is what makes a stuck skeleton impossible, so a * consumer clears its provisional render here and nowhere else. */ declare function isTerminalKindEvent(event: AnyPartialKindEvent | null): event is SupersededKindEvent | RetractedKindEvent; /** * Per-block staleness gate. Events can be re-dispatched, replayed on reconnect, * or arrive out of order; only a strictly higher `seq` advances a block. * Returns null when the event should be ignored. * * Pure: the caller owns the `seen` map, so this composes into a reducer * without hiding module state. */ declare function advancePartialKind(seen: Record, blockId: string, event: AnyPartialKindEvent | null): AnyPartialKindEvent | null; /** * The per-block staleness GATE, as a stateful closure over `advancePartialKind` * — one per stream. * * `upsertRenderBlock` REPLACES the stored block, so an event landing on a block * would regress what the user is looking at (a filled-in quiz snapping back to * two questions, or a terminal being undone by a late `partial`). The highest * accepted event is CARRIED FORWARD rather than dropped, so it is a no-op on * screen instead of a flicker back to the skeleton. * * 🚨 TWO arrivals must be carried, and the second one is the common case: * * 1. A STALE key — a replay or an out-of-order event (`seq <= last`). * 2. **NO key at all.** The producer clears `__ir_partial` at the top of every * stamp and re-adds it only when the value genuinely advanced * (`stream_processor.py::_stamp_partial` → `_partials.advanced(...)`), so * it is CORRECT for it to omit the key — re-shipping an identical payload * every token is pure wire cost. But the block still ships whole, and * replacing the stored block with one that has no partial metadata is what * made the provisional render vanish between advances: every non-advancing * token dropped the user back to the pending skeleton. * * Carrying stops at the TERMINAL, and only there. After `superseded` / * `retracted` the region's truth is its own content or `__ir`, so resurrecting * a provisional value would be a lie that outlives its own contract. * * Returns the metadata to store: the same reference when nothing changed, a * copy with the carried-forward event otherwise. */ declare function makePartialKindStalenessGate(): (blockId: string, metadata: Record | undefined) => Record | undefined; /** * Runtime wrapper kinds — the reader, and THE elision gate. * * Cross-repo contract (system of record): * `common-docs/systems/architecture/content-ir/FEATURE.md`. * * A runtime wrapper is the CLOSED set of envelopes that carry instance * context with a data kind NESTED inside: `node_outcome` (one settled node * invocation) and `run_result` (one terminated run, nesting one * `node_outcome` per terminal node). `tool_result` is registered server-side * but nothing emits it yet, so nothing here reads it. * * ## THE ELISION RULE — do it ONCE, here * * The payload is NEVER sent twice. On the wire `output` is `null` and * `output_ref` names the FRAME field that already holds the value: * * - `"output"` → the frame's own `output` (`node_completed.output`, * the run row's `output`). * - `"output."` → that key of the frame's terminal output map. * * PRESENCE OF `output_ref` IS THE MARKER. A bare `output: null` with NO ref is * a legitimately empty payload — never an elision, never something to go * looking for. This is the same rule the `__ir` envelope follows with * `value_ref`, for the same reason: otherwise every payload is serialized * twice per streamed event, twice per durable row and twice per run read. * * Rehydration happens at the single INGEST GATE (each host's one ingest point — * Matrix's workflow-runs reducer, the Studio's inbound-envelope gate), * before anything reads the wrapper. No renderer, selector or component ever * sees an un-rehydrated wrapper, so none of them may re-implement this. * * ## Never load-bearing * * Assembly is a pure read. A malformed wrapper yields `null` and the frame's * own fields carry the surface exactly as they did before the wrapper * existed — additive on the wire, additive here. */ /** The registered slugs — named once, never spelled by hand elsewhere. */ declare const NODE_OUTCOME_KIND = "node_outcome"; declare const RUN_RESULT_KIND = "run_result"; /** One settled node invocation, with its data kind nested in `output`. */ interface NodeOutcomeWrapper { __kind: typeof NODE_OUTCOME_KIND; run_id: string; node_id: string; workflow_id: string | null; step: number | null; attempt: number; status: string; started_at: string | null; ended_at: string | null; /** `0` is a REAL duration, not "unknown"; `null` is unknown. */ duration_ms: number | null; /** null = the node declared no kind (a loud defect, never a pass). */ output_kind: string | null; /** null = never checked / degraded — NEVER renderable as a pass. */ output_kind_ok: boolean | null; output_kind_errors: string[] | null; /** Rehydrated by {@link rehydrateNodeOutcome}; null = genuinely empty. */ output: unknown; } /** One terminated run. `outputs` is one wrapper per TERMINAL node. */ interface RunResultWrapper { __kind: typeof RUN_RESULT_KIND; run_id: string; workflow_id: string | null; status: string; started_at: string | null; ended_at: string | null; duration_ms: number | null; output_kind: string | null; output: unknown; outputs: NodeOutcomeWrapper[]; } /** * Resolve a dotted `output_ref` against the frame that carries the payload. * Returns `undefined` when the path does not resolve — the caller keeps * `output: null` rather than inventing a value. */ declare function readOutputRef(frame: unknown, ref: string): unknown; /** * Read a `node_outcome` off a frame and rehydrate its elided payload. * * `frame` is the object the wrapper traveled ON — the `node_completed` event, * or the run read response for a `run_result`'s children. Returns null for * anything that is not a node_outcome (including a missing wrapper: the * producer fails OPEN, so an absent wrapper is a normal, non-fatal state). */ declare function rehydrateNodeOutcome(raw: unknown, frame: unknown): NodeOutcomeWrapper | null; /** * Read an ALREADY-REHYDRATED node_outcome value into its typed form. * * Deliberately does NOT require `__kind`: the render bridge strips the root * discriminator before the component sees the value, and it does NOT touch * `output_ref` — by the time anything renders, the ingest gate has already * resolved the elision, and a second resolution attempt against a frame that * is no longer there is how a payload goes missing. */ declare function readNodeOutcomeValue(raw: unknown): NodeOutcomeWrapper | null; /** * Read a `run_result` off the run read response and rehydrate every elided * payload — its own, and each terminal node's. Both resolve against the SAME * frame (the run read response), which is what `"output."` addresses. */ declare function rehydrateRunResult(raw: unknown, frame: unknown): RunResultWrapper | null; /** * Read an ALREADY-REHYDRATED run_result value into its typed form. Same * contract as {@link readNodeOutcomeValue}: no `__kind` requirement, no * second elision pass. */ declare function readRunResultValue(raw: unknown): RunResultWrapper | null; /** * The kind verdict, as three states the UI must keep distinct. * * `unchecked` is NEVER a pass: the engine either did not check (no declared * kind) or checked and could not conclude. Collapsing it into "ok" is how a * confidently-rendered document gets shown for a shape nobody verified. */ type KindVerdict = "passed" | "failed" | "unchecked"; declare function kindVerdictOf(wrapper: { output_kind: string | null; output_kind_ok: boolean | null; }): KindVerdict; /** * The forward composer: given a kind's stored example, produce the EMIT/RENDER * payload — `{ "__kind": , ...data }`. * * Pure kernel module (@ai-matrx/content-ir `wire/`): no React, no Redux, no IO. * * Since 2026-08-23 stored examples and instances ALREADY carry their marker * (`__kind` is part of the data — KINDS_EVERYWHERE_PLAN §4.2), so for a * well-formed row this is an IDENTITY with a guarantee attached: the marker is * the FIRST key and it names the right slug. It stays because it is also the * repair for the legacy rows and hand-typed values that do not, and because a * caller wanting a copy-ready render payload should not have to know which it * has. * * Scalars/arrays are returned unchanged — for those kinds the identity travels * out of band (`root.kind`); there is no key to add. */ declare function withRootKind(kind: string, value: unknown): unknown; /** The copy-ready render payload: pretty JSON of `{ __kind, ...data }`. */ declare function emitPayloadJson(kind: string, value: unknown): string; /** * The copy-ready render BLOCK — the render payload inside a ```json fence, the * exact form an agent emits and a user pastes into a prompt or a message to see * it render live. */ declare function emitPayloadFence(kind: string, value: unknown): string; export { IR_PARTIAL_KEY, NODE_OUTCOME_KIND, RUN_RESULT_KIND, advancePartialKind, emitPayloadFence, emitPayloadJson, isProvisionalKind, isTerminalKindEvent, kindVerdictOf, makePartialKindStalenessGate, readNodeOutcomeValue, readOutputRef, readPartialKindEvent, readRunResultValue, rehydrateNodeOutcome, rehydrateRunResult, sanitizeInboundPartialKindMetadata, withRootKind }; export type { AnyPartialKindEvent, KindVerdict, NodeOutcomeWrapper, PartialKindEvent, PartialKindNode, PartialKindState, RetractedKindEvent, RunResultWrapper, SupersededKindEvent };