aontu 0.53.0 → 0.55.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 (197) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +500 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +94 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +153 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +75 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.d.ts +1 -0
  132. package/dist/vet.js +32 -1
  133. package/dist/vet.js.map +1 -1
  134. package/dist/view.d.ts +90 -0
  135. package/dist/view.js +2168 -0
  136. package/dist/view.js.map +1 -0
  137. package/grammar/aontu.gbnf +18 -10
  138. package/grammar/aontu.lark +15 -10
  139. package/grammar/aontu.tmLanguage.json +184 -0
  140. package/package.json +10 -3
  141. package/skill/grammar-card.md +1 -2
  142. package/src/aontu.ts +37 -7
  143. package/src/cli.ts +553 -8
  144. package/src/ctx.ts +20 -0
  145. package/src/diff.ts +4 -2
  146. package/src/err.ts +8 -1
  147. package/src/graph.ts +125 -75
  148. package/src/hcanon.ts +9 -11
  149. package/src/hints.ts +112 -29
  150. package/src/jsonschema.ts +41 -0
  151. package/src/lang.ts +642 -203
  152. package/src/lsp.ts +75 -6
  153. package/src/mcp.ts +164 -6
  154. package/src/mod-tool.ts +48 -9
  155. package/src/mod.ts +110 -1
  156. package/src/patch.ts +31 -27
  157. package/src/provenance.ts +8 -1
  158. package/src/query.ts +4 -2
  159. package/src/reach.ts +52 -23
  160. package/src/relation.ts +139 -236
  161. package/src/sig.ts +345 -0
  162. package/src/sigdecl.ts +11 -0
  163. package/src/siggate.ts +144 -0
  164. package/src/std.ts +77 -16
  165. package/src/subsume.ts +59 -10
  166. package/src/unify.ts +102 -125
  167. package/src/utility.ts +7 -67
  168. package/src/val/AggFuncVal.ts +231 -4
  169. package/src/val/BagVal.ts +58 -9
  170. package/src/val/ConstraintVal.ts +34 -5
  171. package/src/val/ContainerKindVal.ts +158 -0
  172. package/src/val/CopyFuncVal.ts +0 -7
  173. package/src/val/DisjunctVal.ts +152 -40
  174. package/src/val/ExpectVal.ts +39 -4
  175. package/src/val/FuncBaseVal.ts +21 -8
  176. package/src/val/GraphAtomVal.ts +264 -0
  177. package/src/val/JunctionVal.ts +23 -6
  178. package/src/val/ListVal.ts +30 -37
  179. package/src/val/MapVal.ts +87 -39
  180. package/src/val/PathFuncVal.ts +107 -19
  181. package/src/val/PathVal.ts +221 -0
  182. package/src/val/PlusOpVal.ts +56 -37
  183. package/src/val/PrefVal.ts +186 -38
  184. package/src/val/RecurseVal.ts +285 -0
  185. package/src/val/RefVal.ts +105 -83
  186. package/src/val/ReferFuncVal.ts +445 -100
  187. package/src/val/ScalarKindVal.ts +12 -0
  188. package/src/val/SuperFuncVal.ts +137 -13
  189. package/src/val/TopVal.ts +1 -1
  190. package/src/val/Val.ts +44 -35
  191. package/src/val/arith.ts +4 -1
  192. package/src/vet.ts +41 -4
  193. package/src/view.ts +2882 -0
  194. package/dist/val/IdFuncVal.d.ts +0 -13
  195. package/dist/val/IdFuncVal.js +0 -54
  196. package/dist/val/IdFuncVal.js.map +0 -1
  197. package/src/val/IdFuncVal.ts +0 -91
package/src/err.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
2
2
 
3
3
 
4
+ import { sep } from 'node:path'
5
+
4
6
  import { util } from '@tabnas/jsonic'
5
7
 
6
8
  import { Val, ErrContext } from './type'
@@ -180,7 +182,12 @@ function descErr<NILS extends NilVal | NilVal[]>(
180
182
 
181
183
  function resolveFile(url: string | undefined) {
182
184
  const cwd = process.cwd()
183
- let out = url?.replace(cwd + '/', '') ?? '<no-file>'
185
+ // The PLATFORM'S separator, not '/': a hardcoded slash never matched
186
+ // a Windows cwd, so every Windows report named the absolute path
187
+ // where the POSIX report (and the Go CLI, which prints the entry as
188
+ // typed) named `clash.aon` -- caught by the docs transcript for
189
+ // reading a conflict error, which pins the relative spelling.
190
+ let out = url?.replace(cwd + sep, '') ?? '<no-file>'
184
191
  out = out === cwd || '' === out ? '<no-file>' : out
185
192
  return out
186
193
  }
package/src/graph.ts CHANGED
@@ -1,52 +1,59 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
2
 
3
- // THE DERIVED STRUCTURES (G4 phase 3,
3
+ // THE DERIVED STRUCTURE (G4 phase 3,
4
4
  // docs/capability-review/g4-identity-relations.md): an evaluated
5
- // document has, besides its value, a GRAPH — an entity index (id → the
6
- // tree paths that hold it) and an edge set (the checked links, each
7
- // from one entity to one address).
5
+ // document has, besides its value, a GRAPH — the set of checked links,
6
+ // each from one tree position to another.
8
7
  //
9
- // G4's deliverable is that these exist and are DETERMINISTIC. What is
10
- // built on them — impact analysis ("what reaches svc/auth?"),
11
- // reachability, context-window-sized entity slices — is a traversal,
12
- // and its exposure as verbs and projections belongs to G7. Relation
13
- // properties (acyclicity, inverse consistency) are G4 phase 5's, and
14
- // consume exactly this edge set.
8
+ // The graph is PATH-NATIVE (ADR-014). There is no second namespace to
9
+ // index: a node's address is its path, so the entity index the first
10
+ // design carried is exactly the set of paths already in the edges, and
11
+ // the node a link starts at is derived from where the link sits rather
12
+ // than declared by a mark.
13
+ //
14
+ // G4's deliverable is that this exists and is DETERMINISTIC. What is
15
+ // built on it — impact analysis ("what reaches $.services.auth?"),
16
+ // reachability, context-window-sized slices — is a traversal, and its
17
+ // exposure as verbs and projections belongs to G7. Relation properties
18
+ // (acyclicity, inverse consistency) are G4 phase 5's, and consume
19
+ // exactly this edge set.
15
20
 
16
21
  import type { Val } from './type'
17
22
 
18
23
  import { cmpCodePoint } from './keyorder'
19
24
 
20
25
 
21
- export type EntityEntry = {
22
- // The id, as `id(name)` spelled it.
23
- id: string
24
- // Every tree path that holds this entity, in code-point order. More
25
- // than one is the normal case: the merge puts the entity's value at
26
- // every position that declared it.
27
- paths: string[]
28
- }
29
-
30
26
  export type Edge = {
31
- // The entity the link is INSIDE — the nearest identified ancestor,
32
- // or '' for a link outside every entity. This is the
33
- // entity/component distinction: a node without an id is a component
34
- // of its nearest identified ancestor.
27
+ // The node the link starts at, as a `$.dotted.path`: the link's own
28
+ // position with the relation key and any list indices stripped. `$`
29
+ // when the link sits at the top of the document.
35
30
  from: string
36
- // The RELATION: the nearest map key on the way down from the entity,
37
- // so a link inside a list (`dependsOn: [&: refer(), svc/auth]`) is an
38
- // edge under `dependsOn` rather than under `0`.
31
+ // The RELATION: the key the link hangs under, so a link inside a
32
+ // list (`dependsOn: [refer() & "$.a"]`) is an edge under `dependsOn`
33
+ // rather than under `0`. A rel()-minted link carries its predicate
34
+ // declared rather than inferred.
39
35
  key: string
40
36
  // The address, as the link spells it.
41
37
  to: string
42
38
  // Where the link is, as a `$.dotted.path`, so a report can point at
43
39
  // it.
44
40
  at: string
41
+ // Set when the link sits inside a `hide()`-marked subtree. The link
42
+ // is still checked and still an edge, but a figure that draws it
43
+ // DISCLOSES what the document hides, so the view extractors skip it
44
+ // and report it as `hidden_contribution`.
45
+ hidden?: true
45
46
  }
46
47
 
47
48
  export type Graph = {
48
- entities: EntityEntry[]
49
49
  edges: Edge[]
50
+ // The positions of links written under an UNRESOLVED DISJUNCTION.
51
+ // ADR-007: an unresolved disjunction is not a value, so a link
52
+ // beneath one of its arms is not a fact and is not an edge -- but it
53
+ // is not nothing either, and a figure that silently dropped it would
54
+ // be the failure the views exist to avoid. Absent when there are
55
+ // none, so a graph of a decided document is the shape it always was.
56
+ disjunct?: string[]
50
57
  }
51
58
 
52
59
 
@@ -54,82 +61,125 @@ const formatPath = (path: string[]): string =>
54
61
  0 === path.length ? '$' : '$.' + path.join('.')
55
62
 
56
63
 
57
- // The nearest map key on the path below an entity: list indices are
58
- // positions within a relation, not relations of their own. Digits-only
59
- // segments are the indices, which is exactly how the rest of the engine
60
- // spells them.
61
- const relationKey = (tail: string[]): string => {
62
- for (let i = tail.length - 1; 0 <= i; i--) {
63
- if (!/^[0-9]+$/.test(tail[i])) {
64
- return tail[i]
64
+ // Digits-only segments are list indices, which is exactly how the rest
65
+ // of the engine spells them.
66
+ const isIndex = (seg: string): boolean => /^[0-9]+$/.test(seg)
67
+
68
+
69
+ // The node a link starts at and the relation it hangs under, derived
70
+ // from the link's own position.
71
+ //
72
+ // A DECLARED predicate (rel()-minted) is authoritative: the link is cut
73
+ // at the key the rel() sat on, wherever that is on the way down, which
74
+ // is what makes a MAP-valued relation report the relation rather than
75
+ // the inner label. Without one the relation is INFERRED: strip the list
76
+ // indices, and the first real key above the link is it.
77
+ const cut = (
78
+ at: string[], relkey: string | undefined
79
+ ): { from: string, key: string } => {
80
+ if (undefined !== relkey) {
81
+ for (let i = at.length - 1; 0 <= i; i--) {
82
+ if (at[i] === relkey) {
83
+ return { from: formatPath(at.slice(0, i)), key: relkey }
84
+ }
65
85
  }
66
86
  }
67
- return ''
87
+ let i = at.length - 1
88
+ for (; 0 <= i && isIndex(at[i]); i--) { }
89
+ return 0 > i
90
+ ? { from: formatPath([]), key: relkey ?? '' }
91
+ : { from: formatPath(at.slice(0, i)), key: relkey ?? at[i] }
68
92
  }
69
93
 
70
94
 
71
- // The graph of an evaluated tree. Walks POSITIONS, not values: two
72
- // positions of one entity share a value object after the merge, so a
73
- // walk guarded by object identity would find the entity once and miss
74
- // every other place it is declared. The guard is therefore the
75
- // ancestor chain — which is what a cycle actually is.
95
+ // The graph of an evaluated tree. Walks POSITIONS, not values: a
96
+ // reference or a spread can put one value object at several positions,
97
+ // and a walk guarded by object identity would find the first and miss
98
+ // every other place it is reached. The guard is therefore the ancestor
99
+ // chain — which is what a cycle actually is.
76
100
  export function graphOf(root: Val): Graph {
77
- const byId = new Map<string, string[]>()
78
101
  const edges: Edge[] = []
102
+ const disjunct: string[] = []
79
103
 
104
+ // ONE WALK, with `undecided` saying which side of ADR-007 it is on:
105
+ // below an unresolved disjunction every link is a position the
106
+ // document has not decided, and nothing there is an edge.
80
107
  const visit = (
81
- node: any, path: string[], entity: string, tail: string[],
82
- ancestors: Set<any>
108
+ node: any, path: string[], ancestors: Set<any>, hidden: boolean,
109
+ undecided: boolean
83
110
  ): void => {
84
111
  if (null == node || true !== node.isVal || ancestors.has(node)) {
85
112
  return
86
113
  }
87
114
 
88
- let inside = entity
89
- let below = tail
90
- const name = node.entity
91
- if (null != name) {
92
- let paths = byId.get(name)
93
- if (undefined === paths) {
94
- paths = []
95
- byId.set(name, paths)
96
- }
97
- paths.push(formatPath(path))
98
- // A nested entity is not a component of the one above it: the
99
- // key path restarts at the identified node.
100
- inside = name
101
- below = []
102
- }
115
+ hidden = hidden || true === node.mark?.hide
103
116
 
104
117
  const link = node.link
105
118
  if (null != link) {
106
- edges.push({
107
- from: inside,
108
- key: relationKey(below),
109
- to: link,
110
- at: formatPath(path),
111
- })
119
+ if (undecided) {
120
+ disjunct.push(formatPath(path))
121
+ }
122
+ else {
123
+ const { from, key } = cut(path, node.relkey as string | undefined)
124
+ const edge: Edge = { from, key, to: link, at: formatPath(path) }
125
+ if (hidden) {
126
+ edge.hidden = true
127
+ }
128
+ edges.push(edge)
129
+ }
130
+ }
131
+
132
+ // A graph atom is TRANSPARENT here (RELATIONS P2): it carries the
133
+ // field's value at the field's own position, and the graph is about
134
+ // the value.
135
+ if (true === node.isGraphAtom && undefined !== node.held) {
136
+ visit(node.held, path, ancestors, hidden, undecided)
137
+ }
138
+
139
+ // An unresolved conjunction (a link waiting on a peer that never
140
+ // came, a constraint still open) holds its terms at the SAME
141
+ // position; every link among them is written there, and is an
142
+ // edge. This is what a match-selected branch or a deferred refer()
143
+ // looks like after evaluation.
144
+ if (true === node.isConjunct && Array.isArray(node.peg)) {
145
+ ancestors.add(node)
146
+ for (const term of node.peg) {
147
+ visit(term, path, ancestors, hidden, undecided)
148
+ }
149
+ ancestors.delete(node)
150
+ }
151
+
152
+ // AN UNRESOLVED DISJUNCTION IS NOT A VALUE (ADR-007), so a link
153
+ // under one of its arms is not an edge. Its POSITION is collected
154
+ // instead, so a figure can report what the document leaves
155
+ // undecided rather than drawing it or dropping it in silence.
156
+ if (true === node.isDisjunct && Array.isArray(node.peg)) {
157
+ ancestors.add(node)
158
+ for (const arm of node.peg) {
159
+ visit(arm, path, ancestors, hidden, true)
160
+ }
161
+ ancestors.delete(node)
112
162
  }
113
163
 
114
164
  if ((true === node.isMap || true === node.isList) && null != node.peg) {
115
165
  ancestors.add(node)
116
166
  for (const k of Object.keys(node.peg)) {
117
- visit(node.peg[k], [...path, k], inside, [...below, k], ancestors)
167
+ visit(node.peg[k], [...path, k], ancestors, hidden, undecided)
118
168
  }
119
169
  ancestors.delete(node)
120
170
  }
121
171
  }
122
172
 
123
- visit(root, [], '', [], new Set())
124
-
125
- // DETERMINISTIC by construction, not by luck: ids in code-point
126
- // order, each id's paths in code-point order, edges by the position
127
- // they are written at (which is unique — one link, one place).
128
- const entities: EntityEntry[] = [...byId.keys()]
129
- .sort(cmpCodePoint)
130
- .map((id) => ({ id, paths: (byId.get(id) as string[]).sort(cmpCodePoint) }))
173
+ visit(root, [], new Set(), false, false)
131
174
 
175
+ // DETERMINISTIC by construction, not by luck: edges by the position
176
+ // they are written at, which is unique — one link, one place. That
177
+ // holds through a conjunction too: its terms share the position, and
178
+ // two links there would have had to unify into one.
132
179
  edges.sort((a, b) => cmpCodePoint(a.at, b.at))
133
180
 
134
- return { entities, edges }
181
+ if (0 < disjunct.length) {
182
+ return { edges, disjunct: [...new Set(disjunct)].sort(cmpCodePoint) }
183
+ }
184
+ return { edges }
135
185
  }
package/src/hcanon.ts CHANGED
@@ -67,7 +67,15 @@ function render(v: any, inh: HMarks): string {
67
67
 
68
68
  let s: string
69
69
  if (true === v.isMap) {
70
- const keys = Object.keys(v.peg).sort(cmpCodePoint)
70
+ // Alias declarations are dropped here for the same reason
71
+ // MapVal.canon drops them, and this is the surface where it counts:
72
+ // `aon1-` pins MEANING, so a document written with aliases and the
73
+ // same document written longhand must hash to one string. Two
74
+ // renderers, one rule -- hcanon is not canon and does not inherit
75
+ // the filter (docs/design/ALIASES.0.md §4).
76
+ const keys = Object.keys(v.peg)
77
+ .filter((k) => !v.aliasKeys.includes(k))
78
+ .sort(cmpCodePoint)
71
79
  s = '{' +
72
80
  (v.spread.cj ? '&:' + render(v.spread.cj, inner) +
73
81
  (0 < keys.length ? ',' : '') : '') +
@@ -102,16 +110,6 @@ function render(v: any, inh: HMarks): string {
102
110
  s = v.canon
103
111
  }
104
112
 
105
- // The IDENTITY is innermost, exactly as canon writes it (G4 phase
106
- // 1). It is IN the hash — a node declared `id(svc/auth)` and the
107
- // same node declared `id(svc/billing)` describe different systems,
108
- // and a pin that could not tell them apart would be a pin on the
109
- // shape rather than on the meaning.
110
- const e = v.entity
111
- if (null != e) {
112
- s = 'id(' + JSON.stringify(e) + ')&' + s
113
- }
114
-
115
113
  if (mtype && !inh.type) {
116
114
  s = 'type(' + s + ')'
117
115
  }
package/src/hints.ts CHANGED
@@ -67,6 +67,17 @@ const hints: Record<string, string> = {
67
67
 
68
68
  unify_cycle: 'Circular reference detected during unification.',
69
69
 
70
+ pref_implicit_bag:
71
+ 'A preference marks a VALUE, and a bare key is not one. Without\n' +
72
+ 'braces the `*` took the whole implicit map as its operand, so the\n' +
73
+ 'document became a preferred map rather than a map with a preferred\n' +
74
+ 'entry. Brace the bag, or move the `*` onto the value it marks.' +
75
+ '\n \nExamples:\n' +
76
+ ' *a: 1 -> nil # No braces: the `*` takes the whole map;\n' +
77
+ ' a: *1 -> *1 # ... the `*` belongs on the value;\n' +
78
+ ' *{a: 1} -> *{"a":1} # ... or brace the bag to prefer it whole;\n' +
79
+ ' *{x:1}|*{y:2} -> *{"x":1}|*{"y":2} # which is what disjunction needs.',
80
+
70
81
  constraint:
71
82
  'This value does not satisfy the constraint. A constraint is the\n' +
72
83
  'meet of bound atoms (min, max, above, below) and exclusions (neq)\n' +
@@ -153,20 +164,51 @@ const hints: Record<string, string> = {
153
164
  merge_conflict: 'A version-control conflict marker was found in the source. The\nfile still holds an unresolved merge: resolve it and remove the\n`<<<<<<<`, `=======` and `>>>>>>>` lines before unifying.\n \nExamples:\n <<<<<<< HEAD -> nil # A conflict marker, not a `<` operation;\n ======= -> nil # ... nor a chain of `=` characters;\n >>>>>>> other -> nil # ... nor a `>` operation.',
154
165
 
155
166
  include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"in-root.aon" -> {..} # Inside the confinement root: allowed;\n a:@"../secret.aon" -> nil # ... but escaping the root is denied;\n a:@"/etc/hostname" -> nil # ... and so is an absolute path outside it.',
167
+ include_extension: 'An @\"...\" include named a file the engine does not read. THE\nEXTENSION DECIDES which of two things a file is: `.aon` and\n`.aontu` are Aontu source, with the whole language in them, and\n`.json`, `.jsonld`, `.jsonc`, `.json5`, `.jsonic`, `.jsc`, `.toml`,\n`.yaml`, `.yml` and `.ini` are configuration DATA, read by that\nformat\'s own parser. Anything else -- and a name with no extension\n-- is refused rather than guessed at: a guess produces a document\nthat looks right and is not.\n \nExamples:\n a:@\"model.aon\" -> {..} # Aontu source;\n a:@\"server.toml\" -> {..} # ... config data, read as TOML;\n a:@\"notes.txt\" -> nil # ... but text is not a document;\n a:@\"data\" -> nil # ... and neither is an unnamed kind.',
156
168
 
157
169
  func_arity: 'This function was called with the wrong number of arguments:\n{func} takes {want}, but was given {got}.\n \nExamples:\n upper(\"a\") -> \"A\" # One argument, which is what upper takes;\n upper(\"a\",\"b\") -> nil # ... so two is a mistake in the source;\n key() -> \"\" # key takes none, or one level count;\n neq(1,2,3) -> neq # ... and neq takes one or more exclusions.',
170
+ func_arg: 'This argument does not fit the function\'s signature:\n \n {sig}\n argument {argn} (`{arg}`) was `{got}`.\n \nThe signature is the declared call surface (one line per builtin,\ntest/spec/signature.tsv); an argument slot admits a concrete value\nof its declared kind, `number` admitting every numeric leaf and\n`string` admitting a path.\n \nExamples:\n upper("a") -> "A" # A string fits upper(s: string|number);\n upper(1) -> 1 # ... and so does a number;\n upper(true) -> nil # ... but a boolean fits neither word;\n add([1],2) -> nil # ... and a list is no operand.',
158
171
 
159
172
  elided_value: 'A key or element was written with no value after the colon. An\nelided value is a mistake in the source rather than a null: write\n`null` if that is what was meant, or supply the value.\n \nExamples:\n a:null -> null # An explicit null, which is a value;\n a: -> nil # ... but nothing at all is not;\n a: b:1 -> {..} # A colon chain is not an elision;\n [1,] -> [1] # ... nor is a trailing comma.',
160
173
 
161
- id_name: 'The argument to id() is not an entity name. A name is one or\nmore letters, digits, `_`, `-` or `/`, and NO dots: a dot separates\nan entity name from a path inside that entity, so a dotted name\nwould be ambiguous. A `-` must be quoted, because it is not a\nbare-text character.\n \nExamples:\n id(svc/auth) -> id # Letters, digits and `/` may be bare;\n id("team-pay") -> id # ... a `-` name must be quoted;\n id(svc.auth) -> nil # ... a dot is a path separator, not a name;\n id(1) -> nil # ... and a number is not a name at all.',
162
174
 
163
- id_conflict: 'One value was declared to be two different entities. An id() says\nwhat a value IS, so two names on one node is a contradiction, not a\nmerge — the same kind of failure as unifying 1 with 2. Give the node\none name, or give the two names to two nodes.\n \nExamples:\n id(a) & id(a) & {} -> {..} # One entity, said twice;\n id(a) & {x:1} -> {..} # ... an entity with content;\n id(a) & id(b) & {} -> nil # ... but a node cannot be both.',
164
175
 
165
- id_spread: 'A spread template stamps one id() onto every child. `&: id(x) & …`\nsays that EVERY child of the bag is the entity `x`, and identity\nmerging would then unify all of them into one. Use a\npath-dependent name — `id(key())` — to give each child its own,\nor move the id() to the one child that has it.\n \nExamples:\n {&: id(key()), a:{}, b:{}} -> {..} # A name per child;\n {a: id(x) & {}} -> {..} # ... or one named child;\n {&: id(x), a:{}, b:{}} -> nil # ... but not one name for all.',
166
176
 
167
- refer_address: 'A refer() was given something that is not an entity address. An\naddress is an entity name, optionally followed by a dot-separated path\ninside that entity — and only a STRING can be one.\n \nExamples:\n refer() & "svc/auth" -> "svc/auth" # An entity;\n refer() & "svc/auth.port" -> ... # ... and a node inside it;\n refer() & "svc/auth." -> nil # ... but not a trailing dot;\n refer() & 1 -> nil # ... and not a number.',
177
+ recursion_unexpanded: 'A schema refers to itself here, and no data reached this position\nto expand it against. Guard the recursion -- an optional key\n(next?:) drops when nothing arrives, and a preferred alternative\n(*null | $.Node) generates -- or supply the data.\n \nExamples:\n Node: {v: integer, next?: $.Node}\n t: $.Node & {v: 1} -> {..} # next? drops;\n Node: {v: integer, next: $.Node}\n t: $.Node & {v: 1} -> nil # ... required refuses.',
178
+ recursion_budget: 'A recursive schema expanded past the evaluation depth budget\nwithout meeting concrete data. Expansion is driven by the data --\nfinite data always terminates -- so a chain this deep means two\ndefinitions feeding each other, or data deeper than the budget\n(docs/trust.md raises it deliberately).',
179
+ list_length: 'A literal list alternative in a disjunction admits only a list of\nits own length -- a spread (&:) makes it variadic. Outside a\ndisjunction two statements of one list still merge elementwise.\n \nExamples:\n x: [] | [&: integer]\n x: [1, 2] -> [1,2] # The variadic arm;\n x: [] -> [] # ... or exactly empty;\n y: [a] | [b]\n y: [a, extra] -> nil # ... a literal arm is its length.',
180
+ relation_cycle: 'This relation declared acyclic(), and its edges form a cycle. The\nverdict lands at generation, where every edge is known; the error\npoints at an edge on the cycle and names the nodes it runs\nthrough, closing back on the first.\n \nExamples:\n dependsOn: rel() & acyclic()\n a: {dependsOn: ["$.b"]}\n b: {dependsOn: ["$.a"]} -> nil # $.a -> $.b -> $.a;\n b: {dependsOn: []} -> {..} # ... one edge fewer passes.',
181
+ relation_inverse_missing: 'This relation declared inverse(name), and an edge has no mirroring\nedge under that name: A relates to B, and B does not name A back.\nThe declaration asks for the mirror to be WRITTEN, and never writes\nit for the author -- generation is refused until the document says\nboth directions.\n \nExamples:\n a: {dependsOn: rel() & inverse(dependedOnBy) & [b]}\n b: {dependedOnBy: rel() & [a]} -> {..} # Mirrored;\n b: {dependedOnBy: rel() & []} -> nil # ... and this is not.',
182
+ inverse_name: 'The argument to inverse() is not a relation name. The mirroring\npredicate is a D-1 name -- a letter or `_`, then letters, digits,\n`_` or `-` -- spelled bare or quoted.\n \nExamples:\n inverse(dependedOnBy) -> inverse # A name;\n inverse("dep-on") -> inverse # ... quoted when hyphenated;\n inverse(1) -> nil # ... a number is not a name.',
183
+ rel_address: 'A rel() field holds tree addresses: one path value, a list of path\nvalues, or a map whose leaves are path values. A bare string is never\nan address — `path("...")` is the one conversion — and this value can\nnever be one.\n \nExamples:\n dependsOn: rel() & [path($.ledger)] -> {..} # A list of addresses;\n hostedOn: rel() & path($.bastion) -> {..} # ... or one;\n dependsOn: rel() & ["$.ledger"] -> nil # ... a string is not a path;\n dependsOn: rel() & [7] -> nil # ... and neither is 7.',
184
+ refer_address: 'A refer() was given something that is not a tree address. An address\nis a PATH VALUE — `path($.a.b)` from the document root, `path(.b)`\nfrom the link\'s own sibling scope — and only a path value can be one:\na bare string never is, and `path("...")` is the one conversion.\n \nExamples:\n refer() & path($.services.auth) -> ... # From the root;\n refer() & path(.auth) -> ... # ... or beside the link;\n refer() & "$.services.auth" -> nil # ... a string is not a path;\n refer() & 1 -> nil # ... and neither is a number.',
185
+
186
+ path_address: 'This is not a tree address, so it cannot be a path value. An address\nis `$.a.b` from the document root, or `.b` from the sibling scope with\none more leading dot per parent step -- the grammar path() captures.\nString text with no anchor converts as RELATIVE, and a string\nconverts ONLY through the call\'s own argument.\n \nExamples:\n d: path("$.services.auth") -> path($.services.auth) # A converted string;\n d: path("auth") -> path(.auth) # ... anchorless text is relative;\n d: path("a..b") -> nil # ... but this spells nothing;\n d: path("$") -> nil # ... and the root is not addressable.',
187
+
188
+ rel_unresolved: 'The address names no node in this evaluation. Every position in the\ndocument is addressable; nothing outside it is.\n \nExamples:\n p: {}\n q: rel() & "$.p" -> {..} # $.p is a node;\n q: rel() & "$.nosuch" -> nil # ... $.nosuch is not.',
189
+ refer_unresolved: 'A refer() address names no node in this evaluation. Within one\nevaluation the document-set is fixed, so a link to nothing is an error\nrather than something to resolve later: check the spelling, or add the\nnode it was meant to reach. A relative address that climbs off the top\nof the tree lands here too.\n \nExamples:\n a:{p:1} b:refer()&"$.a" -> "$.a" # $.a is a node;\n a:{p:1} b:refer()&"$.a.p" -> "$.a.p" # ... and so is a node inside it;\n b:refer()&"$.nope" -> nil # ... but nothing is here.',
190
+ view_relation_unknown: 'The relation named to the view has no edges in this document, so\nthe figure would be empty -- and an empty figure and a misspelled name are\nthe same file on disk. Check the spelling against the relations the\nnote lists, or drop the relation to draw every relation at once.',
191
+
192
+ view_kind_unknown: 'The figure kind is not one the verb draws. The kinds are tree, matrix,\ngraph, layer, sets, layers, ladder and poset; the note lists them.',
193
+
194
+ view_profile_unknown: 'The figure kind does not render into the profile asked for: there is no\ntext form of a node-link drawing and no Mermaid form of a matrix. The\nnote lists the profiles the kind declares; the first is its default.',
195
+ view_style_profile: 'Each profile has ONE way to carry the meaning of a figure\'s marks:\nSGR escapes for text, CSS classes for svg. Asking for the other one is\na usage error rather than a silent no-op. `none` works everywhere.',
196
+ view_style_unknown: 'The styles are none, ansi and css, plus `auto` at the command line,\nwhich the command resolves before the library runs: whether the\ndestination is a terminal is not something a library can see.',
197
+
198
+ view_rows_exceeded: 'The figure has more rows than --max-rows allows. This is a REFUSAL,\nnot a truncation: a view that quietly omits things is the failure the\nverb exists to avoid. Narrow the figure with --at or --relation, or\nraise the limit.',
199
+
200
+ view_line_break: 'A label the figure would draw holds a line terminator (U+000A, U+000D,\nU+2028 or U+2029), and a figure line cannot carry one. Label the node\nwith another field, or drop --label / --group-by.',
201
+
202
+ view_relation_ambiguous: 'The document has edges under several relations and the matrix (or the\nlayer diagram) draws exactly one. Name it with --relation; the note\nlists the relations with edges.',
168
203
 
169
- refer_unresolved: 'A refer() address names no entity in this evaluation. Within one\nevaluation the document-set is fixed, so a link to nothing is an\nerror rather than something to resolve later: check the spelling, or\nadd the id() that was meant to declare it.\n \nExamples:\n a:id(svc/x)&{} b:refer()&"svc/x" -> "svc/x" # Declared, so it resolves;\n a:id(svc/x)&{p:1} b:refer()&"svc/x.p" -> "svc/x.p" # ... and so does a node inside it;\n b:refer()&"svc/nope" -> nil # ... but nothing declares this.',
204
+ view_sets_shape: 'The set panel reads generated values: --sets must name a map whose\nvalues each hold the --member field as a list of strings, and\n--universe a map or a list of strings. The path in the finding is the\nvalue that has another shape.',
205
+
206
+ view_sets_required: 'The set panel needs both --sets (the map whose keys are the sets) and\n--member (the field holding each set\'s members).',
207
+
208
+ view_at_required: 'The meet ladder draws the contributions at ONE path, and none was\nnamed. Pass --at with the path, as `aontu why` takes it.',
209
+
210
+ view_group_required: 'The layer diagram puts each node in the band its --group-by field\nnames, and no field was named. Pass --group-by with the field that\nholds each node\'s layer.',
211
+ view_document_shape: 'A view document declares each figure as a map of view options --\nthe flag names without the dashes -- and every declaration must name\nits `kind` and the `out` file it draws into. This one names an option\nthat is not one, gives a value of the wrong shape, or leaves out what\nevery declaration needs. `aontu view --help` lists the options.',
170
212
 
171
213
  pack_data: 'The first argument to pack() is not a bag. `pack` makes one child\nper child of its DATA, so the data has to have children: a list of\nnames, or a map whose keys are the names.\n \nExamples:\n pack([a,b], {x:1}) -> {..} # A list of names;\n pack({a:1,b:2}, {x:1}) -> {..} # ... or a map, keyed by its keys;\n pack(1, {x:1}) -> nil # ... but a scalar has no children.',
172
214
 
@@ -180,8 +222,8 @@ const hints: Record<string, string> = {
180
222
 
181
223
  place_pair: 'Two placeholders met, and neither has a value to fill the other.\n`_` is a HOLE: it is filled by whatever the call is unified with, so\na call holding one needs a peer that does not. Give one side a\nvalue.\n \nExamples:\n upper(_) & hello -> "HELLO" # The peer fills the hole;\n _ + 2 & 1 -> 3 # ... whatever the call is;\n upper(_) & lower(_) -> nil # ... but two holes fill nothing.',
182
224
 
183
- pipe_target: 'The right-hand side of a `|>` is not a function. A pipe puts the\nvalue on its left in as the FIRST argument of the call on its\nright, so the right side has to be one: a call, or the bare name of\na built-in.\n \nExamples:\n hello |> upper -> "HELLO" # A bare name is the call;\n $.names |> pack({}) -> {..} # ... or a call with more arguments;\n 1 |> 2 -> nil # ... but a value is not a function.',
184
225
 
226
+ module_path: 'A module import is domain-shaped and carries a major, but its\npath cannot be a directory on every platform the toolchain runs on --\nso it is refused before anything is built from it. An element may not\nbe empty, may not begin or end with `.` (which is what forbids `..`),\nand may not be a reserved device name. These are Go\'s module-path\nrules, for Go\'s reason: a module path becomes a real directory.\n \nExamples:\n @"corp.example/s@1" -> {..} # An ordinary path is fine;\n @"corp.example/../s@1" -> nil # ... `..` would escape the store;\n @"corp.example/nul@1" -> nil # ... and Windows has no such file.',
185
227
  module_missing: 'A module import names a module that is not in this project. A\nmodule is resolved from LOCAL stores only -- `aon_vendor/` beside the\nproject\'s mod.aon, then the user cache -- because evaluation never\ntouches the network. Fetching is a separate step, and the message\nnames it.\n \nExamples:\n @"corp.example/s@1" -> nil # Not fetched: run aontu mod get;\n @"./local.aon" -> {..} # ... a local path is not a module;\n @"corp.example/s@1#aon1-…" -> {..} # ... and a pin does not fetch it either.',
186
228
 
187
229
  module_integrity: 'A module resolved locally does not have the MEANING it was pinned\nto. The pin is a canon-hash -- the hash of the module unified\nstandalone -- so it survives comments, formatting and refactoring and\nbreaks on any semantic change in the module\'s transitive closure.\nVerification is always local: the registry\'s annotation is advisory.\n \nExamples:\n @"corp.example/s@1" -> {..} # No pin, no check;\n @"corp.example/s@1#aon1-x" -> nil # ... a pin that disagrees refuses;\n aontu hash <file> # ... and this is what it should be.',
@@ -248,6 +290,22 @@ const hints: Record<string, string> = {
248
290
  ' pick([{a:1},{b:2}], a) -> nil # ... the second does not;\n' +
249
291
  ' pick([[9],[8]], 0) -> [9,8] # A list child takes an index.',
250
292
 
293
+ join_member:
294
+ 'A member of this bag is not text and never will be: `{member}`.\n' +
295
+ '`join` folds with `+` seeded with the empty string, and `+` with a\n' +
296
+ 'string on the left RESIDUATES on a map, a list or a null rather\n' +
297
+ 'than refusing — so folding blindly would report the failure at\n' +
298
+ 'generation, naming the whole call instead of the member. It is\n' +
299
+ 'refused here, where the member can still be named. A member that\n' +
300
+ 'is merely UNRESOLVED is a different thing: the call stays\n' +
301
+ 'residual and generation reports it as ordinary incompleteness.' +
302
+ '\n \nExamples:\n' +
303
+ ' join([1,2], "-") -> "1-2" # Numbers render as digits;\n' +
304
+ ' join([true], ",") -> "true" # ... so do booleans;\n' +
305
+ ' join([{a:1}], ",") -> nil # A map is not text;\n' +
306
+ ' join([null], ",") -> nil # ... and neither is null;\n' +
307
+ ' join(pick($.r, n), ",") -> "a,b" # Project first.',
308
+
251
309
  aggregate_data:
252
310
  'This aggregate needs a BAG to fold: a list or a map. `sum`,\n' +
253
311
  '`least` and `greatest` walk the children of the value they are\n' +
@@ -363,8 +421,14 @@ const hints: Record<string, string> = {
363
421
  'required_listelem': 'Required list element is missing. A non-optional list element has no value.',
364
422
 
365
423
  // Junction errors (disjunction/conjection)
366
- '|:empty': 'Empty disjunction. The disjunction has no valid alternatives.',
367
- '|:empty-dist': 'Empty disjunction distribution. All alternatives in the disjunction are invalid.',
424
+ 'empty': 'Empty disjunction. The disjunction has no valid alternatives.',
425
+ 'empty-dist': 'Empty disjunction distribution. All alternatives in the disjunction are invalid.',
426
+
427
+ // ADR-011 R2: two DEFAULTS of equal rank that cannot agree. The
428
+ // fix is a rank, so the hint names it.
429
+ 'pref_rank_clash': 'Two defaults of the same rank disagree.' +
430
+ ' Rank one of them (`**x`) to say which is the weaker layer,' +
431
+ ' or give them the same value.',
368
432
 
369
433
  'max_depth': 'Input nesting is too deep to process safely.',
370
434
 
@@ -406,6 +470,7 @@ const codeClasses: Record<string, string> = {
406
470
  parse_bad_src: 'parse',
407
471
  merge_conflict: 'parse',
408
472
  include_denied: 'parse',
473
+ include_extension: 'parse',
409
474
 
410
475
  // G3 -- the subsumption query's report vocabulary (class compat):
411
476
  // the compat_* codes are its findings, the sub_* codes its undecided
@@ -431,24 +496,37 @@ const codeClasses: Record<string, string> = {
431
496
  patch_ambiguous: 'reference',
432
497
  patch_span_mismatch: 'internal',
433
498
 
434
- // G4 phase 1 -- the identity mark: a name that is not one, and two
435
- // different names on one node. `id_name` is a parse-class refusal
436
- // of the argument; `id_conflict` is a conflict like any other
437
- // failed meet, because that is exactly what it is.
438
- id_name: 'parse',
439
- id_conflict: 'conflict',
440
-
441
- // Clearing rule 3: a constant `id()` inside an `&:` template. Class
442
- // `parse`, because what is wrong is the TEXT of the template rather
443
- // than any pair of values it brought together.
444
- id_spread: 'parse',
445
-
446
- // G4 phase 2 -- the checked link: a string that is not an entity
499
+ // G4 phase 2 -- the checked link: a string that is not a tree
447
500
  // address (class `parse`, the text is wrong), and an address that
448
501
  // names nothing in this evaluation (class `reference`, the same
449
502
  // class as `no_path`, because it is the same kind of miss).
450
503
  refer_address: 'parse',
504
+ rel_address: 'parse',
451
505
  refer_unresolved: 'reference',
506
+ rel_unresolved: 'reference',
507
+
508
+ // The tree view (docs/design/VIEWS.0.md): a relation that draws
509
+ // nothing is refused rather than drawn empty. Class `reference`: a
510
+ // name that does not resolve.
511
+ view_relation_unknown: 'reference',
512
+ view_kind_unknown: 'reference',
513
+ view_profile_unknown: 'reference',
514
+ view_style_profile: 'reference',
515
+ view_style_unknown: 'reference',
516
+ view_rows_exceeded: 'budget',
517
+ view_line_break: 'parse',
518
+ view_relation_ambiguous: 'reference',
519
+ view_sets_shape: 'reference',
520
+ view_sets_required: 'reference',
521
+ view_at_required: 'reference',
522
+ view_group_required: 'reference',
523
+ view_document_shape: 'reference',
524
+
525
+
526
+ // First-class paths (docs/design/PATHS.0.md): text that is not a
527
+ // tree address, met by path()'s capture or promotion. The same
528
+ // class as refer_address, because it is the same mistake.
529
+ path_address: 'parse',
452
530
 
453
531
  // G8 phase 1 -- the generation combinators. All three are class
454
532
  // `parse`: what is wrong is the CALL as written (data that is not a
@@ -471,15 +549,11 @@ const codeClasses: Record<string, string> = {
471
549
  // conflict is.
472
550
  place_pair: 'conflict',
473
551
 
474
- // G8 phase 4 -- the pipe. Class `parse`: a pipe is sugar resolved
475
- // while reading the source, so a pipe into something that is not a
476
- // call is wrong in the TEXT and no later pass can repair it.
477
- pipe_target: 'parse',
478
-
479
552
  // G6 phase 2 -- modules. Both are class `parse`: a module import is
480
553
  // resolved while the source is READ, and neither a module that is
481
554
  // absent nor one whose meaning disagrees with its pin can be
482
555
  // repaired by any later pass.
556
+ module_path: 'parse',
483
557
  module_missing: 'parse',
484
558
  module_integrity: 'parse',
485
559
  module_depth: 'budget',
@@ -490,11 +564,17 @@ const codeClasses: Record<string, string> = {
490
564
  // and a lattice citizen may not be falsified by more information.
491
565
  relation_cycle: 'conflict',
492
566
  relation_inverse_missing: 'conflict',
493
- relation_target_unmet: 'conflict',
567
+ inverse_name: 'parse',
568
+ list_length: 'conflict',
569
+ recursion_unexpanded: 'incomplete',
570
+ recursion_budget: 'budget',
494
571
  func_arity: 'parse',
495
572
  elided_value: 'parse',
496
573
  unify_no_src: 'parse',
497
574
  incomplete_expression: 'parse',
575
+ pref_implicit_bag: 'parse',
576
+ alias_not_toplevel: 'parse',
577
+ alias_in_path: 'parse',
498
578
  not_number: 'parse',
499
579
  negative: 'parse',
500
580
  decimal_syntax: 'parse',
@@ -516,13 +596,15 @@ const codeClasses: Record<string, string> = {
516
596
  literal_nil: 'conflict',
517
597
  nil_gen: 'conflict',
518
598
  unite: 'conflict',
519
- '|:empty': 'conflict',
520
- '|:empty-dist': 'conflict',
599
+ 'empty': 'conflict',
600
+ 'empty-dist': 'conflict',
601
+ pref_rank_clash: 'conflict',
521
602
  exact_float_mix: 'conflict',
522
603
  inexact_integer_sum: 'conflict',
523
604
  pick_key: 'conflict',
524
605
  aggregate_data: 'conflict',
525
606
  aggregate_empty: 'conflict',
607
+ join_member: 'conflict',
526
608
  divide_by_zero: 'conflict',
527
609
  inexact_divide: 'conflict',
528
610
  float_overflow: 'conflict',
@@ -530,6 +612,7 @@ const codeClasses: Record<string, string> = {
530
612
  lossy_integer_literal: 'conflict',
531
613
  arg: 'conflict',
532
614
  'invalid-arg': 'conflict',
615
+ func_arg: 'conflict',
533
616
  no_first_arg: 'conflict',
534
617
  key_level: 'conflict',
535
618
  func: 'conflict',
package/src/jsonschema.ts CHANGED
@@ -109,6 +109,10 @@ const KIND_TYPE: Record<string, string> = {
109
109
  Float: 'number',
110
110
  BigDecimal: 'number',
111
111
  Number: 'number',
112
+ // A path value is its address string at the JSON boundary
113
+ // (docs/design/PATHS.0.md), so the projection says `string` -- the
114
+ // same lossy-projection rule the exact numeric leaves follow.
115
+ Path: 'string',
112
116
  }
113
117
 
114
118
 
@@ -238,7 +242,44 @@ function fromConstraint(ctx: Ctx, path: string[], c: any): any {
238
242
  // (the marker drops LINES, not branches). Go's twin keeps its `nil ==
239
243
  // v` arm because a missing map key there yields a typed nil rather
240
244
  // than an absent property.
245
+ // DEPRECATION IS AN ANNOTATION, and 2020-12 has one: `deprecated`.
246
+ // The export used to drop the whole record in SILENCE -- no keyword,
247
+ // and no loss line even under --strict -- which is the one thing the
248
+ // verb's own contract says it never does (use-cases/BUGS.md §56).
249
+ //
250
+ // The boolean crosses faithfully. The record's STRINGS (msg, use,
251
+ // since) have no home in 2020-12, so they are reported as a loss
252
+ // rather than invented into `description`: this exporter emits no
253
+ // `description` anywhere, and quietly making it mean "deprecation
254
+ // note" would be a mapping a consumer cannot undo.
241
255
  function fromVal(ctx: Ctx, path: string[], v: any): any {
256
+ const out = fromValInner(ctx, path, v)
257
+
258
+ const dep = v?.deprecation
259
+ if (null != dep && null != out && 'object' === typeof out) {
260
+ const said = DEPRECATION_TEXT.filter((k) => null != dep[k])
261
+ if (0 < said.length) {
262
+ lose(ctx, path, 'deprecate',
263
+ 'JSON Schema 2020-12 has the `deprecated` flag and no field for ' +
264
+ 'what it SAYS, so ' + said.join('/') + ' cannot cross; the ' +
265
+ 'schema marks the property deprecated and a consumer must read ' +
266
+ 'the model for the reason')
267
+ }
268
+ return { ...out, deprecated: true }
269
+ }
270
+
271
+ return out
272
+ }
273
+
274
+
275
+ // The record's text keys -- the ones with nothing to carry them.
276
+ // `DEPRECATION_KEYS` in DeprecateFuncVal.ts is the authority on what a
277
+ // record may hold; this is that list, and a key added there without a
278
+ // JSON Schema home belongs here too.
279
+ const DEPRECATION_TEXT = ['msg', 'use', 'since']
280
+
281
+
282
+ function fromValInner(ctx: Ctx, path: string[], v: any): any {
242
283
  // A preference is its inner value plus a DEFAULT. JSON Schema's
243
284
  // `default` is annotation rather than constraint -- it does not
244
285
  // validate -- which is exactly what a preference is when something