@ai-matrx/content-ir 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1601 @@
1
+ import { ComponentType } from 'react';
2
+
3
+ /**
4
+ * The canonical IR contract for structured content.
5
+ *
6
+ * Every source (live agent stream, DB reload, Python-preprocessed results,
7
+ * notes, any future surface) is normalized into these shapes exactly once.
8
+ * Anything already carrying this IR passes through downstream layers by
9
+ * reference — see `core/normalize.ts` for the idempotence law.
10
+ *
11
+ * This file is pure types + path helpers. No React, no Redux, no IO.
12
+ */
13
+ /** Path of a value inside a parsed region (object keys + array indices). */
14
+ type IrPath = Array<string | number>;
15
+ /** How a node's kind was (or wasn't) established. */
16
+ type IrKindState = "resolved" | "speculative" | "pending_kind" | "pending_schema" | "raw";
17
+ /**
18
+ * System-level zero-data-loss channel. Unknown keys are NEVER merged into a
19
+ * node's `value` (they'd be indistinguishable from schema fields); they are
20
+ * carried here verbatim — Protobuf unknown-fields discipline. Distinct from
21
+ * any domain-level `additionalDetails` field, which is an ordinary schema
22
+ * field inside `value`.
23
+ */
24
+ interface IrResidue {
25
+ /** Keys present in the source object but absent from the kind schema. */
26
+ extra: Record<string, unknown> | null;
27
+ /** Optional schema fields the source never provided. */
28
+ optionalMissing: string[] | null;
29
+ /** Structured warnings attached during parsing (speculation backtracks, truncation, …). */
30
+ notices: Array<{
31
+ code: string;
32
+ message: string;
33
+ at?: number;
34
+ }> | null;
35
+ }
36
+ /** Which wire syntax carried the kind discriminator for a node. */
37
+ type IrDiscriminator = {
38
+ format: "json";
39
+ key: "__kind";
40
+ } | {
41
+ format: "xml";
42
+ tag: string;
43
+ } | {
44
+ format: "fence";
45
+ language: string;
46
+ };
47
+ /** A schema-shaped structured node. Children live inside `value` (and carry their own metadata via `nodeIndex`). */
48
+ interface IrStructuredNode {
49
+ role: "structured";
50
+ /** Canonical kind slug. Empty string while pending. */
51
+ kind: string;
52
+ kindState: IrKindState;
53
+ discriminator: IrDiscriminator;
54
+ /** Region-relative path ([] = region root). */
55
+ path: IrPath;
56
+ status: "streaming" | "complete" | "error";
57
+ /** Compliant snapshot: schema fields + __kind ONLY. Unknown keys → residue. */
58
+ value: Record<string, unknown>;
59
+ residue: IrResidue | null;
60
+ }
61
+ /** Envelope version. Bump = migration point for persisted envelopes. */
62
+ declare const IR_VERSION: 1;
63
+ /** Reserved key carrying the envelope inside RenderBlockPayload.data. */
64
+ declare const IR_ENVELOPE_KEY: "__ir";
65
+ /**
66
+ * One parsed region's canonical form. Serializable (plain JSON), carried on
67
+ * render blocks (`data.__ir`), persisted on artifacts and message metadata,
68
+ * and — with `engine: "py-block-detector"` — accepted pre-built from Python.
69
+ */
70
+ interface CanonicalBlockIR {
71
+ v: typeof IR_VERSION;
72
+ /** Provenance: which implementation produced this envelope. */
73
+ engine: "fe-kind-parser" | "py-block-detector";
74
+ /** Stable hash of the region source text — the idempotence / cache key. */
75
+ fingerprint: string;
76
+ root: IrStructuredNode;
77
+ /**
78
+ * pathKey → node metadata for per-path readers (child kinds under root).
79
+ * Carries each child node's residue — child snapshot values inside
80
+ * `root.value` hold schema fields only, so WITHOUT this the envelope would
81
+ * silently drop nested unknown keys (zero-data-loss violation).
82
+ */
83
+ nodeIndex?: Record<string, Pick<IrStructuredNode, "kind" | "kindState" | "status"> & {
84
+ residue?: IrResidue | null;
85
+ }>;
86
+ }
87
+ /** Normalizer output segment: prose stays raw text; structured regions carry IR. */
88
+ type CanonicalSegment = {
89
+ role: "text";
90
+ content: string;
91
+ } | {
92
+ role: "block";
93
+ blockType: string;
94
+ content: string;
95
+ ir: CanonicalBlockIR | null;
96
+ };
97
+ interface CanonicalContent {
98
+ v: typeof IR_VERSION;
99
+ segments: CanonicalSegment[];
100
+ }
101
+ declare function irPathKey(path: IrPath): string;
102
+ declare function irPathsEqual(left: IrPath, right: IrPath): boolean;
103
+ declare function irPathIsUnderOrEqual(path: IrPath, prefix: IrPath): boolean;
104
+ /** Human label for a path ("root", "cards[2].front"). */
105
+ declare function irPathLabel(path: IrPath): string;
106
+ /** True when every residue channel is empty — normalize to null instead. */
107
+ declare function isEmptyResidue(residue: IrResidue): boolean;
108
+
109
+ /**
110
+ * KindSchema — the data-defined field model for a registered kind.
111
+ *
112
+ * `__kind` (KIND_KEY) is the carried discriminator: it is NOT part of a
113
+ * kind's field map; the parser enforces it via `KindSchema.kind` and stamps
114
+ * it onto every compliant snapshot.
115
+ *
116
+ * Moved from app/(dev)/demos/json-block-detector/kind-schemas.ts.
117
+ *
118
+ * 2026-07-15 expressivity extension (A2) — four constructs the Python-owned
119
+ * pydantic schemas need that the v1 vocabulary could not express:
120
+ * - `{type:"json"}` / `{type:"json[]"}` — any JSON value / array of any
121
+ * JSON values (pydantic bare `Any` fields, `items: {}` arrays, `{}`
122
+ * schemas). A `json` value is implicitly nullable — `null` IS a JSON
123
+ * value — so `nullable` is meaningless (and ignored) on it.
124
+ * - `record` values widened to `"json"` (pydantic `dict[str, Any]` /
125
+ * `additionalProperties: true`).
126
+ * - `union` may now carry `kinds` (object unions — anyOf over kind refs,
127
+ * optionally mixed with scalars). Refs externalize to `kind_edge` rows
128
+ * exactly like `array.itemKinds`.
129
+ * - `KindSchema.root` — a NON-OBJECT root form: the kind's VALUE is the
130
+ * root field itself (scalar / array / json / open object), not a `__kind`
131
+ * object with fields. Root-form kinds are data-only: the streaming
132
+ * `__kind` parser cannot type them (a scalar cannot carry a
133
+ * discriminator) and refuses them loudly; validation goes through the
134
+ * emitted JSON Schema (ajv / Pydantic). `root` and a non-empty `fields`
135
+ * are mutually exclusive.
136
+ * - `inline_object.open` — `additionalProperties: true`; fixes the
137
+ * open-empty-object defect where an open `inline_object{fields:{}}`
138
+ * materialized as CLOSED (schema_proposal / item_presentation class).
139
+ *
140
+ * 2026-07-15 input-semantics extension (W3-A, agent-input bridge) — the
141
+ * constructs the Wave-1 sufficiency survey of all 1,529 live agent variables
142
+ * showed FieldSchema could not carry, added so `AgentVariable` ⇄ kind
143
+ * conversion is faithful:
144
+ * - `FieldBase.description` — human guidance; round-trips JSON Schema
145
+ * `description` and VariableDefinition `helpText`.
146
+ * - `FieldBase.default` — the field's default VALUE (JSON Schema `default`,
147
+ * VariableDefinition `defaultValue`). Annotation-level: validators never
148
+ * apply it; emitters carry it verbatim.
149
+ * - `enum.open` — "one of these options OR any string" (the FE's
150
+ * `allowOther`). Emits as `anyOf: [{type:"string", enum}, {type:"string"}]`
151
+ * so the option set survives instead of widening to bare `string`.
152
+ * - `number` bounds `min`/`max`/`step` — JSON Schema
153
+ * `minimum`/`maximum`/`multipleOf` (number/slider components).
154
+ * - `string[].values` (+ `open`) — an items-enum: array of strings drawn
155
+ * from an option set (checkbox components), `open` meaning the set is
156
+ * advisory (`allowOther` on a multi-select).
157
+ * Picklist bindings, scope bindings, and media component identity are
158
+ * PROVENANCE, not structure — they never enter FieldSchema; the bridge
159
+ * carries them out-of-band (see convert/kind-variable-bridge.ts sidecar).
160
+ */
161
+ /** System discriminator — hardcoded, not part of per-kind field schemas. */
162
+ declare const KIND_KEY = "__kind";
163
+ type ScalarFieldType = "string" | "number" | "boolean";
164
+ type ArrayItemScalarType = "string" | "number" | "boolean";
165
+ /** Value domain of a `record` field — typed scalars, or any JSON value. */
166
+ type RecordValueType = ArrayItemScalarType | "json";
167
+ type FieldBase = {
168
+ required?: boolean;
169
+ nullable?: boolean;
170
+ /** Human guidance — JSON Schema `description` / variable `helpText`. */
171
+ description?: string;
172
+ /**
173
+ * Default VALUE (JSON Schema `default` / variable `defaultValue`).
174
+ * Annotation-level: validators never apply it; emitters carry it verbatim.
175
+ */
176
+ default?: unknown;
177
+ };
178
+ type FieldSchema = (FieldBase & {
179
+ type: "string" | "boolean";
180
+ }) | (FieldBase & {
181
+ type: "number";
182
+ /** Inclusive lower bound — JSON Schema `minimum`. */
183
+ min?: number;
184
+ /** Inclusive upper bound — JSON Schema `maximum`. */
185
+ max?: number;
186
+ /** Increment — JSON Schema `multipleOf`. Annotation-level in the parser. */
187
+ step?: number;
188
+ }) | (FieldBase & {
189
+ type: "string[]";
190
+ /** Items-enum: each item must be one of these values (checkbox option sets). */
191
+ values?: string[];
192
+ /** With `values`: the set is advisory — any string item is also legal (`allowOther`). */
193
+ open?: boolean;
194
+ }) | (FieldBase & {
195
+ type: "number[]" | "boolean[]";
196
+ }) | (FieldBase & {
197
+ type: "json";
198
+ }) | (FieldBase & {
199
+ type: "json[]";
200
+ }) | (FieldBase & {
201
+ type: "array";
202
+ itemKinds: string[];
203
+ }) | (FieldBase & {
204
+ type: "object";
205
+ kind: string;
206
+ }) | (FieldBase & {
207
+ type: "inline_object";
208
+ fields: Record<string, FieldSchema>;
209
+ /** additionalProperties: true — unknown keys are legal, not residue-only. */
210
+ open?: boolean;
211
+ }) | (FieldBase & {
212
+ type: "record";
213
+ values: RecordValueType;
214
+ }) | (FieldBase & {
215
+ type: "enum";
216
+ values: string[];
217
+ /** "One of these OR any string" — the option set is advisory (`allowOther`). */
218
+ open?: boolean;
219
+ }) | (FieldBase & {
220
+ type: "union";
221
+ scalars: Array<"string" | "number" | "boolean">;
222
+ /** Object union members — kind refs (anyOf of $refs), may mix with scalars. */
223
+ kinds?: string[];
224
+ });
225
+ /**
226
+ * Domain fields only — __kind is enforced by the parser via KindSchema.kind
227
+ * (block slug). A kind with `root` set has NO field map (fields stays `{}`):
228
+ * its value is the root field's type at the top level. See the module header.
229
+ */
230
+ type KindSchema = {
231
+ kind: string;
232
+ fields: Record<string, FieldSchema>;
233
+ /** Non-object root form — mutually exclusive with a non-empty `fields`. */
234
+ root?: FieldSchema;
235
+ };
236
+ declare function readObjectKind(value: Record<string, unknown>): string | null;
237
+ declare function isScalarArrayType(type: FieldSchema["type"]): type is "string[]" | "number[]" | "boolean[]";
238
+ declare function scalarArrayItemType(type: "string[]" | "number[]" | "boolean[]"): ArrayItemScalarType;
239
+ /**
240
+ * Does this field's value domain accept ANY JSON shape (object/array/scalar/
241
+ * null alike)? True for `json` and `json[]` ITEMS — the parser treats the
242
+ * subtree under such a field as opaque (no kind identification, no raw_object
243
+ * degradation: unknown structure is the declared contract, not a failure).
244
+ */
245
+ declare function isJsonAnyField(field: FieldSchema): boolean;
246
+
247
+ /**
248
+ * KindStreamParser — the streaming, schema-validating, __kind-discriminated
249
+ * JSON parser at the heart of content-ir.
250
+ *
251
+ * Frame-stack pushdown parser over JsonStreamTokenizer tokens. Every value is
252
+ * path-addressed; every object resolves a kind against the schema registry;
253
+ * schema-shaped `block_snapshot` events fire on field arrival so renderers
254
+ * get live partials; unknown/invalid structures degrade to `raw_object`
255
+ * instead of failing the stream.
256
+ *
257
+ * The pushdown discipline (commit → descend → never re-ask → backtrack):
258
+ * - SPECULATIVE DESCENT: when a parent field schema predicts a child's kind
259
+ * ({type:"object", kind} or {type:"array", itemKinds:[K]} with one member),
260
+ * the child commits to that kind THE INSTANT `{` opens and renders a
261
+ * placeholder snapshot. `__kind` arrival confirms (no-op), re-tags (allowed
262
+ * sibling kind), or backtracks to raw (contradiction). An object under a
263
+ * predicting parent doesn't even need `__kind` — prediction alone types it.
264
+ * - PENDING SCHEMA: an identified kind whose schema isn't loaded holds the
265
+ * node open (`pending_schema` event, fields keep accumulating), fires the
266
+ * resolver's cold fetch, and upgrades in place via `notifySchemaArrived` —
267
+ * even after the node (or the whole region) has closed.
268
+ * - POP-UP-ONE-LEVEL: node-scoped problems (duplicate key, schema violation,
269
+ * disallowed itemKind) mark THAT node raw and keep parsing the parent.
270
+ * Only grammar/tokenizer errors are region-fatal — and the host degrades
271
+ * the region to a plain code block, never the stream.
272
+ */
273
+
274
+ /** Kept as the historical name for the parser's event paths. */
275
+ type JsonPath = IrPath;
276
+ /**
277
+ * Schema source abstraction. A plain Record works for static sets; the
278
+ * registry supplies a resolver whose `request` fires a cold fetch and later
279
+ * calls `parser.notifySchemaArrived`.
280
+ */
281
+ interface SchemaResolver {
282
+ get(kind: string): KindSchema | undefined;
283
+ /** Fire-and-forget cold fetch. Absent = static source, unknown kinds go raw. */
284
+ request?(kind: string): void;
285
+ /**
286
+ * Optional per-resolver override for legacy root-key recognition. Most hosts
287
+ * leave this unset and register the lookup once via
288
+ * `setJsonRootKeyLookup` — see that function for why.
289
+ */
290
+ kindForJsonRootKey?(key: string): string | null;
291
+ }
292
+ declare function setJsonRootKeyLookup(lookup: ((key: string) => string | null) | null): void;
293
+ type KindStreamEvent = {
294
+ type: "kind_identified";
295
+ kind: string;
296
+ path: JsonPath;
297
+ /** True when committed from the parent schema before __kind arrived. */
298
+ speculative?: boolean;
299
+ at: number;
300
+ } | {
301
+ type: "pending_kind";
302
+ path: JsonPath;
303
+ at: number;
304
+ } | {
305
+ type: "kind_wait_end";
306
+ path: JsonPath;
307
+ outcome: "identified" | "raw_fallback";
308
+ kind?: string;
309
+ reason?: string;
310
+ at: number;
311
+ } | {
312
+ type: "pending_schema";
313
+ kind: string;
314
+ path: JsonPath;
315
+ at: number;
316
+ } | {
317
+ type: "field";
318
+ kind: string;
319
+ path: JsonPath;
320
+ key: string;
321
+ value: unknown;
322
+ at: number;
323
+ } | {
324
+ type: "object_start";
325
+ path: JsonPath;
326
+ at: number;
327
+ } | {
328
+ type: "object_complete";
329
+ kind: string;
330
+ path: JsonPath;
331
+ value: Record<string, unknown>;
332
+ at: number;
333
+ } | {
334
+ type: "raw_object";
335
+ path: JsonPath;
336
+ value: unknown;
337
+ reason: string;
338
+ /**
339
+ * The kind that WAS identified for this node before it degraded raw —
340
+ * present ONLY for schema-availability failures ("no schema registered"
341
+ * for an identified/declared kind), never for structural failures
342
+ * (missing __kind, duplicate keys, schema violations). Consumers use it
343
+ * to preserve a known-but-unrenderable kind on the envelope so the
344
+ * render seam can route to the generic viewer (or upgrade when the
345
+ * schema arrives late) instead of dumping raw JSON.
346
+ */
347
+ kind?: string;
348
+ at: number;
349
+ } | {
350
+ type: "optional_field_missing";
351
+ kind: string;
352
+ path: JsonPath;
353
+ field: string;
354
+ at: number;
355
+ } | {
356
+ type: "extra_field";
357
+ kind: string;
358
+ path: JsonPath;
359
+ field: string;
360
+ at: number;
361
+ } | {
362
+ type: "block_snapshot";
363
+ kind: string;
364
+ path: JsonPath;
365
+ value: Record<string, unknown>;
366
+ residue: IrResidue | null;
367
+ complete: boolean;
368
+ at: number;
369
+ } | {
370
+ type: "array_start";
371
+ path: JsonPath;
372
+ field: string;
373
+ at: number;
374
+ } | {
375
+ type: "complete";
376
+ kind: string;
377
+ value: unknown;
378
+ at: number;
379
+ } | {
380
+ type: "error";
381
+ reason: string;
382
+ at: number;
383
+ };
384
+ type KindStreamParserOptions = {
385
+ onEvent: (event: KindStreamEvent) => void;
386
+ schemas: Record<string, KindSchema> | SchemaResolver;
387
+ /**
388
+ * Known-context root prediction (e.g. "this agent's output schema is
389
+ * flashcard_set"). The root commits speculatively at `{` open — the Option-1
390
+ * provenance path for agents whose schemas don't carry __kind yet.
391
+ */
392
+ expectedRootKind?: string;
393
+ };
394
+ declare class KindStreamParser {
395
+ private readonly options;
396
+ private readonly resolver;
397
+ private readonly stack;
398
+ private readonly objectKinds;
399
+ private readonly inlineSchemas;
400
+ private readonly recordSchemas;
401
+ private readonly rawObjectPaths;
402
+ /**
403
+ * Subtrees whose value domain is "any JSON" by schema (`json` / `json[]`
404
+ * fields, `record` with `values:"json"` members). OPAQUE by contract: no
405
+ * kind identification, no pending_kind, no raw_object degradation — unknown
406
+ * structure here is the declared shape, not a failure. Propagates to every
407
+ * descendant compound.
408
+ */
409
+ private readonly opaquePaths;
410
+ private readonly deferredFields;
411
+ private readonly awaitingKindPaths;
412
+ /** Paths whose kind came from parent-schema prediction, unconfirmed so far. */
413
+ private readonly speculativeKinds;
414
+ /**
415
+ * Kind named by the root object's FIRST key through the `json_root_key`
416
+ * surface registry — a candidate, adopted only at root finalize.
417
+ */
418
+ private rootSurfaceKind;
419
+ /** kind → paths (by key) waiting for the resolver's cold fetch. */
420
+ private readonly pendingSchemaPaths;
421
+ /** Pending-schema paths whose object already closed. */
422
+ private readonly closedPendingPaths;
423
+ /** Schemas delivered via notifySchemaArrived (overlay over the resolver). */
424
+ private readonly arrivedSchemas;
425
+ private tokenizer;
426
+ private root;
427
+ private rootKind;
428
+ private rootDone;
429
+ private failed;
430
+ constructor(options: KindStreamParserOptions);
431
+ push(chunk: string): void;
432
+ end(): void;
433
+ private resolvePendingSchemasAsRaw;
434
+ /** True once the root value has closed — the region is fully consumed. */
435
+ get isComplete(): boolean;
436
+ get hasFailed(): boolean;
437
+ /**
438
+ * Upgrade-in-place: the registry's cold fetch answered. Pending nodes for
439
+ * this kind validate and complete (closed nodes retroactively); a null
440
+ * schema (fetch miss) drops them to raw. Safe to call after end().
441
+ */
442
+ notifySchemaArrived(kind: string, schema: KindSchema | null): void;
443
+ private handleToken;
444
+ private handleString;
445
+ private handlePunctuation;
446
+ private beginCompound;
447
+ /**
448
+ * THE JSON-ROOT-KEY SURFACE — `content_ir.kind_surface` rows of type
449
+ * `json_root_key`, live at last (they were inert phantom rows until
450
+ * 2026-08-20).
451
+ *
452
+ * A legacy payload such as `{"quiz_title": ...}` carries no `__kind`, but
453
+ * the ONE surface registry knows exactly which kind that root key names —
454
+ * the same lookup the SERVER performs before adapting the payload
455
+ * (`aidream .../processing/blocks/envelope.py`). Consulting it HERE, in the
456
+ * shared parser core, is what makes one place decide it: both hosts — the
457
+ * one-shot `normalizeJsonRegion` (DB reload / reconcile) and the live
458
+ * `openParseSession` (streaming) — build their parser through
459
+ * `createKindStreamParser`, so neither passes an option and neither can
460
+ * drift from the other.
461
+ *
462
+ * Recorded at the first root key; ADOPTED only when the root object closes
463
+ * (`completeTypedObject`). That is the surface registry's complete-only
464
+ * convergence law, and every json_root_key row is `streaming:false` — these
465
+ * legacy shapes are recognised by their whole payload, so speculating
466
+ * mid-stream would flash a kind component over an object that may never
467
+ * satisfy the schema. An explicit `expectedRootKind` (an agent's declared
468
+ * output schema) is stronger context and always wins; an actual `__kind`
469
+ * still wins over both.
470
+ */
471
+ private noteRootSurfaceKind;
472
+ /**
473
+ * Prediction from the parent schema: object field → declared kind; array
474
+ * item → sole itemKind; root → expectedRootKind. Only when the schema is
475
+ * actually resolvable (a prediction we can't validate against is not a
476
+ * commitment worth making).
477
+ */
478
+ private resolveSpeculativeKind;
479
+ /**
480
+ * A schema usable for OBJECT speculation/snapshots — root-form kinds
481
+ * (non-object data-only shapes) are never a valid object commitment.
482
+ */
483
+ private lookupObjectSchema;
484
+ private beginScalar;
485
+ private placeValue;
486
+ private acceptObjectKey;
487
+ private acceptColon;
488
+ private acceptComma;
489
+ private closeCompound;
490
+ /**
491
+ * True when a value placed at `path` sits directly under a json-any
492
+ * placement: a `json`/`json[]` FIELD, or a member of a `record` whose
493
+ * values are `"json"`. (Deeper descendants inherit via `opaquePaths`.)
494
+ */
495
+ private isJsonAnyPlacement;
496
+ private onValueFinalized;
497
+ /** __kind arrived for an object — confirm speculation, identify, or backtrack. */
498
+ private onKindDiscriminatorArrived;
499
+ /** A contradicted speculation may re-tag only where the new kind is legal. */
500
+ private speculativeRetagAllowed;
501
+ private addPendingSchemaPath;
502
+ private completeTypedObject;
503
+ /** Validate + complete an object whose value carries __kind. */
504
+ private finalizeTypedObject;
505
+ /** Validate + complete an object typed purely by parent prediction. */
506
+ private finalizeSpeculatedObject;
507
+ private validateArrayItemKind;
508
+ private completeRoot;
509
+ /** Mark a node raw (node-scoped failure) without killing the stream. */
510
+ private markNodeRaw;
511
+ private emitRawObject;
512
+ private clearKindWait;
513
+ private emitFieldIfReady;
514
+ private flushDeferredFields;
515
+ private getLiveObjectValue;
516
+ private emitBlockSnapshotForObject;
517
+ /**
518
+ * On `complete` snapshots (and post-close schema upgrades) the frame has
519
+ * already been popped — the finalized value lives in the root tree.
520
+ */
521
+ private getFinalizedObjectValue;
522
+ private validateFieldPlacement;
523
+ private emitSchemaNotices;
524
+ private validateObjectAgainstSchema;
525
+ private validateFinalFieldValue;
526
+ private validateValueAgainstField;
527
+ private validateScalarField;
528
+ private validateRecordScalar;
529
+ private validateRecordObject;
530
+ private validateObjectAgainstFields;
531
+ private registerObjectContext;
532
+ private resolveParentFieldSchema;
533
+ private getObjectKindForPath;
534
+ private getDirectObjectKind;
535
+ private isAllowedSchemaField;
536
+ private parentFieldName;
537
+ private fieldKeyFromPath;
538
+ private isKindFieldPath;
539
+ private pathKey;
540
+ private lookupSchema;
541
+ private fail;
542
+ private emit;
543
+ private currentFrame;
544
+ }
545
+ declare function createKindStreamParser(options: KindStreamParserOptions): KindStreamParser;
546
+
547
+ /**
548
+ * IrTree — the immutable, structurally-shared view of a parsed region.
549
+ *
550
+ * Consumes KindStreamEvents and maintains one node per kind-resolved path.
551
+ * Node values are STABILIZED: compound children that are themselves kind
552
+ * nodes are substituted by their current tree value, so an update to
553
+ * cards[7] produces new identities for root → cards → cards[7] ONLY;
554
+ * cards[0..6] keep referential identity and memoized components bail out.
555
+ *
556
+ * Parser frame values are NEVER exposed — everything handed out is rebuilt
557
+ * copy-on-write, so it is safe to freeze (Redux dev-mode immutability) and
558
+ * safe to hold across renders.
559
+ *
560
+ * Both the live ParseSession and the one-shot normalizer build their
561
+ * CanonicalBlockIR from this tree — one assembly path, structurally
562
+ * identical output for stream and static input.
563
+ */
564
+
565
+ interface IrTreeNode {
566
+ kind: string;
567
+ kindState: IrKindState;
568
+ path: IrPath;
569
+ pathKey: string;
570
+ /** Stabilized immutable snapshot value (schema fields + __kind only). */
571
+ value: Record<string, unknown>;
572
+ residue: IrResidue | null;
573
+ complete: boolean;
574
+ /** Bumps on every update to this node (incl. child propagation). */
575
+ version: number;
576
+ }
577
+ declare class IrTree {
578
+ private readonly nodes;
579
+ private readonly rawPaths;
580
+ private readonly dirty;
581
+ /**
582
+ * KIND PRESERVATION (streaming db/cloud kinds): pathKey → identified kind
583
+ * for nodes whose kind is KNOWN (kind_identified / pending_schema) but that
584
+ * have no snapshot node yet because the schema is still cold-fetching.
585
+ * Without this, the envelope reports `kind: ""` for the whole pending
586
+ * window and the render seam can only show raw JSON.
587
+ */
588
+ private readonly identifiedKinds;
589
+ /** pathKeys currently waiting on a schema cold fetch. */
590
+ private readonly pendingSchemaPaths;
591
+ /**
592
+ * Early top-level scalar fields (title, loading_message, …) captured for
593
+ * identified-but-schema-pending nodes — the loading-component fuel. Scalars
594
+ * only (no live parser references can escape through here). Superseded the
595
+ * moment a real snapshot node exists.
596
+ */
597
+ private readonly earlyFields;
598
+ /**
599
+ * pathKey → identified kind preserved through a SCHEMA-AVAILABILITY raw
600
+ * fallback (parser stamped `kind` on the raw_object event). Structural raws
601
+ * (missing __kind, duplicate key, validation failure) never land here.
602
+ */
603
+ private readonly rawKinds;
604
+ private regionStatus;
605
+ private errorReason;
606
+ private rootRawValue;
607
+ /** Notices for degrades that tried to erase already-published data. */
608
+ private readonly rescueNotices;
609
+ private completedKind;
610
+ get status(): "streaming" | "complete" | "error";
611
+ applyEvent(event: KindStreamEvent): void;
612
+ getNode(pathKey: string): IrTreeNode | null;
613
+ listNodes(): IrTreeNode[];
614
+ isRawPath(pathKey: string): boolean;
615
+ /** Dirty pathKeys since the last drain — the flush/notify unit. */
616
+ drainDirty(): string[];
617
+ hasDirty(): boolean;
618
+ private upsertNode;
619
+ /**
620
+ * Substitute kind-node children with their current tree values so sibling
621
+ * identities are stable; deep-copy everything else so no live parser
622
+ * reference ever escapes.
623
+ */
624
+ private stabilizeValue;
625
+ /**
626
+ * COW spine rebuild: replace the child's slot in each ancestor kind-node's
627
+ * value, shallow-copying only the containers along the way. Siblings keep
628
+ * identity; every ancestor gets a new value identity + version bump.
629
+ */
630
+ private propagateToAncestors;
631
+ private findNearestAncestorNode;
632
+ private cloneAlong;
633
+ private markRaw;
634
+ /**
635
+ * A rescue means a degrade tried to erase data a user could already see —
636
+ * an upstream defect. It rides the envelope as a notice so it surfaces in the
637
+ * Error Inspector instead of being silently absorbed (`core/` is a pure
638
+ * kernel: no console, no capture — the notice IS the alarm).
639
+ */
640
+ private recordRescue;
641
+ /**
642
+ * Assemble the canonical envelope. ONE code path for stream + one-shot.
643
+ * Callers supply the fingerprint (one-shot hashes the source; live sessions
644
+ * keep an incremental hasher so no per-flush re-hash happens).
645
+ */
646
+ buildEnvelope(fingerprint: string): CanonicalBlockIR;
647
+ }
648
+
649
+ /**
650
+ * Character-level incremental JSON tokenizer. Survives chunk boundaries
651
+ * mid-token (strings, escapes, unicode sequences, primitives). Pure — no
652
+ * React, no Redux, no IO.
653
+ *
654
+ * Moved from app/(dev)/demos/json-block-detector/reusable-logic.ts (the demo
655
+ * is now a consumer of this library).
656
+ */
657
+ type Punct = "{" | "}" | "[" | "]" | ":" | ",";
658
+ type JsonToken = {
659
+ type: "punct";
660
+ value: Punct;
661
+ at: number;
662
+ } | {
663
+ type: "string";
664
+ value: string;
665
+ at: number;
666
+ } | {
667
+ type: "number";
668
+ value: number;
669
+ at: number;
670
+ } | {
671
+ type: "boolean";
672
+ value: boolean;
673
+ at: number;
674
+ } | {
675
+ type: "null";
676
+ value: null;
677
+ at: number;
678
+ };
679
+ declare class JsonStreamTokenizer {
680
+ private readonly onToken;
681
+ private mode;
682
+ private pos;
683
+ private stringBuffer;
684
+ private primitiveBuffer;
685
+ private unicodeBuffer;
686
+ private tokenStart;
687
+ constructor(onToken: (token: JsonToken) => void);
688
+ get position(): number;
689
+ push(chunk: string): void;
690
+ end(): void;
691
+ private handleNormalChar;
692
+ private emitPrimitive;
693
+ private isDelimiter;
694
+ }
695
+
696
+ /**
697
+ * Compliant snapshot builder — the guarantee that a renderer always receives
698
+ * a well-formed, schema-shaped object even mid-stream.
699
+ *
700
+ * ZERO DATA LOSS: unknown keys are NOT merged into the snapshot value (they
701
+ * would be indistinguishable from schema fields). They are returned on the
702
+ * residue channel and re-merged only at wire-serialization time via
703
+ * `mergeResidueIntoValue`.
704
+ */
705
+
706
+ /** Placeholder for a required field not yet received during streaming. */
707
+ declare function emptyValueForFieldSchema(field: FieldSchema): unknown;
708
+ interface CompliantKindSnapshot {
709
+ /** Schema fields + __kind only. Required-but-missing fields hold typed placeholders. */
710
+ value: Record<string, unknown>;
711
+ /** Unknown keys + missing optionals. Null when both channels are empty. */
712
+ residue: IrResidue | null;
713
+ }
714
+ declare function buildCompliantKindSnapshot(schema: KindSchema, partial: Record<string, unknown>): CompliantKindSnapshot;
715
+ /**
716
+ * Wire/round-trip form: schema fields + unknown keys back together, exactly
717
+ * as the source carried them. `residue.extra` wins nothing — snapshot value
718
+ * and extras are disjoint by construction.
719
+ */
720
+ declare function mergeResidueIntoValue(value: Record<string, unknown>, residue: IrResidue | null): Record<string, unknown>;
721
+
722
+ /**
723
+ * Idempotent normalizer — the "recognizes its own work" property.
724
+ *
725
+ * THE LAW: anything already carrying a current CanonicalBlockIR envelope is
726
+ * returned BY REFERENCE — zero reprocessing, reference equality holds, React
727
+ * bails out. Only raw text (a detected region's source) is ever parsed, and
728
+ * it is parsed exactly once per fingerprint.
729
+ *
730
+ * `normalizeJsonRegion` is the one-shot mode: the same KindStreamParser that
731
+ * powers live streams, run over a complete region string (DB reloads,
732
+ * reconcile passes), assembled through the same IrTree the live session uses
733
+ * — stream and static output are structurally identical by construction.
734
+ */
735
+
736
+ declare function isCanonicalBlockIR(value: unknown): value is CanonicalBlockIR;
737
+ /**
738
+ * The idempotence fast path: return the existing envelope by reference when
739
+ * it still describes this source text; null means "parse needed".
740
+ */
741
+ declare function reuseEnvelopeIfCurrent(source: string, candidate: unknown): CanonicalBlockIR | null;
742
+ interface NormalizeJsonRegionOptions {
743
+ schemas: Record<string, KindSchema> | SchemaResolver;
744
+ /** Known-context root prediction (agent output schema, fence hint). */
745
+ expectedRootKind?: string;
746
+ /**
747
+ * Pass a previously persisted envelope (message metadata, artifact row);
748
+ * when its fingerprint matches, it is returned as-is and nothing parses.
749
+ */
750
+ existing?: unknown;
751
+ }
752
+ interface CompleteValueEnvelopeOptions {
753
+ /**
754
+ * Wire discriminator recorded on the root node — which syntax established
755
+ * the kind. Defaults to the JSON `__kind` key; XML surfaces converging at
756
+ * region finalize pass `xmlDiscriminator(tag)` so round-trip serializers
757
+ * know the original arrival format.
758
+ */
759
+ discriminator?: IrDiscriminator;
760
+ }
761
+ /**
762
+ * Build a resolved, complete envelope directly from an already-structured
763
+ * value (a persisted artifact's `content.data` object, or a completed XML
764
+ * region's strategy output). This is the zero-reprocessing rehydration path:
765
+ * the value IS the reconstructed region value (schema fields + residue extras
766
+ * merged at persist time), so no tokenizer/parser run is needed — the
767
+ * envelope wraps it verbatim. The fingerprint hashes the canonical value
768
+ * serialization, so any two paths producing the same value produce the SAME
769
+ * envelope (stream ≡ static by construction).
770
+ */
771
+ declare function envelopeFromCompleteValue(value: Record<string, unknown>, kind: string, options?: CompleteValueEnvelopeOptions): CanonicalBlockIR;
772
+ /** One-shot: complete region text in → canonical envelope out. */
773
+ declare function normalizeJsonRegion(source: string, options: NormalizeJsonRegionOptions): CanonicalBlockIR;
774
+
775
+ /**
776
+ * Stable, fast content fingerprint for IR envelopes.
777
+ *
778
+ * Used as the idempotence / cache key: a persisted CanonicalBlockIR is only
779
+ * reused when its fingerprint matches the region source text it claims to
780
+ * represent. Not cryptographic — collision resistance at the "same message,
781
+ * same block" scale is all that's required, and it must be synchronous and
782
+ * dependency-free (runs per region on the hot streaming path).
783
+ *
784
+ * FNV-1a 32-bit, applied twice with different seeds and concatenated, so a
785
+ * single 32-bit collision doesn't alias two regions.
786
+ *
787
+ * `createFingerprinter` is the incremental form for live streams: feeding
788
+ * chunks one at a time yields EXACTLY the same fingerprint as
789
+ * `fingerprintText` over the concatenation — sessions never re-hash the
790
+ * whole source per flush.
791
+ */
792
+ interface Fingerprinter {
793
+ push(chunk: string): void;
794
+ /** Fingerprint of everything pushed so far. */
795
+ current(): string;
796
+ }
797
+ declare function createFingerprinter(): Fingerprinter;
798
+ declare function fingerprintText(source: string): string;
799
+
800
+ /**
801
+ * Discriminator abstraction — how a node's kind is established from the wire.
802
+ *
803
+ * The parser never hardcodes "kind comes from a JSON key". JSON resolves via
804
+ * the `__kind` field (implemented today); XML resolves via tag → registry
805
+ * alias (Phase 6 — contract only). `IrStructuredNode.discriminator` records
806
+ * which resolver produced each node so renderers and round-trip serializers
807
+ * can reconstruct the original wire format per node.
808
+ */
809
+
810
+ declare const JSON_DISCRIMINATOR: IrDiscriminator;
811
+ declare function xmlDiscriminator(tag: string): IrDiscriminator;
812
+ declare function fenceDiscriminator(language: string): IrDiscriminator;
813
+ /** What a resolver can say about an opening compound value. */
814
+ type KindResolution = {
815
+ outcome: "kind";
816
+ kind: string;
817
+ } | {
818
+ outcome: "pending";
819
+ } | {
820
+ outcome: "raw";
821
+ reason: string;
822
+ };
823
+
824
+ /**
825
+ * IrEnvelopeCache — the persisted envelope cache carried on MESSAGE PARTS.
826
+ *
827
+ * Phase 5 (reload without re-parse): at stream end, `assembleMessageParts`
828
+ * stamps every completed JSON region's CanonicalBlockIR onto the committed
829
+ * `CxTextContent.metadata.__ir` as this cache — keyed by fingerprint, because
830
+ * one text part can embed several regions. On reload the splitter's envelope
831
+ * memo consults the seeded cache (`registry/region-envelope-memo.ts`) and
832
+ * reuses the persisted envelope BY REFERENCE via `reuseEnvelopeIfCurrent`
833
+ * (exact fingerprint match of the detected region source) — zero re-parse.
834
+ *
835
+ * Shape discipline: the SAME `__ir` metadata key carries two shapes at two
836
+ * levels, disambiguated by validators that reject each other —
837
+ * - render block metadata → a single CanonicalBlockIR (`isCanonicalBlockIR`)
838
+ * - message part metadata → this cache (`isIrEnvelopeCache`)
839
+ *
840
+ * Pure kernel module: types + validators only. No React/Redux/Supabase.
841
+ */
842
+
843
+ /** Cache version. Bump = migration point for persisted part caches. */
844
+ declare const IR_ENVELOPE_CACHE_VERSION: 1;
845
+ /**
846
+ * The envelope cache persisted on a message part's `metadata.__ir`.
847
+ * `blocks` maps each region-source fingerprint to its complete envelope.
848
+ */
849
+ interface IrEnvelopeCache {
850
+ v: typeof IR_ENVELOPE_CACHE_VERSION;
851
+ blocks: Record<string, CanonicalBlockIR>;
852
+ }
853
+ /**
854
+ * Strict whole-cache validation: every entry must be a complete
855
+ * CanonicalBlockIR keyed by its OWN fingerprint. A cache failing this guard
856
+ * came from a buggy or foreign writer — callers surface that loudly and seed
857
+ * nothing (a half-trusted cache is how poisoned envelopes reach renderers).
858
+ */
859
+ declare function isIrEnvelopeCache(value: unknown): value is IrEnvelopeCache;
860
+ /**
861
+ * Build the persistable cache from a run's collected envelopes. Only
862
+ * complete envelopes are cacheable (a streaming/error envelope can never be
863
+ * reused — its fingerprint doesn't describe a finished region). Duplicate
864
+ * regions (same fingerprint) collapse to one entry. Engine-agnostic by
865
+ * design: an aidream-built `engine: "py-block-detector"` envelope is cached
866
+ * identically to an FE-parsed one. Returns null when nothing qualifies so
867
+ * callers skip the metadata stamp entirely.
868
+ */
869
+ declare function envelopeCacheFromEnvelopes(envelopes: readonly CanonicalBlockIR[]): IrEnvelopeCache | null;
870
+
871
+ /**
872
+ * PURE envelope ingestion — reading `metadata.__ir` off a block's metadata
873
+ * and gating SERVER-BUILT envelopes at the wire boundary. Twin-safe: no
874
+ * React / Redux / Supabase / host diagnostics — side effects (seeding the
875
+ * region-envelope memo, screaming to the Error Inspector) are injected by
876
+ * the host through `InboundEnvelopeHooks`.
877
+ *
878
+ * The frontend host shell is `redux/render-block-envelope.ts` (binds the
879
+ * hooks to `seedEnvelope` + `captureError`); aidream's Workflow Studio binds
880
+ * its own. Contract: features/content-ir/docs/PYTHON_ENVELOPE_CONTRACT.md.
881
+ */
882
+
883
+ /** Read a CanonicalBlockIR envelope off a block's metadata (or anything). */
884
+ declare function readEnvelope(metadata: Record<string, unknown> | null | undefined): CanonicalBlockIR | null;
885
+ /** Pure classification of inbound metadata carrying (or not) an envelope. */
886
+ type InboundEnvelopeVerdict = {
887
+ /** No `__ir` key — zero-touch pass, same reference back. */
888
+ outcome: "absent";
889
+ metadata: Record<string, unknown> | undefined;
890
+ } | {
891
+ /** Valid CanonicalBlockIR — same reference back (idempotence law). */
892
+ outcome: "valid";
893
+ metadata: Record<string, unknown>;
894
+ envelope: CanonicalBlockIR;
895
+ } | {
896
+ /** Malformed/foreign `__ir` — a COPY with `__ir` stripped. */
897
+ outcome: "malformed";
898
+ metadata: Record<string, unknown>;
899
+ engine: string;
900
+ raw: unknown;
901
+ };
902
+ declare function classifyInboundEnvelopeMetadata(metadata: Record<string, unknown> | null | undefined): InboundEnvelopeVerdict;
903
+ /** Host-injected side effects for the inbound gate. */
904
+ interface InboundEnvelopeHooks {
905
+ /** Called with every VALID envelope so later re-splits reuse it by reference. */
906
+ seedEnvelope?: (envelope: CanonicalBlockIR) => void;
907
+ /** Called LOUDLY for every malformed envelope — a bad envelope is a defect. */
908
+ reportMalformed?: (info: {
909
+ blockId: string;
910
+ engine: string;
911
+ raw: unknown;
912
+ }) => void;
913
+ }
914
+ /**
915
+ * Ingest guard for SERVER-BUILT envelopes riding `metadata.__ir` on a
916
+ * `render_block` event.
917
+ *
918
+ * - No `__ir` key → the SAME metadata reference back (zero-touch pass).
919
+ * - Valid CanonicalBlockIR → the SAME metadata reference back (reuse-by-
920
+ * reference — the idempotence law) AND `hooks.seedEnvelope` fires so any
921
+ * later re-split of the same region source reuses it instead of parsing.
922
+ * - Malformed/foreign `__ir` → a COPY with `__ir` stripped, plus a loud
923
+ * `hooks.reportMalformed`. A bad envelope must never poison kind routing
924
+ * or the persistence cache; dropping it degrades that block to the
925
+ * ordinary content-driven path, nothing more.
926
+ */
927
+ declare function sanitizeInboundEnvelopeMetadata(metadata: Record<string, unknown> | null | undefined, context: {
928
+ blockId: string;
929
+ }, hooks?: InboundEnvelopeHooks): Record<string, unknown> | undefined;
930
+
931
+ /**
932
+ * Pure envelope→value functions (the zero-data-loss wire round-trip).
933
+ *
934
+ * These live in core/ (the pure kernel) because every layer needs them —
935
+ * including kinds/legacy-bridge-utils, whose bridges are built at MODULE
936
+ * SCOPE inside kinds/<slug>.ts files that system-kinds.ts imports. When these
937
+ * functions lived in redux/render-block-envelope.ts, that file's memo/capture
938
+ * imports closed a module cycle (kinds → bridge-utils → render-block-envelope
939
+ * → region-envelope-memo → kind-registry → system-kinds → kinds), which made
940
+ * SYSTEM_KIND_DEFINITIONS entry-point-fragile (undefined defs when a kinds
941
+ * module was the import entry). Keeping them here makes every kinds module
942
+ * cycle-free BY CONSTRUCTION. redux/render-block-envelope re-exports them for
943
+ * its existing consumers.
944
+ */
945
+
946
+ /**
947
+ * Rebuild the region's full data: walk the root value and merge each node's
948
+ * residue extras back in (root residue + nodeIndex residues). This is the
949
+ * zero-data-loss read: nothing the model emitted is missing, whether or not
950
+ * a schema knew about it.
951
+ */
952
+ declare function reconstructRegionValue(envelope: CanonicalBlockIR): Record<string, unknown>;
953
+ /** Deep-remove the __kind discriminator (the parser injects it into snapshots). */
954
+ declare function stripKindDeep(value: unknown): unknown;
955
+
956
+ /**
957
+ * ContentRegion — the handshake between a host detector and this library.
958
+ *
959
+ * Boundary rule (the answer to the single-root problem): the HOST detector
960
+ * (StreamBlockAccumulator on streams, content-splitter-v2 on DB loads) finds
961
+ * where a structured region begins and ends inside prose; the kind parser
962
+ * owns everything INSIDE the region. A fresh parser is created per region, so
963
+ * "one root object" is true within a region by construction, and multiple
964
+ * blocks per message are just multiple regions.
965
+ *
966
+ * Through Phase 6 the host's fence-close / brace-count logic remains the
967
+ * region-end oracle (bit-level parity with today). In Phase 7 the parser's
968
+ * own frame stack becomes the oracle and the mirrored counters are deleted.
969
+ */
970
+ type RegionFormat = "json";
971
+ type RegionSourceKind = "fence" | "bare";
972
+ interface ContentRegionInit {
973
+ regionId: string;
974
+ format: RegionFormat;
975
+ sourceKind: RegionSourceKind;
976
+ }
977
+ /** How a region ended. Truncation is a normal outcome, never stream-fatal. */
978
+ type RegionEndReason = "closed" | "truncated" | "aborted";
979
+
980
+ /**
981
+ * Schema-shape helpers — structural depth and layout hints derived from a
982
+ * KindSchema. Drives the generic renderer's flat / grid / nested layout and
983
+ * human labels for slugs.
984
+ *
985
+ * Moved from app/(dev)/demos/json-block-detector/schema-structure.ts.
986
+ */
987
+
988
+ /** Human label from a slug or field key (snake_case → Title Case + acronym fixes). */
989
+ declare function formatBlockLabel(key: string): string;
990
+ /**
991
+ * Structural nesting depth of a kind schema — drives generic renderer layout.
992
+ * 0 = flat card (scalars only)
993
+ * 1 = grid (one array/object nesting level)
994
+ * 2+ = expandable grid (parent → child → grandchild)
995
+ */
996
+ declare function schemaStructureDepth(schema: KindSchema, allSchemas: Record<string, KindSchema>, visiting?: Set<string>): number;
997
+ type SchemaLayoutMode = "flat" | "grid" | "nested";
998
+ declare function schemaLayoutMode(schema: KindSchema, allSchemas: Record<string, KindSchema>): SchemaLayoutMode;
999
+
1000
+ /**
1001
+ * ParseSession — one live region parse per stream identity.
1002
+ *
1003
+ * Owns the parser + IrTree OUTSIDE React and Redux. Writers push chunks;
1004
+ * N readers subscribe per path (useIrNode / selectors) and are notified on
1005
+ * the host's flush cadence — never per token.
1006
+ *
1007
+ * Notification contract: `write()`/`end()` never notify. The host (demo rAF
1008
+ * loop, process-stream's dispatchBatch tick) calls `flushNotify()` to publish
1009
+ * dirty paths. Late schema arrivals (registry cold fetch) notify themselves —
1010
+ * they happen outside any streaming tick.
1011
+ */
1012
+
1013
+ interface ParseSessionOptions {
1014
+ identity: string;
1015
+ schemas: Record<string, KindSchema> | SchemaResolver;
1016
+ expectedRootKind?: string;
1017
+ /**
1018
+ * Registry hook: subscribe to cold-fetch answers, return an unsubscribe.
1019
+ * The session forwards arrivals into the parser (upgrade-in-place) and
1020
+ * notifies affected readers immediately.
1021
+ */
1022
+ onSchemaArrived?: (deliver: (kind: string, schema: KindSchema | null) => void) => () => void;
1023
+ /** Observe every parser event (demo inspector, telemetry). */
1024
+ onEvent?: (event: KindStreamEvent) => void;
1025
+ }
1026
+ type Listener = () => void;
1027
+ declare class ParseSession {
1028
+ readonly identity: string;
1029
+ private readonly parser;
1030
+ private readonly tree;
1031
+ private readonly listeners;
1032
+ private readonly anyListeners;
1033
+ private readonly unsubscribeSchemaArrivals;
1034
+ private readonly fingerprinter;
1035
+ private ended;
1036
+ constructor(options: ParseSessionOptions);
1037
+ /** The single writer's input. Does NOT notify — host flushes on its cadence. */
1038
+ write(chunk: string): void;
1039
+ end(): void;
1040
+ get isEnded(): boolean;
1041
+ get status(): "streaming" | "complete" | "error";
1042
+ getNode(pathKey: string): IrTreeNode | null;
1043
+ listNodes(): IrTreeNode[];
1044
+ isRawPath(pathKey: string): boolean;
1045
+ subscribe(pathKey: string, listener: Listener): () => void;
1046
+ /** Structural subscription: fires when ANY node changes (mount lists). */
1047
+ subscribeAny(listener: Listener): () => void;
1048
+ /** Publish dirty paths to their readers. Host-cadence, coalesced. */
1049
+ flushNotify(): void;
1050
+ buildEnvelope(): CanonicalBlockIR;
1051
+ dispose(): void;
1052
+ }
1053
+
1054
+ /**
1055
+ * Session manager — one writer per stream identity, N readers, mechanically
1056
+ * enforced: a second `openParseSession` for a live identity THROWS. Readers
1057
+ * resolve sessions by identity and never gain write access.
1058
+ *
1059
+ * Identities: `"{requestId}:{blockId}"` for agent-stream regions; any unique
1060
+ * string for other hosts (demo runs, notes, one-shot upgrades).
1061
+ *
1062
+ * Ended sessions stay resolvable for late readers (reload happens via the
1063
+ * persisted envelope, but in-session remounts read here) until disposed.
1064
+ */
1065
+
1066
+ declare function openParseSession(options: ParseSessionOptions): ParseSession;
1067
+ declare function getParseSession(identity: string): ParseSession | null;
1068
+ declare function disposeParseSession(identity: string): void;
1069
+
1070
+ /**
1071
+ * KindDefinition — one kind, many facets. THE canonical registry entry.
1072
+ *
1073
+ * This does NOT merge BlockComponentRegistry or artifact-type-registry; the
1074
+ * `legacyBlockType` and `artifact` facets are FACADES pointing into them, so
1075
+ * migration is incremental and each registry keeps its own job.
1076
+ */
1077
+
1078
+ /**
1079
+ * The uniform props contract for kind-driven block components. This is what
1080
+ * de-special-cases flashcards: no component receives bespoke glue — every
1081
+ * kind component gets exactly this shape.
1082
+ */
1083
+ interface KindBlockProps {
1084
+ kind: string;
1085
+ schema: KindSchema;
1086
+ /** Compliant snapshot value (schema fields + __kind). */
1087
+ data: Record<string, unknown>;
1088
+ status: "streaming" | "complete" | "error";
1089
+ residue: IrResidue | null;
1090
+ path: IrPath;
1091
+ /** ParseSession identity for live child subscriptions; null after reload. */
1092
+ identity: string | null;
1093
+ /** Child-kind schema lookup (replaces passing allSchemas around). */
1094
+ resolve: (kind: string) => KindSchema | undefined;
1095
+ }
1096
+ type KindTier = "eager" | "warm" | "cold";
1097
+ interface KindDefinition {
1098
+ /** Canonical slug — THE key. */
1099
+ kind: string;
1100
+ /** null until the warm/cold fetch delivers it. */
1101
+ schema: KindSchema | null;
1102
+ schemaSource: "system" | "flexible_data" | "content_ir";
1103
+ tier: KindTier;
1104
+ /** Component facet — lazy-loaded renderer for this kind. */
1105
+ component?: {
1106
+ load: () => Promise<{
1107
+ default: ComponentType<KindBlockProps>;
1108
+ }>;
1109
+ };
1110
+ /** Facade → BlockComponentRegistry type string (e.g. "flashcards"). */
1111
+ legacyBlockType?: string;
1112
+ /**
1113
+ * Legacy-bridge facet: derive the existing component's `serverData` from a
1114
+ * canonical envelope. This is what lets a kind light up the REAL component
1115
+ * (FlashcardsBlock, …) with zero component changes during migration.
1116
+ */
1117
+ toLegacyServerData?: (envelope: CanonicalBlockIR) => Record<string, unknown> | undefined;
1118
+ /** Facade → artifact-type-registry canvasType. */
1119
+ artifact?: {
1120
+ canvasType: string;
1121
+ };
1122
+ /**
1123
+ * Markdown export facet — the FORWARD leg of artifact ⇄ markdown. Receives
1124
+ * the ZERO-LOSS reconstructed value object (the CALLER reconstructs — for
1125
+ * persisted structured artifacts `content.data` already IS that object;
1126
+ * this facet only renders it; `__kind` discriminators may still be
1127
+ * present and must be ignored). MUST produce human-readable markdown
1128
+ * (headings / lists / bold), never a JSON dump — a fenced json body under
1129
+ * a heading is acceptable only for inherently-code payloads (e.g. a
1130
+ * schema_proposal's JSON Schema). Unknown extra keys the renderer doesn't
1131
+ * understand MUST be appended under a small "Additional details"
1132
+ * (key: value) section so nothing silently vanishes. Kinds without this
1133
+ * facet fall back to `genericKindMarkdown` (kinds/kind-markdown-utils.ts).
1134
+ */
1135
+ toMarkdown?: (value: Record<string, unknown>) => string;
1136
+ persistence?: {
1137
+ persistStructured: boolean;
1138
+ };
1139
+ /** Future XML tags / kind aliases resolving to this kind. */
1140
+ discriminatorAliases?: string[];
1141
+ /**
1142
+ * PARTIAL-READY opt-in — the streaming partial-kinds posture.
1143
+ *
1144
+ * While a structured region streams, the server may announce a PROVISIONAL
1145
+ * instance of this kind on `metadata.__ir_partial` (valid, closed JSON that
1146
+ * may be missing required fields). The default posture is WITHHOLD: a
1147
+ * provisional value is never routed to a component, and the block keeps its
1148
+ * loading skeleton until the region completes — because a component that
1149
+ * throws on an absent field must not be handed one.
1150
+ *
1151
+ * Setting this to `true` declares "this kind's component renders a partial
1152
+ * value without throwing", and the provisional value is routed to the SAME
1153
+ * component that renders the final one, filling in as tokens arrive. A kind
1154
+ * with a `toLegacyServerData` bridge must ALSO pass `{ provisional: true }`
1155
+ * to `makeCompleteEnvelopeBridge` (pinned by a test). A component that
1156
+ * throws anyway is caught, screams, and permanently drops this kind back to
1157
+ * withhold for the session.
1158
+ *
1159
+ * Contract: common-docs/systems/content-ir-system/STREAMING_PARTIAL_KINDS.md
1160
+ */
1161
+ partialReady?: boolean;
1162
+ /**
1163
+ * Loading-library slug (`kind_definition.metadata.loading_component`) —
1164
+ * which of the ~20 hardcoded loading components renders while this kind's
1165
+ * instance streams in / its schema+component fetch is in flight. Null or
1166
+ * unknown slugs fall back to the generic structured skeleton.
1167
+ */
1168
+ loadingComponent?: string | null;
1169
+ }
1170
+
1171
+ /**
1172
+ * Kind ↔ content_ir storage shape — the pure, DB-free reshaping between the
1173
+ * consumed `KindSchema` and the `content_ir.kind_definition.data` array +
1174
+ * `content_ir.kind_edge` rows.
1175
+ *
1176
+ * Two exact inverses:
1177
+ * - `kindSchemaToStorage` — the MIGRATION / write direction: a parsed
1178
+ * `KindSchema` (fields Record, targets inline) → an ORDERED `data` array
1179
+ * (targets stripped) + externalized `kind_edge` specs (targets, keyed by
1180
+ * field PATH). This is what pours `flexible_data` (and future authored
1181
+ * kinds) into the canonical tables.
1182
+ * - `storageToKindSchema` — the ADAPTER / read direction: `data` array +
1183
+ * edges → the same `KindSchema` the parser/emitter consume, re-attaching
1184
+ * `object.kind` / `array.itemKinds` from the edges by path.
1185
+ *
1186
+ * Design invariants (KIND_REGISTRY_STORAGE.md §4):
1187
+ * - The `data` element is `FieldSchema` + `name`, MINUS ref targets. Order is
1188
+ * intrinsic to the array (fixes the jsonb key-reorder bug).
1189
+ * - `kind_edge` is the SINGLE source of truth for kind→kind refs: `object`
1190
+ * → one edge (position null); `array` → N edges (union) ordered by
1191
+ * `position`. `field_name` is a dot-PATH so a ref nested inside an
1192
+ * `inline_object` still gets a distinct, collision-free edge.
1193
+ * - `inline_object` is structural: its `fields` is an ordered array, it never
1194
+ * becomes a registry row and never carries `__kind`. Its nested refs DO get
1195
+ * edges (path-prefixed) so cascade/pinning see every dependency. Its
1196
+ * `open` flag persists on the element (losing it is the open-empty-object
1197
+ * defect).
1198
+ * - `union.kinds` refs externalize to edges exactly like `array.itemKinds`;
1199
+ * the stored element keeps `hasKinds: true` so lost edges scream on read.
1200
+ * - A NON-OBJECT ROOT (`KindSchema.root`) stores as ONE reserved element
1201
+ * named `ROOT_STORAGE_NAME` ("__root") — the only element allowed in that
1202
+ * kind's `data`. Real fields may never use the reserved name.
1203
+ *
1204
+ * NO imports of supabase / the DB — this is pure and unit-tested by round-trip
1205
+ * (`__tests__/kind-storage-transform.test.ts`).
1206
+ */
1207
+
1208
+ /**
1209
+ * Reserved `data[]` element name for a NON-OBJECT ROOT form (`KindSchema.root`).
1210
+ * A root-form kind stores exactly one element under this name; the read
1211
+ * direction reconstructs `{ root }` instead of a field map. The write
1212
+ * direction rejects any REAL field with this name — the name is the marker.
1213
+ */
1214
+ declare const ROOT_STORAGE_NAME = "__root";
1215
+ type StoredFieldBase = {
1216
+ name: string;
1217
+ required?: boolean;
1218
+ nullable?: boolean;
1219
+ /** Human guidance — mirrors FieldBase.description. */
1220
+ description?: string;
1221
+ /** Default VALUE (annotation-level) — mirrors FieldBase.default. */
1222
+ default?: unknown;
1223
+ };
1224
+ /** One element of `kind_definition.data` — FieldSchema + name, ref targets removed. */
1225
+ type StoredFieldElement = (StoredFieldBase & {
1226
+ type: "string" | "boolean";
1227
+ }) | (StoredFieldBase & {
1228
+ type: "number";
1229
+ min?: number;
1230
+ max?: number;
1231
+ step?: number;
1232
+ }) | (StoredFieldBase & {
1233
+ type: "string[]";
1234
+ values?: string[];
1235
+ open?: boolean;
1236
+ }) | (StoredFieldBase & {
1237
+ type: "number[]" | "boolean[]";
1238
+ }) | (StoredFieldBase & {
1239
+ type: "json";
1240
+ }) | (StoredFieldBase & {
1241
+ type: "json[]";
1242
+ }) | (StoredFieldBase & {
1243
+ type: "array";
1244
+ }) | (StoredFieldBase & {
1245
+ type: "object";
1246
+ }) | (StoredFieldBase & {
1247
+ type: "inline_object";
1248
+ fields: StoredFieldElement[];
1249
+ open?: boolean;
1250
+ }) | (StoredFieldBase & {
1251
+ type: "record";
1252
+ values: RecordValueType;
1253
+ }) | (StoredFieldBase & {
1254
+ type: "enum";
1255
+ values: string[];
1256
+ open?: boolean;
1257
+ }) | (StoredFieldBase & {
1258
+ type: "union";
1259
+ scalars: Array<"string" | "number" | "boolean">;
1260
+ /** Marker that this union's kind refs live in edges (positions ordered). */
1261
+ hasKinds?: boolean;
1262
+ });
1263
+ /** One `content_ir.kind_edge` row (child resolved to an id at insert time). */
1264
+ type KindEdgeSpec = {
1265
+ /** Field PATH in the parent's `data` (dot-notation into inline_objects). */
1266
+ fieldPath: string;
1267
+ /** Child kind slug — insert resolves this to `child_definition_id`. */
1268
+ childKind: string;
1269
+ /** Union (anyOf) ordering for array refs; null for a single object ref. */
1270
+ position: number | null;
1271
+ };
1272
+ /** The full write payload for one kind: the ordered data array + its edges. */
1273
+ type KindStorageShape = {
1274
+ data: StoredFieldElement[];
1275
+ edges: KindEdgeSpec[];
1276
+ };
1277
+ declare class KindStorageError extends Error {
1278
+ constructor(message: string);
1279
+ }
1280
+ /**
1281
+ * MIGRATION / write direction. Order of `data` follows `Object.entries(fields)`
1282
+ * — the caller supplies the KindSchema whose field order is authoritative
1283
+ * (prefer the compiled `system-kinds.ts` order for system kinds during the
1284
+ * one-time flexible_data migration; jsonb order is the fallback for user kinds).
1285
+ */
1286
+ declare function kindSchemaToStorage(schema: KindSchema): KindStorageShape;
1287
+ /** ADAPTER / read direction — the exact inverse of `kindSchemaToStorage`. */
1288
+ declare function storageToKindSchema(kind: string, shape: KindStorageShape): KindSchema;
1289
+
1290
+ /**
1291
+ * The dual gate — Arman's law as executable code: a kind is only `is_active`
1292
+ * when its canonical `sample_data` passes BOTH systems.
1293
+ *
1294
+ * 1. Structural (Pydantic): the sample validates against the kind's
1295
+ * `emitted_json_schema`. Python's Pydantic is the AUTHORITATIVE owner of
1296
+ * this leg, but it reads the SAME materialized `emitted_json_schema` — so
1297
+ * this TS ajv check and the Python check validate the same sample against
1298
+ * the same schema and agree by construction. A disagreement IS the
1299
+ * screamer (a schema Pydantic can't express, or an ajv/Pydantic gap).
1300
+ * 2. Render (UI): the sample lights up the kind's real component. TS is the
1301
+ * AUTHORITATIVE owner of this leg. It is a PROXY for a DOM render, not a
1302
+ * DOM render. Exactly what it checks, and nothing more:
1303
+ * · the kind has something to render with (`legacyBlockType` or
1304
+ * `component`);
1305
+ * · for BRIDGED kinds, `toLegacyServerData(sample)` returns a plain
1306
+ * object that is SEMANTICALLY non-empty — see
1307
+ * `describeUnrenderableBridgeOutput` for the exact predicate;
1308
+ * · BRIDGELESS kinds pass with a recorded caveat — nothing is verified.
1309
+ *
1310
+ * This catches the 2026-07-04 "No flashcards available yet" class: a
1311
+ * bridge that returns `undefined`, `{}`, `{language:"json"}` (the raw
1312
+ * code-region annotation), or an object whose every value is empty.
1313
+ *
1314
+ * What it does NOT do — stated plainly, because a gate that overclaims is
1315
+ * worse than no gate: it does not mount the component, and it cannot know
1316
+ * WHICH key carries the payload. `{title:"Cell Biology", cards:[]}` passes
1317
+ * this leg on `title` alone. Proving the payload key is populated needs
1318
+ * per-kind knowledge the gate deliberately does not have; a DOM-level
1319
+ * render check is the deeper leg, deferred to an RTL harness.
1320
+ *
1321
+ * Both legs necessary, neither sufficient. Fail either → `isActive: false` and
1322
+ * the caller reports it loudly (Error Inspector, `content-ir`) and holds the
1323
+ * row out of production. This module is PURE (deps injected) so it runs in the
1324
+ * harness, in CI, and in a browser author-save alike.
1325
+ *
1326
+ * Ownership split (KIND_REGISTRY_STORAGE.md §2): the caller writes the outcome
1327
+ * to the LIVE `content_ir.kind_definition` row's `is_active`; the canonical
1328
+ * `_version_capture` trigger snapshots that state into `history.row_versions`
1329
+ * (never a post-hoc history mutation).
1330
+ */
1331
+
1332
+ /** The facets the render leg needs — a structural subset of KindDefinition. */
1333
+ interface DualGateDefinition {
1334
+ legacyBlockType?: string;
1335
+ toLegacyServerData?: (envelope: CanonicalBlockIR) => Record<string, unknown> | undefined;
1336
+ component?: {
1337
+ load: () => Promise<unknown>;
1338
+ };
1339
+ }
1340
+ /**
1341
+ * The render leg's second satisfier: an ACTIVE `role='output'` component
1342
+ * resolved from `content_ir.kind_component`. Feed this from
1343
+ * `resolveComponent(kind, "web", "output")`.
1344
+ *
1345
+ * Why this exists: the render leg originally consulted only the compiled TS
1346
+ * registry, so an agent-authored kind whose renderer is a `source='db'` row was
1347
+ * structurally unable to pass — `definition` came back null and the gate said
1348
+ * "no component" about a kind that had a live, working one. Six authored kinds
1349
+ * (wine_tasting, employee_card, employee_roster, employee_of_the_week,
1350
+ * flashcard_deck, arman_video_prompt) sat permanently inactive because of it.
1351
+ *
1352
+ * NOT a mirror of `content_ir.evaluate_kind_activation`. The SQL render leg is
1353
+ * presence-only — it checks that an active `role='output'` row exists, because
1354
+ * SQL cannot execute a TypeScript bridge. THIS leg is strictly stronger: for a
1355
+ * compiled kind it also runs `toLegacyServerData` and rejects semantically
1356
+ * empty output (the "No <kind> available" class).
1357
+ *
1358
+ * The asymmetry is deliberate and bounded: SQL is the FLOOR (necessary, and
1359
+ * sufficient for DB-authored components, which own no bridge), while this leg
1360
+ * is the CEILING for compiled kinds. Consequence to know: activating a COMPILED
1361
+ * kind whose bridge is broken would pass the RPC and fail here. Compiled kinds
1362
+ * are activated by developers through `scripts/shape/activate-kinds.ts`, which
1363
+ * runs this leg — the studio control only ever reaches owner-authored kinds.
1364
+ * If that ever stops being true, the browser control must run this gate before
1365
+ * enabling its button.
1366
+ */
1367
+ interface DualGateResolvedComponent {
1368
+ componentKey: string;
1369
+ /** The row's own render-trust verdict (R6). Inactive rows do not satisfy. */
1370
+ isActive: boolean;
1371
+ /** "bundled" | "db" — reported in the leg detail, never gates the verdict. */
1372
+ source: string;
1373
+ }
1374
+ interface DualGateInput {
1375
+ kind: string;
1376
+ /** The canonical instance (kind_definition.sample_data). */
1377
+ sample: Record<string, unknown>;
1378
+ /** The materialized kind_definition.emitted_json_schema (plain, no __kind). */
1379
+ emittedJsonSchema: unknown;
1380
+ /** The registry definition for the kind (or null when unregistered). */
1381
+ definition: DualGateDefinition | null;
1382
+ /**
1383
+ * The resolver's `(kind, web, output)` answer, when the caller has one.
1384
+ * Omit (or pass null) to check the compiled registry alone.
1385
+ */
1386
+ resolvedComponent?: DualGateResolvedComponent | null;
1387
+ /**
1388
+ * True when the kind is a generated data-only contract
1389
+ * (`metadata.family` ∈ workflow_io | tool_io | action_io | agent_io).
1390
+ * Those are passed between nodes and never rendered, so the render leg is
1391
+ * structurally inapplicable — the same `n/a` doctrine the shape doctor uses.
1392
+ * Failing them would be noise, and noise erodes the gate.
1393
+ */
1394
+ dataOnly?: boolean;
1395
+ }
1396
+ interface LegResult {
1397
+ ok: boolean;
1398
+ detail?: string;
1399
+ }
1400
+ interface DualGateResult {
1401
+ /** True only when BOTH legs pass — the value the caller writes to is_active. */
1402
+ isActive: boolean;
1403
+ structural: LegResult;
1404
+ render: LegResult;
1405
+ }
1406
+ /**
1407
+ * The structural leg, exported on its own so the shape doctor
1408
+ * (`shape-doctor.ts`) RECOMPUTES gate validation with the exact same ajv
1409
+ * config + `__kind`-stripping semantics as activation — never a parallel
1410
+ * validator. `sample` is `unknown` (not `Record`) because kind examples may
1411
+ * legitimately be scalars/arrays (workflow I/O kinds like `text`/`number`).
1412
+ */
1413
+ declare function validateStructuralLeg(sample: unknown, emittedJsonSchema: unknown): LegResult;
1414
+ declare function runKindDualGate(input: DualGateInput): DualGateResult;
1415
+ /**
1416
+ * One-line, Error-Inspector-ready reason for a failed gate (empty string when
1417
+ * it passed). The caller feeds this to `captureError({ source: "content-ir" })`
1418
+ * and sets `is_active=false`.
1419
+ */
1420
+ declare function describeDualGateFailure(kind: string, result: DualGateResult): string;
1421
+
1422
+ /**
1423
+ * OpenAI/provider JSON Schema → KindSchema converter + __kind injection.
1424
+ *
1425
+ * The strategic keystone: `buildAgentSchemaWithRenderBlockSupport` turns "a
1426
+ * user/agent defined an output schema" into "the platform has a render
1427
+ * contract for it" by injecting the `__kind` discriminator into the agent's
1428
+ * output schema (root + array items), so the model emits self-identifying
1429
+ * objects from then on.
1430
+ *
1431
+ * Moved from app/(dev)/demos/json-block-detector/schema-converter.ts.
1432
+ */
1433
+
1434
+ type JsonSchemaNode = Record<string, unknown>;
1435
+ type ConversionProblem = {
1436
+ severity: "error" | "warning" | "info";
1437
+ path: string;
1438
+ message: string;
1439
+ };
1440
+ type DroppedMetadata = {
1441
+ path: string;
1442
+ dropped: Record<string, unknown>;
1443
+ };
1444
+ type FieldComparison = {
1445
+ field: string;
1446
+ aiPresent: boolean;
1447
+ blockPresent: boolean;
1448
+ aiSummary: string | null;
1449
+ blockSummary: string | null;
1450
+ status: "match" | "ai_only" | "block_only" | "type_mismatch" | "ai_richer" | "block_richer";
1451
+ detail?: string;
1452
+ };
1453
+ /** One standalone flexible_data block schema row. */
1454
+ type BlockSchemaDraft = {
1455
+ slug: string;
1456
+ label: string;
1457
+ fields: Record<string, FieldSchema>;
1458
+ /**
1459
+ * NON-OBJECT ROOT form (KindSchema.root): the kind's value is this field's
1460
+ * shape at the top level (scalar / array / json / OPEN object). When set,
1461
+ * `fields` is empty — mirror of `KindSchema.root`.
1462
+ */
1463
+ root?: FieldSchema;
1464
+ };
1465
+ type ArrayItemKindBinding = {
1466
+ arrayField: string;
1467
+ itemKindSlug: string;
1468
+ };
1469
+ type SchemaConversionResult = {
1470
+ schemaName: string | null;
1471
+ strict: boolean | null;
1472
+ /** Every block schema row required for the converted shape (1..n). */
1473
+ blockSchemas: BlockSchemaDraft[];
1474
+ /** OPTION 2 — same input shape with __kind injected for agent output. */
1475
+ agentSchemaWithKinds: unknown | null;
1476
+ problems: ConversionProblem[];
1477
+ droppedMetadata: DroppedMetadata[];
1478
+ comparisons: FieldComparison[];
1479
+ };
1480
+ type SavePlanEntry = {
1481
+ draft: BlockSchemaDraft;
1482
+ existsInDb: boolean;
1483
+ willSave: boolean;
1484
+ };
1485
+ type ItemKindRefCheck = {
1486
+ parentSlug: string;
1487
+ field: string;
1488
+ itemKind: string;
1489
+ satisfied: boolean;
1490
+ source: "batch" | "database" | "missing";
1491
+ };
1492
+ type SavePlanValidation = {
1493
+ entries: SavePlanEntry[];
1494
+ itemKindRefs: ItemKindRefCheck[];
1495
+ newCount: number;
1496
+ canSave: boolean;
1497
+ errors: string[];
1498
+ };
1499
+ /**
1500
+ * Inject the `__kind` discriminator into one object schema (const when
1501
+ * strict, enum otherwise; `__kind` prepended to required; strict also pins
1502
+ * additionalProperties:false). Shared with the REVERSE converter
1503
+ * (kind-to-json-schema.ts) so both directions stamp identical discriminators.
1504
+ */
1505
+ declare function injectKindIntoObjectSchema(objectSchema: JsonSchemaNode, kindSlug: string, strict: boolean): JsonSchemaNode;
1506
+ declare function buildAgentSchemaWithRenderBlockSupport(input: unknown, rootKindSlug: string, arrayBindings: ArrayItemKindBinding[], strict: boolean): unknown | null;
1507
+ declare function normalizeAiSchemaInput(input: unknown): {
1508
+ name: string | null;
1509
+ strict: boolean | null;
1510
+ rootSchema: JsonSchemaNode | null;
1511
+ parseErrors: string[];
1512
+ };
1513
+ type ConversionCore = {
1514
+ schemaName: string | null;
1515
+ strict: boolean | null;
1516
+ blockSchemas: BlockSchemaDraft[];
1517
+ problems: ConversionProblem[];
1518
+ droppedMetadata: DroppedMetadata[];
1519
+ };
1520
+ declare function convertAiSchemaToBlockFields(schemaName: string, rootSchema: JsonSchemaNode, strict: boolean): ConversionCore;
1521
+ declare function compareWithExistingKindSchema(convertedFields: Record<string, FieldSchema>, existing: KindSchema | null): FieldComparison[];
1522
+ declare function runSchemaConversion(input: unknown, existingSchemas: Record<string, KindSchema>): SchemaConversionResult & {
1523
+ parseErrors: string[];
1524
+ };
1525
+ declare function validateBlockSchemaSavePlan(blockSchemas: BlockSchemaDraft[], existingSlugs: string[], hasConversionErrors: boolean): SavePlanValidation;
1526
+ declare function fieldsToDbPayload(fields: Record<string, FieldSchema>): Record<string, unknown>;
1527
+ declare function isDuplicateBlockSlug(slug: string, entries: Array<{
1528
+ slug: string;
1529
+ }>): boolean;
1530
+
1531
+ /**
1532
+ * KindSchema → provider-ready JSON Schema — the REVERSE of the OpenAI
1533
+ * converter in openai-schema-converter.ts.
1534
+ *
1535
+ * The requested root kind renders INLINE as the top-level object; every
1536
+ * OTHER kind it transitively references (via `{type:"object", kind}` and
1537
+ * `{type:"array", itemKinds}` fields, including inside inline_objects)
1538
+ * becomes a `$defs` entry referenced with `$ref: "#/$defs/<slug>"`.
1539
+ * References back to the ROOT kind use `"#"` — the standard JSON Schema
1540
+ * recursive-root reference (OpenAI structured outputs support both forms).
1541
+ * Collection is cycle-safe: each kind enters the closure exactly once.
1542
+ *
1543
+ * Round-trip contract — output fed through `runSchemaConversion`
1544
+ * (openai-schema-converter.ts) reproduces the source KindSchemas for the
1545
+ * root and every array-item child (enforced by
1546
+ * __tests__/kind-to-json-schema.test.ts). The 2026-07-15 expressivity
1547
+ * extension closed the three historical asymmetries: `record` now has a
1548
+ * forward target (properties-less objects → record scalar/json), multi-
1549
+ * itemKind arrays read back through items-anyOf when each variant declares
1550
+ * its `__kind`, and nullable anyOf preserves the member list + nullable flag
1551
+ * instead of flattening to `string`. Remaining accepted narrowings:
1552
+ * - multi-kind array items emitted as $refs (not inline __kind objects)
1553
+ * still cannot be read back — the forward converter rejects $ref.
1554
+ * - a nullable object-REF (`anyOf: [$ref, {type:"null"}]`) degrades for the
1555
+ * same $ref reason.
1556
+ * - JSON Schema `integer` narrows to `number` (the map_result precedent).
1557
+ */
1558
+
1559
+ type KindJsonSchemaOptions = {
1560
+ /**
1561
+ * Provider strict mode: additionalProperties:false on every kind object
1562
+ * and inline object, and `const` (vs `enum`) __kind discriminators —
1563
+ * mirrors makeKindJsonSchemaProperty / injectKindIntoObjectSchema.
1564
+ */
1565
+ strict?: boolean;
1566
+ /**
1567
+ * Inject the `__kind` discriminator (+ required) into the root and every
1568
+ * $defs kind object. Default true — this is what makes the emitted
1569
+ * objects self-identifying for the render pipeline.
1570
+ */
1571
+ injectKind?: boolean;
1572
+ };
1573
+ type KindJsonSchemaExport = {
1574
+ /** The root kind slug — doubles as the provider schema name. */
1575
+ name: string;
1576
+ schema: JsonSchemaNode;
1577
+ strict: boolean;
1578
+ /**
1579
+ * Referenced kinds the resolver could not supply. Each still gets a
1580
+ * permissive `$defs` object stub (never additionalProperties:false, even
1581
+ * in strict mode — a __kind-only strict object would reject every real
1582
+ * payload) so the export stays structurally valid. Surface these loudly.
1583
+ */
1584
+ unresolved: string[];
1585
+ };
1586
+ /**
1587
+ * Every kind referenced by a field map — `{type:"object", kind}` refs and
1588
+ * `{type:"array", itemKinds}` members, recursing through inline_objects.
1589
+ * First-sighting order, deduplicated. Shared read helper (the registry
1590
+ * catalog builds its uses / used-by graph from this).
1591
+ */
1592
+ declare function collectReferencedKinds(fields: Record<string, FieldSchema>): string[];
1593
+ /**
1594
+ * Schema-level referenced kinds — field-map refs PLUS the refs a non-object
1595
+ * root form carries. Use this (not `collectReferencedKinds(schema.fields)`)
1596
+ * whenever the schema in hand may be root-form.
1597
+ */
1598
+ declare function collectSchemaReferencedKinds(schema: KindSchema): string[];
1599
+ declare function kindSchemaToJsonSchema(kind: string, resolve: (kind: string) => KindSchema | undefined, options?: KindJsonSchemaOptions): KindJsonSchemaExport | null;
1600
+
1601
+ export { type ArrayItemKindBinding, type ArrayItemScalarType, type BlockSchemaDraft, type CanonicalBlockIR, type CanonicalContent, type CanonicalSegment, type CompleteValueEnvelopeOptions, type CompliantKindSnapshot, type ContentRegionInit, type ConversionProblem, type DroppedMetadata, type DualGateDefinition, type DualGateInput, type DualGateResolvedComponent, type DualGateResult, type FieldComparison, type FieldSchema, type Fingerprinter, IR_ENVELOPE_CACHE_VERSION, IR_ENVELOPE_KEY, IR_VERSION, type InboundEnvelopeHooks, type InboundEnvelopeVerdict, type IrDiscriminator, type IrEnvelopeCache, type IrKindState, type IrPath, type IrResidue, type IrStructuredNode, IrTree, type IrTreeNode, type ItemKindRefCheck, JSON_DISCRIMINATOR, type JsonPath, type JsonSchemaNode, JsonStreamTokenizer, type JsonToken, KIND_KEY, type KindBlockProps, type KindDefinition, type KindEdgeSpec, type KindJsonSchemaExport, type KindJsonSchemaOptions, type KindResolution, type KindSchema, KindStorageError, type KindStorageShape, type KindStreamEvent, KindStreamParser, type KindStreamParserOptions, type KindTier, type LegResult, type NormalizeJsonRegionOptions, ParseSession, type ParseSessionOptions, type Punct, ROOT_STORAGE_NAME, type RecordValueType, type RegionEndReason, type RegionFormat, type RegionSourceKind, type SavePlanEntry, type SavePlanValidation, type ScalarFieldType, type SchemaConversionResult, type SchemaLayoutMode, type SchemaResolver, type StoredFieldElement, buildAgentSchemaWithRenderBlockSupport, buildCompliantKindSnapshot, classifyInboundEnvelopeMetadata, collectReferencedKinds, collectSchemaReferencedKinds, compareWithExistingKindSchema, convertAiSchemaToBlockFields, createFingerprinter, createKindStreamParser, describeDualGateFailure, disposeParseSession, emptyValueForFieldSchema, envelopeCacheFromEnvelopes, envelopeFromCompleteValue, fenceDiscriminator, fieldsToDbPayload, fingerprintText, formatBlockLabel, getParseSession, injectKindIntoObjectSchema, irPathIsUnderOrEqual, irPathKey, irPathLabel, irPathsEqual, isCanonicalBlockIR, isDuplicateBlockSlug, isEmptyResidue, isIrEnvelopeCache, isJsonAnyField, isScalarArrayType, kindSchemaToJsonSchema, kindSchemaToStorage, mergeResidueIntoValue, normalizeAiSchemaInput, normalizeJsonRegion, openParseSession, readEnvelope, readObjectKind, reconstructRegionValue, reuseEnvelopeIfCurrent, runKindDualGate, runSchemaConversion, sanitizeInboundEnvelopeMetadata, scalarArrayItemType, schemaLayoutMode, schemaStructureDepth, setJsonRootKeyLookup, storageToKindSchema, stripKindDeep, validateBlockSchemaSavePlan, validateStructuralLeg, xmlDiscriminator };