@ai-matrx/content-ir 0.5.0 → 0.6.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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 — 2026-08-29
4
+
5
+ - **`$ref` / `$defs` resolution — a kind's child kinds are no longer invisible
6
+ to JSON Schema → KindSchema.** `convertAiSchemaToBlockFields` resolves `#`,
7
+ `#/$defs/<name>` and `#/definitions/<name>` in field position, in array
8
+ `items`, and inside `anyOf`/`oneOf` (including `items: {anyOf: [$ref, …]}`,
9
+ the multi-kind array). Referenced defs are registered as their own drafts,
10
+ with a cycle brake so a self-referencing kind terminates. An unresolvable
11
+ ref is a loud error, never a guess.
12
+
13
+ WHY: `kindSchemaToJsonSchema` externalizes every child kind as a `$def`, and
14
+ the reverse direction rejected `$ref` outright — so every
15
+ `{type:"object", kind}` and `{type:"array", itemKinds}` field came back
16
+ EMPTY. Measured over the 62 live kinds carrying both a stored field list and
17
+ a schema, 36 lost fields this way (sections, slides, nodes, edges, criteria,
18
+ questions, segments, …).
19
+
20
+ - **A `__kind`-carrying record reads back as a record.** An object whose only
21
+ declared property is `__kind`, with typed `additionalProperties`, is the
22
+ forward converter's emission for `{type:"record", values}` — it was widening
23
+ to an open `inline_object` and losing the value type.
24
+
25
+ - Live-corpus result: schema-derived KindSchemas now match the stored field
26
+ list byte-for-byte for 48 of 62 kinds, with 1 strictly richer. The remaining
27
+ 13 are DIVERGENCE BETWEEN THE TWO STORED COPIES, not conversion gaps — 4
28
+ stored lists carry `__kind` as a data field (it is the discriminator, never
29
+ a field), and 9 disagree on enums, bounds, required flags or descriptions
30
+ the schema states and the copy does not. That is the evidence for retiring
31
+ the stored copy.
32
+
3
33
  ## 0.5.0 — 2026-08-29
4
34
 
5
35
  - **A multi-type union is no longer silently narrowed to its first member.**
package/dist/index.cjs CHANGED
@@ -3177,6 +3177,29 @@ function buildAgentSchemaWithRenderBlockSupport(input, rootKindSlug, arrayBindin
3177
3177
  }
3178
3178
  return updatedRoot;
3179
3179
  }
3180
+ function resolveRef(ref, ctx) {
3181
+ if (ref === "#" || ref === "#/") {
3182
+ return { slug: ctx.schemaName, node: ctx.rootSchema };
3183
+ }
3184
+ const match = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
3185
+ if (!match) return null;
3186
+ const name = decodeURIComponent(
3187
+ (match[1] ?? "").replace(/~1/g, "/").replace(/~0/g, "~")
3188
+ );
3189
+ const node = ctx.defs[name];
3190
+ if (!isRecord4(node)) return null;
3191
+ const declared = isRecord4(node.properties) ? readBlockKindFromProperties(node.properties) : null;
3192
+ return { slug: declared ?? name, node };
3193
+ }
3194
+ function refToObjectField(resolved, path, ctx) {
3195
+ if (ctx.refsInProgress.has(resolved.slug)) return;
3196
+ ctx.refsInProgress.add(resolved.slug);
3197
+ try {
3198
+ registerDeclaredKindDraft(resolved.slug, resolved.node, path, ctx);
3199
+ } finally {
3200
+ ctx.refsInProgress.delete(resolved.slug);
3201
+ }
3202
+ }
3180
3203
  function isAnyValueSchema(node) {
3181
3204
  return node.type === void 0 && node.enum === void 0 && node.const === void 0 && node.properties === void 0 && node.items === void 0 && node.additionalProperties === void 0 && node.anyOf === void 0 && node.oneOf === void 0 && node.allOf === void 0 && node.$ref === void 0;
3182
3205
  }
@@ -3213,12 +3236,21 @@ function convertProperty(fieldName, node, required, path, ctx) {
3213
3236
  }
3214
3237
  function convertPropertyCore(fieldName, node, required, path, ctx) {
3215
3238
  if (typeof node.$ref === "string") {
3216
- ctx.problems.push({
3217
- severity: "error",
3218
- path,
3219
- message: `$ref is not supported ("${node.$ref}"). Inline the schema manually.`
3220
- });
3221
- return null;
3239
+ const resolved = resolveRef(node.$ref, ctx);
3240
+ if (!resolved) {
3241
+ ctx.problems.push({
3242
+ severity: "error",
3243
+ path,
3244
+ message: `Unresolvable $ref ("${node.$ref}") \u2014 only "#", "#/$defs/<name>" and "#/definitions/<name>" are supported.`
3245
+ });
3246
+ return null;
3247
+ }
3248
+ refToObjectField(resolved, path, ctx);
3249
+ return {
3250
+ ...requiredNullableFlags(required),
3251
+ type: "object",
3252
+ kind: resolved.slug
3253
+ };
3222
3254
  }
3223
3255
  if (Array.isArray(node.anyOf) || Array.isArray(node.oneOf)) {
3224
3256
  const variants = node.anyOf ?? node.oneOf;
@@ -3234,12 +3266,18 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
3234
3266
  continue;
3235
3267
  }
3236
3268
  if (typeof variant.$ref === "string") {
3237
- ctx.problems.push({
3238
- severity: "error",
3239
- path,
3240
- message: `$ref inside anyOf/oneOf is not supported ("${variant.$ref}"). Inline the schema manually.`
3241
- });
3242
- unsupported = true;
3269
+ const resolvedVariant = resolveRef(variant.$ref, ctx);
3270
+ if (!resolvedVariant) {
3271
+ ctx.problems.push({
3272
+ severity: "error",
3273
+ path,
3274
+ message: `Unresolvable $ref inside anyOf/oneOf ("${variant.$ref}").`
3275
+ });
3276
+ unsupported = true;
3277
+ continue;
3278
+ }
3279
+ refToObjectField(resolvedVariant, path, ctx);
3280
+ memberKinds.push(resolvedVariant.slug);
3243
3281
  continue;
3244
3282
  }
3245
3283
  const { type: type2, nullable: nullable2 } = resolvePrimaryType(variant);
@@ -3442,6 +3480,27 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
3442
3480
  });
3443
3481
  return null;
3444
3482
  }
3483
+ if (typeof itemNode.$ref === "string") {
3484
+ const resolvedItem = resolveRef(itemNode.$ref, ctx);
3485
+ if (!resolvedItem) {
3486
+ ctx.problems.push({
3487
+ severity: "error",
3488
+ path: `${path}[]`,
3489
+ message: `Unresolvable $ref in array items ("${itemNode.$ref}").`
3490
+ });
3491
+ return null;
3492
+ }
3493
+ refToObjectField(resolvedItem, `${path}[]`, ctx);
3494
+ ctx.arrayBindings.push({
3495
+ arrayField: fieldName,
3496
+ itemKindSlug: resolvedItem.slug
3497
+ });
3498
+ return {
3499
+ ...requiredNullableFlags(required, nullable),
3500
+ type: "array",
3501
+ itemKinds: [resolvedItem.slug]
3502
+ };
3503
+ }
3445
3504
  if (isAnyValueSchema(itemNode)) {
3446
3505
  return {
3447
3506
  ...requiredNullableFlags(required, nullable),
@@ -3476,11 +3535,29 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
3476
3535
  }
3477
3536
  const itemKinds = [];
3478
3537
  for (const variant of itemVariants) {
3538
+ if (isRecord4(variant) && typeof variant.$ref === "string") {
3539
+ const resolvedVariant = resolveRef(variant.$ref, ctx);
3540
+ if (!resolvedVariant) {
3541
+ ctx.problems.push({
3542
+ severity: "error",
3543
+ path: `${path}[]`,
3544
+ message: `Unresolvable $ref in array items anyOf ("${variant.$ref}").`
3545
+ });
3546
+ return null;
3547
+ }
3548
+ itemKinds.push(resolvedVariant.slug);
3549
+ refToObjectField(resolvedVariant, `${path}[]<${resolvedVariant.slug}>`, ctx);
3550
+ ctx.arrayBindings.push({
3551
+ arrayField: fieldName,
3552
+ itemKindSlug: resolvedVariant.slug
3553
+ });
3554
+ continue;
3555
+ }
3479
3556
  if (!isRecord4(variant) || !isRecord4(variant.properties)) {
3480
3557
  ctx.problems.push({
3481
3558
  severity: "error",
3482
3559
  path: `${path}[]`,
3483
- message: "Array items anyOf/oneOf variants must be inline objects declaring __kind."
3560
+ message: "Array items anyOf/oneOf variants must be inline objects declaring __kind, or $refs to defs."
3484
3561
  });
3485
3562
  return null;
3486
3563
  }
@@ -3614,6 +3691,14 @@ function convertPropertyCore(fieldName, node, required, path, ctx) {
3614
3691
  kind: referencedKind
3615
3692
  };
3616
3693
  }
3694
+ if (Object.keys(nestedFields).length === 0 && !apIsOpen && isRecord4(ap) && KIND_KEY in node.properties) {
3695
+ const apType = resolvePrimaryType(ap).type;
3696
+ return {
3697
+ ...requiredNullableFlags(required, nullable),
3698
+ type: "record",
3699
+ values: apType === "string" || apType === "number" || apType === "boolean" ? apType : "json"
3700
+ };
3701
+ }
3617
3702
  let open = apIsOpen;
3618
3703
  if (!apIsOpen && isRecord4(ap)) {
3619
3704
  ctx.problems.push({
@@ -3730,12 +3815,21 @@ function normalizeAiSchemaInput(input) {
3730
3815
  return { name: null, strict: null, rootSchema: null, parseErrors };
3731
3816
  }
3732
3817
  function convertAiSchemaToBlockFields(schemaName, rootSchema, strict) {
3818
+ const rawDefs = isRecord4(rootSchema.$defs) ? rootSchema.$defs : isRecord4(rootSchema.definitions) ? rootSchema.definitions : {};
3819
+ const defs = {};
3820
+ for (const [name, node] of Object.entries(rawDefs)) {
3821
+ if (isRecord4(node)) defs[name] = node;
3822
+ }
3733
3823
  const ctx = {
3734
3824
  schemaName,
3825
+ strict,
3735
3826
  problems: [],
3736
3827
  droppedMetadata: [],
3737
3828
  blockSchemas: [],
3738
- arrayBindings: []
3829
+ arrayBindings: [],
3830
+ defs,
3831
+ rootSchema,
3832
+ refsInProgress: /* @__PURE__ */ new Set()
3739
3833
  };
3740
3834
  ctx.droppedMetadata.push(...collectDropped(rootSchema, ""));
3741
3835
  const rootAp = rootSchema.additionalProperties;