@metaobjectsdev/metadata 0.7.0-rc.1 → 0.7.0-rc.11

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 (55) hide show
  1. package/dist/core/parser-yaml.d.ts.map +1 -1
  2. package/dist/core/parser-yaml.js +24 -8
  3. package/dist/core/parser-yaml.js.map +1 -1
  4. package/dist/core/yaml-desugar.d.ts.map +1 -1
  5. package/dist/core/yaml-desugar.js +54 -5
  6. package/dist/core/yaml-desugar.js.map +1 -1
  7. package/dist/core/yaml-positions-walker.d.ts +21 -0
  8. package/dist/core/yaml-positions-walker.d.ts.map +1 -0
  9. package/dist/core/yaml-positions-walker.js +75 -0
  10. package/dist/core/yaml-positions-walker.js.map +1 -0
  11. package/dist/core/yaml-positions.d.ts +19 -0
  12. package/dist/core/yaml-positions.d.ts.map +1 -0
  13. package/dist/core/yaml-positions.js +60 -0
  14. package/dist/core/yaml-positions.js.map +1 -0
  15. package/dist/core-types.d.ts.map +1 -1
  16. package/dist/core-types.js +7 -4
  17. package/dist/core-types.js.map +1 -1
  18. package/dist/errors.d.ts +4 -1
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +13 -0
  21. package/dist/errors.js.map +1 -1
  22. package/dist/loader/meta-data-loader.d.ts.map +1 -1
  23. package/dist/loader/meta-data-loader.js +26 -6
  24. package/dist/loader/meta-data-loader.js.map +1 -1
  25. package/dist/loader/validation-passes.d.ts.map +1 -1
  26. package/dist/loader/validation-passes.js +81 -17
  27. package/dist/loader/validation-passes.js.map +1 -1
  28. package/dist/parser-core.d.ts +16 -1
  29. package/dist/parser-core.d.ts.map +1 -1
  30. package/dist/parser-core.js +266 -8
  31. package/dist/parser-core.js.map +1 -1
  32. package/dist/source.d.ts +29 -1
  33. package/dist/source.d.ts.map +1 -1
  34. package/dist/source.js +25 -0
  35. package/dist/source.js.map +1 -1
  36. package/dist/template/template-constants.d.ts +3 -1
  37. package/dist/template/template-constants.d.ts.map +1 -1
  38. package/dist/template/template-constants.js +22 -6
  39. package/dist/template/template-constants.js.map +1 -1
  40. package/dist/template/template-schema.d.ts.map +1 -1
  41. package/dist/template/template-schema.js +41 -1
  42. package/dist/template/template-schema.js.map +1 -1
  43. package/package.json +1 -1
  44. package/src/core/parser-yaml.ts +24 -8
  45. package/src/core/yaml-desugar.ts +58 -4
  46. package/src/core/yaml-positions-walker.ts +101 -0
  47. package/src/core/yaml-positions.ts +80 -0
  48. package/src/core-types.ts +7 -4
  49. package/src/errors.ts +15 -0
  50. package/src/loader/meta-data-loader.ts +26 -6
  51. package/src/loader/validation-passes.ts +83 -20
  52. package/src/parser-core.ts +306 -10
  53. package/src/source.ts +40 -2
  54. package/src/template/template-constants.ts +23 -6
  55. package/src/template/template-schema.ts +43 -0
@@ -1,10 +1,14 @@
1
- // template.* subtype vocabulary + reserved attribute names (FR-004, R1).
1
+ // template.* subtype vocabulary + reserved attribute names (FR-004, R1; ADR-0011).
2
2
  //
3
- // `template` is the fourth-pillar base type: a renderable text artifact bound to
4
- // a typed payload. Two subtypes (by audience/structure, NOT by format):
5
- // - prompt: LLM-targeted; carries the prompt-overlay attrs and is the home for
6
- // future structured-prompt (role/turn/tool) divergence.
3
+ // `template` is the fourth-pillar base type a typed payload bound to either
4
+ // a rendered text artifact (prompt/output) or to a tool-call envelope.
5
+ // Three subtypes:
6
+ // - prompt: LLM-targeted renderable text. Carries the prompt-overlay attrs.
7
+ // Renderable body required via @textRef.
7
8
  // - output: every other rendered artifact (email, export, docs, config).
9
+ // Renderable body required via @textRef.
10
+ // - toolcall: LLM tool-call envelope (no renderable body — the body IS the
11
+ // structured output schema resolved via @payloadRef). Per ADR-0011.
8
12
  //
9
13
  // Format is the @format ATTRIBUTE (closed set below), never a subtype — the
10
14
  // render engine keys its escaper off @format, so a new format costs one escaper
@@ -12,12 +16,15 @@
12
16
  import { SUBTYPE_BASE } from "../shared/base-types.js";
13
17
  export const TEMPLATE_SUBTYPE_PROMPT = "prompt";
14
18
  export const TEMPLATE_SUBTYPE_OUTPUT = "output";
19
+ export const TEMPLATE_SUBTYPE_TOOLCALL = "toolcall";
15
20
  export const TEMPLATE_SUBTYPES = [
16
21
  SUBTYPE_BASE,
17
22
  TEMPLATE_SUBTYPE_PROMPT,
18
23
  TEMPLATE_SUBTYPE_OUTPUT,
24
+ TEMPLATE_SUBTYPE_TOOLCALL,
19
25
  ];
20
- // Generic reserved attrs (both subtypes). The "@" is applied at wire time.
26
+ // Generic reserved attrs (prompt + output). The "@" is applied at wire time.
27
+ // NOT inherited by toolcall — toolcall has no renderable body.
21
28
  export const TEMPLATE_ATTR_PAYLOAD_REF = "payloadRef";
22
29
  export const TEMPLATE_ATTR_TEXT_REF = "textRef";
23
30
  export const TEMPLATE_ATTR_FORMAT = "format";
@@ -32,6 +39,15 @@ export const TEMPLATE_ATTR_REQUIRED_TAGS = "requiredTags";
32
39
  export const TEMPLATE_ATTR_MAX_TOKENS = "maxTokens";
33
40
  export const TEMPLATE_ATTR_REQUIRED_SLOTS = "requiredSlots";
34
41
  export const TEMPLATE_ATTR_MODEL = "model";
42
+ // Toolcall-specific attrs (template.toolcall only). Vendor-agnostic; vendor
43
+ // wire details (retry semantics, fallback shapes, etc.) are added by consumer
44
+ // providers via registry.extend per ADR-0011.
45
+ //
46
+ // @description is intentionally NOT a toolcall-specific constant — every type
47
+ // gets @description via the documentation common-attrs provider. Tool
48
+ // descriptions surfaced to the LLM use the same @description common attr
49
+ // doc-gen uses.
50
+ export const TEMPLATE_ATTR_TOOL_NAME = "toolName";
35
51
  // Closed format set — escaping/whitespace behavior is keyed off this in the
36
52
  // render engine's escaper registry (FR-004 R7).
37
53
  export const TEMPLATE_FORMATS = [
@@ -1 +1 @@
1
- {"version":3,"file":"template-constants.js","sourceRoot":"","sources":["../../src/template/template-constants.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,iFAAiF;AACjF,wEAAwE;AACxE,iFAAiF;AACjF,4DAA4D;AAC5D,2EAA2E;AAC3E,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,6DAA6D;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAChD,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAEhD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY;IACZ,uBAAuB;IACvB,uBAAuB;CACf,CAAC;AAGX,2EAA2E;AAC3E,MAAM,CAAC,MAAM,yBAAyB,GAAG,YAAY,CAAC;AACtD,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAC3C,mFAAmF;AACnF,+EAA+E;AAC/E,uDAAuD;AACvD,MAAM,CAAC,MAAM,2BAA2B,GAAG,cAAc,CAAC;AAE1D,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AACpD,MAAM,CAAC,MAAM,4BAA4B,GAAG,eAAe,CAAC;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C,4EAA4E;AAC5E,gDAAgD;AAChD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,UAAU;IACV,aAAa;CACL,CAAC"}
1
+ {"version":3,"file":"template-constants.js","sourceRoot":"","sources":["../../src/template/template-constants.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,8EAA8E;AAC9E,uEAAuE;AACvE,kBAAkB;AAClB,8EAA8E;AAC9E,qDAAqD;AACrD,2EAA2E;AAC3E,qDAAqD;AACrD,6EAA6E;AAC7E,kFAAkF;AAClF,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAChF,6DAA6D;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAChD,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAChD,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,YAAY;IACZ,uBAAuB;IACvB,uBAAuB;IACvB,yBAAyB;CACjB,CAAC;AAGX,6EAA6E;AAC7E,+DAA+D;AAC/D,MAAM,CAAC,MAAM,yBAAyB,GAAG,YAAY,CAAC;AACtD,MAAM,CAAC,MAAM,sBAAsB,GAAG,SAAS,CAAC;AAChD,MAAM,CAAC,MAAM,oBAAoB,GAAG,QAAQ,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAClD,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAC3C,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAC3C,mFAAmF;AACnF,+EAA+E;AAC/E,uDAAuD;AACvD,MAAM,CAAC,MAAM,2BAA2B,GAAG,cAAc,CAAC;AAE1D,+CAA+C;AAC/C,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AACpD,MAAM,CAAC,MAAM,4BAA4B,GAAG,eAAe,CAAC;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAE3C,4EAA4E;AAC5E,8EAA8E;AAC9E,8CAA8C;AAC9C,EAAE;AACF,8EAA8E;AAC9E,sEAAsE;AACtE,yEAAyE;AACzE,gBAAgB;AAChB,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;AAElD,4EAA4E;AAC5E,gDAAgD;AAChD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,MAAM;IACN,MAAM;IACN,KAAK;IACL,KAAK;IACL,MAAM;IACN,UAAU;IACV,aAAa;CACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"template-schema.d.ts","sourceRoot":"","sources":["../../src/template/template-schema.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AA6FjD,eAAO,MAAM,kBAAkB,2BAI7B,CAAC"}
1
+ {"version":3,"file":"template-schema.d.ts","sourceRoot":"","sources":["../../src/template/template-schema.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAuIjD,eAAO,MAAM,kBAAkB,2BAK7B,CAAC"}
@@ -7,7 +7,7 @@
7
7
  // `verify` scope (Plan #3), not load-time validation.
8
8
  import { ATTR_SUBTYPE_STRING, ATTR_SUBTYPE_INT, ATTR_SUBTYPE_STRINGARRAY, } from "../core/attr/attr-constants.js";
9
9
  import { SUBTYPE_BASE } from "../shared/base-types.js";
10
- import { TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_FORMAT, TEMPLATE_ATTR_MAX_CHARS, TEMPLATE_ATTR_OWNER, TEMPLATE_ATTR_SINCE, TEMPLATE_ATTR_REQUIRED_TAGS, TEMPLATE_ATTR_MAX_TOKENS, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_MODEL, TEMPLATE_FORMATS, } from "./template-constants.js";
10
+ import { TEMPLATE_SUBTYPE_PROMPT, TEMPLATE_SUBTYPE_OUTPUT, TEMPLATE_SUBTYPE_TOOLCALL, TEMPLATE_ATTR_PAYLOAD_REF, TEMPLATE_ATTR_TEXT_REF, TEMPLATE_ATTR_FORMAT, TEMPLATE_ATTR_MAX_CHARS, TEMPLATE_ATTR_OWNER, TEMPLATE_ATTR_SINCE, TEMPLATE_ATTR_REQUIRED_TAGS, TEMPLATE_ATTR_MAX_TOKENS, TEMPLATE_ATTR_REQUIRED_SLOTS, TEMPLATE_ATTR_MODEL, TEMPLATE_ATTR_TOOL_NAME, TEMPLATE_FORMATS, } from "./template-constants.js";
11
11
  // Generic attrs shared by template.prompt and template.output.
12
12
  const genericAttrs = [
13
13
  {
@@ -76,9 +76,49 @@ const promptOverlayAttrs = [
76
76
  description: "Target model id (LLM-specific).",
77
77
  },
78
78
  ];
79
+ // Toolcall attrs (template.toolcall only — does NOT inherit genericAttrs).
80
+ // Per ADR-0011: vendor-agnostic in core; vendor wire details (retry semantics,
81
+ // fallback shapes, parallel invocation, cache hints) added by consumer
82
+ // providers via registry.extend(TYPE_TEMPLATE, "toolcall", { attributes: [...] }).
83
+ //
84
+ // Critical: @textRef is intentionally NOT required here. A tool-call has no
85
+ // renderable text body — the body IS the structured output schema resolved
86
+ // via @payloadRef. This is the design rationale for toolcall being its own
87
+ // subtype rather than template.output + @toolName.
88
+ //
89
+ // @description is intentionally NOT declared here — it's already a documentation
90
+ // common attr added to every type by docProvider. Tool descriptions surfaced to
91
+ // the LLM read the same @description common attr that doc-gen uses.
92
+ const toolcallAttrs = [
93
+ {
94
+ name: TEMPLATE_ATTR_TOOL_NAME,
95
+ valueType: ATTR_SUBTYPE_STRING,
96
+ required: true,
97
+ description: "Wire tool name surfaced to the LLM (vendor-specific format).",
98
+ },
99
+ {
100
+ name: TEMPLATE_ATTR_PAYLOAD_REF,
101
+ valueType: ATTR_SUBTYPE_STRING,
102
+ required: true,
103
+ description: "Output value-object the tool produces (resolved against the metamodel).",
104
+ },
105
+ {
106
+ name: TEMPLATE_ATTR_OWNER,
107
+ valueType: ATTR_SUBTYPE_STRING,
108
+ required: false,
109
+ description: "Governance: the owner of this toolcall.",
110
+ },
111
+ {
112
+ name: TEMPLATE_ATTR_SINCE,
113
+ valueType: ATTR_SUBTYPE_STRING,
114
+ required: false,
115
+ description: "Governance: the version this toolcall was introduced in.",
116
+ },
117
+ ];
79
118
  export const TEMPLATE_ATTRS_MAP = new Map([
80
119
  [SUBTYPE_BASE, []],
81
120
  [TEMPLATE_SUBTYPE_PROMPT, [...genericAttrs, ...promptOverlayAttrs]],
82
121
  [TEMPLATE_SUBTYPE_OUTPUT, [...genericAttrs]],
122
+ [TEMPLATE_SUBTYPE_TOOLCALL, [...toolcallAttrs]],
83
123
  ]);
84
124
  //# sourceMappingURL=template-schema.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"template-schema.js","sourceRoot":"","sources":["../../src/template/template-schema.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,EAAE;AACF,kFAAkF;AAClF,4EAA4E;AAC5E,8EAA8E;AAC9E,gFAAgF;AAChF,sDAAsD;AAGtD,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,EACnB,2BAA2B,EAC3B,wBAAwB,EACxB,4BAA4B,EAC5B,mBAAmB,EACnB,gBAAgB,GACjB,MAAM,yBAAyB,CAAC;AAEjC,+DAA+D;AAC/D,MAAM,YAAY,GAAiB;IACjC;QACE,IAAI,EAAE,yBAAyB;QAC/B,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,sFAAsF;KACpG;IACD;QACE,IAAI,EAAE,sBAAsB;QAC5B,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,mGAAmG;KACjH;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,MAAM;QACf,aAAa,EAAE,CAAC,GAAG,gBAAgB,CAAC;QACpC,WAAW,EAAE,yEAAyE;KACvF;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,SAAS,EAAE,gBAAgB;QAC3B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,qDAAqD;KACnE;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,yCAAyC;KACvD;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,0DAA0D;KACxE;IACD;QACE,IAAI,EAAE,2BAA2B;QACjC,SAAS,EAAE,wBAAwB;QACnC,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,kFAAkF;KAChG;CACF,CAAC;AAEF,4CAA4C;AAC5C,MAAM,kBAAkB,GAAiB;IACvC;QACE,IAAI,EAAE,wBAAwB;QAC9B,SAAS,EAAE,gBAAgB;QAC3B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,sDAAsD;KACpE;IACD;QACE,IAAI,EAAE,4BAA4B;QAClC,SAAS,EAAE,wBAAwB;QACnC,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,mEAAmE;KACjF;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,SAAS,EAAE,mBAAmB;QAC9B,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,iCAAiC;KAC/C;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAuB;IAC9D,CAAC,YAAY,EAAE,EAAE,CAAC;IAClB,CAAC,uBAAuB,EAAE,CAAC,GAAG,YAAY,EAAE,GAAG,kBAAkB,CAAC,CAAC;IACnE,CAAC,uBAAuB,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC;CAC7C,CAAC,CAAC"}
1
+ {"version":3,"file":"template-schema.js","sourceRoot":"","sources":["../../src/template/template-schema.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/metadata",
3
- "version": "0.7.0-rc.1",
3
+ "version": "0.7.0-rc.11",
4
4
  "description": "Metamodel loader, types, and constants for the MetaObjects standard.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,19 +1,31 @@
1
1
  // Authoring YAML parser.
2
2
  //
3
- // parseYaml is a front-end: yaml.parse → desugar → the shared buildTree
4
- // (parser-core.ts). The desugar applies the four authoring-sugar rules so the
5
- // resulting typed tree is identical to the one the equivalent canonical JSON
6
- // produces.
3
+ // parseYaml is a front-end: parseYamlWithPositions → desugar → the shared
4
+ // buildTree (parser-core.ts). The desugar applies the four authoring-sugar
5
+ // rules so the resulting typed tree is identical to the one the equivalent
6
+ // canonical JSON produces.
7
+ //
8
+ // FR5b: the parse phase now preserves YAML source positions through the
9
+ // pipeline. parseYamlWithPositions attaches a Symbol-keyed position-by-key
10
+ // map onto every mapping object; desugar shallow-copies that property; and
11
+ // buildTree's `format: "yaml"` mode reads the position when stamping
12
+ // `node.source.yamlPosition` (see parser-core.ts).
7
13
 
8
- import { parse as parseYamlText } from "yaml";
9
14
  import { ParseError } from "../errors.js";
10
15
  import { buildTree } from "../parser-core.js";
11
16
  import type { ParseOptions, ParseResult } from "../parser-core.js";
12
17
  import type { ErrorSource } from "../source.js";
13
18
  import { desugar } from "./yaml-desugar.js";
19
+ import { parseYamlWithPositions } from "./yaml-positions-walker.js";
14
20
 
15
21
  /** FR5a / ADR-0009 — build a YAML-source envelope rooted at "$".
16
- * yamlPosition is FR5b territory and not populated here. */
22
+ * yamlPosition on the envelope is left undefined here; envelopes for
23
+ * THROWN parser errors carry positions only when the parse phase pushed
24
+ * the path into the live parser-core builder (see populateNodeSource in
25
+ * parser-core.ts). Errors raised before buildTree (e.g. yaml syntax
26
+ * failures, an empty desugar result) lack a node — `yamlPosition` is
27
+ * intentionally omitted, per the spec's "skip on desugar-synthesized
28
+ * nodes" decision. */
17
29
  function yamlSource(sourceName: string | undefined): ErrorSource {
18
30
  return {
19
31
  format: "yaml",
@@ -31,7 +43,7 @@ export function parseYaml(content: string, opts: ParseOptions): ParseResult {
31
43
  // The loader's per-source try/catch collects the throw into LoadResult.errors.
32
44
  let parsed: unknown;
33
45
  try {
34
- parsed = parseYamlText(normalizedContent);
46
+ parsed = parseYamlWithPositions(normalizedContent).value;
35
47
  } catch (err) {
36
48
  throw new ParseError(
37
49
  `Invalid YAML: ${(err as Error).message}`,
@@ -52,7 +64,10 @@ export function parseYaml(content: string, opts: ParseOptions): ParseResult {
52
64
  );
53
65
  }
54
66
 
55
- const result = buildTree(canonical, opts);
67
+ // FR5b buildTree needs to know the source-format discriminant so
68
+ // populateNodeSource emits `format: "yaml"` envelopes (with the
69
+ // optional yamlPosition) instead of the default `format: "json"`.
70
+ const result = buildTree(canonical, { ...opts, sourceFormat: "yaml" });
56
71
 
57
72
  // Merge collected desugar errors ahead of buildTree's own collected errors.
58
73
  // Each CollectedError carries its own stable code when set (e.g.
@@ -69,5 +84,6 @@ export function parseYaml(content: string, opts: ParseOptions): ParseResult {
69
84
  root: result.root,
70
85
  warnings: result.warnings,
71
86
  errors: [...desugarParseErrors, ...result.errors],
87
+ envelopeWarnings: result.envelopeWarnings,
72
88
  };
73
89
  }
@@ -38,6 +38,11 @@ import {
38
38
  RESERVED_KEY_IS_ARRAY,
39
39
  TYPE_SUBTYPE_SEPARATOR,
40
40
  } from "../shared/structural.js";
41
+ import {
42
+ getPositionMap,
43
+ setPositionMap,
44
+ type PositionMap,
45
+ } from "./yaml-positions.js";
41
46
  import {
42
47
  ATTR_SUBTYPE_STRING,
43
48
  ATTR_SUBTYPE_CLASS,
@@ -99,6 +104,11 @@ function desugarNode(
99
104
  const rawKey = entries[0]!;
100
105
  const rawBody = (input as Record<string, unknown>)[rawKey];
101
106
 
107
+ // FR5b — capture the wrapper-level position-by-key map BEFORE re-keying.
108
+ // The author's raw key (with `[]` suffix and possibly omitted subType) is
109
+ // the lookup key; the desugar's canonical key is what we emit.
110
+ const wrapperPositions = getPositionMap(input);
111
+
102
112
  // Rule 4: a trailing "[]" on the key → isArray.
103
113
  let key = rawKey;
104
114
  let isArray = false;
@@ -111,7 +121,10 @@ function desugarNode(
111
121
  const canonicalKey = resolveKey(key, registry, errors, path);
112
122
 
113
123
  // Rule 2: a scalar body → { name: <scalar> }.
114
- const body = desugarBody(rawBody, registry, canonicalKey, errors, path);
124
+ // FR5b propagate the wrapper-key's position into the synthesized body
125
+ // when the input body was a scalar (no body-side positions to inherit).
126
+ const wrapperKeyPos = wrapperPositions?.[rawKey];
127
+ const body = desugarBody(rawBody, registry, canonicalKey, errors, path, wrapperKeyPos);
115
128
 
116
129
  // Rule 4 (cont.): stamp isArray onto the canonical body.
117
130
  if (isArray) body[RESERVED_KEY_IS_ARRAY] = true;
@@ -131,7 +144,16 @@ function desugarNode(
131
144
  }
132
145
  // A non-array `children` value is left untouched — buildTree reports it.
133
146
 
134
- return { [canonicalKey]: body };
147
+ // FR5b emit a wrapper-level position-by-key map for the canonical wrapper
148
+ // so buildTree's per-child iteration can read the position via the same
149
+ // lookup it uses for JSON input. The single key transformation is
150
+ // rawKey → canonicalKey (Rule 1 fuses the subType, Rule 4 strips `[]`).
151
+ const outWrapper: Record<string, unknown> = { [canonicalKey]: body };
152
+ if (wrapperKeyPos !== undefined) {
153
+ setPositionMap(outWrapper, { [canonicalKey]: wrapperKeyPos });
154
+ }
155
+
156
+ return outWrapper;
135
157
  }
136
158
 
137
159
  // Rule 1 — resolve a possibly-bare key to a fused `type.subType` token.
@@ -169,16 +191,30 @@ function desugarBody(
169
191
  canonicalKey: string,
170
192
  errors: CollectedError[],
171
193
  path: string,
194
+ /** FR5b — position of the WRAPPER key (the `field.string:` line). Used to
195
+ * back-fill `yamlPosition` on synthesized bodies (Rule 2's scalar lift) +
196
+ * empty bodies; for mapping bodies we use the body's own position-by-key
197
+ * map. */
198
+ wrapperKeyPos: { line: number; col: number } | undefined,
172
199
  ): Record<string, unknown> {
173
200
  if (
174
201
  typeof rawBody === "string" ||
175
202
  typeof rawBody === "number" ||
176
203
  typeof rawBody === "boolean"
177
204
  ) {
178
- return { [RESERVED_KEY_NAME]: rawBody };
205
+ // FR5b — the synthesized `{ name: rawBody }` has no YAML-side
206
+ // counterpart; we attribute the `name` slot to the wrapper-key's
207
+ // position (the only YAML position that meaningfully belongs to this
208
+ // synthesis).
209
+ const out: Record<string, unknown> = { [RESERVED_KEY_NAME]: rawBody };
210
+ if (wrapperKeyPos !== undefined) {
211
+ setPositionMap(out, { [RESERVED_KEY_NAME]: wrapperKeyPos });
212
+ }
213
+ return out;
179
214
  }
180
215
  if (rawBody === null || rawBody === undefined) {
181
216
  // An empty body (`field.string:` with nothing after) → an empty node.
217
+ // No body keys to position; the wrapper carries the node's position.
182
218
  return {};
183
219
  }
184
220
  if (Array.isArray(rawBody)) {
@@ -192,20 +228,38 @@ function desugarBody(
192
228
  // Rule D2 (type-coercion guard).
193
229
  const src = rawBody as Record<string, unknown>;
194
230
  const out: Record<string, unknown> = {};
231
+ // FR5b — translate the body's position-by-key map across the sigil-free
232
+ // rewrite. A bare `filterable` key in the source maps to `@filterable` in
233
+ // the canonical body; the YAML position belongs to BOTH names (the YAML
234
+ // author only wrote one). We re-key the position map to match the canonical
235
+ // body's keys so buildTree's per-attr inspection (FR5b follow-ups, e.g.
236
+ // ERR_BAD_ATTR_VALUE) can find the position via the canonical key.
237
+ const srcPositions = getPositionMap(src);
238
+ const outPositions: PositionMap = {};
239
+ let hasOutPositions = false;
195
240
  const schemaIndex = attrSchemaIndex(registry, canonicalKey);
196
241
  for (const key of Object.keys(src)) {
242
+ let outKey: string;
197
243
  if (RESERVED_KEYS.has(key) || key.startsWith(ATTR_PREFIX)) {
198
244
  out[key] = src[key];
245
+ outKey = key;
199
246
  // D2 also applies to author-written @-keys (the awkward form).
200
247
  const attrName = key.startsWith(ATTR_PREFIX) ? key.slice(ATTR_PREFIX.length) : "";
201
248
  if (attrName !== "" && !RESERVED_KEYS.has(attrName)) {
202
249
  checkCoercion(attrName, src[key], schemaIndex, errors, path);
203
250
  }
204
251
  } else {
205
- out[`${ATTR_PREFIX}${key}`] = src[key];
252
+ outKey = `${ATTR_PREFIX}${key}`;
253
+ out[outKey] = src[key];
206
254
  checkCoercion(key, src[key], schemaIndex, errors, path);
207
255
  }
256
+ const pos = srcPositions?.[key];
257
+ if (pos !== undefined) {
258
+ outPositions[outKey] = pos;
259
+ hasOutPositions = true;
260
+ }
208
261
  }
262
+ if (hasOutPositions) setPositionMap(out, outPositions);
209
263
  return out;
210
264
  }
211
265
 
@@ -0,0 +1,101 @@
1
+ // FR5b — YAML AST → JS walker that preserves source positions.
2
+ //
3
+ // This module is the only place inside @metaobjectsdev/metadata that
4
+ // imports the `yaml` package. It lives in core/ alongside parser-yaml.ts,
5
+ // and is reached only via that parser — never via src/index.ts. The
6
+ // browser-safety test guards this invariant.
7
+
8
+ import {
9
+ parseDocument,
10
+ isAlias,
11
+ isMap,
12
+ isScalar,
13
+ isSeq,
14
+ LineCounter,
15
+ type Document,
16
+ } from "yaml";
17
+
18
+ import {
19
+ setPositionMap,
20
+ type PositionMap,
21
+ } from "./yaml-positions.js";
22
+
23
+ /** Result of parsing YAML text with positions retained. */
24
+ export interface YamlParseResult {
25
+ /** The JS object (same shape as `yaml.parse(text)` returns), with
26
+ * position-by-key maps attached to every mapping. */
27
+ value: unknown;
28
+ /** The yaml library's LineCounter — exposed for callers that need to map
29
+ * additional ranges (e.g. surfacing errors raised by the YAML library
30
+ * itself). */
31
+ lineCounter: LineCounter;
32
+ }
33
+
34
+ /** Parse YAML text and return a JS object with positions attached.
35
+ *
36
+ * Mirrors the contract of `yaml.parse(text)` for the shapes the metaobjects
37
+ * authoring grammar uses (mappings, sequences, scalars). Aliases and tags
38
+ * are deferred via the underlying parseDocument call — i.e. they resolve as
39
+ * the library normally would.
40
+ *
41
+ * Throws on YAML syntax errors (same behavior as `yaml.parse`). */
42
+ export function parseYamlWithPositions(text: string): YamlParseResult {
43
+ const lineCounter = new LineCounter();
44
+ const doc = parseDocument(text, { lineCounter });
45
+ // Surface YAML syntax errors as a throw, matching `yaml.parse` behavior.
46
+ // (parseDocument collects them rather than throwing.)
47
+ if (doc.errors.length > 0) {
48
+ throw doc.errors[0]!;
49
+ }
50
+ const value = yamlNodeToJs(doc.contents, lineCounter, doc);
51
+ return { value, lineCounter };
52
+ }
53
+
54
+ // Walk a yaml AST node into a JS structure. For each YAMLMap, attach a
55
+ // position-by-key map onto the resulting JS object — the position of each
56
+ // key is the (line, col) of the KEY token in the YAML source.
57
+ function yamlNodeToJs(
58
+ node: unknown,
59
+ lineCounter: LineCounter,
60
+ doc: Document,
61
+ ): unknown {
62
+ if (node === null || node === undefined) return null;
63
+ if (isScalar(node)) {
64
+ // Honour the library's default scalar typing (numbers / booleans /
65
+ // strings / null all come through Scalar.value).
66
+ return node.value;
67
+ }
68
+ if (isAlias(node)) {
69
+ // Resolve an anchor alias (e.g. `*col` after `&col sku_code`) to its
70
+ // target value — same behaviour as the library's toJS().
71
+ const target = node.resolve(doc);
72
+ return yamlNodeToJs(target, lineCounter, doc);
73
+ }
74
+ if (isMap(node)) {
75
+ const out: Record<string, unknown> = {};
76
+ const positions: PositionMap = {};
77
+ let hasAnyPosition = false;
78
+ for (const pair of node.items) {
79
+ // Only string-keyed entries are valid in metaobjects authoring; ignore
80
+ // exotic keys (numeric / complex) — they'd already break the desugar.
81
+ if (!isScalar(pair.key)) continue;
82
+ const keyText = String(pair.key.value);
83
+ const valueJs = yamlNodeToJs(pair.value, lineCounter, doc);
84
+ out[keyText] = valueJs;
85
+ const keyRange = pair.key.range;
86
+ if (keyRange !== null && keyRange !== undefined) {
87
+ const pos = lineCounter.linePos(keyRange[0]);
88
+ positions[keyText] = { line: pos.line, col: pos.col };
89
+ hasAnyPosition = true;
90
+ }
91
+ }
92
+ if (hasAnyPosition) setPositionMap(out, positions);
93
+ return out;
94
+ }
95
+ if (isSeq(node)) {
96
+ return node.items.map((item) => yamlNodeToJs(item, lineCounter, doc));
97
+ }
98
+ // Tags / unsupported — fall back to null. The metaobjects authoring
99
+ // grammar does not use them.
100
+ return null;
101
+ }
@@ -0,0 +1,80 @@
1
+ // FR5b — YAML authoring source-position carrier (per ADR-0009).
2
+ //
3
+ // This module is split into two layers so the browser bundle (which
4
+ // imports from src/index.ts) stays free of the Node-only `yaml` package:
5
+ //
6
+ // - yaml-positions.ts (this file) — pure types + Symbol + accessors. NO
7
+ // `yaml` import. Imported by parser-core.ts so the source-on-node
8
+ // stamper can read positions when stamping `format: "yaml"` envelopes.
9
+ // - yaml-positions-walker.ts — depends on `yaml`. Imported only by
10
+ // parser-yaml.ts (and parser-yaml itself only ships server-side).
11
+ //
12
+ // Source-map carrier (per the FR5b spec's "open question" §2): a
13
+ // Symbol-keyed, non-enumerable property on the wrapper-mapping object. The
14
+ // symbol is the well-known cross-port key
15
+ // `Symbol.for("@metaobjectsdev/yamlPositionByKey")`, so any plugin that
16
+ // touches the canonical JS can read positions if it knows to look. The
17
+ // map's keys are the wrapper's own keys (e.g. "object.entity" for a
18
+ // wrapper `{ "object.entity": { ... } }` or "name" / "package" /
19
+ // "children" for the body keys of a node).
20
+ //
21
+ // Rationale for "symbol-keyed property" over a parallel sourcemap / wrapper
22
+ // type:
23
+ // - Invisible to JSON.stringify and Object.keys (non-enumerable).
24
+ // - No parallel data structure to keep in sync — the position rides with
25
+ // the node it describes.
26
+ // - No wrapper type — desugar still operates on plain JS objects, so the
27
+ // existing Rule 1–5 logic does not need a rewrite.
28
+ //
29
+ // On desugar-synthesized nodes (Rule 2's scalar-body lift): the synthesized
30
+ // body `{ name: rawScalar }` inherits the wrapper key's position from the
31
+ // parent's position map. On any other synthesis (Rule 4's isArray stamping,
32
+ // for example), the position survives because we shallow-copy via the
33
+ // existing desugar path.
34
+
35
+ /** Cross-port well-known symbol key for the position-by-key map. */
36
+ export const YAML_POSITION_BY_KEY = Symbol.for(
37
+ "@metaobjectsdev/yamlPositionByKey",
38
+ );
39
+
40
+ /** A YAML source position — 1-indexed line and column. */
41
+ export interface YamlPosition {
42
+ readonly line: number;
43
+ readonly col: number;
44
+ }
45
+
46
+ /** The position-by-key map attached to a mapping object. */
47
+ export type PositionMap = Record<string, YamlPosition>;
48
+
49
+ /** Read the position-by-key map from a JS object, if present.
50
+ * Returns undefined for primitives, arrays, null, and untagged objects. */
51
+ export function getPositionMap(obj: unknown): PositionMap | undefined {
52
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
53
+ return undefined;
54
+ }
55
+ return (obj as { [YAML_POSITION_BY_KEY]?: PositionMap })[YAML_POSITION_BY_KEY];
56
+ }
57
+
58
+ /** Read the position for a specific key on a mapping object. */
59
+ export function getYamlPosition(
60
+ obj: unknown,
61
+ key: string,
62
+ ): YamlPosition | undefined {
63
+ const map = getPositionMap(obj);
64
+ return map?.[key];
65
+ }
66
+
67
+ /** Attach (or replace) the position-by-key map on a mapping object. The map
68
+ * property is non-enumerable so JSON.stringify and `for (const k in obj)`
69
+ * loops do not see it. */
70
+ export function setPositionMap(
71
+ obj: Record<string, unknown>,
72
+ positions: PositionMap,
73
+ ): void {
74
+ Object.defineProperty(obj, YAML_POSITION_BY_KEY, {
75
+ value: positions,
76
+ enumerable: false,
77
+ writable: true,
78
+ configurable: true,
79
+ });
80
+ }
package/src/core-types.ts CHANGED
@@ -279,10 +279,13 @@ function registerCoreTypeDefs(registry: TypeRegistry): void {
279
279
  );
280
280
  }
281
281
 
282
- // template — renderable text artifacts (FR-004). prompt + output; attr-only
283
- // children. A single MetaTemplate class backs both subtypes (mirrors source);
284
- // per-subtype attr schemas drive validation (both require @payloadRef +
285
- // @textRef; @format is a closed enum; template.prompt adds the LLM overlay).
282
+ // template — renderable text artifacts (FR-004) + tool-call envelopes
283
+ // (ADR-0011). Three subtypes: prompt + output + toolcall; attr-only children.
284
+ // A single MetaTemplate class backs all three subtypes (mirrors source);
285
+ // per-subtype attr schemas drive validation (prompt + output require
286
+ // @payloadRef + @textRef + @format closed enum; prompt adds the LLM overlay;
287
+ // toolcall has its own set — @toolName + @payloadRef + @description, no
288
+ // @textRef requirement since toolcalls have no renderable body).
286
289
  for (const subType of TEMPLATE_SUBTYPES) {
287
290
  const templateAttrs = TEMPLATE_ATTRS_MAP.get(subType) ?? [];
288
291
  registry.register(
package/src/errors.ts CHANGED
@@ -52,9 +52,24 @@ export const ERROR_CODES = [
52
52
  // ADR-0006 D2 — YAML type-coercion guard. Emitted by every port's YAML
53
53
  // loader when a coerced scalar mismatches the schema-declared type.
54
54
  "ERR_YAML_COERCION",
55
+ // FR5c — multi-file overlay merge produced a conflicting attribute value:
56
+ // two contributors set the same @attr to different non-empty values.
57
+ "ERR_MERGE_CONFLICT",
55
58
  "ERR_UNKNOWN",
56
59
  ] as const;
57
60
 
61
+ /** Warning codes — same envelope shape as errors but advisory. */
62
+ export const WARNING_CODES = [
63
+ // FR5c — two contributors declared the same node identically (no semantic
64
+ // change). Emitted at the overlay-merge boundary.
65
+ "WARN_DUPLICATE_DECLARATION",
66
+ // Pre-FR5c legacy: parser/validator messages still surface as plain
67
+ // strings; wrapped at the loader boundary into the envelope shape with
68
+ // this code. Retired as those sites are migrated to envelopes.
69
+ "WARN_LEGACY",
70
+ ] as const;
71
+ export type WarningCode = (typeof WARNING_CODES)[number];
72
+
58
73
  export type ErrorCode = (typeof ERROR_CODES)[number];
59
74
 
60
75
  /**
@@ -15,7 +15,7 @@ import { composeRegistry } from "../provider.js";
15
15
  import { TYPE_METADATA, SUBTYPE_ROOT } from "../shared/base-types.js";
16
16
  import { ParseError } from "../errors.js";
17
17
  import type { LoaderWarning } from "../source.js";
18
- import { codeSource } from "../source.js";
18
+ import { codeSource, resolvedSource } from "../source.js";
19
19
  import { parseJson } from "../parser-json.js";
20
20
  import { validateDataGridSortFields, validateFilterableHasIndex, validateOriginPaths, validateDataGridFilterValues, validateFieldObjectStorage, validateTemplatePayloadRefs } from "./validation-passes.js";
21
21
  import { validateSourceRoles } from "../persistence/source/validate-source-roles.js";
@@ -321,6 +321,11 @@ export class MetaDataLoader {
321
321
  this._state = "loading";
322
322
  const warnings: string[] = [];
323
323
  const errors: Error[] = [];
324
+ // FR5c — envelope-shaped warnings (WARN_DUPLICATE_DECLARATION et al.)
325
+ // surface here untouched. Distinct from the legacy `warnings: string[]`
326
+ // channel: those are wrapped in a WARN_LEGACY envelope at the boundary,
327
+ // while these already carry their own code + source.
328
+ const envelopeWarnings: LoaderWarning[] = [];
324
329
 
325
330
  // Pre-load the YAML parser via dynamic import if any source declares
326
331
  // YAML format. This keeps `parseSource` synchronous and the package root
@@ -363,6 +368,10 @@ export class MetaDataLoader {
363
368
  const parseResult = this.parseSource(content, source, parseOpts);
364
369
  warnings.push(...parseResult.warnings);
365
370
  errors.push(...parseResult.errors);
371
+ // FR5c — collect envelope-shaped warnings (already carry code +
372
+ // source). The legacy `warnings` channel still flows into the
373
+ // WARN_LEGACY-wrapping path below for unchanged behavior.
374
+ envelopeWarnings.push(...parseResult.envelopeWarnings);
366
375
  root = parseResult.root;
367
376
  } catch (err) {
368
377
  errors.push(
@@ -379,10 +388,17 @@ export class MetaDataLoader {
379
388
  if (root !== undefined) {
380
389
  const failures = resolveDeferredSupers(root);
381
390
  for (const failure of failures) {
391
+ // FR5d — emit format=resolved with referrer + target. The referrer's
392
+ // parse-time source supplies files + jsonPath (the location of the
393
+ // broken `extends:` on disk); referrer = the declaring node's FQN;
394
+ // target = the unresolved supertype ref.
382
395
  errors.push(
383
396
  new ParseError(
384
397
  `the SuperClass '${failure.ref}' does not exist (referenced by ${failure.nodeFqn})`,
385
- { code: "ERR_UNRESOLVED_SUPER", source: failure.source },
398
+ {
399
+ code: "ERR_UNRESOLVED_SUPER",
400
+ source: resolvedSource(failure.source, failure.nodeFqn, failure.ref),
401
+ },
386
402
  ),
387
403
  );
388
404
  }
@@ -447,13 +463,17 @@ export class MetaDataLoader {
447
463
  // LoaderWarning envelopes at the loader boundary. The parser/validator
448
464
  // surface keeps its `string[]` shape internally (parser-core is shared
449
465
  // with parseJson() / parseYaml() callers who consume string warnings
450
- // directly). FR5c will retire WARN_LEGACY by routing the duplicate-
451
- // declaration site through a proper envelope-shaped emit helper.
452
- const envelopeWarnings: LoaderWarning[] = warnings.map((msg) => ({
466
+ // directly). FR5c-onward sites emit proper envelopes via parseResult.
467
+ // envelopeWarnings (collected above) those surface unchanged.
468
+ const wrappedLegacy: LoaderWarning[] = warnings.map((msg) => ({
453
469
  code: "WARN_LEGACY",
454
470
  message: msg,
455
471
  source: codeSource("MetaDataLoader"),
456
472
  }));
457
- return { root, warnings: envelopeWarnings, errors };
473
+ return {
474
+ root,
475
+ warnings: [...envelopeWarnings, ...wrappedLegacy],
476
+ errors,
477
+ };
458
478
  }
459
479
  }