@ai-matrx/content-ir 0.3.0 → 0.5.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.
- package/CHANGELOG.md +42 -0
- package/dist/index.cjs +78 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -13
- package/dist/index.d.ts +60 -13
- package/dist/index.js +78 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 — 2026-08-29
|
|
4
|
+
|
|
5
|
+
- **A multi-type union is no longer silently narrowed to its first member.**
|
|
6
|
+
`resolvePrimaryType` now reports every non-null member, and
|
|
7
|
+
`convertAiSchemaToBlockFields` branches on them: a union spanning
|
|
8
|
+
objects/arrays becomes `json`, a scalar-only union becomes
|
|
9
|
+
`{type:"union", scalars}`, and anything unnameable stays `json` with a
|
|
10
|
+
warning. Single-typed fields are untouched.
|
|
11
|
+
|
|
12
|
+
WHY: `type: ["string","number","boolean","object","array","null"]` —
|
|
13
|
+
pydantic's `Any`, and the commonest construct in user-authored kinds —
|
|
14
|
+
converted to plain `string`. The loss was invisible and it inverted the
|
|
15
|
+
field's meaning: an `estimated_count` of `1` then FAILED validation, so a
|
|
16
|
+
correct payload read as a broken instance. Widening is safe; silently
|
|
17
|
+
narrowing is not.
|
|
18
|
+
|
|
19
|
+
- New `__tests__/json-schema-to-kind.test.ts` pins JSON Schema → KindSchema
|
|
20
|
+
over the constructs a stored field list historically could not hold —
|
|
21
|
+
pydantic-Any unions, `items: {}` arrays, arrays of `__kind` children,
|
|
22
|
+
nested `__kind` objects, and markerless inline objects. All convert with
|
|
23
|
+
zero errors, which is the evidence that `emitted_json_schema` is sufficient
|
|
24
|
+
on its own and a second stored copy of a kind's fields is unnecessary.
|
|
25
|
+
|
|
26
|
+
## 0.4.0 — 2026-08-29
|
|
27
|
+
|
|
28
|
+
- **`unverified` — "we never checked it" is no longer "we checked it and it
|
|
29
|
+
failed."** `IrKindState` gains `unverified`, and `raw_object` events carry a
|
|
30
|
+
`cause` (`"unverified" | "invalid"`, absent reads as `"invalid"`). The four
|
|
31
|
+
no-schema-registered degrades — the two pending-schema resolutions, plus the
|
|
32
|
+
typed and speculated finalizers — now report `unverified`; every validation
|
|
33
|
+
failure, array-item mismatch, duplicate key, bad placement, and contradicted
|
|
34
|
+
speculation still reports `invalid`. `IrTree` carries the cause onto
|
|
35
|
+
`root.kindState` and into `nodeIndex`.
|
|
36
|
+
|
|
37
|
+
WHY THIS EXISTS: `raw` meant both things, and on 2026-08-28 the render route
|
|
38
|
+
began reading `raw` as "broken instance" and diverting it away from the
|
|
39
|
+
kind's component. Every kind whose schema cannot be reconstructed — nested
|
|
40
|
+
objects, arrays of child kinds, loose fields — degrades on the no-schema
|
|
41
|
+
path, so ~221 live kinds with purpose-built components silently started
|
|
42
|
+
rendering as key/value dumps. Consumers MUST NOT treat `unverified` as a
|
|
43
|
+
failure: the value is intact and was never examined.
|
|
44
|
+
|
|
3
45
|
## 0.3.0 — 2026-08-29
|
|
4
46
|
|
|
5
47
|
- **KIND PRESERVATION now covers STRUCTURAL raws.** A node that degrades to
|
package/dist/index.cjs
CHANGED
|
@@ -111,11 +111,20 @@ var IrTree = class {
|
|
|
111
111
|
*/
|
|
112
112
|
earlyFields = /* @__PURE__ */ new Map();
|
|
113
113
|
/**
|
|
114
|
-
* pathKey → identified kind preserved through a
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* pathKey → identified kind preserved through a raw fallback (parser stamped
|
|
115
|
+
* `kind` on the raw_object event) — schema-availability degrades and, since
|
|
116
|
+
* 2026-08-29, structural ones too. Only a node nothing ever identified (no
|
|
117
|
+
* `__kind` at all) is absent here.
|
|
117
118
|
*/
|
|
118
119
|
rawKinds = /* @__PURE__ */ new Map();
|
|
120
|
+
/**
|
|
121
|
+
* pathKey → WHY the node degraded. `"unverified"` means no schema was
|
|
122
|
+
* available and nothing was ever checked; `"invalid"` means a check ran and
|
|
123
|
+
* failed. THE RENDER ROUTE BRANCHES ON THIS — see `IrKindState`. Absent =
|
|
124
|
+
* `"invalid"`, so a parser that predates the `cause` field (or any consumer
|
|
125
|
+
* hand-building events) keeps the strict, safe reading.
|
|
126
|
+
*/
|
|
127
|
+
rawCauses = /* @__PURE__ */ new Map();
|
|
119
128
|
regionStatus = "streaming";
|
|
120
129
|
errorReason = null;
|
|
121
130
|
rootRawValue = null;
|
|
@@ -168,6 +177,7 @@ var IrTree = class {
|
|
|
168
177
|
this.pendingSchemaPaths.delete(pathKey);
|
|
169
178
|
this.earlyFields.delete(pathKey);
|
|
170
179
|
if (event.kind) this.rawKinds.set(pathKey, event.kind);
|
|
180
|
+
this.rawCauses.set(pathKey, event.cause ?? "invalid");
|
|
171
181
|
this.markRaw(event.path, event.reason, event.value);
|
|
172
182
|
return;
|
|
173
183
|
}
|
|
@@ -366,10 +376,11 @@ var IrTree = class {
|
|
|
366
376
|
const isRaw = rootRawReason !== null;
|
|
367
377
|
const identifiedKind = this.identifiedKinds.get("") ?? "";
|
|
368
378
|
const rootKind = isRaw ? this.rawKinds.get("") ?? "" : rootNode?.kind ?? (this.completedKind || identifiedKind);
|
|
379
|
+
const rootRawState = this.rawCauses.get("") === "unverified" ? "unverified" : "raw";
|
|
369
380
|
const root = {
|
|
370
381
|
role: "structured",
|
|
371
382
|
kind: rootKind,
|
|
372
|
-
kindState: isRaw ?
|
|
383
|
+
kindState: isRaw ? rootRawState : rootNode ? rootNode.kindState : this.pendingSchemaPaths.has("") ? "pending_schema" : this.regionStatus === "streaming" ? identifiedKind ? "pending_schema" : "pending_kind" : "raw",
|
|
373
384
|
discriminator: JSON_DISCRIMINATOR,
|
|
374
385
|
path: [],
|
|
375
386
|
status: this.regionStatus,
|
|
@@ -392,7 +403,7 @@ var IrTree = class {
|
|
|
392
403
|
if (pathKey === "") continue;
|
|
393
404
|
nodeIndex[pathKey] = {
|
|
394
405
|
kind: this.rawKinds.get(pathKey) ?? "",
|
|
395
|
-
kindState: "raw",
|
|
406
|
+
kindState: this.rawCauses.get(pathKey) === "unverified" ? "unverified" : "raw",
|
|
396
407
|
status: "complete"
|
|
397
408
|
};
|
|
398
409
|
}
|
|
@@ -732,7 +743,8 @@ var KindStreamParser = class {
|
|
|
732
743
|
safeCopy(value),
|
|
733
744
|
`No block schema registered for "${kind}".`,
|
|
734
745
|
at,
|
|
735
|
-
kind
|
|
746
|
+
kind,
|
|
747
|
+
"unverified"
|
|
736
748
|
);
|
|
737
749
|
}
|
|
738
750
|
}
|
|
@@ -766,7 +778,8 @@ var KindStreamParser = class {
|
|
|
766
778
|
safeCopy(value ?? {}),
|
|
767
779
|
`No block schema registered for "${kind}".`,
|
|
768
780
|
at,
|
|
769
|
-
kind
|
|
781
|
+
kind,
|
|
782
|
+
"unverified"
|
|
770
783
|
);
|
|
771
784
|
this.closedPendingPaths.delete(pathKey);
|
|
772
785
|
continue;
|
|
@@ -1312,7 +1325,8 @@ var KindStreamParser = class {
|
|
|
1312
1325
|
objectValue,
|
|
1313
1326
|
`No block schema registered for "${kind}".`,
|
|
1314
1327
|
at,
|
|
1315
|
-
kind
|
|
1328
|
+
kind,
|
|
1329
|
+
"unverified"
|
|
1316
1330
|
);
|
|
1317
1331
|
return;
|
|
1318
1332
|
}
|
|
@@ -1346,7 +1360,8 @@ var KindStreamParser = class {
|
|
|
1346
1360
|
objectValue,
|
|
1347
1361
|
`No block schema registered for "${kind}".`,
|
|
1348
1362
|
at,
|
|
1349
|
-
kind
|
|
1363
|
+
kind,
|
|
1364
|
+
"unverified"
|
|
1350
1365
|
);
|
|
1351
1366
|
return;
|
|
1352
1367
|
}
|
|
@@ -1419,7 +1434,15 @@ var KindStreamParser = class {
|
|
|
1419
1434
|
this.speculativeKinds.delete(pathKey);
|
|
1420
1435
|
this.emitRawObject(path, safeCopy(liveValue), reason, at, identifiedKind);
|
|
1421
1436
|
}
|
|
1422
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Degrade ONE node off the resolved path.
|
|
1439
|
+
*
|
|
1440
|
+
* `cause` defaults to `"invalid"` deliberately: every call site that omits
|
|
1441
|
+
* it is a real failure (validation, duplicate key, placement, contradicted
|
|
1442
|
+
* speculation). ONLY the "no schema registered" sites pass `"unverified"`,
|
|
1443
|
+
* and they are the reason the parameter exists — see `IrKindState`.
|
|
1444
|
+
*/
|
|
1445
|
+
emitRawObject(path, value, reason, at, identifiedKind, cause = "invalid") {
|
|
1423
1446
|
const pathKey = this.pathKey(path);
|
|
1424
1447
|
if (this.rawObjectPaths.has(pathKey)) return;
|
|
1425
1448
|
this.rawObjectPaths.add(pathKey);
|
|
@@ -1430,6 +1453,7 @@ var KindStreamParser = class {
|
|
|
1430
1453
|
value,
|
|
1431
1454
|
reason,
|
|
1432
1455
|
...identifiedKind !== void 0 && { kind: identifiedKind },
|
|
1456
|
+
cause,
|
|
1433
1457
|
at
|
|
1434
1458
|
});
|
|
1435
1459
|
}
|
|
@@ -2999,21 +3023,26 @@ function fieldSchemaSummary(field) {
|
|
|
2999
3023
|
function resolvePrimaryType(node) {
|
|
3000
3024
|
const raw = node.type;
|
|
3001
3025
|
if (typeof raw === "string") {
|
|
3002
|
-
|
|
3026
|
+
const t = raw === "integer" ? "number" : raw;
|
|
3027
|
+
return { type: t, nullable: false, members: [t] };
|
|
3003
3028
|
}
|
|
3004
3029
|
if (Array.isArray(raw)) {
|
|
3005
3030
|
const types = raw.filter((t) => typeof t === "string");
|
|
3006
3031
|
const nullable = types.includes("null");
|
|
3007
|
-
const
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
if (node.
|
|
3016
|
-
|
|
3032
|
+
const members = [
|
|
3033
|
+
...new Set(
|
|
3034
|
+
types.filter((t) => t !== "null").map((t) => t === "integer" ? "number" : t)
|
|
3035
|
+
)
|
|
3036
|
+
];
|
|
3037
|
+
const primary = members[0] ?? (nullable && types.length === 1 ? "null" : null);
|
|
3038
|
+
return { type: primary ?? null, nullable, members };
|
|
3039
|
+
}
|
|
3040
|
+
if (node.enum) return { type: "string", nullable: false, members: ["string"] };
|
|
3041
|
+
if (node.properties)
|
|
3042
|
+
return { type: "object", nullable: false, members: ["object"] };
|
|
3043
|
+
if (node.items)
|
|
3044
|
+
return { type: "array", nullable: false, members: ["array"] };
|
|
3045
|
+
return { type: null, nullable: false, members: [] };
|
|
3017
3046
|
}
|
|
3018
3047
|
function carriedMetadataKeys(field) {
|
|
3019
3048
|
if (field === null) return /* @__PURE__ */ new Set();
|
|
@@ -3331,7 +3360,34 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
|
|
|
3331
3360
|
});
|
|
3332
3361
|
return { ...requiredNullableFlags(required), type: "json" };
|
|
3333
3362
|
}
|
|
3334
|
-
const { type, nullable } = resolvePrimaryType(node);
|
|
3363
|
+
const { type, nullable, members } = resolvePrimaryType(node);
|
|
3364
|
+
if (members.length > 1) {
|
|
3365
|
+
const objectish = members.some((m) => m === "object" || m === "array");
|
|
3366
|
+
if (objectish) {
|
|
3367
|
+
ctx.problems.push({
|
|
3368
|
+
severity: "info",
|
|
3369
|
+
path,
|
|
3370
|
+
message: `Union of [${members.join(", ")}] at "${path || "(root)"}" spans objects/arrays \u2014 carried as any JSON value (json).`
|
|
3371
|
+
});
|
|
3372
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
3373
|
+
}
|
|
3374
|
+
const scalars = members.filter(
|
|
3375
|
+
(m) => m === "string" || m === "number" || m === "boolean"
|
|
3376
|
+
);
|
|
3377
|
+
if (scalars.length === members.length && scalars.length > 1) {
|
|
3378
|
+
return {
|
|
3379
|
+
...requiredNullableFlags(required, nullable),
|
|
3380
|
+
type: "union",
|
|
3381
|
+
scalars
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
ctx.problems.push({
|
|
3385
|
+
severity: "warning",
|
|
3386
|
+
path,
|
|
3387
|
+
message: `Union of [${members.join(", ")}] at "${path || "(root)"}" has no exact field type \u2014 carried as any JSON value (json) rather than narrowed to "${type}".`
|
|
3388
|
+
});
|
|
3389
|
+
return { ...requiredNullableFlags(required), type: "json" };
|
|
3390
|
+
}
|
|
3335
3391
|
if (type === "string") {
|
|
3336
3392
|
if (Array.isArray(node.enum)) {
|
|
3337
3393
|
const values = node.enum.filter(
|