aontu 0.52.1 → 0.53.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 (272) 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 +14 -3
  7. package/dist/aontu.js +96 -4
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +44 -1
  10. package/dist/cli.js +2401 -44
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +13 -0
  13. package/dist/ctx.js +43 -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 +38 -7
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +16 -0
  22. package/dist/graph.js +73 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +146 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +167 -4
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +391 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +512 -69
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -47
  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 +814 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +498 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +31 -0
  47. package/dist/mod.js +250 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +44 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +40 -0
  53. package/dist/provenance.js +335 -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 +14 -0
  59. package/dist/reach.js +140 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +19 -0
  62. package/dist/relation.js +305 -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/site.d.ts +4 -0
  68. package/dist/site.js +31 -0
  69. package/dist/site.js.map +1 -1
  70. package/dist/std.d.ts +1 -0
  71. package/dist/std.js +73 -0
  72. package/dist/std.js.map +1 -0
  73. package/dist/subsume.d.ts +39 -0
  74. package/dist/subsume.js +526 -0
  75. package/dist/subsume.js.map +1 -0
  76. package/dist/trim.d.ts +19 -0
  77. package/dist/trim.js +155 -0
  78. package/dist/trim.js.map +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/dist/type.d.ts +17 -1
  81. package/dist/type.js.map +1 -1
  82. package/dist/unify.d.ts +2 -1
  83. package/dist/unify.js +253 -36
  84. package/dist/unify.js.map +1 -1
  85. package/dist/utility.d.ts +9 -1
  86. package/dist/utility.js +122 -1
  87. package/dist/utility.js.map +1 -1
  88. package/dist/val/AggFuncVal.d.ts +33 -0
  89. package/dist/val/AggFuncVal.js +202 -0
  90. package/dist/val/AggFuncVal.js.map +1 -0
  91. package/dist/val/ArithFuncVal.d.ts +31 -0
  92. package/dist/val/ArithFuncVal.js +62 -0
  93. package/dist/val/ArithFuncVal.js.map +1 -0
  94. package/dist/val/BagVal.d.ts +5 -0
  95. package/dist/val/BagVal.js +96 -5
  96. package/dist/val/BagVal.js.map +1 -1
  97. package/dist/val/CloseFuncVal.js +9 -1
  98. package/dist/val/CloseFuncVal.js.map +1 -1
  99. package/dist/val/ConjunctVal.d.ts +1 -1
  100. package/dist/val/ConjunctVal.js +19 -0
  101. package/dist/val/ConjunctVal.js.map +1 -1
  102. package/dist/val/ConstraintVal.d.ts +7 -1
  103. package/dist/val/ConstraintVal.js +490 -40
  104. package/dist/val/ConstraintVal.js.map +1 -1
  105. package/dist/val/CopyFuncVal.d.ts +1 -2
  106. package/dist/val/CopyFuncVal.js +7 -0
  107. package/dist/val/CopyFuncVal.js.map +1 -1
  108. package/dist/val/Decimal.d.ts +1 -0
  109. package/dist/val/Decimal.js +13 -0
  110. package/dist/val/Decimal.js.map +1 -1
  111. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  112. package/dist/val/DeprecateFuncVal.js +47 -0
  113. package/dist/val/DeprecateFuncVal.js.map +1 -0
  114. package/dist/val/DisjunctVal.js +130 -21
  115. package/dist/val/DisjunctVal.js.map +1 -1
  116. package/dist/val/EachFuncVal.d.ts +15 -0
  117. package/dist/val/EachFuncVal.js +75 -0
  118. package/dist/val/EachFuncVal.js.map +1 -0
  119. package/dist/val/ExpectVal.js +27 -4
  120. package/dist/val/ExpectVal.js.map +1 -1
  121. package/dist/val/FeatureVal.js +1 -1
  122. package/dist/val/FeatureVal.js.map +1 -1
  123. package/dist/val/FilterFuncVal.d.ts +15 -0
  124. package/dist/val/FilterFuncVal.js +91 -0
  125. package/dist/val/FilterFuncVal.js.map +1 -0
  126. package/dist/val/FuncBaseVal.d.ts +6 -1
  127. package/dist/val/FuncBaseVal.js +171 -1
  128. package/dist/val/FuncBaseVal.js.map +1 -1
  129. package/dist/val/HideFuncVal.js.map +1 -1
  130. package/dist/val/IdFuncVal.d.ts +13 -0
  131. package/dist/val/IdFuncVal.js +54 -0
  132. package/dist/val/IdFuncVal.js.map +1 -0
  133. package/dist/val/JunctionVal.js +7 -1
  134. package/dist/val/JunctionVal.js.map +1 -1
  135. package/dist/val/KeyFuncVal.d.ts +1 -1
  136. package/dist/val/KeyFuncVal.js +38 -30
  137. package/dist/val/KeyFuncVal.js.map +1 -1
  138. package/dist/val/ListVal.js +101 -14
  139. package/dist/val/ListVal.js.map +1 -1
  140. package/dist/val/LowerFuncVal.js.map +1 -1
  141. package/dist/val/MapVal.js +93 -8
  142. package/dist/val/MapVal.js.map +1 -1
  143. package/dist/val/MatchFuncVal.d.ts +15 -0
  144. package/dist/val/MatchFuncVal.js +107 -0
  145. package/dist/val/MatchFuncVal.js.map +1 -0
  146. package/dist/val/MoveFuncVal.js.map +1 -1
  147. package/dist/val/NilVal.js +24 -0
  148. package/dist/val/NilVal.js.map +1 -1
  149. package/dist/val/OpBaseVal.d.ts +1 -1
  150. package/dist/val/OpBaseVal.js +19 -1
  151. package/dist/val/OpBaseVal.js.map +1 -1
  152. package/dist/val/OpenFuncVal.js +4 -1
  153. package/dist/val/OpenFuncVal.js.map +1 -1
  154. package/dist/val/PackFuncVal.d.ts +15 -0
  155. package/dist/val/PackFuncVal.js +108 -0
  156. package/dist/val/PackFuncVal.js.map +1 -0
  157. package/dist/val/PathFuncVal.js.map +1 -1
  158. package/dist/val/PlaceVal.d.ts +13 -0
  159. package/dist/val/PlaceVal.js +131 -0
  160. package/dist/val/PlaceVal.js.map +1 -0
  161. package/dist/val/PlusOpVal.js +11 -2
  162. package/dist/val/PlusOpVal.js.map +1 -1
  163. package/dist/val/PrefFuncVal.js.map +1 -1
  164. package/dist/val/PrefVal.d.ts +2 -2
  165. package/dist/val/PrefVal.js +78 -23
  166. package/dist/val/PrefVal.js.map +1 -1
  167. package/dist/val/RefVal.d.ts +1 -1
  168. package/dist/val/RefVal.js +89 -7
  169. package/dist/val/RefVal.js.map +1 -1
  170. package/dist/val/ReferFuncVal.d.ts +36 -0
  171. package/dist/val/ReferFuncVal.js +303 -0
  172. package/dist/val/ReferFuncVal.js.map +1 -0
  173. package/dist/val/ScalarKindVal.d.ts +1 -2
  174. package/dist/val/ScalarKindVal.js +0 -11
  175. package/dist/val/ScalarKindVal.js.map +1 -1
  176. package/dist/val/TopVal.js.map +1 -1
  177. package/dist/val/TypeFuncVal.js.map +1 -1
  178. package/dist/val/UpperFuncVal.js.map +1 -1
  179. package/dist/val/Val.d.ts +8 -1
  180. package/dist/val/Val.js +150 -1
  181. package/dist/val/Val.js.map +1 -1
  182. package/dist/val/VarVal.js.map +1 -1
  183. package/dist/val/arith.d.ts +6 -0
  184. package/dist/val/arith.js +170 -0
  185. package/dist/val/arith.js.map +1 -0
  186. package/dist/vet.d.ts +45 -0
  187. package/dist/vet.js +776 -0
  188. package/dist/vet.js.map +1 -0
  189. package/dist/walk.d.ts +2 -0
  190. package/dist/walk.js +91 -0
  191. package/dist/walk.js.map +1 -0
  192. package/grammar/aontu.gbnf +130 -0
  193. package/grammar/aontu.lark +113 -0
  194. package/package.json +21 -6
  195. package/skill/SKILL.md +37 -0
  196. package/skill/error-codes.md +62 -0
  197. package/skill/examples.md +99 -0
  198. package/skill/grammar-card.md +57 -0
  199. package/src/agentsmd.ts +135 -0
  200. package/src/aontu.ts +135 -4
  201. package/src/cli.ts +2858 -71
  202. package/src/ctx.ts +73 -1
  203. package/src/diff.ts +196 -0
  204. package/src/err.ts +42 -7
  205. package/src/graph.ts +135 -0
  206. package/src/hcanon.ts +169 -0
  207. package/src/hints.ts +208 -4
  208. package/src/jsonschema.ts +511 -0
  209. package/src/lang.ts +570 -70
  210. package/src/lsp.ts +281 -48
  211. package/src/mcp-server.ts +187 -0
  212. package/src/mcp.ts +993 -0
  213. package/src/mod-tool.ts +679 -0
  214. package/src/mod.ts +344 -0
  215. package/src/patch.ts +624 -0
  216. package/src/provenance.ts +430 -0
  217. package/src/query.ts +379 -0
  218. package/src/reach.ts +184 -0
  219. package/src/relation.ts +395 -0
  220. package/src/report-sarif.ts +137 -0
  221. package/src/site.ts +36 -1
  222. package/src/std.ts +73 -0
  223. package/src/subsume.ts +690 -0
  224. package/src/trim.ts +195 -0
  225. package/src/tsconfig.json +10 -4
  226. package/src/type.ts +51 -2
  227. package/src/unify.ts +274 -36
  228. package/src/utility.ts +139 -1
  229. package/src/val/AggFuncVal.ts +319 -0
  230. package/src/val/ArithFuncVal.ts +108 -0
  231. package/src/val/BagVal.ts +101 -4
  232. package/src/val/CloseFuncVal.ts +9 -1
  233. package/src/val/ConjunctVal.ts +20 -0
  234. package/src/val/ConstraintVal.ts +542 -43
  235. package/src/val/CopyFuncVal.ts +7 -1
  236. package/src/val/Decimal.ts +15 -0
  237. package/src/val/DeprecateFuncVal.ts +84 -0
  238. package/src/val/DisjunctVal.ts +139 -28
  239. package/src/val/EachFuncVal.ts +133 -0
  240. package/src/val/ExpectVal.ts +28 -6
  241. package/src/val/FeatureVal.ts +1 -1
  242. package/src/val/FilterFuncVal.ts +154 -0
  243. package/src/val/FuncBaseVal.ts +192 -1
  244. package/src/val/HideFuncVal.ts +0 -2
  245. package/src/val/IdFuncVal.ts +91 -0
  246. package/src/val/JunctionVal.ts +7 -1
  247. package/src/val/KeyFuncVal.ts +39 -35
  248. package/src/val/ListVal.ts +108 -15
  249. package/src/val/LowerFuncVal.ts +0 -1
  250. package/src/val/MapVal.ts +99 -8
  251. package/src/val/MatchFuncVal.ts +176 -0
  252. package/src/val/MoveFuncVal.ts +0 -2
  253. package/src/val/NilVal.ts +25 -0
  254. package/src/val/OpBaseVal.ts +20 -1
  255. package/src/val/OpenFuncVal.ts +4 -2
  256. package/src/val/PackFuncVal.ts +175 -0
  257. package/src/val/PathFuncVal.ts +0 -1
  258. package/src/val/PlaceVal.ts +193 -0
  259. package/src/val/PlusOpVal.ts +11 -2
  260. package/src/val/PrefFuncVal.ts +0 -1
  261. package/src/val/PrefVal.ts +79 -36
  262. package/src/val/RefVal.ts +91 -8
  263. package/src/val/ReferFuncVal.ts +387 -0
  264. package/src/val/ScalarKindVal.ts +0 -13
  265. package/src/val/TopVal.ts +0 -1
  266. package/src/val/TypeFuncVal.ts +0 -2
  267. package/src/val/UpperFuncVal.ts +0 -1
  268. package/src/val/Val.ts +205 -2
  269. package/src/val/VarVal.ts +0 -1
  270. package/src/val/arith.ts +316 -0
  271. package/src/vet.ts +992 -0
  272. 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'];
@@ -328,6 +449,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
328
449
  this.neqs = [];
329
450
  this.res = [];
330
451
  this.uniq = false;
452
+ this.uniqBy = [];
331
453
  this.musts = [];
332
454
  if (spec.state) {
333
455
  this.domain = spec.state.domain;
@@ -340,6 +462,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
340
462
  this.res = spec.state.res ?? [];
341
463
  this.count = spec.state.count;
342
464
  this.uniq = spec.state.uniq ?? false;
465
+ this.uniqBy = spec.state.uniqBy ?? [];
343
466
  this.musts = spec.state.musts ?? [];
344
467
  this.invalid = spec.state.invalid;
345
468
  }
@@ -351,14 +474,22 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
351
474
  // in unify, where there is a ctx to resolve through, and the
352
475
  // residual is built from the settled arguments. Only a settled
353
476
  // argument of the wrong shape is an `invalid-arg`.
354
- if (args.some((a) => true !== a?.done)) {
477
+ // The effectful-argument refusal happens HERE, on the written
478
+ // form, and not in fromAtom: settling is what runs the effect, so
479
+ // by the time fromAtom sees a settled `move($.b)` the move has
480
+ // already happened and the argument is just its result.
481
+ if ('must' === spec.atom && args.some((a) => holdsMove(a))) {
482
+ this.invalid = 'invalid-arg';
483
+ }
484
+ else if (args.some((a) => true !== a?.done)) {
355
485
  this.pending = { atom: spec.atom, args };
356
486
  }
357
487
  else {
358
488
  this.fromAtom(spec.atom, args);
359
489
  }
360
490
  }
361
- if (null != this.count || this.uniq || 0 < this.musts.length ||
491
+ if (null != this.count || this.uniq || 0 < this.uniqBy.length ||
492
+ 0 < this.musts.length ||
362
493
  (null != this.pending && lateAtom(this.pending.atom))) {
363
494
  this.cjo = LATE_CJO;
364
495
  }
@@ -377,17 +508,30 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
377
508
  // so a shape this cannot use is a genuine `invalid-arg`, not a
378
509
  // not-yet.
379
510
  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).
511
+ // Mark the residual invalid. It answers NOTHING: every caller
512
+ // spells its exit `return bad(...)` and none reads a value, so a
513
+ // return type would be a value nobody consumes -- and a routine
514
+ // that answers on some paths and not others is the shape a reader
515
+ // (and a static analyser) has to stop and check.
382
516
  const bad = (why) => {
383
517
  this.invalid = why;
384
- return true;
385
518
  };
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.
519
+ // `unique()` is a property of the container -- not a comparison
520
+ // against a value -- so with no argument it says the members are
521
+ // pairwise DISTINCT. THE ONE ARGUMENT IS A PROJECTOR (the review's
522
+ // finding I: "unique()-by-field is reserved but absent", and the
523
+ // arity was reserved for exactly this): `unique(port)` says no two
524
+ // members share a `port`, which is how "no two services share a
525
+ // port" and "event ids are unique" are said.
389
526
  if ('unique' === atom) {
390
- this.uniq = true;
527
+ if (0 === args.length) {
528
+ this.uniq = true;
529
+ return;
530
+ }
531
+ if (1 !== args.length || !stringLeaf(args[0])) {
532
+ return bad('invalid-arg');
533
+ }
534
+ this.uniqBy = [args[0].peg];
391
535
  return;
392
536
  }
393
537
  // `must(c, msg)` is Band B: an evaluate-only check against the
@@ -411,6 +555,9 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
411
555
  if (holdsNil(args[0])) {
412
556
  return bad('invalid-arg');
413
557
  }
558
+ // (An effectful argument is refused at construction, in the
559
+ // constructor: by the time this arm sees a settled `move($.b)`
560
+ // the move has already run.)
414
561
  this.musts = [{ v: args[0], msg: args[1] }];
415
562
  return;
416
563
  }
@@ -609,16 +756,17 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
609
756
  if (null == peer || peer.isTop) {
610
757
  return again;
611
758
  }
612
- if (peer.isNil) {
613
- return peer;
614
- }
759
+ // No nil-peer arm: `unite` returns a nil operand before dispatching
760
+ // to any Val's unify (ts/src/unify.ts), so a nil never reaches here
761
+ // — and were one to, the conjunct below folds to it unchanged.
615
762
  return new ConjunctVal_1.ConjunctVal({ peg: [again, peer] }, ctx);
616
763
  }
617
764
  // Membership: the peer scalar passes every part of the residual, or
618
765
  // the whole meet is a located conflict.
619
766
  admit(peer, ctx) {
620
- // No scalar has members, so a `unique()` residual admits none.
621
- if (this.uniq) {
767
+ // No scalar has members, so a `unique()` residual admits none --
768
+ // and neither does a `unique(k)` one, for the same reason.
769
+ if (this.uniq || 0 < this.uniqBy.length) {
622
770
  return this.fail(ctx, peer);
623
771
  }
624
772
  if (!stateAdmits(this, peer)) {
@@ -635,7 +783,10 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
635
783
  return this.fail(ctx, peer);
636
784
  }
637
785
  }
638
- const bad = this.checkMusts(peer, ctx);
786
+ // A SCALAR HAS NO MEMBERS to accumulate, so its musts are decided
787
+ // here and never residuate: the final reading is the only reading
788
+ // a scalar has.
789
+ const bad = this.checkMusts(peer, ctx, true);
639
790
  if (null != bad) {
640
791
  return bad;
641
792
  }
@@ -649,10 +800,29 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
649
800
  // The trial runs in an isolated collect context so a failing check
650
801
  // leaves nothing on the caller's error list — only the located nil
651
802
  // this returns, carrying the author's own message.
652
- checkMusts(peer, ctx) {
803
+ // `final` is the generation-time reading (see settleContainer): a
804
+ // must whose own check is a SIZING atom inherits that atom's
805
+ // provisionality, because `must(length(2), …)` against a map that may
806
+ // still gain members has not failed yet -- it has not been answered.
807
+ // Without this the must would decide against whatever the container
808
+ // held when it first settled, which is the very defect the sizing
809
+ // atoms were fixed for (use-cases/BUGS.md §16, §17).
810
+ checkMusts(peer, ctx, final) {
653
811
  for (const m of this.musts) {
654
812
  const trial = ctx.clone({ err: [], collect: true });
655
- const got = (0, unify_1.unite)(trial, m.v.clone(trial), peer.clone(trial), 'must');
813
+ let got = (0, unify_1.unite)(trial, m.v.clone(trial), peer.clone(trial), 'must');
814
+ // A must whose check is a SIZING atom answers a residue rather
815
+ // than a verdict, because the atom is waiting for members that
816
+ // may still arrive. Before generation that residue IS the answer
817
+ // -- the must has not failed, it has not been asked yet -- and at
818
+ // generation it is settled, because nothing more can arrive.
819
+ const residue = (0, BagVal_1.sizingResidue)(got);
820
+ if (undefined !== residue) {
821
+ if (true !== final) {
822
+ continue;
823
+ }
824
+ got = residue.con.settleContainer(residue.bag, trial);
825
+ }
656
826
  if (true === got?.isNil || 0 < trial.err.length) {
657
827
  return (0, err_1.makeNilErr)(ctx, 'must', this, peer, undefined, {
658
828
  message: m.msg.peg,
@@ -663,6 +833,14 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
663
833
  }
664
834
  return undefined;
665
835
  }
836
+ // THE FINAL READING of a sizing atom over a container: the same
837
+ // checks admitContainer makes, with the provisional ones taken as
838
+ // decided. Called from ConjunctVal.gen, which is where no more
839
+ // members can arrive. Returns the container (to generate) or the
840
+ // constraint's own nil (to report).
841
+ settleContainer(peer, ctx) {
842
+ return this.admitContainer(peer, ctx, true);
843
+ }
666
844
  // Membership for a container peer. Only the SIZING atoms have anything
667
845
  // to say about a map or a list; every other atom is scalar-domain and
668
846
  // refuses one.
@@ -674,7 +852,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
674
852
  // isolated collect context so nothing leaks into the caller's errors.
675
853
  // A mirror would be a second copy of a filter that has already grown
676
854
  // subtle, free to drift from it; asking is correct by construction.
677
- admitContainer(peer, ctx) {
855
+ admitContainer(peer, ctx, final) {
678
856
  // A scalar-domain residual has no reading over a container.
679
857
  if (null != this.domain) {
680
858
  return this.fail(ctx, peer);
@@ -686,21 +864,76 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
686
864
  this.dc = 0;
687
865
  return new ConjunctVal_1.ConjunctVal({ peg: [this, peer] }, ctx);
688
866
  }
689
- const bad = this.checkMusts(peer, ctx);
867
+ const bad = this.checkMusts(peer, ctx, final);
690
868
  if (null != bad) {
691
869
  return bad;
692
870
  }
693
- if (!this.uniq && null == this.count) {
694
- return peer;
871
+ // A MUST OVER A CONTAINER RESIDUATES with the atoms, for the same
872
+ // reason (use-cases/BUGS.md §17): `must({t: max(60)}, …)` beside a
873
+ // `{t: integer}` schema was answered against the schema layer
874
+ // ALONE, and discharged before the data it was written to judge
875
+ // ever arrived. It is decided at generation, where the value is
876
+ // whole -- unless there is nothing else on the constraint AND the
877
+ // reading is already final.
878
+ if (!this.uniq && 0 === this.uniqBy.length && null == this.count) {
879
+ if (true === final || 0 === this.musts.length) {
880
+ return peer;
881
+ }
882
+ return this.hold(peer, ctx);
695
883
  }
696
884
  const members = emittedMembers(peer, ctx);
697
885
  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);
886
+ // NOT COUNTABLE YET IS NOT A PASS. A container that cannot
887
+ // generate cannot be counted, and a SCHEMA is exactly that: the
888
+ // members of `{a: integer}` are types, so nothing is emitted and
889
+ // nothing can be counted. Discharging the atom here was the §16
890
+ // defect wearing its other face -- `length(min(2)) & {a: integer}`
891
+ // dropped its bound while still alone, so the data half of a
892
+ // `vet` meet was never measured at all and short data vetted
893
+ // clean against a bound the evaluator enforces.
894
+ //
895
+ // At generation the reading IS final: a container that still
896
+ // cannot generate has its own error, and that error is the one
897
+ // worth reporting, so it passes through untouched.
898
+ if (true === final) {
899
+ return peer;
900
+ }
901
+ return this.hold(peer, ctx);
902
+ }
903
+ // MONOTONE READINGS ONLY (the review's finding C, use-cases/BUGS.md
904
+ // §16). Members ACCUMULATE under unification -- a meet adds keys and
905
+ // never removes them -- so a settled container is settled ON ITS
906
+ // OWN and not against every value that may still contribute. The
907
+ // schema half of a `vet` meet, a document that is about to receive
908
+ // an `@` include, a bag a later `pack` will fill: each settles, and
909
+ // an atom that decided there decided too early.
910
+ //
911
+ // So a verdict is taken only when MORE MEMBERS CANNOT CHANGE IT:
912
+ // - an upper bound VIOLATED is permanent -> refuse now;
913
+ // satisfied is provisional -> keep the atom, re-check later.
914
+ // - a lower bound SATISFIED is permanent -> that part may go;
915
+ // violated is provisional -> keep the atom (which is why
916
+ // `length(min(1))` beside a template no longer kills the schema
917
+ // it was written for).
918
+ // - a DUPLICATE is permanent -> refuse now; distinctness is
919
+ // provisional -> keep the atom.
920
+ // Anything provisional residuates, exactly as an unsettled
921
+ // container does above, and the two spellings then agree.
922
+ const count = null == this.count ? undefined : this.count;
923
+ const n = members.length;
924
+ if (null != count) {
925
+ if (null != count.hi && !stateAdmits({ ...count, lo: undefined }, countVal(n))) {
926
+ return this.fail(ctx, peer);
927
+ }
928
+ if (0 < count.neqs.length && !stateAdmits({ ...count, lo: undefined, hi: undefined }, countVal(n))) {
929
+ return this.fail(ctx, peer);
930
+ }
931
+ // The provisional half, decided only when nothing more can
932
+ // arrive: a lower bound still short is a refusal at generation
933
+ // and a residue before it.
934
+ if (true === final && !stateAdmits(count, countVal(n))) {
935
+ return this.fail(ctx, peer);
936
+ }
704
937
  }
705
938
  if (this.uniq) {
706
939
  // Members compare by CANONICAL FORM, which reduces to scalar
@@ -717,7 +950,65 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
717
950
  seen.add(key);
718
951
  }
719
952
  }
720
- return peer;
953
+ // ... and the same test per PROJECTED key. A member that has no such
954
+ // key FAILS the constraint rather than being skipped: distinctness
955
+ // it cannot be shown to have is distinctness it does not have, and
956
+ // skipping would let one keyless record hide a duplicate.
957
+ for (const field of this.uniqBy) {
958
+ const seen = new Set();
959
+ for (const m of members) {
960
+ const at = true === m.isMap ?
961
+ m.peg[field] : undefined;
962
+ if (null == at) {
963
+ return this.fail(ctx, peer);
964
+ }
965
+ const key = at.canon;
966
+ if (seen.has(key)) {
967
+ return this.fail(ctx, peer);
968
+ }
969
+ seen.add(key);
970
+ }
971
+ }
972
+ // WHAT IS LEFT IS PROVISIONAL, so the atom stays on the value. A
973
+ // lower bound already met is the one reading that cannot be undone,
974
+ // and an atom holding nothing else is spent: that is when it goes.
975
+ const spent = true === final || (0 === this.musts.length &&
976
+ !this.uniq && 0 === this.uniqBy.length &&
977
+ (null == count ||
978
+ (null == count.hi && 0 === count.neqs.length &&
979
+ stateAdmits(count, countVal(n)))));
980
+ if (spent) {
981
+ return peer;
982
+ }
983
+ // DONE, unlike the unsettled case above. The container HAS settled;
984
+ // the atom is kept only because a LATER value could still add
985
+ // members, and a residue that reported itself unresolved would
986
+ // leave every enclosing value unresolved with it -- a `type()`
987
+ // waiting on its argument for ever, and use case 09's whole
988
+ // registry with it.
989
+ // The ATOM is done too, not just the conjunct. An earlier pass may
990
+ // have set `dc = 0` on the unsettled branch above, and a conjunct
991
+ // recomputes its own doneness from its terms on every later meet --
992
+ // so a stale 0 here would drag the residue, and every value holding
993
+ // it, back to unresolved for ever.
994
+ return this.hold(peer, ctx);
995
+ }
996
+ // The atom kept on a value whose reading is still provisional.
997
+ // DONE, unlike the unsettled container above: the value HAS settled;
998
+ // the atom is kept only because a LATER value could still add
999
+ // members, and a residue that reported itself unresolved would leave
1000
+ // every enclosing value unresolved with it -- a `type()` waiting on
1001
+ // its argument for ever, and use case 09's whole registry with it.
1002
+ // The ATOM is done too, not just the conjunct. An earlier pass may
1003
+ // have set `dc = 0` on the unsettled branch, and a conjunct
1004
+ // recomputes its own doneness from its terms on every later meet --
1005
+ // so a stale 0 here would drag the residue, and every value holding
1006
+ // it, back to unresolved for ever.
1007
+ hold(peer, ctx) {
1008
+ this.dc = type_1.DONE;
1009
+ const held = new ConjunctVal_1.ConjunctVal({ peg: [this, peer] }, ctx);
1010
+ held.dc = type_1.DONE;
1011
+ return held;
721
1012
  }
722
1013
  // Meet with a kind: `number` (or `string` on the string domain) is
723
1014
  // already implied by an ORDER atom's argument; a numeric LEAF narrows
@@ -782,6 +1073,11 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
782
1073
  null == peer.count ? this.count : meetCount(this.count, peer.count);
783
1074
  // `unique()` is idempotent: two of them are one.
784
1075
  merged.uniq = this.uniq || peer.uniq;
1076
+ // `unique(a) & unique(b)` is BOTH, not the later one: each names a
1077
+ // key on which the members must differ, and dropping either would
1078
+ // silently weaken the constraint. Sorted and deduplicated, so the
1079
+ // meet is commutative and `unique(a) & unique(a)` is one atom.
1080
+ merged.uniqBy = [...new Set([...this.uniqBy, ...peer.uniqBy])].sort();
785
1081
  // Band B checks accumulate in written order and are never merged,
786
1082
  // deduplicated or reordered: each carries its own author message,
787
1083
  // and two checks with the same shape may still say different things.
@@ -822,6 +1118,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
822
1118
  res: [...this.res],
823
1119
  count: this.count,
824
1120
  uniq: this.uniq,
1121
+ uniqBy: [...this.uniqBy],
825
1122
  musts: [...this.musts],
826
1123
  invalid: this.invalid,
827
1124
  };
@@ -839,6 +1136,7 @@ class ConstraintVal extends FeatureVal_1.FeatureVal {
839
1136
  out.res = [...this.res];
840
1137
  out.count = this.count;
841
1138
  out.uniq = this.uniq;
1139
+ out.uniqBy = [...this.uniqBy];
842
1140
  out.musts = [...this.musts];
843
1141
  out.pending = this.pending;
844
1142
  out.cjo = this.cjo;
@@ -903,10 +1201,11 @@ function dedupSorted(domain, neqs) {
903
1201
  // Is this value, or anything inside it, a nil? A written argument that
904
1202
  // holds one can never be satisfied, so the atom refuses it as an
905
1203
  // argument rather than reporting a mystery failure against every peer.
1204
+ // Every caller passes a Val: `args[0]` comes from atomArgs, and the
1205
+ // walk below descends only into container pegs, which hold Vals. The
1206
+ // null/non-Val guard this function used to open with was therefore
1207
+ // unreachable, and the coverage gate said so.
906
1208
  function holdsNil(v) {
907
- if (null == v || true !== v.isVal) {
908
- return false;
909
- }
910
1209
  if (true === v.isNil) {
911
1210
  return true;
912
1211
  }
@@ -923,6 +1222,29 @@ function holdsNil(v) {
923
1222
  }
924
1223
  return false;
925
1224
  }
1225
+ // Whether a value, or anything inside it, is an effectful call — one
1226
+ // whose evaluation changes a node OTHER than the one being computed.
1227
+ // `move()` is the only builtin that does: it hides its resolution
1228
+ // target in place. Band B refuses one as an argument, because settling
1229
+ // it runs the effect against the live root before `must`'s trial clone
1230
+ // is taken, and a check that mutates cannot be report-only.
1231
+ function holdsMove(v) {
1232
+ if (true === v.isFunc && 'move' === v.funcname?.()) {
1233
+ return true;
1234
+ }
1235
+ const peg = v.peg;
1236
+ if (Array.isArray(peg)) {
1237
+ return peg.some((c) => holdsMove(c));
1238
+ }
1239
+ if (null != peg && 'object' === typeof peg) {
1240
+ for (const k in peg) {
1241
+ if (holdsMove(peg[k])) {
1242
+ return true;
1243
+ }
1244
+ }
1245
+ }
1246
+ return false;
1247
+ }
926
1248
  // The written arguments of an atom, flattened.
927
1249
  //
928
1250
  // A multi-argument call arrives from the func-paren grammar as ONE
@@ -979,6 +1301,12 @@ function canonState(s) {
979
1301
  if (s.uniq) {
980
1302
  parts.push('unique()');
981
1303
  }
1304
+ // After the bare atom, and sorted: canon is a normal form, so two
1305
+ // documents writing the same keys in different orders must render
1306
+ // the same string.
1307
+ for (const key of s.uniqBy) {
1308
+ parts.push('unique(' + JSON.stringify(key) + ')');
1309
+ }
982
1310
  for (const m of s.musts) {
983
1311
  parts.push('must(' + m.v.canon + ',' + m.msg.canon + ')');
984
1312
  }
@@ -988,6 +1316,110 @@ function canonState(s) {
988
1316
  }
989
1317
  return parts.join('&');
990
1318
  }
1319
+ // SUBSUMPTION over two residuals (G3, the query built on the table in
1320
+ // docs/reference-language.md "Subsumption"): does every value `s`
1321
+ // admits pass `g` too? Implemented here because the compare machinery
1322
+ // (cmpVal, sameScalar, the recursive count state) is this module's.
1323
+ //
1324
+ // Three-valued: true / false / 'undecided'. `must` on the GENERAL side
1325
+ // is undecided by construction (a Band B predicate is opaque — G3 maps
1326
+ // the table's "never" to the query's honest sub_evaluate_only); every
1327
+ // other approximation folds toward false, the safe direction.
1328
+ function constraintStateSubsumes(g, s) {
1329
+ // A Band B predicate on the general side makes its admitted set
1330
+ // unknowable; an extra `must` on the SPECIFIC side only narrows it
1331
+ // and is ignored.
1332
+ if (0 < g.musts.length) {
1333
+ return 'undecided';
1334
+ }
1335
+ // Domains must agree where both constrain one; a sizing-only residual
1336
+ // has no domain and passes this gate.
1337
+ if (null != g.domain && g.domain !== s.domain) {
1338
+ return false;
1339
+ }
1340
+ // A general leaf restriction requires the same leaf on the specific
1341
+ // side (leaves are disjoint; an unrestricted specific admits other
1342
+ // leaves the general refuses).
1343
+ if (null != g.kind && g.kind !== s.kind) {
1344
+ return false;
1345
+ }
1346
+ // Interval containment: the general's endpoints at or beyond the
1347
+ // specific's, and where they coincide the general's may not be the
1348
+ // open one.
1349
+ const d = g.domain ?? s.domain;
1350
+ if (null != g.lo) {
1351
+ if (null == s.lo || null == d) {
1352
+ return false;
1353
+ }
1354
+ const c = cmpVal(d, g.lo.v, s.lo.v);
1355
+ if (0 < c || (0 === c && g.lo.open && !s.lo.open)) {
1356
+ return false;
1357
+ }
1358
+ }
1359
+ if (null != g.hi) {
1360
+ if (null == s.hi || null == d) {
1361
+ return false;
1362
+ }
1363
+ const c = cmpVal(d, g.hi.v, s.hi.v);
1364
+ if (c < 0 || (0 === c && g.hi.open && !s.hi.open)) {
1365
+ return false;
1366
+ }
1367
+ }
1368
+ // Excluding FEWER values is more general: every general exclusion
1369
+ // must be excluded by the specific too.
1370
+ for (const n of g.neqs) {
1371
+ if (!s.neqs.some((m) => sameScalar(n, m))) {
1372
+ return false;
1373
+ }
1374
+ }
1375
+ // Patterns compare as TEXT sets (the sanctioned approximation:
1376
+ // deciding regex containment is what this algebra refuses to do).
1377
+ for (const r of g.res) {
1378
+ if (!s.res.some((q) => q.src === r.src)) {
1379
+ return false;
1380
+ }
1381
+ }
1382
+ // `unique()` subsumes only itself on that axis: a general uniqueness
1383
+ // demand admits no container the unconstrained specific also admits.
1384
+ // ... and a general `unique(k)` needs the same key on the specific
1385
+ // side: distinctness on `port` says nothing about distinctness on
1386
+ // `name`.
1387
+ if (g.uniqBy.some((k) => !s.uniqBy.includes(k))) {
1388
+ return false;
1389
+ }
1390
+ if (g.uniq && !s.uniq) {
1391
+ return false;
1392
+ }
1393
+ // The count atom reuses this same table over the integer domain.
1394
+ if (null != g.count) {
1395
+ if (null == s.count) {
1396
+ return false;
1397
+ }
1398
+ return constraintStateSubsumes(g.count, s.count);
1399
+ }
1400
+ return true;
1401
+ }
1402
+ // The query surface for ts/src/subsume.ts: residual-vs-residual, and
1403
+ // residual-vs-scalar membership (with `must` and `unique` making the
1404
+ // scalar case undecided/false exactly as the meet would).
1405
+ function constraintSubsumesConstraint(g, s) {
1406
+ return constraintStateSubsumes(g, s);
1407
+ }
1408
+ function constraintAdmitsScalar(g, scalar) {
1409
+ if (0 < g.musts.length) {
1410
+ return 'undecided';
1411
+ }
1412
+ if (g.uniq || 0 < g.uniqBy.length) {
1413
+ return false;
1414
+ }
1415
+ // A count residual admits by LENGTH, which is the meet's business;
1416
+ // the query answers false (the safe direction) rather than
1417
+ // reimplementing the sizing walk here.
1418
+ if (null != g.count) {
1419
+ return false;
1420
+ }
1421
+ return stateAdmits(g, scalar);
1422
+ }
991
1423
  // Does this residual's ORDER and MEMBERSHIP part admit the scalar? The
992
1424
  // sizing atoms are deliberately not consulted: `admit` applies them to
993
1425
  // the peer's length, and the count check applies this same function to
@@ -1096,7 +1528,8 @@ function stateEmpty(s) {
1096
1528
  // members, so `integer & length(3)` and `min(2) & unique()` admit
1097
1529
  // nothing. Uniqueness over the string domain is empty for the same
1098
1530
  // reason -- a string's members are not values the algebra compares.
1099
- if ('number' === d && (null != s.count || s.uniq)) {
1531
+ if ('number' === d && (null != s.count || s.uniq ||
1532
+ 0 < s.uniqBy.length)) {
1100
1533
  return true;
1101
1534
  }
1102
1535
  if ('string' === d && s.uniq) {
@@ -1121,7 +1554,9 @@ function countBase() {
1121
1554
  neqs: [],
1122
1555
  res: [],
1123
1556
  musts: [],
1557
+ // A COUNT is a number, and a number has no members to be distinct.
1124
1558
  uniq: false,
1559
+ uniqBy: [],
1125
1560
  };
1126
1561
  }
1127
1562
  // A count as a Val, so the count residual can be applied by exactly the
@@ -1138,13 +1573,16 @@ function countVal(n) {
1138
1573
  function meetCount(a, b) {
1139
1574
  return {
1140
1575
  domain: 'number',
1141
- kind: a.kind ?? b.kind,
1576
+ // `a` is always a countBase()-seeded residual, so its kind is
1577
+ // always Integer — there is no kindless side to fall back from.
1578
+ kind: a.kind,
1142
1579
  lo: tighter('number', a.lo, b.lo, true),
1143
1580
  hi: tighter('number', a.hi, b.hi, false),
1144
1581
  neqs: dedupSorted('number', [...a.neqs, ...b.neqs]),
1145
1582
  res: [],
1146
1583
  musts: [],
1147
1584
  uniq: false,
1585
+ uniqBy: [],
1148
1586
  clash: true === a.clash || true === b.clash ||
1149
1587
  (null != a.kind && null != b.kind && a.kind !== b.kind),
1150
1588
  };
@@ -1167,14 +1605,15 @@ function countArgState(arg) {
1167
1605
  domain: 'number',
1168
1606
  lo: { v: arg, open: false },
1169
1607
  hi: { v: arg, open: false },
1170
- neqs: [], res: [], musts: [], uniq: false,
1608
+ neqs: [], res: [], musts: [], uniq: false, uniqBy: [],
1171
1609
  };
1172
1610
  }
1173
1611
  if (true === arg?.isConstraint) {
1174
1612
  const c = arg;
1175
1613
  // A pattern, a sizing atom or a string bound inside a count is not
1176
1614
  // 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 ||
1615
+ if (null != c.invalid || 0 < c.res.length || c.uniq ||
1616
+ 0 < c.uniqBy.length || null != c.count ||
1178
1617
  'number' !== c.domain) {
1179
1618
  return undefined;
1180
1619
  }
@@ -1184,17 +1623,23 @@ function countArgState(arg) {
1184
1623
  lo: c.lo,
1185
1624
  hi: c.hi,
1186
1625
  neqs: [...c.neqs],
1187
- res: [], musts: [], uniq: false,
1626
+ res: [], musts: [], uniq: false, uniqBy: [],
1188
1627
  };
1189
1628
  }
1190
1629
  if (true === arg?.isScalarKind) {
1191
1630
  const marker = arg.peg;
1192
1631
  if (Number === marker) {
1193
- return { domain: 'number', neqs: [], res: [], musts: [], uniq: false };
1632
+ return {
1633
+ domain: 'number', neqs: [], res: [], musts: [],
1634
+ uniq: false, uniqBy: [],
1635
+ };
1194
1636
  }
1195
1637
  if (ScalarKindVal_1.Integer === marker || ScalarKindVal_1.Float === marker ||
1196
1638
  ScalarKindVal_1.BigInteger === marker || ScalarKindVal_1.BigDecimal === marker) {
1197
- return { domain: 'number', kind: marker, neqs: [], res: [], musts: [], uniq: false };
1639
+ return {
1640
+ domain: 'number', kind: marker, neqs: [], res: [], musts: [],
1641
+ uniq: false, uniqBy: [],
1642
+ };
1198
1643
  }
1199
1644
  return undefined;
1200
1645
  }
@@ -1221,7 +1666,12 @@ function genable(child) {
1221
1666
  return true === child.isScalar || true === child.isMap ||
1222
1667
  true === child.isList || true === child.isPref ||
1223
1668
  true === child.isRef || true === child.isDisjunct ||
1224
- true === child.isNil;
1669
+ true === child.isNil ||
1670
+ // A settled sizing residue generates through ConjunctVal.gen, so it
1671
+ // is a member like any other -- and a `length` that did not count
1672
+ // its sibling's `unique` would be the early-fold defect again, one
1673
+ // level up.
1674
+ undefined !== (0, BagVal_1.sizingResidue)(child);
1225
1675
  }
1226
1676
  // The children a bag would EMIT, mirroring the selection in
1227
1677
  // `BagVal.gen` — type/hide marks skipped, non-generable residue and
@@ -1343,6 +1793,6 @@ class UniqueConstraintVal extends ConstraintVal {
1343
1793
  constructor(spec, ctx) {
1344
1794
  super({ ...spec, atom: 'unique' }, ctx);
1345
1795
  }
1346
- } /* node:coverage ignore next 19 */
1796
+ } /* node:coverage ignore next 23 */
1347
1797
  exports.UniqueConstraintVal = UniqueConstraintVal;
1348
1798
  //# sourceMappingURL=ConstraintVal.js.map