@ai-matrx/content-ir 0.2.2 → 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 +58 -0
- package/dist/index.cjs +88 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -17
- package/dist/index.d.ts +64 -17
- package/dist/index.js +88 -27
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
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
|
+
|
|
45
|
+
## 0.3.0 — 2026-08-29
|
|
46
|
+
|
|
47
|
+
- **KIND PRESERVATION now covers STRUCTURAL raws.** A node that degrades to
|
|
48
|
+
`raw_object` because its value failed schema validation, carried a duplicate
|
|
49
|
+
key, or violated the parent's `itemKinds` keeps its identified kind on the
|
|
50
|
+
raw event, the envelope root (`root.kind` with `kindState: "raw"`), and the
|
|
51
|
+
`nodeIndex` entry for nested children. Previously only schema-availability
|
|
52
|
+
raws preserved the kind, so a payload that was off by ONE field rendered as
|
|
53
|
+
anonymous raw JSON with no repaint subscription and no route — the single
|
|
54
|
+
most damaging class of the 2026-08-29 "kind slips through the cracks" audit
|
|
55
|
+
(crack #9). The render seam's registration boundary is unchanged: an
|
|
56
|
+
unregistered slug is still never claimed. Consumers should route a
|
|
57
|
+
registered kind-preserved raw to their generic floor and surface
|
|
58
|
+
`residue.notices` (the exact `raw_fallback` reason is recorded there).
|
|
59
|
+
Pinned by two new kind-parser tests.
|
|
60
|
+
|
|
3
61
|
## 0.2.2 — 2026-08-24
|
|
4
62
|
|
|
5
63
|
- `rehydrateRunResult` now rejects nested entries whose `__kind` is not
|
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,
|
|
@@ -390,7 +401,11 @@ var IrTree = class {
|
|
|
390
401
|
}
|
|
391
402
|
for (const [pathKey] of this.rawPaths) {
|
|
392
403
|
if (pathKey === "") continue;
|
|
393
|
-
nodeIndex[pathKey] = {
|
|
404
|
+
nodeIndex[pathKey] = {
|
|
405
|
+
kind: this.rawKinds.get(pathKey) ?? "",
|
|
406
|
+
kindState: this.rawCauses.get(pathKey) === "unverified" ? "unverified" : "raw",
|
|
407
|
+
status: "complete"
|
|
408
|
+
};
|
|
394
409
|
}
|
|
395
410
|
return {
|
|
396
411
|
v: IR_VERSION,
|
|
@@ -728,7 +743,8 @@ var KindStreamParser = class {
|
|
|
728
743
|
safeCopy(value),
|
|
729
744
|
`No block schema registered for "${kind}".`,
|
|
730
745
|
at,
|
|
731
|
-
kind
|
|
746
|
+
kind,
|
|
747
|
+
"unverified"
|
|
732
748
|
);
|
|
733
749
|
}
|
|
734
750
|
}
|
|
@@ -762,7 +778,8 @@ var KindStreamParser = class {
|
|
|
762
778
|
safeCopy(value ?? {}),
|
|
763
779
|
`No block schema registered for "${kind}".`,
|
|
764
780
|
at,
|
|
765
|
-
kind
|
|
781
|
+
kind,
|
|
782
|
+
"unverified"
|
|
766
783
|
);
|
|
767
784
|
this.closedPendingPaths.delete(pathKey);
|
|
768
785
|
continue;
|
|
@@ -905,7 +922,7 @@ var KindStreamParser = class {
|
|
|
905
922
|
* Recorded at the first root key; ADOPTED only when the root object closes
|
|
906
923
|
* (`completeTypedObject`). That is the surface registry's complete-only
|
|
907
924
|
* convergence law, and every json_root_key row is `streaming:false` — these
|
|
908
|
-
* legacy shapes are
|
|
925
|
+
* legacy shapes are recognized by their whole payload, so speculating
|
|
909
926
|
* mid-stream would flash a kind component over an object that may never
|
|
910
927
|
* satisfy the schema. An explicit `expectedRootKind` (an agent's declared
|
|
911
928
|
* output schema) is stronger context and always wins; an actual `__kind`
|
|
@@ -1308,18 +1325,19 @@ var KindStreamParser = class {
|
|
|
1308
1325
|
objectValue,
|
|
1309
1326
|
`No block schema registered for "${kind}".`,
|
|
1310
1327
|
at,
|
|
1311
|
-
kind
|
|
1328
|
+
kind,
|
|
1329
|
+
"unverified"
|
|
1312
1330
|
);
|
|
1313
1331
|
return;
|
|
1314
1332
|
}
|
|
1315
1333
|
const arrayItemError = this.validateArrayItemKind(path, kind);
|
|
1316
1334
|
if (arrayItemError) {
|
|
1317
|
-
this.emitRawObject(path, objectValue, arrayItemError, at);
|
|
1335
|
+
this.emitRawObject(path, objectValue, arrayItemError, at, kind);
|
|
1318
1336
|
return;
|
|
1319
1337
|
}
|
|
1320
1338
|
const outcome = this.validateObjectAgainstSchema(objectValue, schema);
|
|
1321
1339
|
if (outcome.error) {
|
|
1322
|
-
this.emitRawObject(path, objectValue, outcome.error, at);
|
|
1340
|
+
this.emitRawObject(path, objectValue, outcome.error, at, kind);
|
|
1323
1341
|
return;
|
|
1324
1342
|
}
|
|
1325
1343
|
this.objectKinds.set(pathKey, kind);
|
|
@@ -1342,7 +1360,8 @@ var KindStreamParser = class {
|
|
|
1342
1360
|
objectValue,
|
|
1343
1361
|
`No block schema registered for "${kind}".`,
|
|
1344
1362
|
at,
|
|
1345
|
-
kind
|
|
1363
|
+
kind,
|
|
1364
|
+
"unverified"
|
|
1346
1365
|
);
|
|
1347
1366
|
return;
|
|
1348
1367
|
}
|
|
@@ -1351,7 +1370,7 @@ var KindStreamParser = class {
|
|
|
1351
1370
|
schema
|
|
1352
1371
|
);
|
|
1353
1372
|
if (outcome.error) {
|
|
1354
|
-
this.emitRawObject(path, objectValue, outcome.error, at);
|
|
1373
|
+
this.emitRawObject(path, objectValue, outcome.error, at, kind);
|
|
1355
1374
|
return;
|
|
1356
1375
|
}
|
|
1357
1376
|
this.emitSchemaNotices(path, kind, outcome, at);
|
|
@@ -1411,10 +1430,19 @@ var KindStreamParser = class {
|
|
|
1411
1430
|
markNodeRaw(path, liveValue, reason, at) {
|
|
1412
1431
|
const pathKey = this.pathKey(path);
|
|
1413
1432
|
if (this.rawObjectPaths.has(pathKey)) return;
|
|
1433
|
+
const identifiedKind = this.objectKinds.get(pathKey) ?? (typeof liveValue === "object" && liveValue !== null && !Array.isArray(liveValue) ? readObjectKind(liveValue) ?? void 0 : void 0);
|
|
1414
1434
|
this.speculativeKinds.delete(pathKey);
|
|
1415
|
-
this.emitRawObject(path, safeCopy(liveValue), reason, at);
|
|
1435
|
+
this.emitRawObject(path, safeCopy(liveValue), reason, at, identifiedKind);
|
|
1416
1436
|
}
|
|
1417
|
-
|
|
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") {
|
|
1418
1446
|
const pathKey = this.pathKey(path);
|
|
1419
1447
|
if (this.rawObjectPaths.has(pathKey)) return;
|
|
1420
1448
|
this.rawObjectPaths.add(pathKey);
|
|
@@ -1425,6 +1453,7 @@ var KindStreamParser = class {
|
|
|
1425
1453
|
value,
|
|
1426
1454
|
reason,
|
|
1427
1455
|
...identifiedKind !== void 0 && { kind: identifiedKind },
|
|
1456
|
+
cause,
|
|
1428
1457
|
at
|
|
1429
1458
|
});
|
|
1430
1459
|
}
|
|
@@ -2994,21 +3023,26 @@ function fieldSchemaSummary(field) {
|
|
|
2994
3023
|
function resolvePrimaryType(node) {
|
|
2995
3024
|
const raw = node.type;
|
|
2996
3025
|
if (typeof raw === "string") {
|
|
2997
|
-
|
|
3026
|
+
const t = raw === "integer" ? "number" : raw;
|
|
3027
|
+
return { type: t, nullable: false, members: [t] };
|
|
2998
3028
|
}
|
|
2999
3029
|
if (Array.isArray(raw)) {
|
|
3000
3030
|
const types = raw.filter((t) => typeof t === "string");
|
|
3001
3031
|
const nullable = types.includes("null");
|
|
3002
|
-
const
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
if (node.
|
|
3011
|
-
|
|
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: [] };
|
|
3012
3046
|
}
|
|
3013
3047
|
function carriedMetadataKeys(field) {
|
|
3014
3048
|
if (field === null) return /* @__PURE__ */ new Set();
|
|
@@ -3326,7 +3360,34 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
|
|
|
3326
3360
|
});
|
|
3327
3361
|
return { ...requiredNullableFlags(required), type: "json" };
|
|
3328
3362
|
}
|
|
3329
|
-
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
|
+
}
|
|
3330
3391
|
if (type === "string") {
|
|
3331
3392
|
if (Array.isArray(node.enum)) {
|
|
3332
3393
|
const values = node.enum.filter(
|