aontu 0.52.1 → 0.54.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 (310) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
@@ -3,11 +3,14 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.MustConstraintVal = exports.UniqueConstraintVal = exports.LengthConstraintVal = exports.ReConstraintVal = exports.NeqConstraintVal = exports.BelowConstraintVal = exports.AboveConstraintVal = exports.MaxConstraintVal = exports.MinConstraintVal = exports.ConstraintVal = void 0;
5
5
  exports.normaliseRe = normaliseRe;
6
+ exports.constraintSubsumesConstraint = constraintSubsumesConstraint;
7
+ exports.constraintAdmitsScalar = constraintAdmitsScalar;
6
8
  const type_1 = require("../type");
7
9
  const utility_1 = require("../utility");
8
10
  const Val_1 = require("./Val");
9
11
  const keyorder_1 = require("../keyorder");
10
12
  const ConjunctVal_1 = require("./ConjunctVal");
13
+ const BagVal_1 = require("./BagVal");
11
14
  const top_1 = require("./top");
12
15
  const unify_1 = require("../unify");
13
16
  const IntegerVal_1 = require("./IntegerVal");
@@ -71,6 +74,17 @@ const numcmp_1 = require("./numcmp");
71
74
  // This function is mirrored statement for statement in go/constraint.go,
72
75
  // and `test/spec/files/regex-corpus.txt` pins that the two produce
73
76
  // byte-identical output for every pattern in a generated corpus.
77
+ // The largest repeat count Aontu's pattern language admits. RE2 caps a
78
+ // repeat at 1000 and refuses to compile past it, where JavaScript's
79
+ // backtracking engine accepts any count -- so `re("^a{1001}$")` was
80
+ // VALID in TypeScript and an `error` verdict in Go, a cross-port
81
+ // verdict flip on a schema an agent could be handed
82
+ // (status-2026-08-21.md section 4). Under ADR-003 the host's own
83
+ // compile failure must never be what a user sees, so the bound is
84
+ // Aontu's: at most RE_REPEAT_MAX, refused identically in both ports
85
+ // before either engine compiles. Nested products beyond the cap were
86
+ // already refused, by the quantified-group rule below.
87
+ const RE_REPEAT_MAX = 1000;
74
88
  // The normative expansions. These are Aontu's definitions, not either
75
89
  // host's; both hosts are rewritten to them.
76
90
  const RE_CLASS_DIGIT = '0-9';
@@ -84,6 +98,65 @@ const RE_ESCAPE_PUNCT = '\\.+*?()[]{}|^$/';
84
98
  // Escapes passed through unchanged: the control characters, the ASCII
85
99
  // word boundary, and `\xHH`. Each was probed in both engines.
86
100
  const RE_ESCAPE_PASS = 'tnrfv';
101
+ // A counted quantifier `{n}`, `{n,}` or `{n,m}` starting at `src[at]`.
102
+ // Two rules, both about what the hosts do NOT share:
103
+ //
104
+ // - A bound above RE_REPEAT_MAX: RE2 refuses to compile it, JavaScript
105
+ // accepts it.
106
+ // - A brace that does not open a well-formed counted quantifier at
107
+ // all (`x{y}`, `a{`, `{,5}`): JavaScript compiled with the `u` flag
108
+ // makes it a SYNTAX ERROR, while RE2 reads it as a literal brace, so
109
+ // `re("^x{y}$")` was `constraint_pattern` in TypeScript and a plain
110
+ // unresolved value in Go -- the same class of verdict flip as the
111
+ // repeat cap, found by sweeping around it. A literal brace is
112
+ // written escaped (`a\\{1001\\}`), which both engines share.
113
+ function repeatWhy(src, at) {
114
+ const bad = (what) => [
115
+ 'a ' + what + ', which the two engines do not read the same way', -1
116
+ ];
117
+ let i = at + 1;
118
+ let digits = '';
119
+ const bounds = [];
120
+ let commas = 0;
121
+ for (; i < src.length; i++) {
122
+ const c = src[i];
123
+ if ('0' <= c && c <= '9') {
124
+ digits += c;
125
+ continue;
126
+ }
127
+ if (',' === c) {
128
+ if ('' === digits || 0 < commas) {
129
+ return bad('{ that does not open a counted quantifier');
130
+ }
131
+ bounds.push(parseInt(digits, 10));
132
+ digits = '';
133
+ commas++;
134
+ continue;
135
+ }
136
+ if ('}' === c) {
137
+ if ('' !== digits) {
138
+ bounds.push(parseInt(digits, 10));
139
+ }
140
+ else if (0 === commas) {
141
+ return bad('{ that does not open a counted quantifier');
142
+ }
143
+ break;
144
+ }
145
+ return bad('{ that does not open a counted quantifier');
146
+ }
147
+ if (i >= src.length) {
148
+ return bad('{ that does not open a counted quantifier');
149
+ }
150
+ for (const b of bounds) {
151
+ if (RE_REPEAT_MAX < b) {
152
+ return ['a repeat count above ' + RE_REPEAT_MAX +
153
+ ', which RE2 refuses to compile', -1];
154
+ }
155
+ }
156
+ // A descending range (`{5,2}`) is refused by both engines' own
157
+ // compilers, so it needs no rule here.
158
+ return ['', i];
159
+ }
87
160
  function isHexDigit(c) {
88
161
  return null != c && (('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'));
89
162
  }
@@ -178,14 +251,32 @@ function normaliseRe(src) {
178
251
  groups[groups.length - 1][k] = true;
179
252
  }
180
253
  };
254
+ // Where the counted quantifier validated below closes, so its own
255
+ // `}` is told apart from a stray one; and whether the atom just
256
+ // emitted was `^` or `$`, which cannot be quantified.
257
+ let repeatEnd = -1;
258
+ let anchorPrev = false;
181
259
  for (let i = 0; i < src.length; i++) {
182
260
  const c = src[i];
261
+ const afterAnchor = anchorPrev;
262
+ anchorPrev = false;
183
263
  if ('\\' === c) {
184
264
  const [emit, why, extra] = normaliseEscape(src[i + 1], src, i, inClass);
185
265
  if ('' !== why) {
186
266
  return ['', why];
187
267
  }
188
268
  out.push(emit);
269
+ // `\b` AND `\B` ARE ASSERTIONS TOO, and quantifying one is the
270
+ // same disagreement `^{1}` is: JavaScript under `u` calls it a
271
+ // syntax error, RE2 quantifies the assertion happily. The rule
272
+ // said "`\b` and `\B` quantify identically in both and are left
273
+ // alone" and that was measured wrong -- `re("\\b{1}x")` is
274
+ // `constraint_pattern` in TypeScript and an accepted schema in
275
+ // Go. Only OUTSIDE a class: inside one, `\b` is a backspace in
276
+ // both engines and repeats like any other character.
277
+ if (!inClass && ('b' === src[i + 1] || 'B' === src[i + 1])) {
278
+ anchorPrev = true;
279
+ }
189
280
  i += 1 + extra;
190
281
  continue;
191
282
  }
@@ -256,11 +347,41 @@ function normaliseRe(src) {
256
347
  continue;
257
348
  }
258
349
  if ('*' === c || '+' === c || '?' === c || '{' === c) {
350
+ // Nothing to repeat. JavaScript under the `u` flag makes this a
351
+ // SYNTAX ERROR, where RE2 quantifies the assertion happily and
352
+ // matches, so `re("^{1}")` was `constraint_pattern` in
353
+ // TypeScript and an accepted schema in Go. The four assertions
354
+ // behave alike here -- `^`, `$`, `\b` and `\B` -- so the rule
355
+ // names all four.
356
+ if (afterAnchor) {
357
+ return ['', 'a quantifier applied to `^`, `$`, `\\b` or ' +
358
+ '`\\B`, which has nothing to repeat'];
359
+ }
360
+ if ('{' === c) {
361
+ const [why, end] = repeatWhy(src, i);
362
+ if ('' !== why) {
363
+ return ['', why];
364
+ }
365
+ repeatEnd = end;
366
+ }
259
367
  mark('q');
260
368
  out.push(c);
261
369
  continue;
262
370
  }
371
+ // A `}` that closes no counted quantifier: JavaScript under `u`
372
+ // refuses it as a lone quantifier bracket, RE2 reads it as a
373
+ // literal. Written escaped inside a class (`[}]`) it is a literal
374
+ // in both, which is the spelling that survives.
375
+ if ('}' === c) {
376
+ if (i !== repeatEnd) {
377
+ return ['', 'a `}` that closes no counted quantifier, which ' +
378
+ 'the two engines do not read the same way'];
379
+ }
380
+ out.push(c);
381
+ continue;
382
+ }
263
383
  out.push(c);
384
+ anchorPrev = ('^' === c || '$' === c);
264
385
  }
265
386
  if (inClass) {
266
387
  return ['', 'an unterminated character class'];
@@ -280,6 +401,15 @@ function numericLeaf(v) {
280
401
  function stringLeaf(v) {
281
402
  return true === v?.isScalar && 'string' === typeof v.peg && v.isString;
282
403
  }
404
+ // The VALUE-admission widening of stringLeaf: a path value is a string
405
+ // with more structure (path <: string, ADR-016), so the string-domain
406
+ // constraints admit one -- `refer() & re("auth$")` must still check a
407
+ // link whose value is now a path. The ARGUMENT sites keep the strict
408
+ // test: a pattern, a bound or an exclusion is spelled with a plain
409
+ // string, never a path.
410
+ function stringishLeaf(v) {
411
+ return stringLeaf(v) || (true === v?.isScalar && true === v.isPath);
412
+ }
283
413
  // Scalar identity: leaf AND value, the lattice's own rule (1 and 1.0
284
414
  // are different scalars). Exact numeric comparison decides the value
285
415
  // half for numeric leaves.
@@ -287,6 +417,12 @@ function sameScalar(a, b) {
287
417
  if (numericLeaf(a) && numericLeaf(b)) {
288
418
  return (0, numcmp_1.towerRank)(a) === (0, numcmp_1.towerRank)(b) && 0 === (0, numcmp_1.cmpNumeric)(a, b);
289
419
  }
420
+ if (true === a?.isPath || true === b?.isPath) {
421
+ // Path identity is kind AND spelling (ADR-016): `neq(path($.x))`
422
+ // excludes exactly that address, and never a plain string that
423
+ // happens to spell it.
424
+ return true === a?.isPath && true === b?.isPath && a.peg === b.peg;
425
+ }
290
426
  if (stringLeaf(a) && stringLeaf(b)) {
291
427
  return a.peg === b.peg;
292
428
  }
@@ -328,6 +464,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
328
464
  this.neqs = [];
329
465
  this.res = [];
330
466
  this.uniq = false;
467
+ this.uniqBy = [];
331
468
  this.musts = [];
332
469
  if (spec.state) {
333
470
  this.domain = spec.state.domain;
@@ -340,6 +477,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
340
477
  this.res = spec.state.res ?? [];
341
478
  this.count = spec.state.count;
342
479
  this.uniq = spec.state.uniq ?? false;
480
+ this.uniqBy = spec.state.uniqBy ?? [];
343
481
  this.musts = spec.state.musts ?? [];
344
482
  this.invalid = spec.state.invalid;
345
483
  }
@@ -351,14 +489,22 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
351
489
  // in unify, where there is a ctx to resolve through, and the
352
490
  // residual is built from the settled arguments. Only a settled
353
491
  // argument of the wrong shape is an `invalid-arg`.
354
- if (args.some((a) => true !== a?.done)) {
492
+ // The effectful-argument refusal happens HERE, on the written
493
+ // form, and not in fromAtom: settling is what runs the effect, so
494
+ // by the time fromAtom sees a settled `move($.b)` the move has
495
+ // already happened and the argument is just its result.
496
+ if ('must' === spec.atom && args.some((a) => holdsMove(a))) {
497
+ this.invalid = 'invalid-arg';
498
+ }
499
+ else if (args.some((a) => true !== a?.done)) {
355
500
  this.pending = { atom: spec.atom, args };
356
501
  }
357
502
  else {
358
503
  this.fromAtom(spec.atom, args);
359
504
  }
360
505
  }
361
- if (null != this.count || this.uniq || 0 < this.musts.length ||
506
+ if (null != this.count || this.uniq || 0 < this.uniqBy.length ||
507
+ 0 < this.musts.length ||
362
508
  (null != this.pending && lateAtom(this.pending.atom))) {
363
509
  this.cjo = LATE_CJO;
364
510
  }
@@ -377,17 +523,30 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
377
523
  // so a shape this cannot use is a genuine `invalid-arg`, not a
378
524
  // not-yet.
379
525
  fromAtom(atom, args) {
380
- // Mark the residual invalid and report so (a plain boolean, so no
381
- // void value is consumed by the callers' `return` statements).
526
+ // Mark the residual invalid. It answers NOTHING: every caller
527
+ // spells its exit `return bad(...)` and none reads a value, so a
528
+ // return type would be a value nobody consumes -- and a routine
529
+ // that answers on some paths and not others is the shape a reader
530
+ // (and a static analyser) has to stop and check.
382
531
  const bad = (why) => {
383
532
  this.invalid = why;
384
- return true;
385
533
  };
386
- // `unique()` takes no argument: it is a property of the container,
387
- // not a comparison against a value. Arity is checked at parse, so a
388
- // written argument never reaches here.
534
+ // `unique()` is a property of the container -- not a comparison
535
+ // against a value -- so with no argument it says the members are
536
+ // pairwise DISTINCT. THE ONE ARGUMENT IS A PROJECTOR (the review's
537
+ // finding I: "unique()-by-field is reserved but absent", and the
538
+ // arity was reserved for exactly this): `unique(port)` says no two
539
+ // members share a `port`, which is how "no two services share a
540
+ // port" and "event ids are unique" are said.
389
541
  if ('unique' === atom) {
390
- this.uniq = true;
542
+ if (0 === args.length) {
543
+ this.uniq = true;
544
+ return;
545
+ }
546
+ if (1 !== args.length || !stringLeaf(args[0])) {
547
+ return bad('invalid-arg');
548
+ }
549
+ this.uniqBy = [args[0].peg];
391
550
  return;
392
551
  }
393
552
  // `must(c, msg)` is Band B: an evaluate-only check against the
@@ -411,6 +570,9 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
411
570
  if (holdsNil(args[0])) {
412
571
  return bad('invalid-arg');
413
572
  }
573
+ // (An effectful argument is refused at construction, in the
574
+ // constructor: by the time this arm sees a settled `move($.b)`
575
+ // the move has already run.)
414
576
  this.musts = [{ v: args[0], msg: args[1] }];
415
577
  return;
416
578
  }
@@ -420,7 +582,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
420
582
  }
421
583
  let domain = undefined;
422
584
  for (const a of args) {
423
- const d = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined;
585
+ const d = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined;
424
586
  if (null == d || (null != domain && d !== domain)) {
425
587
  return bad('invalid-arg');
426
588
  }
@@ -499,7 +661,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
499
661
  }
500
662
  return;
501
663
  }
502
- const domain = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined;
664
+ const domain = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined;
503
665
  if (null == domain) {
504
666
  return bad('invalid-arg');
505
667
  }
@@ -518,7 +680,20 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
518
680
  // Every branch of the ladder assigns, so no initialiser: a
519
681
  // residual is stable and the ladder is total.
520
682
  let out;
521
- if (null != this.pending) {
683
+ // A rel residual or a graph atom HOLDS constraints for the value
684
+ // they stand for (RELATIONS P1/P2): hand the drive over before
685
+ // any settling, exactly as a container does, so `rel(t) & re(x)`
686
+ // reads the same in either order. Reached through the INCLUDE
687
+ // flow (a loaded schema's conjunct re-drives with the constraint
688
+ // on the left) -- inline documents route rel/atom peers through
689
+ // unite's b-drives first, which is why use-cases/12-relations
690
+ // refused without this arm while every inline row stayed green;
691
+ // pinned by constraint-hands-drive-to-rel-and-atom.
692
+ if (true === peer?.isRel
693
+ || true === peer?.isGraphAtom) {
694
+ out = peer.unify(this, ctx);
695
+ }
696
+ else if (null != this.pending) {
522
697
  out = this.settle(peer, ctx);
523
698
  }
524
699
  else if (null != this.invalid) {
@@ -609,16 +784,17 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
609
784
  if (null == peer || peer.isTop) {
610
785
  return again;
611
786
  }
612
- if (peer.isNil) {
613
- return peer;
614
- }
787
+ // No nil-peer arm: `unite` returns a nil operand before dispatching
788
+ // to any Val's unify (ts/src/unify.ts), so a nil never reaches here
789
+ // — and were one to, the conjunct below folds to it unchanged.
615
790
  return new ConjunctVal_1.ConjunctVal({ peg: [again, peer] }, ctx);
616
791
  }
617
792
  // Membership: the peer scalar passes every part of the residual, or
618
793
  // the whole meet is a located conflict.
619
794
  admit(peer, ctx) {
620
- // No scalar has members, so a `unique()` residual admits none.
621
- if (this.uniq) {
795
+ // No scalar has members, so a `unique()` residual admits none --
796
+ // and neither does a `unique(k)` one, for the same reason.
797
+ if (this.uniq || 0 < this.uniqBy.length) {
622
798
  return this.fail(ctx, peer);
623
799
  }
624
800
  if (!stateAdmits(this, peer)) {
@@ -628,14 +804,17 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
628
804
  // Only a string among the scalars has a length, and it is counted
629
805
  // in CODE POINTS -- not UTF-16 units (this host's native count)
630
806
  // and not bytes (Go's). Iterating a string yields code points.
631
- if (!stringLeaf(peer)) {
807
+ if (!stringishLeaf(peer)) {
632
808
  return this.fail(ctx, peer);
633
809
  }
634
810
  if (!stateAdmits(this.count, countVal([...peer.peg].length))) {
635
811
  return this.fail(ctx, peer);
636
812
  }
637
813
  }
638
- const bad = this.checkMusts(peer, ctx);
814
+ // A SCALAR HAS NO MEMBERS to accumulate, so its musts are decided
815
+ // here and never residuate: the final reading is the only reading
816
+ // a scalar has.
817
+ const bad = this.checkMusts(peer, ctx, true);
639
818
  if (null != bad) {
640
819
  return bad;
641
820
  }
@@ -649,10 +828,29 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
649
828
  // The trial runs in an isolated collect context so a failing check
650
829
  // leaves nothing on the caller's error list — only the located nil
651
830
  // this returns, carrying the author's own message.
652
- checkMusts(peer, ctx) {
831
+ // `final` is the generation-time reading (see settleContainer): a
832
+ // must whose own check is a SIZING atom inherits that atom's
833
+ // provisionality, because `must(length(2), …)` against a map that may
834
+ // still gain members has not failed yet -- it has not been answered.
835
+ // Without this the must would decide against whatever the container
836
+ // held when it first settled, which is the very defect the sizing
837
+ // atoms were fixed for (use-cases/BUGS.md §16, §17).
838
+ checkMusts(peer, ctx, final) {
653
839
  for (const m of this.musts) {
654
840
  const trial = ctx.clone({ err: [], collect: true });
655
- const got = (0, unify_1.unite)(trial, m.v.clone(trial), peer.clone(trial), 'must');
841
+ let got = (0, unify_1.unite)(trial, m.v.clone(trial), peer.clone(trial), 'must');
842
+ // A must whose check is a SIZING atom answers a residue rather
843
+ // than a verdict, because the atom is waiting for members that
844
+ // may still arrive. Before generation that residue IS the answer
845
+ // -- the must has not failed, it has not been asked yet -- and at
846
+ // generation it is settled, because nothing more can arrive.
847
+ const residue = (0, BagVal_1.sizingResidue)(got);
848
+ if (undefined !== residue) {
849
+ if (true !== final) {
850
+ continue;
851
+ }
852
+ got = residue.con.settleContainer(residue.bag, trial);
853
+ }
656
854
  if (true === got?.isNil || 0 < trial.err.length) {
657
855
  return (0, err_1.makeNilErr)(ctx, 'must', this, peer, undefined, {
658
856
  message: m.msg.peg,
@@ -663,6 +861,14 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
663
861
  }
664
862
  return undefined;
665
863
  }
864
+ // THE FINAL READING of a sizing atom over a container: the same
865
+ // checks admitContainer makes, with the provisional ones taken as
866
+ // decided. Called from ConjunctVal.gen, which is where no more
867
+ // members can arrive. Returns the container (to generate) or the
868
+ // constraint's own nil (to report).
869
+ settleContainer(peer, ctx) {
870
+ return this.admitContainer(peer, ctx, true);
871
+ }
666
872
  // Membership for a container peer. Only the SIZING atoms have anything
667
873
  // to say about a map or a list; every other atom is scalar-domain and
668
874
  // refuses one.
@@ -674,7 +880,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
674
880
  // isolated collect context so nothing leaks into the caller's errors.
675
881
  // A mirror would be a second copy of a filter that has already grown
676
882
  // subtle, free to drift from it; asking is correct by construction.
677
- admitContainer(peer, ctx) {
883
+ admitContainer(peer, ctx, final) {
678
884
  // A scalar-domain residual has no reading over a container.
679
885
  if (null != this.domain) {
680
886
  return this.fail(ctx, peer);
@@ -686,21 +892,76 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
686
892
  this.dc = 0;
687
893
  return new ConjunctVal_1.ConjunctVal({ peg: [this, peer] }, ctx);
688
894
  }
689
- const bad = this.checkMusts(peer, ctx);
895
+ const bad = this.checkMusts(peer, ctx, final);
690
896
  if (null != bad) {
691
897
  return bad;
692
898
  }
693
- if (!this.uniq && null == this.count) {
694
- return peer;
899
+ // A MUST OVER A CONTAINER RESIDUATES with the atoms, for the same
900
+ // reason (use-cases/BUGS.md §17): `must({t: max(60)}, …)` beside a
901
+ // `{t: integer}` schema was answered against the schema layer
902
+ // ALONE, and discharged before the data it was written to judge
903
+ // ever arrived. It is decided at generation, where the value is
904
+ // whole -- unless there is nothing else on the constraint AND the
905
+ // reading is already final.
906
+ if (!this.uniq && 0 === this.uniqBy.length && null == this.count) {
907
+ if (true === final || 0 === this.musts.length) {
908
+ return peer;
909
+ }
910
+ return this.hold(peer, ctx);
695
911
  }
696
912
  const members = emittedMembers(peer, ctx);
697
913
  if (null == members) {
698
- // The container cannot generate at all; its own error is the one
699
- // worth reporting, so pass it through untouched.
700
- return peer;
701
- }
702
- if (null != this.count && !stateAdmits(this.count, countVal(members.length))) {
703
- return this.fail(ctx, peer);
914
+ // NOT COUNTABLE YET IS NOT A PASS. A container that cannot
915
+ // generate cannot be counted, and a SCHEMA is exactly that: the
916
+ // members of `{a: integer}` are types, so nothing is emitted and
917
+ // nothing can be counted. Discharging the atom here was the §16
918
+ // defect wearing its other face -- `length(min(2)) & {a: integer}`
919
+ // dropped its bound while still alone, so the data half of a
920
+ // `vet` meet was never measured at all and short data vetted
921
+ // clean against a bound the evaluator enforces.
922
+ //
923
+ // At generation the reading IS final: a container that still
924
+ // cannot generate has its own error, and that error is the one
925
+ // worth reporting, so it passes through untouched.
926
+ if (true === final) {
927
+ return peer;
928
+ }
929
+ return this.hold(peer, ctx);
930
+ }
931
+ // MONOTONE READINGS ONLY (the review's finding C, use-cases/BUGS.md
932
+ // §16). Members ACCUMULATE under unification -- a meet adds keys and
933
+ // never removes them -- so a settled container is settled ON ITS
934
+ // OWN and not against every value that may still contribute. The
935
+ // schema half of a `vet` meet, a document that is about to receive
936
+ // an `@` include, a bag a later `pack` will fill: each settles, and
937
+ // an atom that decided there decided too early.
938
+ //
939
+ // So a verdict is taken only when MORE MEMBERS CANNOT CHANGE IT:
940
+ // - an upper bound VIOLATED is permanent -> refuse now;
941
+ // satisfied is provisional -> keep the atom, re-check later.
942
+ // - a lower bound SATISFIED is permanent -> that part may go;
943
+ // violated is provisional -> keep the atom (which is why
944
+ // `length(min(1))` beside a template no longer kills the schema
945
+ // it was written for).
946
+ // - a DUPLICATE is permanent -> refuse now; distinctness is
947
+ // provisional -> keep the atom.
948
+ // Anything provisional residuates, exactly as an unsettled
949
+ // container does above, and the two spellings then agree.
950
+ const count = null == this.count ? undefined : this.count;
951
+ const n = members.length;
952
+ if (null != count) {
953
+ if (null != count.hi && !stateAdmits({ ...count, lo: undefined }, countVal(n))) {
954
+ return this.fail(ctx, peer);
955
+ }
956
+ if (0 < count.neqs.length && !stateAdmits({ ...count, lo: undefined, hi: undefined }, countVal(n))) {
957
+ return this.fail(ctx, peer);
958
+ }
959
+ // The provisional half, decided only when nothing more can
960
+ // arrive: a lower bound still short is a refusal at generation
961
+ // and a residue before it.
962
+ if (true === final && !stateAdmits(count, countVal(n))) {
963
+ return this.fail(ctx, peer);
964
+ }
704
965
  }
705
966
  if (this.uniq) {
706
967
  // Members compare by CANONICAL FORM, which reduces to scalar
@@ -717,7 +978,65 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
717
978
  seen.add(key);
718
979
  }
719
980
  }
720
- return peer;
981
+ // ... and the same test per PROJECTED key. A member that has no such
982
+ // key FAILS the constraint rather than being skipped: distinctness
983
+ // it cannot be shown to have is distinctness it does not have, and
984
+ // skipping would let one keyless record hide a duplicate.
985
+ for (const field of this.uniqBy) {
986
+ const seen = new Set();
987
+ for (const m of members) {
988
+ const at = true === m.isMap ?
989
+ m.peg[field] : undefined;
990
+ if (null == at) {
991
+ return this.fail(ctx, peer);
992
+ }
993
+ const key = at.canon;
994
+ if (seen.has(key)) {
995
+ return this.fail(ctx, peer);
996
+ }
997
+ seen.add(key);
998
+ }
999
+ }
1000
+ // WHAT IS LEFT IS PROVISIONAL, so the atom stays on the value. A
1001
+ // lower bound already met is the one reading that cannot be undone,
1002
+ // and an atom holding nothing else is spent: that is when it goes.
1003
+ const spent = true === final || (0 === this.musts.length &&
1004
+ !this.uniq && 0 === this.uniqBy.length &&
1005
+ (null == count ||
1006
+ (null == count.hi && 0 === count.neqs.length &&
1007
+ stateAdmits(count, countVal(n)))));
1008
+ if (spent) {
1009
+ return peer;
1010
+ }
1011
+ // DONE, unlike the unsettled case above. The container HAS settled;
1012
+ // the atom is kept only because a LATER value could still add
1013
+ // members, and a residue that reported itself unresolved would
1014
+ // leave every enclosing value unresolved with it -- a `type()`
1015
+ // waiting on its argument for ever, and use case 09's whole
1016
+ // registry with it.
1017
+ // The ATOM is done too, not just the conjunct. An earlier pass may
1018
+ // have set `dc = 0` on the unsettled branch above, and a conjunct
1019
+ // recomputes its own doneness from its terms on every later meet --
1020
+ // so a stale 0 here would drag the residue, and every value holding
1021
+ // it, back to unresolved for ever.
1022
+ return this.hold(peer, ctx);
1023
+ }
1024
+ // The atom kept on a value whose reading is still provisional.
1025
+ // DONE, unlike the unsettled container above: the value HAS settled;
1026
+ // the atom is kept only because a LATER value could still add
1027
+ // members, and a residue that reported itself unresolved would leave
1028
+ // every enclosing value unresolved with it -- a `type()` waiting on
1029
+ // its argument for ever, and use case 09's whole registry with it.
1030
+ // The ATOM is done too, not just the conjunct. An earlier pass may
1031
+ // have set `dc = 0` on the unsettled branch, and a conjunct
1032
+ // recomputes its own doneness from its terms on every later meet --
1033
+ // so a stale 0 here would drag the residue, and every value holding
1034
+ // it, back to unresolved for ever.
1035
+ hold(peer, ctx) {
1036
+ this.dc = type_1.DONE;
1037
+ const held = new ConjunctVal_1.ConjunctVal({ peg: [this, peer] }, ctx);
1038
+ held.dc = type_1.DONE;
1039
+ return held;
721
1040
  }
722
1041
  // Meet with a kind: `number` (or `string` on the string domain) is
723
1042
  // already implied by an ORDER atom's argument; a numeric LEAF narrows
@@ -782,6 +1101,11 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
782
1101
  null == peer.count ? this.count : meetCount(this.count, peer.count);
783
1102
  // `unique()` is idempotent: two of them are one.
784
1103
  merged.uniq = this.uniq || peer.uniq;
1104
+ // `unique(a) & unique(b)` is BOTH, not the later one: each names a
1105
+ // key on which the members must differ, and dropping either would
1106
+ // silently weaken the constraint. Sorted and deduplicated, so the
1107
+ // meet is commutative and `unique(a) & unique(a)` is one atom.
1108
+ merged.uniqBy = [...new Set([...this.uniqBy, ...peer.uniqBy])].sort();
785
1109
  // Band B checks accumulate in written order and are never merged,
786
1110
  // deduplicated or reordered: each carries its own author message,
787
1111
  // and two checks with the same shape may still say different things.
@@ -822,6 +1146,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
822
1146
  res: [...this.res],
823
1147
  count: this.count,
824
1148
  uniq: this.uniq,
1149
+ uniqBy: [...this.uniqBy],
825
1150
  musts: [...this.musts],
826
1151
  invalid: this.invalid,
827
1152
  };
@@ -839,6 +1164,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
839
1164
  out.res = [...this.res];
840
1165
  out.count = this.count;
841
1166
  out.uniq = this.uniq;
1167
+ out.uniqBy = [...this.uniqBy];
842
1168
  out.musts = [...this.musts];
843
1169
  out.pending = this.pending;
844
1170
  out.cjo = this.cjo;
@@ -903,10 +1229,11 @@ function dedupSorted(domain, neqs) {
903
1229
  // Is this value, or anything inside it, a nil? A written argument that
904
1230
  // holds one can never be satisfied, so the atom refuses it as an
905
1231
  // argument rather than reporting a mystery failure against every peer.
1232
+ // Every caller passes a Val: `args[0]` comes from atomArgs, and the
1233
+ // walk below descends only into container pegs, which hold Vals. The
1234
+ // null/non-Val guard this function used to open with was therefore
1235
+ // unreachable, and the coverage gate said so.
906
1236
  function holdsNil(v) {
907
- if (null == v || true !== v.isVal) {
908
- return false;
909
- }
910
1237
  if (true === v.isNil) {
911
1238
  return true;
912
1239
  }
@@ -923,6 +1250,29 @@ function holdsNil(v) {
923
1250
  }
924
1251
  return false;
925
1252
  }
1253
+ // Whether a value, or anything inside it, is an effectful call — one
1254
+ // whose evaluation changes a node OTHER than the one being computed.
1255
+ // `move()` is the only builtin that does: it hides its resolution
1256
+ // target in place. Band B refuses one as an argument, because settling
1257
+ // it runs the effect against the live root before `must`'s trial clone
1258
+ // is taken, and a check that mutates cannot be report-only.
1259
+ function holdsMove(v) {
1260
+ if (true === v.isFunc && 'move' === v.funcname?.()) {
1261
+ return true;
1262
+ }
1263
+ const peg = v.peg;
1264
+ if (Array.isArray(peg)) {
1265
+ return peg.some((c) => holdsMove(c));
1266
+ }
1267
+ if (null != peg && 'object' === typeof peg) {
1268
+ for (const k in peg) {
1269
+ if (holdsMove(peg[k])) {
1270
+ return true;
1271
+ }
1272
+ }
1273
+ }
1274
+ return false;
1275
+ }
926
1276
  // The written arguments of an atom, flattened.
927
1277
  //
928
1278
  // A multi-argument call arrives from the func-paren grammar as ONE
@@ -979,6 +1329,12 @@ function canonState(s) {
979
1329
  if (s.uniq) {
980
1330
  parts.push('unique()');
981
1331
  }
1332
+ // After the bare atom, and sorted: canon is a normal form, so two
1333
+ // documents writing the same keys in different orders must render
1334
+ // the same string.
1335
+ for (const key of s.uniqBy) {
1336
+ parts.push('unique(' + JSON.stringify(key) + ')');
1337
+ }
982
1338
  for (const m of s.musts) {
983
1339
  parts.push('must(' + m.v.canon + ',' + m.msg.canon + ')');
984
1340
  }
@@ -988,13 +1344,117 @@ function canonState(s) {
988
1344
  }
989
1345
  return parts.join('&');
990
1346
  }
1347
+ // SUBSUMPTION over two residuals (G3, the query built on the table in
1348
+ // docs/reference-language.md "Subsumption"): does every value `s`
1349
+ // admits pass `g` too? Implemented here because the compare machinery
1350
+ // (cmpVal, sameScalar, the recursive count state) is this module's.
1351
+ //
1352
+ // Three-valued: true / false / 'undecided'. `must` on the GENERAL side
1353
+ // is undecided by construction (a Band B predicate is opaque — G3 maps
1354
+ // the table's "never" to the query's honest sub_evaluate_only); every
1355
+ // other approximation folds toward false, the safe direction.
1356
+ function constraintStateSubsumes(g, s) {
1357
+ // A Band B predicate on the general side makes its admitted set
1358
+ // unknowable; an extra `must` on the SPECIFIC side only narrows it
1359
+ // and is ignored.
1360
+ if (0 < g.musts.length) {
1361
+ return 'undecided';
1362
+ }
1363
+ // Domains must agree where both constrain one; a sizing-only residual
1364
+ // has no domain and passes this gate.
1365
+ if (null != g.domain && g.domain !== s.domain) {
1366
+ return false;
1367
+ }
1368
+ // A general leaf restriction requires the same leaf on the specific
1369
+ // side (leaves are disjoint; an unrestricted specific admits other
1370
+ // leaves the general refuses).
1371
+ if (null != g.kind && g.kind !== s.kind) {
1372
+ return false;
1373
+ }
1374
+ // Interval containment: the general's endpoints at or beyond the
1375
+ // specific's, and where they coincide the general's may not be the
1376
+ // open one.
1377
+ const d = g.domain ?? s.domain;
1378
+ if (null != g.lo) {
1379
+ if (null == s.lo || null == d) {
1380
+ return false;
1381
+ }
1382
+ const c = cmpVal(d, g.lo.v, s.lo.v);
1383
+ if (0 < c || (0 === c && g.lo.open && !s.lo.open)) {
1384
+ return false;
1385
+ }
1386
+ }
1387
+ if (null != g.hi) {
1388
+ if (null == s.hi || null == d) {
1389
+ return false;
1390
+ }
1391
+ const c = cmpVal(d, g.hi.v, s.hi.v);
1392
+ if (c < 0 || (0 === c && g.hi.open && !s.hi.open)) {
1393
+ return false;
1394
+ }
1395
+ }
1396
+ // Excluding FEWER values is more general: every general exclusion
1397
+ // must be excluded by the specific too.
1398
+ for (const n of g.neqs) {
1399
+ if (!s.neqs.some((m) => sameScalar(n, m))) {
1400
+ return false;
1401
+ }
1402
+ }
1403
+ // Patterns compare as TEXT sets (the sanctioned approximation:
1404
+ // deciding regex containment is what this algebra refuses to do).
1405
+ for (const r of g.res) {
1406
+ if (!s.res.some((q) => q.src === r.src)) {
1407
+ return false;
1408
+ }
1409
+ }
1410
+ // `unique()` subsumes only itself on that axis: a general uniqueness
1411
+ // demand admits no container the unconstrained specific also admits.
1412
+ // ... and a general `unique(k)` needs the same key on the specific
1413
+ // side: distinctness on `port` says nothing about distinctness on
1414
+ // `name`.
1415
+ if (g.uniqBy.some((k) => !s.uniqBy.includes(k))) {
1416
+ return false;
1417
+ }
1418
+ if (g.uniq && !s.uniq) {
1419
+ return false;
1420
+ }
1421
+ // The count atom reuses this same table over the integer domain.
1422
+ if (null != g.count) {
1423
+ if (null == s.count) {
1424
+ return false;
1425
+ }
1426
+ return constraintStateSubsumes(g.count, s.count);
1427
+ }
1428
+ return true;
1429
+ }
1430
+ // The query surface for ts/src/subsume.ts: residual-vs-residual, and
1431
+ // residual-vs-scalar membership (with `must` and `unique` making the
1432
+ // scalar case undecided/false exactly as the meet would).
1433
+ function constraintSubsumesConstraint(g, s) {
1434
+ return constraintStateSubsumes(g, s);
1435
+ }
1436
+ function constraintAdmitsScalar(g, scalar) {
1437
+ if (0 < g.musts.length) {
1438
+ return 'undecided';
1439
+ }
1440
+ if (g.uniq || 0 < g.uniqBy.length) {
1441
+ return false;
1442
+ }
1443
+ // A count residual admits by LENGTH, which is the meet's business;
1444
+ // the query answers false (the safe direction) rather than
1445
+ // reimplementing the sizing walk here.
1446
+ if (null != g.count) {
1447
+ return false;
1448
+ }
1449
+ return stateAdmits(g, scalar);
1450
+ }
991
1451
  // Does this residual's ORDER and MEMBERSHIP part admit the scalar? The
992
1452
  // sizing atoms are deliberately not consulted: `admit` applies them to
993
1453
  // the peer's length, and the count check applies this same function to
994
1454
  // the count.
995
1455
  function stateAdmits(s, peer) {
996
1456
  const domainOf = numericLeaf(peer) ? 'number' :
997
- stringLeaf(peer) ? 'string' : undefined;
1457
+ stringishLeaf(peer) ? 'string' : undefined;
998
1458
  if (null == s.domain) {
999
1459
  // A sizing residual has no domain, and admits any scalar the sizing
1000
1460
  // atoms can then rule on. Booleans and null are not among them:
@@ -1096,7 +1556,8 @@ function stateEmpty(s) {
1096
1556
  // members, so `integer & length(3)` and `min(2) & unique()` admit
1097
1557
  // nothing. Uniqueness over the string domain is empty for the same
1098
1558
  // reason -- a string's members are not values the algebra compares.
1099
- if ('number' === d && (null != s.count || s.uniq)) {
1559
+ if ('number' === d && (null != s.count || s.uniq ||
1560
+ 0 < s.uniqBy.length)) {
1100
1561
  return true;
1101
1562
  }
1102
1563
  if ('string' === d && s.uniq) {
@@ -1121,7 +1582,9 @@ function countBase() {
1121
1582
  neqs: [],
1122
1583
  res: [],
1123
1584
  musts: [],
1585
+ // A COUNT is a number, and a number has no members to be distinct.
1124
1586
  uniq: false,
1587
+ uniqBy: [],
1125
1588
  };
1126
1589
  }
1127
1590
  // A count as a Val, so the count residual can be applied by exactly the
@@ -1138,13 +1601,16 @@ function countVal(n) {
1138
1601
  function meetCount(a, b) {
1139
1602
  return {
1140
1603
  domain: 'number',
1141
- kind: a.kind ?? b.kind,
1604
+ // `a` is always a countBase()-seeded residual, so its kind is
1605
+ // always Integer — there is no kindless side to fall back from.
1606
+ kind: a.kind,
1142
1607
  lo: tighter('number', a.lo, b.lo, true),
1143
1608
  hi: tighter('number', a.hi, b.hi, false),
1144
1609
  neqs: dedupSorted('number', [...a.neqs, ...b.neqs]),
1145
1610
  res: [],
1146
1611
  musts: [],
1147
1612
  uniq: false,
1613
+ uniqBy: [],
1148
1614
  clash: true === a.clash || true === b.clash ||
1149
1615
  (null != a.kind && null != b.kind && a.kind !== b.kind),
1150
1616
  };
@@ -1167,14 +1633,15 @@ function countArgState(arg) {
1167
1633
  domain: 'number',
1168
1634
  lo: { v: arg, open: false },
1169
1635
  hi: { v: arg, open: false },
1170
- neqs: [], res: [], musts: [], uniq: false,
1636
+ neqs: [], res: [], musts: [], uniq: false, uniqBy: [],
1171
1637
  };
1172
1638
  }
1173
1639
  if (true === arg?.isConstraint) {
1174
1640
  const c = arg;
1175
1641
  // A pattern, a sizing atom or a string bound inside a count is not
1176
1642
  // a count constraint at all, and neither is a broken one.
1177
- if (null != c.invalid || 0 < c.res.length || c.uniq || null != c.count ||
1643
+ if (null != c.invalid || 0 < c.res.length || c.uniq ||
1644
+ 0 < c.uniqBy.length || null != c.count ||
1178
1645
  'number' !== c.domain) {
1179
1646
  return undefined;
1180
1647
  }
@@ -1184,17 +1651,23 @@ function countArgState(arg) {
1184
1651
  lo: c.lo,
1185
1652
  hi: c.hi,
1186
1653
  neqs: [...c.neqs],
1187
- res: [], musts: [], uniq: false,
1654
+ res: [], musts: [], uniq: false, uniqBy: [],
1188
1655
  };
1189
1656
  }
1190
1657
  if (true === arg?.isScalarKind) {
1191
1658
  const marker = arg.peg;
1192
1659
  if (Number === marker) {
1193
- return { domain: 'number', neqs: [], res: [], musts: [], uniq: false };
1660
+ return {
1661
+ domain: 'number', neqs: [], res: [], musts: [],
1662
+ uniq: false, uniqBy: [],
1663
+ };
1194
1664
  }
1195
1665
  if (ScalarKindVal_1.Integer === marker || ScalarKindVal_1.Float === marker ||
1196
1666
  ScalarKindVal_1.BigInteger === marker || ScalarKindVal_1.BigDecimal === marker) {
1197
- return { domain: 'number', kind: marker, neqs: [], res: [], musts: [], uniq: false };
1667
+ return {
1668
+ domain: 'number', kind: marker, neqs: [], res: [], musts: [],
1669
+ uniq: false, uniqBy: [],
1670
+ };
1198
1671
  }
1199
1672
  return undefined;
1200
1673
  }
@@ -1221,7 +1694,12 @@ function genable(child) {
1221
1694
  return true === child.isScalar || true === child.isMap ||
1222
1695
  true === child.isList || true === child.isPref ||
1223
1696
  true === child.isRef || true === child.isDisjunct ||
1224
- true === child.isNil;
1697
+ true === child.isNil ||
1698
+ // A settled sizing residue generates through ConjunctVal.gen, so it
1699
+ // is a member like any other -- and a `length` that did not count
1700
+ // its sibling's `unique` would be the early-fold defect again, one
1701
+ // level up.
1702
+ undefined !== (0, BagVal_1.sizingResidue)(child);
1225
1703
  }
1226
1704
  // The children a bag would EMIT, mirroring the selection in
1227
1705
  // `BagVal.gen` — type/hide marks skipped, non-generable residue and
@@ -1343,6 +1821,6 @@ class UniqueConstraintVal extends ConstraintVal {
1343
1821
  constructor(spec, ctx) {
1344
1822
  super({ ...spec, atom: 'unique' }, ctx);
1345
1823
  }
1346
- } /* node:coverage ignore next 19 */
1824
+ } /* node:coverage ignore next 23 */
1347
1825
  exports.UniqueConstraintVal = UniqueConstraintVal;
1348
1826
  //# sourceMappingURL=ConstraintVal.js.map