@ai-matrx/content-ir 0.9.0 → 0.10.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +24 -5
  3. package/dist/convert.cjs +1680 -0
  4. package/dist/convert.cjs.map +1 -0
  5. package/dist/convert.d.cts +230 -0
  6. package/dist/convert.d.ts +230 -0
  7. package/dist/convert.js +1666 -0
  8. package/dist/convert.js.map +1 -0
  9. package/dist/core.cjs +2493 -0
  10. package/dist/core.cjs.map +1 -0
  11. package/dist/core.d.cts +370 -0
  12. package/dist/core.d.ts +370 -0
  13. package/dist/core.js +2452 -0
  14. package/dist/core.js.map +1 -0
  15. package/dist/index.cjs +3 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +9 -2030
  18. package/dist/index.d.ts +9 -2030
  19. package/dist/index.js +3 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/ir-tree-DbLVxbf1.d.cts +441 -0
  22. package/dist/ir-tree-Dsc_66ek.d.ts +441 -0
  23. package/dist/ir-types-95bA2cXH.d.cts +119 -0
  24. package/dist/ir-types-95bA2cXH.d.ts +119 -0
  25. package/dist/kind-schema.types-CwncWj9U.d.cts +139 -0
  26. package/dist/kind-schema.types-CwncWj9U.d.ts +139 -0
  27. package/dist/registry.cjs +468 -0
  28. package/dist/registry.cjs.map +1 -0
  29. package/dist/registry.d.cts +357 -0
  30. package/dist/registry.d.ts +357 -0
  31. package/dist/registry.js +456 -0
  32. package/dist/registry.js.map +1 -0
  33. package/dist/session.cjs +2052 -0
  34. package/dist/session.cjs.map +1 -0
  35. package/dist/session.d.cts +75 -0
  36. package/dist/session.d.ts +75 -0
  37. package/dist/session.js +2047 -0
  38. package/dist/session.js.map +1 -0
  39. package/dist/wire.cjs +310 -0
  40. package/dist/wire.cjs.map +1 -0
  41. package/dist/wire.d.cts +326 -0
  42. package/dist/wire.d.ts +326 -0
  43. package/dist/wire.js +291 -0
  44. package/dist/wire.js.map +1 -0
  45. package/package.json +73 -1
@@ -0,0 +1,2047 @@
1
+ // core/kind-schema.types.ts
2
+ var KIND_KEY = "__kind";
3
+ function readObjectKind(value) {
4
+ const kind = value[KIND_KEY];
5
+ return typeof kind === "string" ? kind : null;
6
+ }
7
+ function isScalarArrayType(type) {
8
+ return type === "string[]" || type === "number[]" || type === "boolean[]";
9
+ }
10
+ function scalarArrayItemType(type) {
11
+ if (type === "number[]") return "number";
12
+ if (type === "boolean[]") return "boolean";
13
+ return "string";
14
+ }
15
+ function isJsonAnyField(field) {
16
+ return field.type === "json" || field.type === "json[]";
17
+ }
18
+
19
+ // core/discriminator.ts
20
+ var JSON_DISCRIMINATOR = {
21
+ format: "json",
22
+ key: KIND_KEY
23
+ };
24
+
25
+ // core/ir-types.ts
26
+ var IR_VERSION = 1;
27
+ function irPathKey(path) {
28
+ return path.map((segment) => String(segment)).join(".");
29
+ }
30
+ function isEmptyResidue(residue) {
31
+ return (residue.extra === null || Object.keys(residue.extra).length === 0) && (residue.optionalMissing === null || residue.optionalMissing.length === 0) && (residue.notices === null || residue.notices.length === 0);
32
+ }
33
+
34
+ // core/ir-tree.ts
35
+ function isRecord(value) {
36
+ return typeof value === "object" && value !== null && !Array.isArray(value);
37
+ }
38
+ function mergeWithoutLoss(prior, incoming) {
39
+ if (!isRecord(prior)) return { value: incoming, rescued: [] };
40
+ if (!isRecord(incoming)) {
41
+ if (incoming !== null && incoming !== void 0) {
42
+ return { value: incoming, rescued: [] };
43
+ }
44
+ return { value: prior, rescued: Object.keys(prior) };
45
+ }
46
+ const rescued = Object.keys(prior).filter((key) => !(key in incoming));
47
+ if (rescued.length === 0) return { value: incoming, rescued };
48
+ return { value: { ...prior, ...incoming }, rescued };
49
+ }
50
+ var IrTree = class {
51
+ nodes = /* @__PURE__ */ new Map();
52
+ rawPaths = /* @__PURE__ */ new Map();
53
+ // pathKey → reason
54
+ dirty = /* @__PURE__ */ new Set();
55
+ /**
56
+ * KIND PRESERVATION (streaming db/cloud kinds): pathKey → identified kind
57
+ * for nodes whose kind is KNOWN (kind_identified / pending_schema) but that
58
+ * have no snapshot node yet because the schema is still cold-fetching.
59
+ * Without this, the envelope reports `kind: ""` for the whole pending
60
+ * window and the render seam can only show raw JSON.
61
+ */
62
+ identifiedKinds = /* @__PURE__ */ new Map();
63
+ /** pathKeys currently waiting on a schema cold fetch. */
64
+ pendingSchemaPaths = /* @__PURE__ */ new Set();
65
+ /**
66
+ * Early top-level scalar fields (title, loading_message, …) captured for
67
+ * identified-but-schema-pending nodes — the loading-component fuel. Scalars
68
+ * only (no live parser references can escape through here). Superseded the
69
+ * moment a real snapshot node exists.
70
+ */
71
+ earlyFields = /* @__PURE__ */ new Map();
72
+ /**
73
+ * pathKey → identified kind preserved through a raw fallback (parser stamped
74
+ * `kind` on the raw_object event) — schema-availability degrades and, since
75
+ * 2026-08-29, structural ones too. Only a node nothing ever identified (no
76
+ * `__kind` at all) is absent here.
77
+ */
78
+ rawKinds = /* @__PURE__ */ new Map();
79
+ /**
80
+ * pathKey → WHY the node degraded. `"unverified"` means no schema was
81
+ * available and nothing was ever checked; `"invalid"` means a check ran and
82
+ * failed. THE RENDER ROUTE BRANCHES ON THIS — see `IrKindState`. Absent =
83
+ * `"invalid"`, so a parser that predates the `cause` field (or any consumer
84
+ * hand-building events) keeps the strict, safe reading.
85
+ */
86
+ rawCauses = /* @__PURE__ */ new Map();
87
+ regionStatus = "streaming";
88
+ errorReason = null;
89
+ rootRawValue = null;
90
+ /** Notices for degrades that tried to erase already-published data. */
91
+ rescueNotices = [];
92
+ completedKind = "";
93
+ get status() {
94
+ return this.regionStatus;
95
+ }
96
+ applyEvent(event) {
97
+ switch (event.type) {
98
+ case "kind_identified": {
99
+ this.identifiedKinds.set(irPathKey(event.path), event.kind);
100
+ this.dirty.add(irPathKey(event.path));
101
+ return;
102
+ }
103
+ case "pending_schema": {
104
+ const pathKey = irPathKey(event.path);
105
+ this.identifiedKinds.set(pathKey, event.kind);
106
+ this.pendingSchemaPaths.add(pathKey);
107
+ this.dirty.add(pathKey);
108
+ return;
109
+ }
110
+ case "field": {
111
+ const parentKey = irPathKey(event.path.slice(0, -1));
112
+ if (this.nodes.has(parentKey)) return;
113
+ if (!this.identifiedKinds.has(parentKey)) return;
114
+ const v = event.value;
115
+ if (v !== null && typeof v === "object") return;
116
+ const bucket = this.earlyFields.get(parentKey) ?? {};
117
+ bucket[event.key] = v;
118
+ this.earlyFields.set(parentKey, bucket);
119
+ this.dirty.add(parentKey);
120
+ return;
121
+ }
122
+ case "block_snapshot": {
123
+ const pathKey = irPathKey(event.path);
124
+ this.pendingSchemaPaths.delete(pathKey);
125
+ this.earlyFields.delete(pathKey);
126
+ this.upsertNode(event.path, {
127
+ kind: event.kind,
128
+ value: event.value,
129
+ residue: event.residue,
130
+ complete: event.complete
131
+ });
132
+ return;
133
+ }
134
+ case "raw_object": {
135
+ const pathKey = irPathKey(event.path);
136
+ this.pendingSchemaPaths.delete(pathKey);
137
+ this.earlyFields.delete(pathKey);
138
+ if (event.kind) this.rawKinds.set(pathKey, event.kind);
139
+ this.rawCauses.set(pathKey, event.cause ?? "invalid");
140
+ this.markRaw(event.path, event.reason, event.value);
141
+ return;
142
+ }
143
+ case "complete": {
144
+ this.regionStatus = this.errorReason ? "error" : "complete";
145
+ this.completedKind = event.kind;
146
+ if (this.rawPaths.has("") && isRecord(event.value)) {
147
+ const merged = mergeWithoutLoss(this.rootRawValue, event.value);
148
+ this.recordRescue("", "complete", merged.rescued);
149
+ if (isRecord(merged.value)) this.rootRawValue = merged.value;
150
+ }
151
+ this.dirty.add("");
152
+ return;
153
+ }
154
+ case "error": {
155
+ this.errorReason = event.reason;
156
+ this.regionStatus = "error";
157
+ this.dirty.add("");
158
+ return;
159
+ }
160
+ default:
161
+ return;
162
+ }
163
+ }
164
+ getNode(pathKey) {
165
+ return this.nodes.get(pathKey) ?? null;
166
+ }
167
+ listNodes() {
168
+ return [...this.nodes.values()];
169
+ }
170
+ isRawPath(pathKey) {
171
+ return this.rawPaths.has(pathKey);
172
+ }
173
+ /** Dirty pathKeys since the last drain — the flush/notify unit. */
174
+ drainDirty() {
175
+ const drained = [...this.dirty];
176
+ this.dirty.clear();
177
+ return drained;
178
+ }
179
+ hasDirty() {
180
+ return this.dirty.size > 0;
181
+ }
182
+ // -------------------------------------------------------------------------
183
+ upsertNode(path, payload) {
184
+ const pathKey = irPathKey(path);
185
+ if (this.rawPaths.has(pathKey)) return;
186
+ const stabilized = this.stabilizeValue(
187
+ payload.value,
188
+ path,
189
+ pathKey
190
+ );
191
+ const prior = this.nodes.get(pathKey);
192
+ const node = {
193
+ kind: payload.kind,
194
+ kindState: payload.complete ? "resolved" : prior?.kindState ?? "resolved",
195
+ path,
196
+ pathKey,
197
+ value: stabilized,
198
+ residue: payload.residue,
199
+ complete: payload.complete,
200
+ version: (prior?.version ?? 0) + 1
201
+ };
202
+ this.nodes.set(pathKey, node);
203
+ this.dirty.add(pathKey);
204
+ this.propagateToAncestors(path, node.value);
205
+ }
206
+ /**
207
+ * Substitute kind-node children with their current tree values so sibling
208
+ * identities are stable; deep-copy everything else so no live parser
209
+ * reference ever escapes.
210
+ */
211
+ stabilizeValue(value, path, applyingPathKey) {
212
+ if (Array.isArray(value)) {
213
+ return value.map(
214
+ (item, index) => this.stabilizeValue(item, [...path, index], applyingPathKey)
215
+ );
216
+ }
217
+ if (isRecord(value)) {
218
+ const pathKey = irPathKey(path);
219
+ if (pathKey !== applyingPathKey) {
220
+ const childNode = this.nodes.get(pathKey);
221
+ if (childNode) return childNode.value;
222
+ }
223
+ const out = {};
224
+ for (const [key, child] of Object.entries(value)) {
225
+ out[key] = this.stabilizeValue(child, [...path, key], applyingPathKey);
226
+ }
227
+ return out;
228
+ }
229
+ return value;
230
+ }
231
+ /**
232
+ * COW spine rebuild: replace the child's slot in each ancestor kind-node's
233
+ * value, shallow-copying only the containers along the way. Siblings keep
234
+ * identity; every ancestor gets a new value identity + version bump.
235
+ */
236
+ propagateToAncestors(childPath, childValue) {
237
+ let currentPath = childPath;
238
+ let currentValue = childValue;
239
+ while (currentPath.length > 0) {
240
+ const ancestor = this.findNearestAncestorNode(currentPath);
241
+ if (!ancestor) return;
242
+ const relative = currentPath.slice(ancestor.path.length);
243
+ const rebuilt = this.cloneAlong(ancestor.value, relative, currentValue);
244
+ if (rebuilt === ancestor.value) return;
245
+ const updated = {
246
+ ...ancestor,
247
+ value: rebuilt,
248
+ version: ancestor.version + 1
249
+ };
250
+ this.nodes.set(ancestor.pathKey, updated);
251
+ this.dirty.add(ancestor.pathKey);
252
+ currentPath = ancestor.path;
253
+ currentValue = updated.value;
254
+ }
255
+ }
256
+ findNearestAncestorNode(path) {
257
+ for (let len = path.length - 1; len >= 0; len--) {
258
+ const node = this.nodes.get(irPathKey(path.slice(0, len)));
259
+ if (node) return node;
260
+ }
261
+ return null;
262
+ }
263
+ cloneAlong(container, relative, leaf) {
264
+ if (relative.length === 0) return leaf;
265
+ const [head, ...rest] = relative;
266
+ if (Array.isArray(container)) {
267
+ const index = typeof head === "number" ? head : Number(head);
268
+ const copy = container.slice();
269
+ while (copy.length <= index) copy.push(void 0);
270
+ copy[index] = this.cloneAlong(container[index], rest, leaf);
271
+ return copy;
272
+ }
273
+ if (isRecord(container)) {
274
+ const key = String(head);
275
+ return {
276
+ ...container,
277
+ [key]: this.cloneAlong(container[key], rest, leaf)
278
+ };
279
+ }
280
+ const built = typeof head === "number" ? this.cloneAlong([], relative, leaf) : this.cloneAlong({}, relative, leaf);
281
+ return built;
282
+ }
283
+ markRaw(path, reason, value) {
284
+ const pathKey = irPathKey(path);
285
+ const prior = path.length === 0 ? this.nodes.get("")?.value ?? this.rootRawValue : this.nodes.get(pathKey)?.value;
286
+ const merged = mergeWithoutLoss(prior, value);
287
+ this.recordRescue(pathKey, reason, merged.rescued);
288
+ this.rawPaths.set(pathKey, reason);
289
+ this.nodes.delete(pathKey);
290
+ this.dirty.add(pathKey);
291
+ if (path.length === 0) {
292
+ if (isRecord(merged.value)) this.rootRawValue = merged.value;
293
+ return;
294
+ }
295
+ this.propagateToAncestors(path, merged.value);
296
+ }
297
+ /**
298
+ * A rescue means a degrade tried to erase data a user could already see —
299
+ * an upstream defect. It rides the envelope as a notice so it surfaces in the
300
+ * Error Inspector instead of being silently absorbed (`core/` is a pure
301
+ * kernel: no console, no capture — the notice IS the alarm).
302
+ */
303
+ recordRescue(pathKey, reason, rescued) {
304
+ if (rescued.length === 0) return;
305
+ this.rescueNotices.push({
306
+ code: "degrade_data_rescued",
307
+ message: `degrade (${reason}) at path "${pathKey || "<root>"}" would have dropped: ${rescued.join(", ")}`
308
+ });
309
+ }
310
+ /**
311
+ * Assemble the canonical envelope. ONE code path for stream + one-shot.
312
+ * Callers supply the fingerprint (one-shot hashes the source; live sessions
313
+ * keep an incremental hasher so no per-flush re-hash happens).
314
+ */
315
+ buildEnvelope(fingerprint) {
316
+ const rootNode = this.nodes.get("");
317
+ const rootRawReason = this.rawPaths.get("") ?? null;
318
+ const notices = [];
319
+ if (this.errorReason) {
320
+ notices.push({ code: "parse_error", message: this.errorReason });
321
+ }
322
+ if (rootRawReason) {
323
+ notices.push({ code: "raw_fallback", message: rootRawReason });
324
+ }
325
+ notices.push(...this.rescueNotices);
326
+ const baseResidue = rootNode?.residue ?? null;
327
+ let residue = baseResidue;
328
+ if (notices.length > 0) {
329
+ residue = {
330
+ extra: baseResidue?.extra ?? null,
331
+ optionalMissing: baseResidue?.optionalMissing ?? null,
332
+ notices: [...baseResidue?.notices ?? [], ...notices]
333
+ };
334
+ }
335
+ const isRaw = rootRawReason !== null;
336
+ const identifiedKind = this.identifiedKinds.get("") ?? "";
337
+ const rootKind = isRaw ? this.rawKinds.get("") ?? "" : rootNode?.kind ?? (this.completedKind || identifiedKind);
338
+ const rootRawState = this.rawCauses.get("") === "unverified" ? "unverified" : "raw";
339
+ const root = {
340
+ role: "structured",
341
+ kind: rootKind,
342
+ kindState: isRaw ? rootRawState : rootNode ? rootNode.kindState : this.pendingSchemaPaths.has("") ? "pending_schema" : this.regionStatus === "streaming" ? identifiedKind ? "pending_schema" : "pending_kind" : "raw",
343
+ discriminator: JSON_DISCRIMINATOR,
344
+ path: [],
345
+ status: this.regionStatus,
346
+ value: rootNode?.value ?? this.rootRawValue ?? // Copy: the early-fields bucket keeps mutating as fields arrive; the
347
+ // envelope must be freezable (Redux dev-mode immutability).
348
+ (this.earlyFields.has("") ? { ...this.earlyFields.get("") } : {}),
349
+ residue
350
+ };
351
+ const nodeIndex = {};
352
+ for (const node of this.nodes.values()) {
353
+ if (node.pathKey === "") continue;
354
+ nodeIndex[node.pathKey] = {
355
+ kind: node.kind,
356
+ kindState: node.kindState,
357
+ status: node.complete ? "complete" : "streaming",
358
+ ...node.residue ? { residue: node.residue } : {}
359
+ };
360
+ }
361
+ for (const [pathKey] of this.rawPaths) {
362
+ if (pathKey === "") continue;
363
+ nodeIndex[pathKey] = {
364
+ kind: this.rawKinds.get(pathKey) ?? "",
365
+ kindState: this.rawCauses.get(pathKey) === "unverified" ? "unverified" : "raw",
366
+ status: "complete"
367
+ };
368
+ }
369
+ return {
370
+ v: IR_VERSION,
371
+ engine: "fe-kind-parser",
372
+ fingerprint,
373
+ root,
374
+ ...Object.keys(nodeIndex).length > 0 ? { nodeIndex } : {}
375
+ };
376
+ }
377
+ };
378
+
379
+ // core/fingerprint.ts
380
+ var SEED_A = 2166136261;
381
+ var SEED_B = 16777619;
382
+ function fnv1aStep(hash, input) {
383
+ let h = hash >>> 0;
384
+ for (let i = 0; i < input.length; i++) {
385
+ h ^= input.charCodeAt(i);
386
+ h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
387
+ }
388
+ return h >>> 0;
389
+ }
390
+ function createFingerprinter() {
391
+ let a = SEED_A;
392
+ let b = SEED_B;
393
+ let length = 0;
394
+ return {
395
+ push(chunk) {
396
+ a = fnv1aStep(a, chunk);
397
+ b = fnv1aStep(b, chunk);
398
+ length += chunk.length;
399
+ },
400
+ current() {
401
+ return `${length.toString(36)}-${a.toString(36)}${b.toString(36)}`;
402
+ }
403
+ };
404
+ }
405
+
406
+ // core/json-tokenizer.ts
407
+ var JsonStreamTokenizer = class {
408
+ mode = "normal";
409
+ pos = 0;
410
+ stringBuffer = "";
411
+ primitiveBuffer = "";
412
+ unicodeBuffer = "";
413
+ tokenStart = 0;
414
+ onToken;
415
+ // Explicit field rather than a parameter property: consumers compile this
416
+ // source directly, and a strict host (the dashboard) sets
417
+ // `erasableSyntaxOnly`, under which parameter properties are a hard error.
418
+ constructor(onToken) {
419
+ this.onToken = onToken;
420
+ }
421
+ get position() {
422
+ return this.pos;
423
+ }
424
+ push(chunk) {
425
+ for (let i = 0; i < chunk.length; i++) {
426
+ const ch = chunk[i];
427
+ const at = this.pos;
428
+ this.pos += 1;
429
+ if (ch === void 0) continue;
430
+ if (this.mode === "primitive") {
431
+ if (this.isDelimiter(ch)) {
432
+ this.emitPrimitive();
433
+ this.handleNormalChar(ch, at);
434
+ } else {
435
+ this.primitiveBuffer += ch;
436
+ }
437
+ continue;
438
+ }
439
+ if (this.mode === "string") {
440
+ if (ch === '"') {
441
+ this.onToken({
442
+ type: "string",
443
+ value: this.stringBuffer,
444
+ at: this.tokenStart
445
+ });
446
+ this.stringBuffer = "";
447
+ this.mode = "normal";
448
+ continue;
449
+ }
450
+ if (ch === "\\") {
451
+ this.mode = "escape";
452
+ continue;
453
+ }
454
+ if (ch === "\n" || ch === "\r") {
455
+ throw new Error(`Invalid unescaped newline in JSON string at ${at}`);
456
+ }
457
+ this.stringBuffer += ch;
458
+ continue;
459
+ }
460
+ if (this.mode === "escape") {
461
+ if (ch === "u") {
462
+ this.unicodeBuffer = "";
463
+ this.mode = "unicode";
464
+ continue;
465
+ }
466
+ const escaped = {
467
+ '"': '"',
468
+ "\\": "\\",
469
+ "/": "/",
470
+ b: "\b",
471
+ f: "\f",
472
+ n: "\n",
473
+ r: "\r",
474
+ t: " "
475
+ };
476
+ if (!(ch in escaped)) {
477
+ throw new Error(`Invalid JSON escape sequence at ${at}`);
478
+ }
479
+ this.stringBuffer += escaped[ch];
480
+ this.mode = "string";
481
+ continue;
482
+ }
483
+ if (this.mode === "unicode") {
484
+ if (!/[0-9a-fA-F]/.test(ch)) {
485
+ throw new Error(`Invalid unicode escape at ${at}`);
486
+ }
487
+ this.unicodeBuffer += ch;
488
+ if (this.unicodeBuffer.length === 4) {
489
+ this.stringBuffer += String.fromCharCode(
490
+ parseInt(this.unicodeBuffer, 16)
491
+ );
492
+ this.unicodeBuffer = "";
493
+ this.mode = "string";
494
+ }
495
+ continue;
496
+ }
497
+ this.handleNormalChar(ch, at);
498
+ }
499
+ }
500
+ end() {
501
+ if (this.mode === "primitive") {
502
+ this.emitPrimitive();
503
+ return;
504
+ }
505
+ if (this.mode !== "normal") {
506
+ throw new Error(
507
+ `Stream ended while parsing JSON ${this.mode} at ${this.pos}`
508
+ );
509
+ }
510
+ }
511
+ handleNormalChar(ch, at) {
512
+ if (/\s/.test(ch)) return;
513
+ if (ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") {
514
+ this.onToken({ type: "punct", value: ch, at });
515
+ return;
516
+ }
517
+ if (ch === '"') {
518
+ this.mode = "string";
519
+ this.stringBuffer = "";
520
+ this.tokenStart = at;
521
+ return;
522
+ }
523
+ if (/[-0-9tfn]/.test(ch)) {
524
+ this.mode = "primitive";
525
+ this.primitiveBuffer = ch;
526
+ this.tokenStart = at;
527
+ return;
528
+ }
529
+ throw new Error(`Unexpected character "${ch}" at ${at}`);
530
+ }
531
+ emitPrimitive() {
532
+ const raw = this.primitiveBuffer;
533
+ const at = this.tokenStart;
534
+ this.primitiveBuffer = "";
535
+ this.mode = "normal";
536
+ let value;
537
+ try {
538
+ value = JSON.parse(raw);
539
+ } catch {
540
+ throw new Error(`Invalid JSON primitive "${raw}" at ${at}`);
541
+ }
542
+ if (typeof value === "number") {
543
+ this.onToken({ type: "number", value, at });
544
+ return;
545
+ }
546
+ if (typeof value === "boolean") {
547
+ this.onToken({ type: "boolean", value, at });
548
+ return;
549
+ }
550
+ if (value === null) {
551
+ this.onToken({ type: "null", value, at });
552
+ return;
553
+ }
554
+ throw new Error(`Unsupported JSON primitive "${raw}" at ${at}`);
555
+ }
556
+ isDelimiter(ch) {
557
+ return /\s/.test(ch) || ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",";
558
+ }
559
+ };
560
+
561
+ // core/kind-snapshot.ts
562
+ function emptyValueForFieldSchema(field) {
563
+ if (field.nullable) return null;
564
+ switch (field.type) {
565
+ case "string":
566
+ return "";
567
+ case "number":
568
+ return 0;
569
+ case "boolean":
570
+ return false;
571
+ case "json":
572
+ return null;
573
+ case "string[]":
574
+ case "number[]":
575
+ case "boolean[]":
576
+ case "json[]":
577
+ case "array":
578
+ return [];
579
+ case "object":
580
+ case "inline_object":
581
+ case "record":
582
+ return {};
583
+ case "enum":
584
+ return "";
585
+ case "union":
586
+ if (field.scalars.includes("string")) return "";
587
+ if (field.scalars.includes("number")) return 0;
588
+ if (field.scalars.includes("boolean")) return false;
589
+ return {};
590
+ default:
591
+ return null;
592
+ }
593
+ }
594
+ function buildCompliantKindSnapshot(schema, partial) {
595
+ const value = {
596
+ [KIND_KEY]: schema.kind
597
+ };
598
+ const optionalMissing = [];
599
+ for (const [fieldName, fieldSchema] of Object.entries(schema.fields)) {
600
+ if (fieldName in partial && partial[fieldName] !== void 0) {
601
+ value[fieldName] = partial[fieldName];
602
+ } else if (fieldSchema.required) {
603
+ value[fieldName] = emptyValueForFieldSchema(fieldSchema);
604
+ } else {
605
+ optionalMissing.push(fieldName);
606
+ }
607
+ }
608
+ let extra = null;
609
+ for (const [fieldName, fieldValue] of Object.entries(partial)) {
610
+ if (fieldName === KIND_KEY) continue;
611
+ if (fieldName in schema.fields) continue;
612
+ if (extra === null) extra = {};
613
+ extra[fieldName] = fieldValue;
614
+ }
615
+ const residue = {
616
+ extra,
617
+ optionalMissing: optionalMissing.length > 0 ? optionalMissing : null,
618
+ notices: null
619
+ };
620
+ return { value, residue: isEmptyResidue(residue) ? null : residue };
621
+ }
622
+
623
+ // core/kind-parser.ts
624
+ var jsonRootKeyLookup = null;
625
+ function isSchemaResolver(source) {
626
+ return typeof source.get === "function";
627
+ }
628
+ function safeCopy(value) {
629
+ try {
630
+ return structuredClone(value);
631
+ } catch {
632
+ return value;
633
+ }
634
+ }
635
+ var KindStreamParser = class {
636
+ resolver;
637
+ stack = [];
638
+ objectKinds = /* @__PURE__ */ new Map();
639
+ inlineSchemas = /* @__PURE__ */ new Map();
640
+ recordSchemas = /* @__PURE__ */ new Map();
641
+ rawObjectPaths = /* @__PURE__ */ new Set();
642
+ /**
643
+ * Subtrees whose value domain is "any JSON" by schema (`json` / `json[]`
644
+ * fields, `record` with `values:"json"` members). OPAQUE by contract: no
645
+ * kind identification, no pending_kind, no raw_object degradation — unknown
646
+ * structure here is the declared shape, not a failure. Propagates to every
647
+ * descendant compound.
648
+ */
649
+ opaquePaths = /* @__PURE__ */ new Set();
650
+ deferredFields = /* @__PURE__ */ new Map();
651
+ awaitingKindPaths = /* @__PURE__ */ new Set();
652
+ /** Paths whose kind came from parent-schema prediction, unconfirmed so far. */
653
+ speculativeKinds = /* @__PURE__ */ new Set();
654
+ /**
655
+ * Kind named by the root object's FIRST key through the `json_root_key`
656
+ * surface registry — a candidate, adopted only at root finalize.
657
+ */
658
+ rootSurfaceKind = null;
659
+ /** kind → paths (by key) waiting for the resolver's cold fetch. */
660
+ pendingSchemaPaths = /* @__PURE__ */ new Map();
661
+ /** Pending-schema paths whose object already closed. */
662
+ closedPendingPaths = /* @__PURE__ */ new Set();
663
+ /** Schemas delivered via notifySchemaArrived (overlay over the resolver). */
664
+ arrivedSchemas = /* @__PURE__ */ new Map();
665
+ tokenizer;
666
+ root;
667
+ rootKind = "";
668
+ rootDone = false;
669
+ failed = false;
670
+ options;
671
+ // Explicit field, not a parameter property — see the note in
672
+ // `json-tokenizer.ts`: `erasableSyntaxOnly` hosts compile this source.
673
+ constructor(options) {
674
+ this.options = options;
675
+ this.resolver = isSchemaResolver(options.schemas) ? options.schemas : {
676
+ get: (kind) => options.schemas[kind]
677
+ };
678
+ this.tokenizer = new JsonStreamTokenizer(
679
+ (token) => this.handleToken(token)
680
+ );
681
+ }
682
+ push(chunk) {
683
+ if (this.failed || this.rootDone) return;
684
+ try {
685
+ this.tokenizer.push(chunk);
686
+ } catch (error) {
687
+ this.fail(
688
+ error instanceof Error ? error.message : String(error),
689
+ this.tokenizer.position
690
+ );
691
+ }
692
+ }
693
+ end() {
694
+ if (this.failed) return;
695
+ try {
696
+ this.tokenizer.end();
697
+ } catch (error) {
698
+ this.fail(
699
+ error instanceof Error ? error.message : String(error),
700
+ this.tokenizer.position
701
+ );
702
+ return;
703
+ }
704
+ this.resolvePendingSchemasAsRaw();
705
+ if (!this.rootDone) {
706
+ this.fail(
707
+ "Stream ended before the root JSON object was complete.",
708
+ this.tokenizer.position
709
+ );
710
+ }
711
+ }
712
+ resolvePendingSchemasAsRaw() {
713
+ const at = this.tokenizer.position;
714
+ for (const [kind, paths] of [...this.pendingSchemaPaths]) {
715
+ this.pendingSchemaPaths.delete(kind);
716
+ for (const [pathKey, path] of paths) {
717
+ this.closedPendingPaths.delete(pathKey);
718
+ if (this.rawObjectPaths.has(pathKey)) continue;
719
+ const value = this.getLiveObjectValue(path) ?? this.getFinalizedObjectValue(path) ?? {};
720
+ this.emitRawObject(
721
+ path,
722
+ safeCopy(value),
723
+ `No block schema registered for "${kind}".`,
724
+ at,
725
+ kind,
726
+ "unverified"
727
+ );
728
+ }
729
+ }
730
+ }
731
+ /** True once the root value has closed — the region is fully consumed. */
732
+ get isComplete() {
733
+ return this.rootDone;
734
+ }
735
+ get hasFailed() {
736
+ return this.failed;
737
+ }
738
+ /**
739
+ * Upgrade-in-place: the registry's cold fetch answered. Pending nodes for
740
+ * this kind validate and complete (closed nodes retroactively); a null
741
+ * schema (fetch miss) drops them to raw. Safe to call after end().
742
+ */
743
+ notifySchemaArrived(kind, schema) {
744
+ const waiting = this.pendingSchemaPaths.get(kind);
745
+ if (!waiting) return;
746
+ this.pendingSchemaPaths.delete(kind);
747
+ if (schema) {
748
+ this.arrivedSchemas.set(kind, schema);
749
+ }
750
+ const at = this.tokenizer.position;
751
+ for (const [pathKey, path] of waiting) {
752
+ if (this.rawObjectPaths.has(pathKey)) continue;
753
+ const value = this.getLiveObjectValue(path) ?? this.getFinalizedObjectValue(path);
754
+ if (!schema) {
755
+ this.emitRawObject(
756
+ path,
757
+ safeCopy(value ?? {}),
758
+ `No block schema registered for "${kind}".`,
759
+ at,
760
+ kind,
761
+ "unverified"
762
+ );
763
+ this.closedPendingPaths.delete(pathKey);
764
+ continue;
765
+ }
766
+ if (this.closedPendingPaths.has(pathKey)) {
767
+ this.closedPendingPaths.delete(pathKey);
768
+ if (value) {
769
+ this.finalizeTypedObject(path, value, at);
770
+ }
771
+ } else {
772
+ this.emitBlockSnapshotForObject(path, at);
773
+ }
774
+ }
775
+ }
776
+ handleToken(token) {
777
+ if (this.failed) return;
778
+ if (this.rootDone) {
779
+ this.fail("Unexpected token after complete JSON object.", token.at);
780
+ return;
781
+ }
782
+ if (token.type === "punct") {
783
+ this.handlePunctuation(token);
784
+ return;
785
+ }
786
+ if (token.type === "string") {
787
+ this.handleString(token);
788
+ return;
789
+ }
790
+ this.beginScalar(token.value, token.at);
791
+ }
792
+ handleString(token) {
793
+ const frame = this.currentFrame();
794
+ if (frame?.kind === "object" && (frame.expecting === "keyOrEnd" || frame.expecting === "key")) {
795
+ this.acceptObjectKey(frame, token.value, token.at);
796
+ return;
797
+ }
798
+ this.beginScalar(token.value, token.at);
799
+ }
800
+ handlePunctuation(token) {
801
+ switch (token.value) {
802
+ case "{":
803
+ this.beginCompound("object", token.at);
804
+ return;
805
+ case "[":
806
+ this.beginCompound("array", token.at);
807
+ return;
808
+ case "}":
809
+ this.closeCompound("object", token.at);
810
+ return;
811
+ case "]":
812
+ this.closeCompound("array", token.at);
813
+ return;
814
+ case ":":
815
+ this.acceptColon(token.at);
816
+ return;
817
+ case ",":
818
+ this.acceptComma(token.at);
819
+ return;
820
+ }
821
+ }
822
+ beginCompound(kind, at) {
823
+ const value = kind === "object" ? {} : [];
824
+ const container = this.currentFrame();
825
+ const path = this.placeValue(value, kind, at, false);
826
+ if (!path || this.failed) return;
827
+ const opaque = container !== void 0 && this.opaquePaths.has(this.pathKey(container.path)) || this.isJsonAnyPlacement(path);
828
+ if (opaque) this.opaquePaths.add(this.pathKey(path));
829
+ if (kind === "object") {
830
+ this.emit({ type: "object_start", path, at });
831
+ if (opaque) {
832
+ this.stack.push({
833
+ kind: "object",
834
+ path,
835
+ value,
836
+ expecting: "keyOrEnd",
837
+ keyCount: 0
838
+ });
839
+ return;
840
+ }
841
+ this.registerObjectContext(path);
842
+ const pathKey = this.pathKey(path);
843
+ this.stack.push({
844
+ kind: "object",
845
+ path,
846
+ value,
847
+ expecting: "keyOrEnd",
848
+ keyCount: 0
849
+ });
850
+ if (this.inlineSchemas.has(pathKey) || this.recordSchemas.has(pathKey)) {
851
+ return;
852
+ }
853
+ const speculated = this.resolveSpeculativeKind(path);
854
+ if (speculated) {
855
+ this.objectKinds.set(pathKey, speculated);
856
+ this.speculativeKinds.add(pathKey);
857
+ if (path.length === 0) {
858
+ this.rootKind = speculated;
859
+ }
860
+ this.emit({
861
+ type: "kind_identified",
862
+ kind: speculated,
863
+ path,
864
+ speculative: true,
865
+ at
866
+ });
867
+ this.emitBlockSnapshotForObject(path, at);
868
+ return;
869
+ }
870
+ this.awaitingKindPaths.add(pathKey);
871
+ this.emit({ type: "pending_kind", path, at });
872
+ return;
873
+ }
874
+ const parentField = this.parentFieldName(path);
875
+ if (parentField) {
876
+ this.emit({ type: "array_start", path, field: parentField, at });
877
+ }
878
+ this.stack.push({
879
+ kind: "array",
880
+ path,
881
+ value,
882
+ expecting: "valueOrEnd",
883
+ nextIndex: 0
884
+ });
885
+ }
886
+ /**
887
+ * THE JSON-ROOT-KEY SURFACE — `content_ir.kind_surface` rows of type
888
+ * `json_root_key`, live at last (they were inert phantom rows until
889
+ * 2026-08-20).
890
+ *
891
+ * A legacy payload such as `{"quiz_title": ...}` carries no `__kind`, but
892
+ * the ONE surface registry knows exactly which kind that root key names —
893
+ * the same lookup the SERVER performs before adapting the payload
894
+ * (`aidream .../processing/blocks/envelope.py`). Consulting it HERE, in the
895
+ * shared parser core, is what makes one place decide it: both hosts — the
896
+ * one-shot `normalizeJsonRegion` (DB reload / reconcile) and the live
897
+ * `openParseSession` (streaming) — build their parser through
898
+ * `createKindStreamParser`, so neither passes an option and neither can
899
+ * drift from the other.
900
+ *
901
+ * Recorded at the first root key; ADOPTED only when the root object closes
902
+ * (`completeTypedObject`). That is the surface registry's complete-only
903
+ * convergence law, and every json_root_key row is `streaming:false` — these
904
+ * legacy shapes are recognized by their whole payload, so speculating
905
+ * mid-stream would flash a kind component over an object that may never
906
+ * satisfy the schema. An explicit `expectedRootKind` (an agent's declared
907
+ * output schema) is stronger context and always wins; an actual `__kind`
908
+ * still wins over both.
909
+ */
910
+ noteRootSurfaceKind(key) {
911
+ if (key === KIND_KEY) return;
912
+ if (this.options.expectedRootKind) return;
913
+ if (this.rootKind || this.objectKinds.has(this.pathKey([]))) return;
914
+ const kind = this.resolver.kindForJsonRootKey?.(key) ?? jsonRootKeyLookup?.(key);
915
+ if (!kind) return;
916
+ this.rootSurfaceKind = kind;
917
+ if (!this.lookupSchema(kind)) {
918
+ this.resolver.request?.(kind);
919
+ }
920
+ }
921
+ /**
922
+ * Prediction from the parent schema: object field → declared kind; array
923
+ * item → sole itemKind; root → expectedRootKind. Only when the schema is
924
+ * actually resolvable (a prediction we can't validate against is not a
925
+ * commitment worth making).
926
+ */
927
+ resolveSpeculativeKind(path) {
928
+ if (path.length === 0) {
929
+ const expected = this.options.expectedRootKind;
930
+ return expected && this.lookupObjectSchema(expected) ? expected : null;
931
+ }
932
+ const last = path[path.length - 1];
933
+ if (typeof last === "string") {
934
+ const fieldSchema2 = this.resolveParentFieldSchema(path);
935
+ if (fieldSchema2?.type === "object" && this.lookupObjectSchema(fieldSchema2.kind)) {
936
+ return fieldSchema2.kind;
937
+ }
938
+ return null;
939
+ }
940
+ const fieldName = this.parentFieldName(path);
941
+ if (!fieldName) return null;
942
+ const ownerPath = path.slice(0, -2);
943
+ const ownerKind = this.getObjectKindForPath(ownerPath);
944
+ if (!ownerKind) return null;
945
+ const fieldSchema = this.lookupSchema(ownerKind)?.fields[fieldName];
946
+ const soleItemKind = fieldSchema?.type === "array" && fieldSchema.itemKinds.length === 1 ? fieldSchema.itemKinds[0] : void 0;
947
+ if (soleItemKind !== void 0 && this.lookupObjectSchema(soleItemKind)) {
948
+ return soleItemKind;
949
+ }
950
+ return null;
951
+ }
952
+ /**
953
+ * A schema usable for OBJECT speculation/snapshots — root-form kinds
954
+ * (non-object data-only shapes) are never a valid object commitment.
955
+ */
956
+ lookupObjectSchema(kind) {
957
+ const schema = this.lookupSchema(kind);
958
+ return schema && !schema.root ? schema : void 0;
959
+ }
960
+ beginScalar(value, at) {
961
+ this.placeValue(value, "scalar", at, true);
962
+ }
963
+ placeValue(value, valueKind, at, finalizedImmediately) {
964
+ if (this.root === void 0) {
965
+ if (valueKind !== "object") {
966
+ this.fail("Root value must be a JSON object.", at);
967
+ return null;
968
+ }
969
+ this.root = value;
970
+ return [];
971
+ }
972
+ const parent = this.currentFrame();
973
+ if (!parent) {
974
+ this.fail("Unexpected value after root object.", at);
975
+ return null;
976
+ }
977
+ let path;
978
+ let fieldKey;
979
+ if (parent.kind === "object") {
980
+ if (parent.expecting !== "value") {
981
+ this.fail(
982
+ `Unexpected value inside object. Expected ${parent.expecting}.`,
983
+ at
984
+ );
985
+ return null;
986
+ }
987
+ if (parent.currentKey === void 0) {
988
+ this.fail("Internal parser error: missing object key.", at);
989
+ return null;
990
+ }
991
+ fieldKey = parent.currentKey;
992
+ path = [...parent.path, fieldKey];
993
+ const placementError = this.validateFieldPlacement(
994
+ parent,
995
+ fieldKey,
996
+ valueKind,
997
+ value
998
+ );
999
+ if (placementError) {
1000
+ this.markNodeRaw(parent.path, parent.value, placementError, at);
1001
+ }
1002
+ parent.value[fieldKey] = value;
1003
+ parent.currentKey = void 0;
1004
+ parent.expecting = "commaOrEnd";
1005
+ } else {
1006
+ if (parent.expecting !== "valueOrEnd" && parent.expecting !== "value") {
1007
+ this.fail(
1008
+ `Unexpected value inside array. Expected ${parent.expecting}.`,
1009
+ at
1010
+ );
1011
+ return null;
1012
+ }
1013
+ const index = parent.nextIndex;
1014
+ path = [...parent.path, index];
1015
+ fieldKey = this.parentFieldName(path);
1016
+ parent.value.push(value);
1017
+ parent.nextIndex += 1;
1018
+ parent.expecting = "commaOrEnd";
1019
+ }
1020
+ if (finalizedImmediately) {
1021
+ this.onValueFinalized(path, value, at);
1022
+ }
1023
+ return path;
1024
+ }
1025
+ acceptObjectKey(frame, key, at) {
1026
+ if (frame.expecting !== "keyOrEnd" && frame.expecting !== "key") {
1027
+ this.fail(`Unexpected object key "${key}".`, at);
1028
+ return;
1029
+ }
1030
+ if (Object.prototype.hasOwnProperty.call(frame.value, key)) {
1031
+ this.markNodeRaw(
1032
+ frame.path,
1033
+ frame.value,
1034
+ `Duplicate key "${key}".`,
1035
+ at
1036
+ );
1037
+ }
1038
+ if (frame.path.length === 0 && frame.keyCount === 0) {
1039
+ this.noteRootSurfaceKind(key);
1040
+ }
1041
+ frame.currentKey = key;
1042
+ frame.keyCount += 1;
1043
+ frame.expecting = "colon";
1044
+ }
1045
+ acceptColon(at) {
1046
+ const frame = this.currentFrame();
1047
+ if (!frame || frame.kind !== "object" || frame.expecting !== "colon") {
1048
+ this.fail("Unexpected colon.", at);
1049
+ return;
1050
+ }
1051
+ frame.expecting = "value";
1052
+ }
1053
+ acceptComma(at) {
1054
+ const frame = this.currentFrame();
1055
+ if (!frame || frame.expecting !== "commaOrEnd") {
1056
+ this.fail("Unexpected comma.", at);
1057
+ return;
1058
+ }
1059
+ if (frame.kind === "object") {
1060
+ frame.expecting = "key";
1061
+ } else {
1062
+ frame.expecting = "value";
1063
+ }
1064
+ }
1065
+ closeCompound(kind, at) {
1066
+ const frame = this.currentFrame();
1067
+ if (!frame || frame.kind !== kind) {
1068
+ this.fail(
1069
+ `Unexpected closing ${kind === "object" ? "brace" : "bracket"}.`,
1070
+ at
1071
+ );
1072
+ return;
1073
+ }
1074
+ if (frame.kind === "object") {
1075
+ if (frame.expecting !== "keyOrEnd" && frame.expecting !== "commaOrEnd") {
1076
+ this.fail(`Object closed too early. Expected ${frame.expecting}.`, at);
1077
+ return;
1078
+ }
1079
+ } else if (frame.expecting !== "valueOrEnd" && frame.expecting !== "commaOrEnd") {
1080
+ this.fail(`Array closed too early. Expected ${frame.expecting}.`, at);
1081
+ return;
1082
+ }
1083
+ this.stack.pop();
1084
+ if (this.stack.length === 0) {
1085
+ this.rootDone = true;
1086
+ }
1087
+ this.onValueFinalized(frame.path, frame.value, at);
1088
+ }
1089
+ /**
1090
+ * True when a value placed at `path` sits directly under a json-any
1091
+ * placement: a `json`/`json[]` FIELD, or a member of a `record` whose
1092
+ * values are `"json"`. (Deeper descendants inherit via `opaquePaths`.)
1093
+ */
1094
+ isJsonAnyPlacement(path) {
1095
+ const fieldSchema = this.resolveParentFieldSchema(path);
1096
+ if (fieldSchema && isJsonAnyField(fieldSchema)) return true;
1097
+ const last = path[path.length - 1];
1098
+ if (typeof last === "string") {
1099
+ const parentKey = this.pathKey(path.slice(0, -1));
1100
+ if (this.recordSchemas.get(parentKey) === "json") return true;
1101
+ }
1102
+ return false;
1103
+ }
1104
+ onValueFinalized(path, value, at) {
1105
+ if (this.failed) return;
1106
+ const pathKey = this.pathKey(path);
1107
+ const parentIsOpaque = path.length > 0 && this.opaquePaths.has(this.pathKey(path.slice(0, -1)));
1108
+ if (this.opaquePaths.has(pathKey)) {
1109
+ if (!parentIsOpaque) this.emitFieldIfReady(path, value, at);
1110
+ return;
1111
+ }
1112
+ if (parentIsOpaque) return;
1113
+ if (this.isKindFieldPath(path)) {
1114
+ if (typeof value !== "string") {
1115
+ return;
1116
+ }
1117
+ this.onKindDiscriminatorArrived(path.slice(0, -1), value, at);
1118
+ return;
1119
+ }
1120
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1121
+ const objectValue = value;
1122
+ const inlineFields = this.inlineSchemas.get(pathKey);
1123
+ if (inlineFields) {
1124
+ const inlineError = this.validateObjectAgainstFields(
1125
+ objectValue,
1126
+ inlineFields
1127
+ );
1128
+ if (inlineError.error) {
1129
+ this.emitRawObject(path, objectValue, inlineError.error, at);
1130
+ return;
1131
+ }
1132
+ this.inlineSchemas.delete(pathKey);
1133
+ this.emitSchemaNotices(path, "inline_object", inlineError, at);
1134
+ this.emit({
1135
+ type: "object_complete",
1136
+ kind: "inline_object",
1137
+ path,
1138
+ value: objectValue,
1139
+ at
1140
+ });
1141
+ } else {
1142
+ const recordValueType = this.recordSchemas.get(pathKey);
1143
+ if (recordValueType) {
1144
+ const recordError = this.validateRecordObject(
1145
+ objectValue,
1146
+ recordValueType
1147
+ );
1148
+ if (recordError) {
1149
+ this.emitRawObject(path, objectValue, recordError, at);
1150
+ return;
1151
+ }
1152
+ this.recordSchemas.delete(pathKey);
1153
+ this.emit({
1154
+ type: "object_complete",
1155
+ kind: "record",
1156
+ path,
1157
+ value: objectValue,
1158
+ at
1159
+ });
1160
+ } else {
1161
+ this.completeTypedObject(path, objectValue, at);
1162
+ }
1163
+ }
1164
+ }
1165
+ this.emitFieldIfReady(path, value, at);
1166
+ if (path.length === 0) {
1167
+ this.completeRoot(value, at);
1168
+ }
1169
+ }
1170
+ /** __kind arrived for an object — confirm speculation, identify, or backtrack. */
1171
+ onKindDiscriminatorArrived(objectPath, kind, at) {
1172
+ const objectPathKey = this.pathKey(objectPath);
1173
+ if (this.rawObjectPaths.has(objectPathKey)) return;
1174
+ const prior = this.objectKinds.get(objectPathKey);
1175
+ const wasSpeculative = this.speculativeKinds.has(objectPathKey);
1176
+ if (prior !== void 0 && wasSpeculative) {
1177
+ this.speculativeKinds.delete(objectPathKey);
1178
+ if (prior === kind) {
1179
+ this.emitBlockSnapshotForObject(objectPath, at);
1180
+ return;
1181
+ }
1182
+ if (this.lookupSchema(kind) && this.validateArrayItemKind(objectPath, kind) === null && this.speculativeRetagAllowed(objectPath, kind)) {
1183
+ this.objectKinds.set(objectPathKey, kind);
1184
+ if (objectPath.length === 0) {
1185
+ this.rootKind = kind;
1186
+ }
1187
+ this.emit({ type: "kind_identified", kind, path: objectPath, at });
1188
+ this.emitBlockSnapshotForObject(objectPath, at);
1189
+ return;
1190
+ }
1191
+ const live = this.getLiveObjectValue(objectPath);
1192
+ this.objectKinds.delete(objectPathKey);
1193
+ if (objectPath.length === 0) {
1194
+ this.rootKind = "";
1195
+ }
1196
+ this.emitRawObject(
1197
+ objectPath,
1198
+ safeCopy(live ?? {}),
1199
+ `Speculated kind "${prior}" contradicted by ${KIND_KEY} "${kind}".`,
1200
+ at
1201
+ );
1202
+ return;
1203
+ }
1204
+ this.objectKinds.set(objectPathKey, kind);
1205
+ this.emit({
1206
+ type: "kind_identified",
1207
+ kind,
1208
+ path: objectPath,
1209
+ at
1210
+ });
1211
+ this.clearKindWait(objectPath, "identified", at, kind);
1212
+ if (objectPath.length === 0) {
1213
+ this.rootKind = kind;
1214
+ }
1215
+ if (!this.lookupSchema(kind) && this.resolver.request) {
1216
+ this.addPendingSchemaPath(kind, objectPath);
1217
+ this.emit({ type: "pending_schema", kind, path: objectPath, at });
1218
+ this.resolver.request(kind);
1219
+ return;
1220
+ }
1221
+ this.flushDeferredFields(objectPath, kind, at);
1222
+ this.emitBlockSnapshotForObject(objectPath, at);
1223
+ }
1224
+ /** A contradicted speculation may re-tag only where the new kind is legal. */
1225
+ speculativeRetagAllowed(path, kind) {
1226
+ if (path.length === 0) {
1227
+ return true;
1228
+ }
1229
+ const last = path[path.length - 1];
1230
+ if (typeof last === "number") {
1231
+ const fieldName = this.parentFieldName(path);
1232
+ const ownerKind = this.getObjectKindForPath(path.slice(0, -2));
1233
+ if (!fieldName || !ownerKind) return false;
1234
+ const fieldSchema = this.lookupSchema(ownerKind)?.fields[fieldName];
1235
+ return fieldSchema?.type === "array" && fieldSchema.itemKinds.includes(kind);
1236
+ }
1237
+ return false;
1238
+ }
1239
+ addPendingSchemaPath(kind, path) {
1240
+ const pathKey = this.pathKey(path);
1241
+ const existing = this.pendingSchemaPaths.get(kind) ?? /* @__PURE__ */ new Map();
1242
+ existing.set(pathKey, path);
1243
+ this.pendingSchemaPaths.set(kind, existing);
1244
+ }
1245
+ completeTypedObject(path, objectValue, at) {
1246
+ const pathKey = this.pathKey(path);
1247
+ if (this.rawObjectPaths.has(pathKey)) return;
1248
+ const declaredKind = readObjectKind(objectValue);
1249
+ const committedKind = this.objectKinds.get(pathKey);
1250
+ if (committedKind && this.pendingSchemaPaths.get(committedKind)?.has(pathKey)) {
1251
+ this.closedPendingPaths.add(pathKey);
1252
+ return;
1253
+ }
1254
+ if (!declaredKind && committedKind && this.speculativeKinds.has(pathKey)) {
1255
+ this.speculativeKinds.delete(pathKey);
1256
+ this.finalizeSpeculatedObject(path, objectValue, committedKind, at);
1257
+ return;
1258
+ }
1259
+ if (path.length === 0 && !declaredKind && !committedKind && this.rootSurfaceKind) {
1260
+ const surfaceKind = this.rootSurfaceKind;
1261
+ this.rootSurfaceKind = null;
1262
+ this.objectKinds.set(pathKey, surfaceKind);
1263
+ this.rootKind = surfaceKind;
1264
+ this.emit({
1265
+ type: "kind_identified",
1266
+ kind: surfaceKind,
1267
+ path,
1268
+ speculative: true,
1269
+ at
1270
+ });
1271
+ this.clearKindWait(path, "identified", at, surfaceKind);
1272
+ this.finalizeSpeculatedObject(path, objectValue, surfaceKind, at);
1273
+ return;
1274
+ }
1275
+ if (!declaredKind) {
1276
+ this.emitRawObject(
1277
+ path,
1278
+ objectValue,
1279
+ `Object is missing "${KIND_KEY}".`,
1280
+ at
1281
+ );
1282
+ return;
1283
+ }
1284
+ this.speculativeKinds.delete(pathKey);
1285
+ this.finalizeTypedObject(path, objectValue, at);
1286
+ }
1287
+ /** Validate + complete an object whose value carries __kind. */
1288
+ finalizeTypedObject(path, objectValue, at) {
1289
+ const pathKey = this.pathKey(path);
1290
+ const kind = readObjectKind(objectValue);
1291
+ if (!kind) {
1292
+ this.emitRawObject(
1293
+ path,
1294
+ objectValue,
1295
+ `Object is missing "${KIND_KEY}".`,
1296
+ at
1297
+ );
1298
+ return;
1299
+ }
1300
+ const schema = this.lookupSchema(kind);
1301
+ if (!schema) {
1302
+ this.emitRawObject(
1303
+ path,
1304
+ objectValue,
1305
+ `No block schema registered for "${kind}".`,
1306
+ at,
1307
+ kind,
1308
+ "unverified"
1309
+ );
1310
+ return;
1311
+ }
1312
+ const arrayItemError = this.validateArrayItemKind(path, kind);
1313
+ if (arrayItemError) {
1314
+ this.emitRawObject(path, objectValue, arrayItemError, at, kind);
1315
+ return;
1316
+ }
1317
+ const outcome = this.validateObjectAgainstSchema(objectValue, schema);
1318
+ if (outcome.error) {
1319
+ this.emitRawObject(path, objectValue, outcome.error, at, kind);
1320
+ return;
1321
+ }
1322
+ this.objectKinds.set(pathKey, kind);
1323
+ this.emitSchemaNotices(path, kind, outcome, at);
1324
+ this.emitBlockSnapshotForObject(path, at, true);
1325
+ this.emit({
1326
+ type: "object_complete",
1327
+ kind,
1328
+ path,
1329
+ value: objectValue,
1330
+ at
1331
+ });
1332
+ }
1333
+ /** Validate + complete an object typed purely by parent prediction. */
1334
+ finalizeSpeculatedObject(path, objectValue, kind, at) {
1335
+ const schema = this.lookupSchema(kind);
1336
+ if (!schema) {
1337
+ this.emitRawObject(
1338
+ path,
1339
+ objectValue,
1340
+ `No block schema registered for "${kind}".`,
1341
+ at,
1342
+ kind,
1343
+ "unverified"
1344
+ );
1345
+ return;
1346
+ }
1347
+ const outcome = this.validateObjectAgainstSchema(
1348
+ { ...objectValue, [KIND_KEY]: kind },
1349
+ schema
1350
+ );
1351
+ if (outcome.error) {
1352
+ this.emitRawObject(path, objectValue, outcome.error, at, kind);
1353
+ return;
1354
+ }
1355
+ this.emitSchemaNotices(path, kind, outcome, at);
1356
+ this.emitBlockSnapshotForObject(path, at, true);
1357
+ this.emit({
1358
+ type: "object_complete",
1359
+ kind,
1360
+ path,
1361
+ value: objectValue,
1362
+ at
1363
+ });
1364
+ }
1365
+ validateArrayItemKind(path, kind) {
1366
+ const last = path[path.length - 1];
1367
+ if (typeof last !== "number") return null;
1368
+ const fieldName = this.parentFieldName(path);
1369
+ if (!fieldName) return null;
1370
+ const ownerObjectPath = path.slice(0, -2);
1371
+ const parentKind = this.getObjectKindForPath(ownerObjectPath);
1372
+ if (!parentKind) return null;
1373
+ const fieldSchema = this.lookupSchema(parentKind)?.fields[fieldName];
1374
+ if (!fieldSchema || fieldSchema.type !== "array") return null;
1375
+ if (!fieldSchema.itemKinds.includes(kind)) {
1376
+ return `Kind "${kind}" is not allowed in "${fieldName}" on "${parentKind}" (expected one of: ${fieldSchema.itemKinds.join(", ")}).`;
1377
+ }
1378
+ return null;
1379
+ }
1380
+ completeRoot(rootObject, at) {
1381
+ const pathKey = this.pathKey([]);
1382
+ if (this.rawObjectPaths.has(pathKey)) {
1383
+ this.emit({
1384
+ type: "complete",
1385
+ kind: readObjectKind(rootObject) ?? "",
1386
+ value: rootObject,
1387
+ at
1388
+ });
1389
+ return;
1390
+ }
1391
+ const committedKind = this.objectKinds.get(pathKey);
1392
+ if (committedKind && this.pendingSchemaPaths.get(committedKind)?.has(pathKey)) {
1393
+ this.emit({
1394
+ type: "complete",
1395
+ kind: committedKind,
1396
+ value: rootObject,
1397
+ at
1398
+ });
1399
+ return;
1400
+ }
1401
+ this.emit({
1402
+ type: "complete",
1403
+ kind: this.objectKinds.get(pathKey) ?? readObjectKind(rootObject) ?? "",
1404
+ value: rootObject,
1405
+ at
1406
+ });
1407
+ }
1408
+ /** Mark a node raw (node-scoped failure) without killing the stream. */
1409
+ markNodeRaw(path, liveValue, reason, at) {
1410
+ const pathKey = this.pathKey(path);
1411
+ if (this.rawObjectPaths.has(pathKey)) return;
1412
+ const identifiedKind = this.objectKinds.get(pathKey) ?? (typeof liveValue === "object" && liveValue !== null && !Array.isArray(liveValue) ? readObjectKind(liveValue) ?? void 0 : void 0);
1413
+ this.speculativeKinds.delete(pathKey);
1414
+ this.emitRawObject(path, safeCopy(liveValue), reason, at, identifiedKind);
1415
+ }
1416
+ /**
1417
+ * Degrade ONE node off the resolved path.
1418
+ *
1419
+ * `cause` defaults to `"invalid"` deliberately: every call site that omits
1420
+ * it is a real failure (validation, duplicate key, placement, contradicted
1421
+ * speculation). ONLY the "no schema registered" sites pass `"unverified"`,
1422
+ * and they are the reason the parameter exists — see `IrKindState`.
1423
+ */
1424
+ emitRawObject(path, value, reason, at, identifiedKind, cause = "invalid") {
1425
+ const pathKey = this.pathKey(path);
1426
+ if (this.rawObjectPaths.has(pathKey)) return;
1427
+ this.rawObjectPaths.add(pathKey);
1428
+ this.clearKindWait(path, "raw_fallback", at, void 0, reason);
1429
+ this.emit({
1430
+ type: "raw_object",
1431
+ path,
1432
+ value,
1433
+ reason,
1434
+ ...identifiedKind !== void 0 && { kind: identifiedKind },
1435
+ cause,
1436
+ at
1437
+ });
1438
+ }
1439
+ clearKindWait(path, outcome, at, kind, reason) {
1440
+ const pathKey = this.pathKey(path);
1441
+ if (!this.awaitingKindPaths.has(pathKey)) return;
1442
+ this.awaitingKindPaths.delete(pathKey);
1443
+ this.emit({
1444
+ type: "kind_wait_end",
1445
+ path,
1446
+ outcome,
1447
+ ...kind !== void 0 && { kind },
1448
+ ...reason !== void 0 && { reason },
1449
+ at
1450
+ });
1451
+ }
1452
+ emitFieldIfReady(path, value, at) {
1453
+ const fieldKey = this.fieldKeyFromPath(path);
1454
+ if (!fieldKey || fieldKey === KIND_KEY) return;
1455
+ const parentPath = path.slice(0, -1);
1456
+ const objectKind = this.getDirectObjectKind(parentPath);
1457
+ if (!objectKind) {
1458
+ const parentPathKey = this.pathKey(parentPath);
1459
+ const deferred = this.deferredFields.get(parentPathKey) ?? [];
1460
+ deferred.push({ key: fieldKey, value, at });
1461
+ this.deferredFields.set(parentPathKey, deferred);
1462
+ return;
1463
+ }
1464
+ const schemaResolved = this.lookupSchema(objectKind) !== void 0;
1465
+ if (schemaResolved && !this.isAllowedSchemaField(parentPath, fieldKey, objectKind)) {
1466
+ return;
1467
+ }
1468
+ this.emit({
1469
+ type: "field",
1470
+ kind: objectKind,
1471
+ path,
1472
+ key: fieldKey,
1473
+ value,
1474
+ at
1475
+ });
1476
+ this.emitBlockSnapshotForObject(parentPath, at);
1477
+ }
1478
+ flushDeferredFields(objectPath, kind, at) {
1479
+ const pathKey = this.pathKey(objectPath);
1480
+ const deferred = this.deferredFields.get(pathKey);
1481
+ if (!deferred) return;
1482
+ for (const entry of deferred) {
1483
+ if (entry.key === KIND_KEY) continue;
1484
+ if (!this.isAllowedSchemaField(objectPath, entry.key, kind)) continue;
1485
+ this.emit({
1486
+ type: "field",
1487
+ kind,
1488
+ path: [...objectPath, entry.key],
1489
+ key: entry.key,
1490
+ value: entry.value,
1491
+ at: entry.at
1492
+ });
1493
+ }
1494
+ this.deferredFields.delete(pathKey);
1495
+ this.emitBlockSnapshotForObject(objectPath, at);
1496
+ }
1497
+ getLiveObjectValue(path) {
1498
+ const pathKey = this.pathKey(path);
1499
+ for (let i = this.stack.length - 1; i >= 0; i--) {
1500
+ const frame = this.stack[i];
1501
+ if (!frame) continue;
1502
+ if (frame.kind === "object" && this.pathKey(frame.path) === pathKey) {
1503
+ return frame.value;
1504
+ }
1505
+ }
1506
+ return null;
1507
+ }
1508
+ emitBlockSnapshotForObject(objectPath, at, complete = false) {
1509
+ const pathKey = this.pathKey(objectPath);
1510
+ if (this.rawObjectPaths.has(pathKey)) return;
1511
+ const kind = this.getDirectObjectKind(objectPath);
1512
+ if (!kind) return;
1513
+ const schema = this.lookupObjectSchema(kind);
1514
+ if (!schema) return;
1515
+ const partial = complete ? this.getFinalizedObjectValue(objectPath) : this.getLiveObjectValue(objectPath);
1516
+ if (!partial) return;
1517
+ const { value, residue } = buildCompliantKindSnapshot(schema, partial);
1518
+ this.emit({
1519
+ type: "block_snapshot",
1520
+ kind,
1521
+ path: objectPath,
1522
+ value,
1523
+ residue,
1524
+ complete,
1525
+ at
1526
+ });
1527
+ }
1528
+ /**
1529
+ * On `complete` snapshots (and post-close schema upgrades) the frame has
1530
+ * already been popped — the finalized value lives in the root tree.
1531
+ */
1532
+ getFinalizedObjectValue(path) {
1533
+ const live = this.getLiveObjectValue(path);
1534
+ if (live) return live;
1535
+ if (path.length === 0) {
1536
+ return typeof this.root === "object" && this.root !== null && !Array.isArray(this.root) ? this.root : null;
1537
+ }
1538
+ let cursor = this.root;
1539
+ for (const segment of path) {
1540
+ if (cursor === null || typeof cursor !== "object") return null;
1541
+ cursor = cursor[segment];
1542
+ }
1543
+ return typeof cursor === "object" && cursor !== null && !Array.isArray(cursor) ? cursor : null;
1544
+ }
1545
+ validateFieldPlacement(parent, fieldKey, valueKind, value) {
1546
+ const parentPathKey = this.pathKey(parent.path);
1547
+ const inlineFields = this.inlineSchemas.get(parentPathKey);
1548
+ if (inlineFields) {
1549
+ const fieldSchema = inlineFields[fieldKey];
1550
+ if (!fieldSchema) {
1551
+ return null;
1552
+ }
1553
+ return this.validateValueAgainstField(
1554
+ fieldSchema,
1555
+ valueKind,
1556
+ value,
1557
+ fieldKey
1558
+ );
1559
+ }
1560
+ const recordValueType = this.recordSchemas.get(parentPathKey);
1561
+ if (recordValueType) {
1562
+ if (recordValueType === "json") return null;
1563
+ if (valueKind !== "scalar") {
1564
+ return `Record field "${fieldKey}" must be a scalar.`;
1565
+ }
1566
+ return this.validateRecordScalar(recordValueType, value, fieldKey);
1567
+ }
1568
+ return null;
1569
+ }
1570
+ emitSchemaNotices(path, kind, outcome, at) {
1571
+ for (const field of outcome.optionalMissing) {
1572
+ this.emit({
1573
+ type: "optional_field_missing",
1574
+ kind,
1575
+ path,
1576
+ field,
1577
+ at
1578
+ });
1579
+ }
1580
+ for (const field of outcome.extraFields) {
1581
+ this.emit({
1582
+ type: "extra_field",
1583
+ kind,
1584
+ path,
1585
+ field,
1586
+ at
1587
+ });
1588
+ }
1589
+ }
1590
+ validateObjectAgainstSchema(objectValue, schema) {
1591
+ const optionalMissing = [];
1592
+ const extraFields = [];
1593
+ const emptyOutcome = { optionalMissing, extraFields };
1594
+ if (schema.root) {
1595
+ return {
1596
+ error: `Kind "${schema.kind}" has a non-object root form and cannot be a "${KIND_KEY}" object.`,
1597
+ ...emptyOutcome
1598
+ };
1599
+ }
1600
+ const kind = readObjectKind(objectValue);
1601
+ if (kind !== schema.kind) {
1602
+ return {
1603
+ error: `Object "${KIND_KEY}" is "${kind ?? "missing"}", expected "${schema.kind}".`,
1604
+ ...emptyOutcome
1605
+ };
1606
+ }
1607
+ for (const [fieldName, fieldSchema] of Object.entries(schema.fields)) {
1608
+ if (fieldSchema.required) {
1609
+ if (!(fieldName in objectValue)) {
1610
+ return {
1611
+ error: `Kind "${schema.kind}" is missing required field "${fieldName}".`,
1612
+ ...emptyOutcome
1613
+ };
1614
+ }
1615
+ continue;
1616
+ }
1617
+ if (!(fieldName in objectValue)) {
1618
+ optionalMissing.push(fieldName);
1619
+ }
1620
+ }
1621
+ for (const [fieldName, fieldValue] of Object.entries(objectValue)) {
1622
+ if (fieldName === KIND_KEY) continue;
1623
+ const fieldSchema = schema.fields[fieldName];
1624
+ if (!fieldSchema) {
1625
+ extraFields.push(fieldName);
1626
+ continue;
1627
+ }
1628
+ const error = this.validateFinalFieldValue(
1629
+ fieldSchema,
1630
+ fieldValue,
1631
+ fieldName,
1632
+ schema.kind
1633
+ );
1634
+ if (error) {
1635
+ return { error, optionalMissing, extraFields };
1636
+ }
1637
+ }
1638
+ return { error: null, optionalMissing, extraFields };
1639
+ }
1640
+ validateFinalFieldValue(fieldSchema, value, fieldName, objectKind) {
1641
+ if (fieldSchema.type === "json") return null;
1642
+ if (fieldSchema.type === "json[]") {
1643
+ if (value === null) {
1644
+ return fieldSchema.nullable ? null : `Field "${fieldName}" on kind "${objectKind}" cannot be null.`;
1645
+ }
1646
+ if (!Array.isArray(value)) {
1647
+ return `Field "${fieldName}" on kind "${objectKind}" must be an array.`;
1648
+ }
1649
+ return null;
1650
+ }
1651
+ if (isScalarArrayType(fieldSchema.type)) {
1652
+ if (!Array.isArray(value)) {
1653
+ return `Field "${fieldName}" on kind "${objectKind}" must be an array.`;
1654
+ }
1655
+ const itemType = scalarArrayItemType(fieldSchema.type);
1656
+ if (!value.every((item) => typeof item === itemType)) {
1657
+ return `Field "${fieldName}" on kind "${objectKind}" must be an array of ${itemType}s.`;
1658
+ }
1659
+ if (fieldSchema.type === "string[]" && fieldSchema.values !== void 0 && !fieldSchema.open) {
1660
+ const allowed = fieldSchema.values;
1661
+ const bad = value.find(
1662
+ (item) => typeof item === "string" && !allowed.includes(item)
1663
+ );
1664
+ if (bad !== void 0) {
1665
+ return `Field "${fieldName}" on kind "${objectKind}" items must be one of: ${allowed.join(", ")}.`;
1666
+ }
1667
+ }
1668
+ return null;
1669
+ }
1670
+ if (fieldSchema.type === "array") {
1671
+ if (!Array.isArray(value)) {
1672
+ return `Field "${fieldName}" on kind "${objectKind}" must be an array.`;
1673
+ }
1674
+ return null;
1675
+ }
1676
+ if (fieldSchema.type === "object") {
1677
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1678
+ return `Field "${fieldName}" on kind "${objectKind}" must be an object.`;
1679
+ }
1680
+ const nestedKind = readObjectKind(value);
1681
+ if (nestedKind !== fieldSchema.kind) {
1682
+ return `Field "${fieldName}" on kind "${objectKind}" must be kind "${fieldSchema.kind}".`;
1683
+ }
1684
+ const nestedSchema = this.lookupSchema(fieldSchema.kind);
1685
+ if (!nestedSchema) {
1686
+ return `Unknown nested kind "${fieldSchema.kind}" on field "${fieldName}".`;
1687
+ }
1688
+ return this.validateObjectAgainstSchema(
1689
+ value,
1690
+ nestedSchema
1691
+ ).error;
1692
+ }
1693
+ if (fieldSchema.type === "inline_object") {
1694
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1695
+ return `Field "${fieldName}" on kind "${objectKind}" must be an inline object.`;
1696
+ }
1697
+ return this.validateObjectAgainstFields(
1698
+ value,
1699
+ fieldSchema.fields
1700
+ ).error;
1701
+ }
1702
+ if (fieldSchema.type === "record") {
1703
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1704
+ return `Field "${fieldName}" on kind "${objectKind}" must be a record object.`;
1705
+ }
1706
+ return this.validateRecordObject(
1707
+ value,
1708
+ fieldSchema.values
1709
+ );
1710
+ }
1711
+ if (fieldSchema.type === "union") {
1712
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1713
+ const kinds = fieldSchema.kinds ?? [];
1714
+ if (kinds.length === 0) {
1715
+ return `Field "${fieldName}" on kind "${objectKind}" must be ${fieldSchema.scalars.join(" | ")}.`;
1716
+ }
1717
+ const memberKind = readObjectKind(value);
1718
+ if (!memberKind || !kinds.includes(memberKind)) {
1719
+ return `Field "${fieldName}" on kind "${objectKind}" must be one of kinds: ${kinds.join(", ")}.`;
1720
+ }
1721
+ const memberSchema = this.lookupSchema(memberKind);
1722
+ if (!memberSchema) {
1723
+ return `Unknown union member kind "${memberKind}" on field "${fieldName}".`;
1724
+ }
1725
+ return this.validateObjectAgainstSchema(
1726
+ value,
1727
+ memberSchema
1728
+ ).error;
1729
+ }
1730
+ return this.validateScalarField(fieldSchema, value, fieldName);
1731
+ }
1732
+ if (fieldSchema.type === "string" || fieldSchema.type === "number" || fieldSchema.type === "boolean" || fieldSchema.type === "enum") {
1733
+ return this.validateScalarField(fieldSchema, value, fieldName);
1734
+ }
1735
+ return `Unsupported field schema for "${fieldName}".`;
1736
+ }
1737
+ validateValueAgainstField(fieldSchema, valueKind, value, fieldName) {
1738
+ if (fieldSchema.type === "json") {
1739
+ return null;
1740
+ }
1741
+ if (fieldSchema.type === "array" || fieldSchema.type === "json[]") {
1742
+ return valueKind === "array" ? null : `Field "${fieldName}" must be an array.`;
1743
+ }
1744
+ if (fieldSchema.type === "object" || fieldSchema.type === "inline_object") {
1745
+ return valueKind === "object" ? null : `Field "${fieldName}" must be an object.`;
1746
+ }
1747
+ if (fieldSchema.type === "record") {
1748
+ return valueKind === "object" ? null : `Field "${fieldName}" must be a record object.`;
1749
+ }
1750
+ if (isScalarArrayType(fieldSchema.type)) {
1751
+ return valueKind === "array" ? null : `Field "${fieldName}" must be an array.`;
1752
+ }
1753
+ if (fieldSchema.type === "union" && (fieldSchema.kinds?.length ?? 0) > 0) {
1754
+ if (valueKind === "object") return null;
1755
+ }
1756
+ if (valueKind !== "scalar") {
1757
+ return `Field "${fieldName}" must be a scalar.`;
1758
+ }
1759
+ return this.validateScalarField(fieldSchema, value, fieldName);
1760
+ }
1761
+ validateScalarField(fieldSchema, value, fieldName) {
1762
+ if (value === null) {
1763
+ return fieldSchema.nullable ? null : `Field "${fieldName}" cannot be null.`;
1764
+ }
1765
+ if (fieldSchema.type === "enum") {
1766
+ if (typeof value !== "string") {
1767
+ return `Field "${fieldName}" must be a string.`;
1768
+ }
1769
+ if (!fieldSchema.open && !fieldSchema.values.includes(value)) {
1770
+ return `Field "${fieldName}" must be one of: ${fieldSchema.values.join(", ")}.`;
1771
+ }
1772
+ return null;
1773
+ }
1774
+ if (fieldSchema.type === "union") {
1775
+ const valueType = typeof value;
1776
+ if (valueType !== "string" && valueType !== "number" && valueType !== "boolean") {
1777
+ return `Field "${fieldName}" must be ${fieldSchema.scalars.join(" | ")}.`;
1778
+ }
1779
+ if (!fieldSchema.scalars.includes(
1780
+ valueType
1781
+ )) {
1782
+ return `Field "${fieldName}" must be ${fieldSchema.scalars.join(" | ")}.`;
1783
+ }
1784
+ return null;
1785
+ }
1786
+ if (fieldSchema.type === "string" || fieldSchema.type === "number" || fieldSchema.type === "boolean") {
1787
+ if (typeof value !== fieldSchema.type) {
1788
+ return `Field "${fieldName}" must be ${fieldSchema.type}.`;
1789
+ }
1790
+ if (fieldSchema.type === "number" && typeof value === "number") {
1791
+ if (fieldSchema.min !== void 0 && value < fieldSchema.min) {
1792
+ return `Field "${fieldName}" must be >= ${fieldSchema.min}.`;
1793
+ }
1794
+ if (fieldSchema.max !== void 0 && value > fieldSchema.max) {
1795
+ return `Field "${fieldName}" must be <= ${fieldSchema.max}.`;
1796
+ }
1797
+ }
1798
+ return null;
1799
+ }
1800
+ return `Field "${fieldName}" is not a scalar.`;
1801
+ }
1802
+ validateRecordScalar(valueType, value, fieldName) {
1803
+ if (valueType === "json") return null;
1804
+ if (typeof value !== valueType) {
1805
+ return `Record field "${fieldName}" must be ${valueType}.`;
1806
+ }
1807
+ return null;
1808
+ }
1809
+ validateRecordObject(objectValue, valueType) {
1810
+ if (valueType === "json") return null;
1811
+ for (const [key, entry] of Object.entries(objectValue)) {
1812
+ if (typeof entry !== valueType) {
1813
+ return `Record key "${key}" must be ${valueType}.`;
1814
+ }
1815
+ }
1816
+ return null;
1817
+ }
1818
+ validateObjectAgainstFields(objectValue, fields) {
1819
+ const optionalMissing = [];
1820
+ const extraFields = [];
1821
+ const emptyOutcome = { optionalMissing, extraFields };
1822
+ for (const [fieldName, fieldSchema] of Object.entries(fields)) {
1823
+ if (fieldSchema.required) {
1824
+ if (!(fieldName in objectValue)) {
1825
+ return {
1826
+ error: `Inline object is missing required field "${fieldName}".`,
1827
+ ...emptyOutcome
1828
+ };
1829
+ }
1830
+ continue;
1831
+ }
1832
+ if (!(fieldName in objectValue)) {
1833
+ optionalMissing.push(fieldName);
1834
+ }
1835
+ }
1836
+ for (const [fieldName, fieldValue] of Object.entries(objectValue)) {
1837
+ const fieldSchema = fields[fieldName];
1838
+ if (!fieldSchema) {
1839
+ extraFields.push(fieldName);
1840
+ continue;
1841
+ }
1842
+ const error = this.validateFinalFieldValue(
1843
+ fieldSchema,
1844
+ fieldValue,
1845
+ fieldName,
1846
+ "inline_object"
1847
+ );
1848
+ if (error) {
1849
+ return { error, optionalMissing, extraFields };
1850
+ }
1851
+ }
1852
+ return { error: null, optionalMissing, extraFields };
1853
+ }
1854
+ registerObjectContext(path) {
1855
+ const fieldSchema = this.resolveParentFieldSchema(path);
1856
+ if (!fieldSchema) return;
1857
+ const pathKey = this.pathKey(path);
1858
+ if (fieldSchema.type === "inline_object") {
1859
+ this.inlineSchemas.set(pathKey, fieldSchema.fields);
1860
+ }
1861
+ if (fieldSchema.type === "record") {
1862
+ this.recordSchemas.set(pathKey, fieldSchema.values);
1863
+ }
1864
+ }
1865
+ resolveParentFieldSchema(path) {
1866
+ const fieldKey = this.fieldKeyFromPath(path);
1867
+ if (!fieldKey) return void 0;
1868
+ const parentPath = path.slice(0, -1);
1869
+ const parentKind = this.getObjectKindForPath(parentPath);
1870
+ if (!parentKind) return void 0;
1871
+ return this.lookupSchema(parentKind)?.fields[fieldKey];
1872
+ }
1873
+ getObjectKindForPath(path) {
1874
+ return this.objectKinds.get(this.pathKey(path)) ?? (path.length === 0 ? this.rootKind || null : null);
1875
+ }
1876
+ getDirectObjectKind(objectPath) {
1877
+ if (objectPath.length === 0) {
1878
+ return this.rootKind || null;
1879
+ }
1880
+ return this.objectKinds.get(this.pathKey(objectPath)) ?? null;
1881
+ }
1882
+ isAllowedSchemaField(objectPath, fieldKey, objectKind) {
1883
+ const pathKey = this.pathKey(objectPath);
1884
+ const inlineFields = this.inlineSchemas.get(pathKey);
1885
+ if (inlineFields) {
1886
+ return fieldKey in inlineFields;
1887
+ }
1888
+ if (this.recordSchemas.has(pathKey)) {
1889
+ return true;
1890
+ }
1891
+ const schema = this.lookupSchema(objectKind);
1892
+ return !!schema?.fields[fieldKey];
1893
+ }
1894
+ parentFieldName(path) {
1895
+ if (path.length === 0) return void 0;
1896
+ const parent = path[path.length - 2];
1897
+ return typeof parent === "string" ? parent : void 0;
1898
+ }
1899
+ fieldKeyFromPath(path) {
1900
+ const last = path[path.length - 1];
1901
+ return typeof last === "string" ? last : null;
1902
+ }
1903
+ isKindFieldPath(path) {
1904
+ return path[path.length - 1] === KIND_KEY;
1905
+ }
1906
+ pathKey(path) {
1907
+ return path.map((segment) => String(segment)).join(".");
1908
+ }
1909
+ lookupSchema(kind) {
1910
+ return this.resolver.get(kind) ?? this.arrivedSchemas.get(kind);
1911
+ }
1912
+ fail(reason, at) {
1913
+ if (this.failed) return;
1914
+ this.failed = true;
1915
+ this.emit({ type: "error", reason, at });
1916
+ }
1917
+ emit(event) {
1918
+ this.options.onEvent(event);
1919
+ }
1920
+ currentFrame() {
1921
+ return this.stack[this.stack.length - 1];
1922
+ }
1923
+ };
1924
+ function createKindStreamParser(options) {
1925
+ return new KindStreamParser(options);
1926
+ }
1927
+
1928
+ // session/parse-session.ts
1929
+ var ParseSession = class {
1930
+ identity;
1931
+ parser;
1932
+ tree = new IrTree();
1933
+ listeners = /* @__PURE__ */ new Map();
1934
+ anyListeners = /* @__PURE__ */ new Set();
1935
+ unsubscribeSchemaArrivals;
1936
+ fingerprinter = createFingerprinter();
1937
+ ended = false;
1938
+ constructor(options) {
1939
+ this.identity = options.identity;
1940
+ this.parser = createKindStreamParser({
1941
+ schemas: options.schemas,
1942
+ ...options.expectedRootKind !== void 0 && {
1943
+ expectedRootKind: options.expectedRootKind
1944
+ },
1945
+ onEvent: (event) => {
1946
+ this.tree.applyEvent(event);
1947
+ options.onEvent?.(event);
1948
+ }
1949
+ });
1950
+ this.unsubscribeSchemaArrivals = options.onSchemaArrived?.((kind, schema) => {
1951
+ this.parser.notifySchemaArrived(kind, schema);
1952
+ this.flushNotify();
1953
+ }) ?? null;
1954
+ }
1955
+ /** The single writer's input. Does NOT notify — host flushes on its cadence. */
1956
+ write(chunk) {
1957
+ this.fingerprinter.push(chunk);
1958
+ this.parser.push(chunk);
1959
+ }
1960
+ end() {
1961
+ if (this.ended) return;
1962
+ this.ended = true;
1963
+ this.parser.end();
1964
+ }
1965
+ get isEnded() {
1966
+ return this.ended;
1967
+ }
1968
+ get status() {
1969
+ return this.tree.status;
1970
+ }
1971
+ getNode(pathKey) {
1972
+ return this.tree.getNode(pathKey);
1973
+ }
1974
+ listNodes() {
1975
+ return this.tree.listNodes();
1976
+ }
1977
+ isRawPath(pathKey) {
1978
+ return this.tree.isRawPath(pathKey);
1979
+ }
1980
+ subscribe(pathKey, listener) {
1981
+ const set = this.listeners.get(pathKey) ?? /* @__PURE__ */ new Set();
1982
+ set.add(listener);
1983
+ this.listeners.set(pathKey, set);
1984
+ return () => {
1985
+ set.delete(listener);
1986
+ if (set.size === 0) this.listeners.delete(pathKey);
1987
+ };
1988
+ }
1989
+ /** Structural subscription: fires when ANY node changes (mount lists). */
1990
+ subscribeAny(listener) {
1991
+ this.anyListeners.add(listener);
1992
+ return () => {
1993
+ this.anyListeners.delete(listener);
1994
+ };
1995
+ }
1996
+ /** Publish dirty paths to their readers. Host-cadence, coalesced. */
1997
+ flushNotify() {
1998
+ if (!this.tree.hasDirty()) return;
1999
+ const dirty = this.tree.drainDirty();
2000
+ for (const pathKey of dirty) {
2001
+ const set = this.listeners.get(pathKey);
2002
+ if (!set) continue;
2003
+ for (const listener of set) listener();
2004
+ }
2005
+ if (this.anyListeners.size > 0) {
2006
+ for (const listener of this.anyListeners) listener();
2007
+ }
2008
+ }
2009
+ buildEnvelope() {
2010
+ return this.tree.buildEnvelope(this.fingerprinter.current());
2011
+ }
2012
+ dispose() {
2013
+ this.unsubscribeSchemaArrivals?.();
2014
+ this.listeners.clear();
2015
+ this.anyListeners.clear();
2016
+ }
2017
+ };
2018
+
2019
+ // session/session-manager.ts
2020
+ var SESSIONS_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.content-ir.parse-sessions");
2021
+ var slotHolder = globalThis;
2022
+ var sessions = slotHolder[SESSIONS_SLOT] ??= /* @__PURE__ */ new Map();
2023
+ function openParseSession(options) {
2024
+ const existing = sessions.get(options.identity);
2025
+ if (existing && !existing.isEnded) {
2026
+ throw new Error(
2027
+ `content-ir: a writer is already open for identity "${options.identity}". One writer per stream identity \u2014 everyone else reads.`
2028
+ );
2029
+ }
2030
+ existing?.dispose();
2031
+ const session = new ParseSession(options);
2032
+ sessions.set(options.identity, session);
2033
+ return session;
2034
+ }
2035
+ function getParseSession(identity) {
2036
+ return sessions.get(identity) ?? null;
2037
+ }
2038
+ function disposeParseSession(identity) {
2039
+ const session = sessions.get(identity);
2040
+ if (!session) return;
2041
+ session.dispose();
2042
+ sessions.delete(identity);
2043
+ }
2044
+
2045
+ export { ParseSession, disposeParseSession, getParseSession, openParseSession };
2046
+ //# sourceMappingURL=session.js.map
2047
+ //# sourceMappingURL=session.js.map