aontu 0.53.0 → 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 (196) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +426 -7
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +90 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +133 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +72 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.js +27 -1
  132. package/dist/vet.js.map +1 -1
  133. package/dist/view.d.ts +85 -0
  134. package/dist/view.js +1948 -0
  135. package/dist/view.js.map +1 -0
  136. package/grammar/aontu.gbnf +18 -10
  137. package/grammar/aontu.lark +15 -10
  138. package/grammar/aontu.tmLanguage.json +184 -0
  139. package/package.json +10 -3
  140. package/skill/grammar-card.md +1 -2
  141. package/src/aontu.ts +37 -7
  142. package/src/cli.ts +472 -7
  143. package/src/ctx.ts +20 -0
  144. package/src/diff.ts +4 -2
  145. package/src/err.ts +8 -1
  146. package/src/graph.ts +125 -75
  147. package/src/hcanon.ts +9 -11
  148. package/src/hints.ts +108 -29
  149. package/src/jsonschema.ts +41 -0
  150. package/src/lang.ts +642 -203
  151. package/src/lsp.ts +75 -6
  152. package/src/mcp.ts +143 -6
  153. package/src/mod-tool.ts +48 -9
  154. package/src/mod.ts +110 -1
  155. package/src/patch.ts +31 -27
  156. package/src/provenance.ts +8 -1
  157. package/src/query.ts +4 -2
  158. package/src/reach.ts +52 -23
  159. package/src/relation.ts +139 -236
  160. package/src/sig.ts +345 -0
  161. package/src/sigdecl.ts +11 -0
  162. package/src/siggate.ts +144 -0
  163. package/src/std.ts +74 -16
  164. package/src/subsume.ts +59 -10
  165. package/src/unify.ts +102 -125
  166. package/src/utility.ts +7 -67
  167. package/src/val/AggFuncVal.ts +231 -4
  168. package/src/val/BagVal.ts +58 -9
  169. package/src/val/ConstraintVal.ts +34 -5
  170. package/src/val/ContainerKindVal.ts +158 -0
  171. package/src/val/CopyFuncVal.ts +0 -7
  172. package/src/val/DisjunctVal.ts +152 -40
  173. package/src/val/ExpectVal.ts +39 -4
  174. package/src/val/FuncBaseVal.ts +21 -8
  175. package/src/val/GraphAtomVal.ts +264 -0
  176. package/src/val/JunctionVal.ts +23 -6
  177. package/src/val/ListVal.ts +30 -37
  178. package/src/val/MapVal.ts +87 -39
  179. package/src/val/PathFuncVal.ts +107 -19
  180. package/src/val/PathVal.ts +221 -0
  181. package/src/val/PlusOpVal.ts +56 -37
  182. package/src/val/PrefVal.ts +186 -38
  183. package/src/val/RecurseVal.ts +285 -0
  184. package/src/val/RefVal.ts +105 -83
  185. package/src/val/ReferFuncVal.ts +445 -100
  186. package/src/val/ScalarKindVal.ts +12 -0
  187. package/src/val/SuperFuncVal.ts +137 -13
  188. package/src/val/TopVal.ts +1 -1
  189. package/src/val/Val.ts +44 -35
  190. package/src/val/arith.ts +4 -1
  191. package/src/vet.ts +36 -3
  192. package/src/view.ts +2563 -0
  193. package/dist/val/IdFuncVal.d.ts +0 -13
  194. package/dist/val/IdFuncVal.js +0 -54
  195. package/dist/val/IdFuncVal.js.map +0 -1
  196. package/src/val/IdFuncVal.ts +0 -91
@@ -1,7 +1,7 @@
1
1
  /* Copyright (c) 2025 Richard Rodger, MIT License */
2
2
 
3
- // AGGREGATION AND PROJECTION: `sum(d)`, `least(d)`, `greatest(d)`
4
- // and `pick(d, k)`.
3
+ // AGGREGATION, PROJECTION AND THE STRING FOLD: `sum(d)`, `least(d)`,
4
+ // `greatest(d)`, `pick(d, k)` and `join(d, sep?)`.
5
5
  //
6
6
  // The review's finding I: "No aggregation. `length()` counts but
7
7
  // nothing sums: an invoice total, a fleet-wide resource budget, a quota
@@ -55,9 +55,12 @@ import {
55
55
  import { makeNilErr } from '../err'
56
56
  import { IntegerVal } from './IntegerVal'
57
57
  import { ListVal } from './ListVal'
58
+ import { StringVal } from './StringVal'
58
59
  import { FuncBaseVal } from './FuncBaseVal'
59
60
  import { arith } from './arith'
60
61
  import { cmpNumeric } from './numcmp'
62
+ import { cmpCodePoint } from '../keyorder'
63
+ import { plusText } from './PlusOpVal'
61
64
 
62
65
 
63
66
  type AggOp = 'sum' | 'least' | 'greatest'
@@ -72,7 +75,14 @@ function bagChildren(data: any): Val[] | undefined {
72
75
  return data.peg as Val[]
73
76
  }
74
77
  if (true === data?.isMap) {
75
- return Object.keys(data.peg).sort().map((k: string) => data.peg[k])
78
+ // THE ONE MAP-KEY ORDER (../keyorder.ts), not a bare `.sort()`:
79
+ // JavaScript compares by UTF-16 code unit, so an astral key's
80
+ // leading surrogate sorts BELOW U+E000-U+FFFF and `pick` answered
81
+ // in a different order from `each`, from canon, and from Go --
82
+ // which sorts UTF-8 bytes, i.e. code points. `pick` is the
83
+ // order-preserving projection, so that was one model producing two
84
+ // different generated files (BUGS.md 62).
85
+ return Object.keys(data.peg).sort(cmpCodePoint).map((k: string) => data.peg[k])
76
86
  }
77
87
  return undefined
78
88
  }
@@ -294,6 +304,222 @@ class PickFuncVal extends FuncBaseVal {
294
304
  }
295
305
 
296
306
 
307
+
308
+ // THE FOLD TO A STRING: `join(coll, sep?)` -- G9 phase 2.
309
+ //
310
+ // The one primitive between a model and a generated file. A spread can
311
+ // put a separator AFTER each element; putting one BETWEEN N elements is
312
+ // a reduction over strings, and the language had none: `sum` is numeric,
313
+ // `+` does not reduce a list, and indexed concatenation needs the arity
314
+ // known in advance. So a generated SQL column list carried a trailing
315
+ // comma and did not parse (use-cases/15-code-generation).
316
+ //
317
+ // IT FOLDS WITH `+` SEEDED WITH `""`, exactly as `sum` folds with `add`
318
+ // seeded with `0`. That is not a figure of speech: the members are
319
+ // rendered by `plusText`, which IS the function `+`'s string branch
320
+ // calls, so the language keeps exactly one answer to "how does a number
321
+ // become text" and the two cannot drift. `join(coll)` is therefore
322
+ // concatenation and no `concat` is needed; `join(coll, "\n")` is lines
323
+ // and no `lines` is needed. ONE BUILTIN, NOT A FAMILY.
324
+ //
325
+ // `split`/`words` is the inverse -- an input-PARSING operation with no
326
+ // generation use -- and is deliberately absent.
327
+ //
328
+ // EMPTY IS `""`, concatenation's identity, the exact parallel of
329
+ // `sum([]) == 0` and the opposite of `least([])`: a fold with an
330
+ // identity can answer the empty bag, and one without has to refuse it.
331
+ //
332
+ // MEMBERS ARE VALIDATED BEFORE THE FOLD, which is the whole reason this
333
+ // is not three lines. `+` with a string on the left RESIDUATES on a
334
+ // container or a null rather than refusing (`"" + {b:1}` and `"x" +
335
+ // null` both reach generation as `mapval_no_gen`), so folding blindly
336
+ // would turn a bad member into a useless late error pointing at the
337
+ // wrong thing. The three verdicts below are that check.
338
+ type MemberVerdict = 'text' | 'never' | 'notyet'
339
+
340
+
341
+ // Which of the three a value is.
342
+ //
343
+ // `text` -- `+` would take it, so the fold can.
344
+ //
345
+ // `never` -- a SETTLED value that will never become text: a map, a
346
+ // list, a null. This is `join_member`, class `conflict`, and it names
347
+ // the member rather than the call, because "one of these is not a
348
+ // string" is only actionable if you are told which.
349
+ //
350
+ // `notyet` -- an unresolved kind, a top, a stable residue. NOT a join
351
+ // failure at all: the call stays residual and generation reports
352
+ // `mapval_no_gen`, class `incomplete`, exactly as docs/trust.md
353
+ // requires ("a stable residue ... is ordinary incompleteness"). Getting
354
+ // this split wrong in either direction is the defect that matters here:
355
+ // refusing a residue makes `join` unusable inside a schema, and
356
+ // deferring on a map makes a real error arrive as a shrug.
357
+ function memberVerdict(v: any): MemberVerdict {
358
+ const u = unpref(v)
359
+ if (undefined !== plusText(u)) {
360
+ return 'text'
361
+ }
362
+ if (true === u?.isMap || true === u?.isList || true === u?.isNull) {
363
+ return 'never'
364
+ }
365
+ return 'notyet'
366
+ }
367
+
368
+
369
+ // The separator is a STRING or it is nothing.
370
+ //
371
+ // A number would render perfectly well through `+`, and is still
372
+ // refused: the separator is not a member of the fold, it is the
373
+ // parameter naming the text between members, and `join(x, 5)` is far
374
+ // likelier a mistake than an intent to separate with "5". `pick`'s key
375
+ // argument draws the same line for the same reason. This is the
376
+ // direction that can be loosened later without breaking a document;
377
+ // the other direction cannot.
378
+ function sepVerdict(v: any): MemberVerdict {
379
+ const u = unpref(v)
380
+ if (u?.isVal && u.isScalar) {
381
+ return 'string' === typeof u.peg ? 'text' : 'never'
382
+ }
383
+ if (true === u?.isMap || true === u?.isList || true === u?.isNull) {
384
+ return 'never'
385
+ }
386
+ return 'notyet'
387
+ }
388
+
389
+
390
+ class JoinFuncVal extends FuncBaseVal {
391
+ isJoinFunc = true
392
+
393
+ // The bag must settle before it is folded, exactly as it must before
394
+ // it is summed or projected.
395
+ staged = true
396
+
397
+ constructor(
398
+ spec: ValSpec,
399
+ ctx?: AontuContext
400
+ ) {
401
+ super(spec, ctx)
402
+ }
403
+
404
+
405
+ // A `make` OVERRIDE, WHERE ITS AggFuncVal SIBLINGS HAVE NONE, and the
406
+ // difference is worth stating because it is not arbitrary. `sum`,
407
+ // `pick` and the rest are staged and nothing more: they residuate
408
+ // before `unify` reaches the rebuild branch, so a `make` there would
409
+ // be unreachable code pretending to be a contract, and the base's
410
+ // `func:<name>` refusal is the loud answer if that ever changes.
411
+ //
412
+ // `join` is the first builtin that is BOTH staged AND defers its
413
+ // resolution (see `deferResolve`). A deferred call has settled
414
+ // arguments, so it does reach the rebuild branch, and without this it
415
+ // raised `func:join` on the first document with an unresolved member
416
+ // — where the Go port residuated and reported `mapval_no_gen`.
417
+ // Opposite answers on `join($.m, ",")` with `m: [string]`, caught by
418
+ // running both engines rather than by either test suite. `super` and
419
+ // bare `id`, the two other deferring calls, each carry the same
420
+ // override for the same reason.
421
+ make(_ctx: AontuContext, spec: ValSpec): Val {
422
+ return new JoinFuncVal(spec)
423
+ }
424
+
425
+
426
+ funcname() {
427
+ return 'join'
428
+ }
429
+
430
+
431
+ // The base does not drive: `unify` drives by hand, because a staged
432
+ // func must advance what it is waiting on every pass rather than only
433
+ // on the pass it fires.
434
+ prepare(_ctx: AontuContext, _args: Val[]) {
435
+ return null
436
+ }
437
+
438
+
439
+ unify(peer: Val, ctx: AontuContext): Val {
440
+ // BOTH arguments, unlike `pick`, which drives only its bag: `pick`'s
441
+ // key is a bare word the parser has already made a string, while a
442
+ // separator is an ordinary expression and `join($.rows, $.sep)` has
443
+ // to wait for it.
444
+ const ready = this.driveStagedArgs(ctx, 2)
445
+
446
+ if (!ready || !ctx.settle) {
447
+ return this.residuate(peer, ctx)
448
+ }
449
+
450
+ return super.unify(peer, ctx)
451
+ }
452
+
453
+
454
+ // The `notyet` half of the verdict, taken before `resolve` runs: with
455
+ // arguments settled but a member still a kind, the call rides the
456
+ // ordinary args-not-done path and residuates, which is what makes an
457
+ // unresolved member ordinary incompleteness rather than a refusal.
458
+ deferResolve(_ctx: AontuContext, args?: Val[]): boolean {
459
+ const children = bagChildren(args?.[0])
460
+ if (undefined === children) {
461
+ // Not a bag at all: let `resolve` say so rather than waiting for
462
+ // a settling that has already happened.
463
+ return false
464
+ }
465
+ const sep = args?.[1]
466
+ if (undefined !== sep && 'notyet' === sepVerdict(sep)) {
467
+ return true
468
+ }
469
+ return children.some((c) => 'notyet' === memberVerdict(c))
470
+ }
471
+
472
+
473
+ resolve(ctx: AontuContext, args: Val[]) {
474
+ const children = bagChildren(args?.[0])
475
+
476
+ if (undefined === children) {
477
+ return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
478
+ 'join'))
479
+ }
480
+
481
+ // Arity is [1,2], so a second argument is present or the separator
482
+ // is the empty string -- which makes `join(coll)` concatenation.
483
+ // The separator's SHAPE is settled before resolve runs: the
484
+ // signature gate refuses every concrete non-string
485
+ // (docs/design/SIGNATURES.0.md; join(d: map|list, sep?: string)),
486
+ // and deferResolve holds the call while sepVerdict answers notyet
487
+ // -- so a present separator here is text.
488
+ let sep = ''
489
+ if (1 < args.length) {
490
+ sep = unpref(args[1]).peg
491
+ }
492
+
493
+ // NO NIL-MEMBER GUARD, WHERE `sum` HAS ONE, and the difference is
494
+ // the fold's shape rather than an oversight. `sum` folds with
495
+ // `arith`, which MINTS a nil part-way through — a non-numeric child
496
+ // or an overflow — so it has to stop and return it. `join` folds
497
+ // already-unified values, and a nil among a list's elements
498
+ // collapses the list before this call resolves: `join([least([])],
499
+ // ",")` reports `aggregate_empty` at the member's own path,
500
+ // `$.o.0`, and never reaches here. A guard was written, the
501
+ // ADR-002 gate found it unexecuted, and probing confirmed no
502
+ // spelling reaches it, so it is removed rather than excused.
503
+ const parts: string[] = []
504
+ for (const child of children) {
505
+ const u: any = unpref(child)
506
+ const text = plusText(u)
507
+ if (undefined === text) {
508
+ return this.place(makeNilErr(ctx, 'join_member', this, undefined,
509
+ 'join', { member: String(u?.canon) }))
510
+ }
511
+ parts.push(text)
512
+ }
513
+
514
+ // Every part is already a string, so this IS the `+` fold seeded
515
+ // with `""` -- written as one concatenation because a loop of `+`
516
+ // over settled strings cannot differ from it, and because the Go
517
+ // twin's strings.Join must produce the same bytes.
518
+ return this.place(new StringVal({ peg: parts.join(sep) }))
519
+ }
520
+ }
521
+
522
+
297
523
  // The three the registry names. Each is its operation and nothing else.
298
524
  class SumFuncVal extends AggFuncVal {
299
525
  constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'sum') }
@@ -307,11 +533,12 @@ class GreatestFuncVal extends AggFuncVal {
307
533
  constructor(spec: ValSpec, ctx?: AontuContext) {
308
534
  super(spec, ctx, 'greatest')
309
535
  }
310
- } /* node:coverage ignore next 9 */
536
+ } /* node:coverage ignore next 10 */
311
537
 
312
538
 
313
539
  export {
314
540
  AggFuncVal,
541
+ JoinFuncVal,
315
542
  PickFuncVal,
316
543
  SumFuncVal,
317
544
  LeastFuncVal,
package/src/val/BagVal.ts CHANGED
@@ -30,6 +30,17 @@ abstract class BagVal extends FeatureVal {
30
30
  closed: boolean = false
31
31
  optionalKeys: string[] = []
32
32
 
33
+ // ALIAS DECLARATIONS, by key. `%uint8: …` binds a name for this file
34
+ // and is not a field of the document: it does not generate and does
35
+ // not appear in canon, so a document using aliases and its expanded
36
+ // twin are the SAME document and hash identically
37
+ // (docs/design/ALIASES.0.md §4).
38
+ //
39
+ // Keyed on the map rather than marked on the value, because a
40
+ // reference copies the value it resolves to -- a mark riding the
41
+ // value would erase the referring field along with the declaration.
42
+ aliasKeys: string[] = []
43
+
33
44
  spread = {
34
45
  cj: (undefined as Val | undefined),
35
46
  }
@@ -107,6 +118,17 @@ abstract class BagVal extends FeatureVal {
107
118
  this.mark.hide !== peer.mark.hide) {
108
119
  return false
109
120
  }
121
+ // The SPREAD is part of the shape (BUGS.md §52 regime 4): `[]`
122
+ // and `[&: T]` share an empty key set, and calling them the same
123
+ // value collapsed the disjunction to its first arm before any
124
+ // data could pick -- the vacuous-schema hole. Two spreads are the
125
+ // same when their canons are.
126
+ const scj: any = (this as any).spread?.cj
127
+ const pcj: any = peer.spread?.cj
128
+ if ((null == scj) !== (null == pcj) ||
129
+ (null != scj && null != pcj && scj.canon !== pcj.canon)) {
130
+ return false
131
+ }
110
132
 
111
133
  const keys = Object.keys(this.peg)
112
134
  if (keys.length !== Object.keys(peer.peg).length) {
@@ -157,6 +179,14 @@ abstract class BagVal extends FeatureVal {
157
179
  continue
158
180
  }
159
181
 
182
+ // An alias declaration contributes no field, and unlike a marked
183
+ // one it is skipped even under `probe`: the probe descends
184
+ // through output marks to check what a `--at` anchor really
185
+ // holds, and an alias is not part of the document at all.
186
+ if (this.aliasKeys.includes('' + p)) {
187
+ continue
188
+ }
189
+
160
190
  const optional = this.optionalKeys.includes('' + p)
161
191
 
162
192
  // Lists append compactly: a skipped element (hidden, dropped
@@ -192,15 +222,7 @@ abstract class BagVal extends FeatureVal {
192
222
  // atom decides, in ConjunctVal.gen. Any OTHER conjunct is
193
223
  // unresolved and falls through to the residue error below,
194
224
  // exactly as before.
195
- else if (child.isScalar
196
- || child.isMap
197
- || child.isList
198
- || child.isPref
199
- || child.isRef
200
- || child.isDisjunct
201
- || child.isNil
202
- || undefined !== sizingResidue(child)
203
- ) {
225
+ else if (bagGenable(child)) {
204
226
  // An optional child is generated in an isolated collect context so an
205
227
  // unresolved inner value (a bare type that survived unification, e.g.
206
228
  // an absent optional sub-map's `field: string`) is dropped rather than
@@ -281,3 +303,30 @@ export function sizingResidue(v: any): { con: any, bag: any } | undefined {
281
303
  return undefined !== con && (true === bag?.isMap || true === bag?.isList) ?
282
304
  { con, bag } : undefined
283
305
  }
306
+
307
+ // bagGenable is the bag's generability whitelist. A graph atom
308
+ // (RELATIONS P2) is exactly as generable as the value it carries: the
309
+ // atom is transparent at generation, so wrapping a field's value in
310
+ // acyclic() must not change whether the bag accepts it -- an unmet
311
+ // rel() refuses required generation with or without the atom, and a
312
+ // BARE atom generates nothing and is dropped, exactly as an unmet
313
+ // rel() under an optional key is.
314
+ export function bagGenable(child: any): boolean {
315
+ if (true === child.isGraphAtom) {
316
+ return undefined === child.held || bagGenable(child.held)
317
+ }
318
+ // The recursive residual carries its own generation refusal
319
+ // (recursion_unexpanded), which names the schema and the site --
320
+ // the bag's generic residue error would bury both.
321
+ if (true === child.isRecurse) {
322
+ return true
323
+ }
324
+ return true === child.isScalar
325
+ || true === child.isMap
326
+ || true === child.isList
327
+ || true === child.isPref
328
+ || true === child.isRef
329
+ || true === child.isDisjunct
330
+ || true === child.isNil
331
+ || undefined !== sizingResidue(child)
332
+ }
@@ -544,6 +544,16 @@ function stringLeaf(v: any): boolean {
544
544
  return true === v?.isScalar && 'string' === typeof v.peg && v.isString
545
545
  }
546
546
 
547
+ // The VALUE-admission widening of stringLeaf: a path value is a string
548
+ // with more structure (path <: string, ADR-016), so the string-domain
549
+ // constraints admit one -- `refer() & re("auth$")` must still check a
550
+ // link whose value is now a path. The ARGUMENT sites keep the strict
551
+ // test: a pattern, a bound or an exclusion is spelled with a plain
552
+ // string, never a path.
553
+ function stringishLeaf(v: any): boolean {
554
+ return stringLeaf(v) || (true === v?.isScalar && true === v.isPath)
555
+ }
556
+
547
557
 
548
558
  // Scalar identity: leaf AND value, the lattice's own rule (1 and 1.0
549
559
  // are different scalars). Exact numeric comparison decides the value
@@ -552,6 +562,12 @@ function sameScalar(a: any, b: any): boolean {
552
562
  if (numericLeaf(a) && numericLeaf(b)) {
553
563
  return towerRank(a) === towerRank(b) && 0 === cmpNumeric(a, b)
554
564
  }
565
+ if (true === a?.isPath || true === b?.isPath) {
566
+ // Path identity is kind AND spelling (ADR-016): `neq(path($.x))`
567
+ // excludes exactly that address, and never a plain string that
568
+ // happens to spell it.
569
+ return true === a?.isPath && true === b?.isPath && a.peg === b.peg
570
+ }
555
571
  if (stringLeaf(a) && stringLeaf(b)) {
556
572
  return a.peg === b.peg
557
573
  }
@@ -745,7 +761,7 @@ class ConstraintVal extends FeatureVal {
745
761
  }
746
762
  let domain: 'number' | 'string' | undefined = undefined
747
763
  for (const a of args) {
748
- const d = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined
764
+ const d = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined
749
765
  if (null == d || (null != domain && d !== domain)) {
750
766
  return bad('invalid-arg')
751
767
  }
@@ -828,7 +844,7 @@ class ConstraintVal extends FeatureVal {
828
844
  return
829
845
  }
830
846
 
831
- const domain = numericLeaf(a) ? 'number' : stringLeaf(a) ? 'string' : undefined
847
+ const domain = numericLeaf(a) ? 'number' : stringishLeaf(a) ? 'string' : undefined
832
848
  if (null == domain) {
833
849
  return bad('invalid-arg')
834
850
  }
@@ -852,7 +868,20 @@ class ConstraintVal extends FeatureVal {
852
868
  // residual is stable and the ladder is total.
853
869
  let out: Val
854
870
 
855
- if (null != this.pending) {
871
+ // A rel residual or a graph atom HOLDS constraints for the value
872
+ // they stand for (RELATIONS P1/P2): hand the drive over before
873
+ // any settling, exactly as a container does, so `rel(t) & re(x)`
874
+ // reads the same in either order. Reached through the INCLUDE
875
+ // flow (a loaded schema's conjunct re-drives with the constraint
876
+ // on the left) -- inline documents route rel/atom peers through
877
+ // unite's b-drives first, which is why use-cases/12-relations
878
+ // refused without this arm while every inline row stayed green;
879
+ // pinned by constraint-hands-drive-to-rel-and-atom.
880
+ if (true === (peer as any)?.isRel
881
+ || true === (peer as any)?.isGraphAtom) {
882
+ out = (peer as any).unify(this, ctx)
883
+ }
884
+ else if (null != this.pending) {
856
885
  out = this.settle(peer, ctx)
857
886
  }
858
887
  else if (null != this.invalid) {
@@ -975,7 +1004,7 @@ class ConstraintVal extends FeatureVal {
975
1004
  // Only a string among the scalars has a length, and it is counted
976
1005
  // in CODE POINTS -- not UTF-16 units (this host's native count)
977
1006
  // and not bytes (Go's). Iterating a string yields code points.
978
- if (!stringLeaf(peer)) {
1007
+ if (!stringishLeaf(peer)) {
979
1008
  return this.fail(ctx, peer)
980
1009
  }
981
1010
  if (!stateAdmits(this.count, countVal([...peer.peg].length))) {
@@ -1710,7 +1739,7 @@ function constraintAdmitsScalar(
1710
1739
  // the count.
1711
1740
  function stateAdmits(s: ConstraintState, peer: any): boolean {
1712
1741
  const domainOf = numericLeaf(peer) ? 'number' :
1713
- stringLeaf(peer) ? 'string' : undefined
1742
+ stringishLeaf(peer) ? 'string' : undefined
1714
1743
 
1715
1744
  if (null == s.domain) {
1716
1745
  // A sizing residual has no domain, and admits any scalar the sizing
@@ -0,0 +1,158 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // CONTAINER KINDS (docs/design/PATHS.0.md). `{}` and `[]` are the
4
+ // container UNITS: they admit any map (or list) AND generate empty
5
+ // when nothing arrives. `map()` and `list()` are the container KINDS
6
+ // proper: they admit exactly the same values and default to NOTHING,
7
+ // as `string` does -- the spelling of "this must be a map, and it
8
+ // must be supplied", which the unit cannot say because an unmet unit
9
+ // silently manufactures its empty value.
10
+ //
11
+ // The vacuous call is the kind; the literal is the unit. That is the
12
+ // whole convention, and it is why neither function takes arguments:
13
+ // element constraints already belong to the spreads (`{&: V}`,
14
+ // `[&: V]`), and a second spelling of them here would drift.
15
+ //
16
+ // No null/top/nil arms, as RelVal records: unite's dispatch ladder
17
+ // absorbs those peers before any Val's own unify is consulted, and
18
+ // FuncBaseVal returns a done resolution directly against a top peer.
19
+
20
+ import type {
21
+ Val,
22
+ ValSpec,
23
+ } from '../type'
24
+
25
+ import {
26
+ DONE,
27
+ } from '../type'
28
+
29
+ import {
30
+ AontuContext,
31
+ } from '../ctx'
32
+
33
+ import { makeNilErr } from '../err'
34
+
35
+ import { FeatureVal } from './FeatureVal'
36
+ import { FuncBaseVal } from './FuncBaseVal'
37
+
38
+
39
+ class MapKindVal extends FeatureVal {
40
+ isContainerKind = true
41
+ isMapKind = true
42
+
43
+ constructor(spec: ValSpec, ctx?: AontuContext) {
44
+ super(spec, ctx)
45
+ this.dc = DONE
46
+ }
47
+
48
+ unify(peer: Val, ctx: AontuContext): Val {
49
+ const p: any = peer
50
+ if (true === p.isMap) {
51
+ return peer
52
+ }
53
+ if (true === p.isMapKind) {
54
+ return this
55
+ }
56
+ // The unit's own refusal code: a kind mismatch here is the same
57
+ // fact `{} & 1` reports, and two codes for one fact would drift.
58
+ return makeNilErr(ctx, 'map', this, peer)
59
+ }
60
+
61
+ get canon() {
62
+ return 'map()'
63
+ }
64
+
65
+ same(peer: any): boolean {
66
+ return true === peer?.isMapKind
67
+ }
68
+
69
+ } /* node:coverage ignore next 4 */
70
+
71
+
72
+ class ListKindVal extends FeatureVal {
73
+ isContainerKind = true
74
+ isListKind = true
75
+
76
+ constructor(spec: ValSpec, ctx?: AontuContext) {
77
+ super(spec, ctx)
78
+ this.dc = DONE
79
+ }
80
+
81
+ unify(peer: Val, ctx: AontuContext): Val {
82
+ const p: any = peer
83
+ if (true === p.isList) {
84
+ return peer
85
+ }
86
+ if (true === p.isListKind) {
87
+ return this
88
+ }
89
+ return makeNilErr(ctx, 'list', this, peer)
90
+ }
91
+
92
+ get canon() {
93
+ return 'list()'
94
+ }
95
+
96
+ same(peer: any): boolean {
97
+ return true === peer?.isListKind
98
+ }
99
+
100
+ } /* node:coverage ignore next 4 */
101
+
102
+
103
+ class MapFuncVal extends FuncBaseVal {
104
+ isMapFunc = true
105
+
106
+ constructor(spec: ValSpec, ctx?: AontuContext) {
107
+ super(spec, ctx)
108
+ }
109
+
110
+ make(_ctx: AontuContext, spec: ValSpec): Val {
111
+ return new MapFuncVal(spec)
112
+ }
113
+
114
+ funcname() {
115
+ return 'map'
116
+ }
117
+
118
+ resolve(ctx: AontuContext, _args: Val[]) {
119
+ const out = new MapKindVal({}, ctx)
120
+ out.site = this.site
121
+ out.path = this.path
122
+ return out
123
+ }
124
+
125
+ } /* node:coverage ignore next 4 */
126
+
127
+
128
+ class ListFuncVal extends FuncBaseVal {
129
+ isListFunc = true
130
+
131
+ constructor(spec: ValSpec, ctx?: AontuContext) {
132
+ super(spec, ctx)
133
+ }
134
+
135
+ make(_ctx: AontuContext, spec: ValSpec): Val {
136
+ return new ListFuncVal(spec)
137
+ }
138
+
139
+ funcname() {
140
+ return 'list'
141
+ }
142
+
143
+ resolve(ctx: AontuContext, _args: Val[]) {
144
+ const out = new ListKindVal({}, ctx)
145
+ out.site = this.site
146
+ out.path = this.path
147
+ return out
148
+ }
149
+
150
+ } /* node:coverage ignore next 8 */
151
+
152
+
153
+ export {
154
+ MapKindVal,
155
+ ListKindVal,
156
+ MapFuncVal,
157
+ ListFuncVal,
158
+ }
@@ -65,13 +65,6 @@ class CopyFuncVal extends FuncBaseVal {
65
65
  // console.log('WALK', val)
66
66
  val.mark.type = false
67
67
  val.mark.hide = false
68
- // `copy()` CLEARS IDENTITY (G4 phase 1, clearing rule 2),
69
- // consistent with its clearing of the marks: a copy of an
70
- // entity is a second value shaped like it, and leaving the id
71
- // on would merge the copy straight back into the original —
72
- // making `copy()` a no-op for exactly the values it exists to
73
- // detach.
74
- val.entity = undefined
75
68
  return val
76
69
  })
77
70
  }