aontu 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +500 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +94 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +153 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +75 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.d.ts +1 -0
  132. package/dist/vet.js +32 -1
  133. package/dist/vet.js.map +1 -1
  134. package/dist/view.d.ts +90 -0
  135. package/dist/view.js +2168 -0
  136. package/dist/view.js.map +1 -0
  137. package/grammar/aontu.gbnf +18 -10
  138. package/grammar/aontu.lark +15 -10
  139. package/grammar/aontu.tmLanguage.json +184 -0
  140. package/package.json +10 -3
  141. package/skill/grammar-card.md +1 -2
  142. package/src/aontu.ts +37 -7
  143. package/src/cli.ts +553 -8
  144. package/src/ctx.ts +20 -0
  145. package/src/diff.ts +4 -2
  146. package/src/err.ts +8 -1
  147. package/src/graph.ts +125 -75
  148. package/src/hcanon.ts +9 -11
  149. package/src/hints.ts +112 -29
  150. package/src/jsonschema.ts +41 -0
  151. package/src/lang.ts +642 -203
  152. package/src/lsp.ts +75 -6
  153. package/src/mcp.ts +164 -6
  154. package/src/mod-tool.ts +48 -9
  155. package/src/mod.ts +110 -1
  156. package/src/patch.ts +31 -27
  157. package/src/provenance.ts +8 -1
  158. package/src/query.ts +4 -2
  159. package/src/reach.ts +52 -23
  160. package/src/relation.ts +139 -236
  161. package/src/sig.ts +345 -0
  162. package/src/sigdecl.ts +11 -0
  163. package/src/siggate.ts +144 -0
  164. package/src/std.ts +77 -16
  165. package/src/subsume.ts +59 -10
  166. package/src/unify.ts +102 -125
  167. package/src/utility.ts +7 -67
  168. package/src/val/AggFuncVal.ts +231 -4
  169. package/src/val/BagVal.ts +58 -9
  170. package/src/val/ConstraintVal.ts +34 -5
  171. package/src/val/ContainerKindVal.ts +158 -0
  172. package/src/val/CopyFuncVal.ts +0 -7
  173. package/src/val/DisjunctVal.ts +152 -40
  174. package/src/val/ExpectVal.ts +39 -4
  175. package/src/val/FuncBaseVal.ts +21 -8
  176. package/src/val/GraphAtomVal.ts +264 -0
  177. package/src/val/JunctionVal.ts +23 -6
  178. package/src/val/ListVal.ts +30 -37
  179. package/src/val/MapVal.ts +87 -39
  180. package/src/val/PathFuncVal.ts +107 -19
  181. package/src/val/PathVal.ts +221 -0
  182. package/src/val/PlusOpVal.ts +56 -37
  183. package/src/val/PrefVal.ts +186 -38
  184. package/src/val/RecurseVal.ts +285 -0
  185. package/src/val/RefVal.ts +105 -83
  186. package/src/val/ReferFuncVal.ts +445 -100
  187. package/src/val/ScalarKindVal.ts +12 -0
  188. package/src/val/SuperFuncVal.ts +137 -13
  189. package/src/val/TopVal.ts +1 -1
  190. package/src/val/Val.ts +44 -35
  191. package/src/val/arith.ts +4 -1
  192. package/src/vet.ts +41 -4
  193. package/src/view.ts +2882 -0
  194. package/dist/val/IdFuncVal.d.ts +0 -13
  195. package/dist/val/IdFuncVal.js +0 -54
  196. package/dist/val/IdFuncVal.js.map +0 -1
  197. package/src/val/IdFuncVal.ts +0 -91
@@ -14,6 +14,7 @@ import {
14
14
  import { AontuContext } from '../ctx'
15
15
 
16
16
  import { AontuError, descErr, makeNilErr } from '../err'
17
+ import { exactJSON } from '../exactjson'
17
18
  import { unite } from '../unify'
18
19
 
19
20
  import {
@@ -70,7 +71,12 @@ class DisjunctVal extends JunctionVal {
70
71
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Disjunct', this, peer)
71
72
 
72
73
  if (!this.prefsRanked) {
73
- this.rankPrefs(ctx)
74
+ const ranked = this.rankPrefs(ctx)
75
+ // A clash between equal-rank defaults refuses for the whole
76
+ // disjunction (R2): the disagreement IS the answer.
77
+ if (null != ranked && ranked.isNil) {
78
+ return ranked
79
+ }
74
80
  }
75
81
 
76
82
  // // // console.log('DISJUNCT-unify-A', this.id, this.canon)
@@ -95,6 +101,16 @@ class DisjunctVal extends JunctionVal {
95
101
  // before return.
96
102
  const savedErr = ctx.err
97
103
  const savedTrialMode = ctx._trialMode
104
+ // A MEMBER'S TYPE FLOW IS PART OF THAT MEMBER. `refer(t)`/`rel(t)`
105
+ // assert `t` on ANOTHER node, and the assertion may only take
106
+ // effect if the member that makes it survives: committing every
107
+ // member's flow puts `t_A & t_B` on the target, which no reading
108
+ // of `A | B` licenses and which leaves the target MORE constrained
109
+ // than either alternative. The flow record is therefore staged per
110
+ // trial, like `ctx.err`, and only the survivors' records are
111
+ // merged into the real one below.
112
+ const savedFlows: Map<string, Val> | undefined = (ctx as any).referflows
113
+ const staged: (Map<string, Val> | undefined)[] = []
98
114
  // C1-inner: tell `makeNilErr` to return TRIAL_NIL in this scope
99
115
  // instead of allocating per-failure NilVals. Save/restore so
100
116
  // nested DisjunctVal trials (and the outer non-trial code) are
@@ -108,6 +124,10 @@ class DisjunctVal extends JunctionVal {
108
124
  const v = this.peg[vI]
109
125
  const trialErr: any[] = []
110
126
  ctx.err = trialErr
127
+ if (undefined !== savedFlows) {
128
+ staged[vI] = new Map()
129
+ ;(ctx as any).referflows = staged[vI]
130
+ }
111
131
 
112
132
  oval[vI] = unite(te ? ctx.clone({ explain: ec(te, 'DIST:' + vI) }) : ctx, v, peer, 'dj-peer')
113
133
 
@@ -140,7 +160,7 @@ class DisjunctVal extends JunctionVal {
140
160
  // plus `k:'autoo'` answered "autoo", and `*8080|(integer&neq(80))`
141
161
  // admitted 80 (use-cases/BUGS.md §1-2). An inadmissible override
142
162
  // now fails the pref member's trial, and when every member is
143
- // gone the meet is the existing `|:empty` refusal.
163
+ // gone the meet is the existing `empty` refusal.
144
164
  //
145
165
  // SCALAR preferred values only, exactly the kind gate's own
146
166
  // boundary (test/spec/pref.tsv, "THE GATE IS A SCALAR GATE"): a
@@ -176,6 +196,7 @@ class DisjunctVal extends JunctionVal {
176
196
  finally {
177
197
  ctx._trialMode = savedTrialMode
178
198
  ctx.err = savedErr
199
+ ;(ctx as any).referflows = savedFlows
179
200
  }
180
201
 
181
202
  // // // console.log('DISJUNCT-unify-B', this.id, oval.map(v => v.canon))
@@ -224,6 +245,31 @@ class DisjunctVal extends JunctionVal {
224
245
  for (let kI = vI + 1; kI < oval.length; kI++) {
225
246
  if (oval[kI].same(oval[vI])) {
226
247
  oval[kI] = TRIAL_NIL
248
+ continue
249
+ }
250
+
251
+ // TWO PREFERENCES OVER ONE VALUE ARE ONE PREFERENCE, and
252
+ // the lower rank is the one that generates: `*1 | **1` is
253
+ // `*1`. Ranking folds EQUAL ranks (R2); this folds the rest,
254
+ // which R5 stopped discarding.
255
+ //
256
+ // A PLAIN arm holding that same value is NOT a duplicate of
257
+ // it and must not be folded away: it is the sibling that
258
+ // ADMITS an override under ADR-004's gate, which is the
259
+ // whole point of writing `*x | x`. Folding it turned that
260
+ // idiom's own default into a `pref_not_instance` finding,
261
+ // and `*top | top` is pinned as its control.
262
+ const a: any = oval[vI]
263
+ const b: any = oval[kI]
264
+ if (true === a.isPref && true === b.isPref
265
+ && prefInnerPeg(a).same(prefInnerPeg(b))) {
266
+ if ((a as PrefVal).rank <= (b as PrefVal).rank) {
267
+ oval[kI] = TRIAL_NIL
268
+ }
269
+ else {
270
+ oval[vI] = TRIAL_NIL
271
+ break
272
+ }
227
273
  }
228
274
  }
229
275
  }
@@ -231,9 +277,27 @@ class DisjunctVal extends JunctionVal {
231
277
  // // // console.log('DISJUNCT-unify-D', this.id, oval.map(v => v.canon))
232
278
  }
233
279
 
280
+ // THE SURVIVORS' FLOWS, AND ONLY THEIRS. Members knocked out by
281
+ // the trial, by the admission gate or by the dedup above are all
282
+ // nil here, so this runs after every one of those and before the
283
+ // filter that drops them.
284
+ if (undefined !== savedFlows) {
285
+ for (let vI = 0; vI < staged.length; vI++) {
286
+ const st = staged[vI]
287
+ if (undefined === st || true === oval[vI]?.isNil) {
288
+ continue
289
+ }
290
+ for (const [k, fv] of st) {
291
+ const prev = savedFlows.get(k)
292
+ savedFlows.set(k, undefined === prev ? fv
293
+ : unite(ctx, prev, fv, 'refer-flow-record'))
294
+ }
295
+ }
296
+ }
297
+
234
298
  // Outside the 1<length block: a SINGLE-member disjunction (e.g. a
235
299
  // rankPrefs collapse) whose one member fails the trial or the
236
- // admission gate must reach the `|:empty` refusal below, not
300
+ // admission gate must reach the `empty` refusal below, not
237
301
  // return the trial sentinel as if it were the answer.
238
302
  oval = oval.filter(v => !v.isNil)
239
303
 
@@ -243,7 +307,7 @@ class DisjunctVal extends JunctionVal {
243
307
  out = oval[0]
244
308
  }
245
309
  else if (0 == oval.length) {
246
- return makeNilErr(ctx, '|:empty', this, peer)
310
+ return makeNilErr(ctx, 'empty', this, peer)
247
311
  }
248
312
  else {
249
313
  out = new DisjunctVal({ peg: oval }, ctx)
@@ -268,52 +332,61 @@ class DisjunctVal extends JunctionVal {
268
332
  }
269
333
 
270
334
 
271
- // Answers the sole surviving preference when ranking collapsed the
272
- // disjunction to one -- which the RECURSIVE call below consumes to
273
- // lift a nested disjunct's winner into this one. Undefined when
274
- // more than one alternative survives, so the type says both.
275
- rankPrefs(ctx: AontuContext): PrefVal | undefined {
276
- let lastpref: PrefVal | undefined = undefined
277
- let lastprefI = -1
335
+ // RANK ORDERS THE SURVIVORS, IT DOES NOT DISCARD AT PARSE (ADR-011
336
+ // R5, docs/design/DEFAULTS.0.md). Every rank is KEPT here, and
337
+ // generation takes the lowest-rank preference still standing -- so
338
+ // eliminating the lower arm PROMOTES the next instead of taking the
339
+ // whole ladder with it. This ran once, before the member trials,
340
+ // and discarded every arm but the lowest: `*1 | **2` met by
341
+ // `neq(1)` lost the default entirely and refused, where the ladder
342
+ // exists precisely to answer 2. Rank is a preference order over
343
+ // WHAT SURVIVES, which is not knowable until the trials have run.
344
+ //
345
+ // Only EQUAL ranks fold, because two defaults at one rank are one
346
+ // decision: compatible pegs merge, and a disagreement is the
347
+ // `pref_rank_clash` refusal (R2), which belongs to the whole
348
+ // disjunction -- there is no alternative to fall back to.
349
+ //
350
+ // Answers the sole surviving preference when the disjunction holds
351
+ // exactly one -- which the RECURSIVE call below consumes to lift a
352
+ // nested disjunct's winner into this one -- or the clash refusal.
353
+ rankPrefs(ctx: AontuContext): Val | undefined {
354
+ // The kept index per rank, so an equal-rank twin folds into the
355
+ // arm already standing for that rank.
356
+ const atRank: Record<number, number> = {}
278
357
 
279
358
  // // // console.log('RP-A', this.peg.map((p: Val) => p.canon))
280
359
 
281
360
  for (let vI = 0; vI < this.peg.length; vI++) {
282
361
  const v = this.peg[vI]
362
+ let pref: PrefVal | undefined = undefined
363
+
283
364
  if (v instanceof PrefVal) {
284
- if (null != lastpref) {
285
- if (v.rank === lastpref.rank) {
286
- const pref = v.unify(lastpref, ctx) as PrefVal
287
- if (pref.isNil) {
288
- return pref
289
- }
290
- else {
291
- this.peg[lastprefI] = pref
292
- lastpref = pref
293
- this.peg[vI] = null
294
- }
295
- // return Nil.make(ctx, '|:prefs', lastpref, v, 'associate')
296
- }
297
- else if (v.rank < lastpref.rank) {
298
- this.peg[lastprefI] = null
299
- lastpref = v
300
- lastprefI = vI
301
- }
302
- else {
303
- this.peg[vI] = null
304
- }
305
- }
306
- else {
307
- lastpref = v
308
- lastprefI = vI
309
- }
365
+ pref = v
310
366
  }
311
367
  else if (v.isDisjunct) {
312
368
  const subrank = (v as DisjunctVal).rankPrefs(ctx)
369
+ if (null != subrank && subrank.isNil) {
370
+ return subrank
371
+ }
313
372
  if (subrank instanceof PrefVal) {
314
373
  this.peg[vI] = subrank
315
- lastpref = subrank
316
- lastprefI = vI
374
+ pref = subrank
375
+ }
376
+ }
377
+
378
+ if (undefined !== pref) {
379
+ const at = atRank[pref.rank]
380
+ if (undefined === at) {
381
+ atRank[pref.rank] = vI
382
+ }
383
+ else {
384
+ const folded = pref.unify(this.peg[at], ctx)
385
+ if (folded.isNil) {
386
+ return folded
387
+ }
388
+ this.peg[at] = folded
389
+ this.peg[vI] = null
317
390
  }
318
391
  }
319
392
  }
@@ -373,6 +446,33 @@ class DisjunctVal extends JunctionVal {
373
446
 
374
447
  const prefs = this.peg.filter((v: Val) => v instanceof PrefVal)
375
448
 
449
+ // ALTERNATIVES THAT GENERATE THE SAME VALUE ARE RESOLVED
450
+ // (ADR-007's rule, asked at the moment it matters): `[] | [&:
451
+ // T]` met by an empty list keeps both arms -- they differ as
452
+ // SCHEMAS, which is what stops the dedup collapsing the
453
+ // template away (BUGS.md §52 regime 4) -- but both generate
454
+ // the empty list, and one generated value is one value.
455
+ if (0 === prefs.length && 1 < this.peg.length) {
456
+ const gctx = ctx.clone({ err: [], collect: true })
457
+ let firstOut: any
458
+ let allSame = true
459
+ for (let gI = 0; gI < this.peg.length && allSame; gI++) {
460
+ const gout = this.peg[gI].gen(gctx)
461
+ if (0 < gctx.err.length || undefined === gout) {
462
+ allSame = false
463
+ }
464
+ else if (0 === gI) {
465
+ firstOut = gout
466
+ }
467
+ else {
468
+ allSame = exactJSON(gout) === exactJSON(firstOut)
469
+ }
470
+ }
471
+ if (allSame) {
472
+ return firstOut
473
+ }
474
+ }
475
+
376
476
  if (0 === prefs.length && 1 < this.peg.length) {
377
477
  const nerr = makeNilErr(ctx, 'disjunct_no_gen', this)
378
478
  descErr(nerr, ctx)
@@ -385,7 +485,19 @@ class DisjunctVal extends JunctionVal {
385
485
  return undefined
386
486
  }
387
487
 
388
- return (0 < prefs.length ? prefs[0] : this.peg[0]).gen(ctx)
488
+ // THE LOWEST-RANK SURVIVOR (R5). Ranking no longer discards the
489
+ // weaker arms, so the choice is made here, over what is left:
490
+ // `*1 | **2` answers 1, and answers 2 once `*1` is gone.
491
+ let best: any = this.peg[0]
492
+ if (0 < prefs.length) {
493
+ best = prefs[0]
494
+ for (const p of prefs) {
495
+ if ((p as PrefVal).rank < best.rank) {
496
+ best = p
497
+ }
498
+ }
499
+ }
500
+ return best.gen(ctx)
389
501
  }
390
502
 
391
503
  return super.gen(ctx)
@@ -47,6 +47,9 @@ class ExpectVal extends FeatureVal {
47
47
  // canon reparsed into a document that accepts values the original
48
48
  // rejects. A canon that silently drops a constraint is worse than one
49
49
  // that fails to parse. Go's ExpectVal.Canon renders the same peg.
50
+ //
51
+ // Which is why `peg` is kept the WHOLE expectation as peers arrive
52
+ // (BUGS.md §48) rather than only what was first written: see unify.
50
53
  get canon() { return this.peg.canon }
51
54
 
52
55
  constructor(
@@ -73,12 +76,27 @@ class ExpectVal extends FeatureVal {
73
76
  let out: Val = this
74
77
 
75
78
  if (!peer.isTop) {
79
+ // THE PEER MEETS THE WHOLE EXPECTATION. `peg` already carries
80
+ // every peer met so far (see below), so meeting the incoming peer
81
+ // against the ACCUMULATED `peer` first -- as this did -- refused
82
+ // against only the atom that happened to reject. A conflict with
83
+ // `peg` is a conflict with the accumulation too, since peg
84
+ // subsumes it, so nothing stops being refused: only the sentence
85
+ // changes, to the residual the error's own hint promises
86
+ // (BUGS.md §48).
87
+ const peeru =
88
+ unite(te ? ctx.clone({ explain: ec(te, 'EXPECT') }) : ctx, peer, this.peg, 'expect-self')
89
+
90
+ // Accumulated for the `expect` finding's operand. Computed BEFORE
91
+ // the escape below, where it has always been: an expectation that
92
+ // escapes still passed through here, and moving it after the
93
+ // escape leaves the accumulating arm reachable only by a node
94
+ // that both carries an accumulation AND fails to escape -- which
95
+ // the suite never produces, so it reads as dead code against the
96
+ // ADR-002 gate. Go's ExpectVal.Unify keeps the same order.
76
97
  const acc = undefined === this.peer ? peer :
77
98
  unite(te ? ctx.clone({ explain: ec(te, 'PEER') }) : ctx, this.peer, peer, 'expect-peer')
78
99
 
79
- const peeru =
80
- unite(te ? ctx.clone({ explain: ec(te, 'EXPECT') }) : ctx, acc, this.peg, 'expect-self')
81
-
82
100
  if (peeru.isGenable) {
83
101
  out = peeru
84
102
  }
@@ -86,7 +104,24 @@ class ExpectVal extends FeatureVal {
86
104
  // Still an expectation: carry the accumulated peer forward in a
87
105
  // fresh node stored at THIS destination by the caller, leaving
88
106
  // `this` -- possibly a shared template's child -- untouched.
89
- const e = new ExpectVal({ peg: this.peg }, ctx)
107
+ //
108
+ // THE MEET IS THE NEW PEG (BUGS.md §48). An expectation that
109
+ // has met a peer without being freed by it stands for `peg &
110
+ // peer` from then on -- that is what a later peer must satisfy,
111
+ // and what canon has to state. Rebuilding from the ORIGINAL peg
112
+ // dropped the peer everywhere the node was later copied (the
113
+ // bag re-wrap, Val.clone), so `b:{z:1} b:{u8:min(0)}
114
+ // a:$.b.u8&max(15)` -- whose reference resolves a pass late, so
115
+ // `max(15)` arrives as a peer -- canoned as `min(0)`. That text
116
+ // reparses into a document admitting 20, which the original
117
+ // rejects, and hashed differently in each port. Storing the
118
+ // meet in `peg` needs no new field and no carrying: every copy
119
+ // site already preserves `peg`.
120
+ //
121
+ // Purity is untouched -- this is a NEW node, so a shared
122
+ // template's own expectation keeps the peg it was written with
123
+ // (§6-§7).
124
+ const e = new ExpectVal({ peg: peeru }, ctx)
90
125
  e.key = this.key
91
126
  e.parent = this.parent
92
127
  e.peer = acc
@@ -22,6 +22,7 @@ import {
22
22
  } from '../utility'
23
23
 
24
24
  import { makeNilErr, AontuError } from '../err'
25
+ import { sigRefuse } from '../siggate'
25
26
 
26
27
  import {
27
28
  top
@@ -271,16 +272,17 @@ class FuncBaseVal extends FeatureVal {
271
272
 
272
273
  // console.log('FUNCBASE-PEG', this.id, pegdone, this.peg.map((p: any) => p?.canon))
273
274
 
274
- if (pegdone) {
275
- const resolved = this.resolve(ctx, newpeg)
275
+ if (pegdone && !this.deferResolve(ctx, newpeg)) {
276
+ // THE SIGNATURE GATE (docs/design/SIGNATURES.0.md): the
277
+ // driven arguments against the declared signature, before
278
+ // the builtin's own logic sees them. See siggate.ts for
279
+ // what the gate owns and what stays with the builtins.
280
+ const resolved = sigRefuse(ctx, this, newpeg) ??
281
+ this.resolve(ctx, newpeg)
276
282
  // console.log('FUNC-RESOLVED', ctx.cc, resolved?.canon)
277
283
 
278
- // The TOP peer is DROPPED as the unit it is — unless it
279
- // carries an identity (G4 phase 1), which is content rather
280
- // than the unit: `id(x) & id(y)` resolves both sides to a
281
- // top, and taking this shortcut would silently keep one
282
- // name and lose the other instead of refusing the pair.
283
- out = resolved.done && peer.isTop && null == peer.entity ? resolved :
284
+ // The TOP peer is DROPPED as the unit it is.
285
+ out = resolved.done && peer.isTop ? resolved :
284
286
  unite(te ? ctx.clone({ explain: ec(te, 'PEG') }) : ctx,
285
287
  resolved, peer, 'func-' + this.funcname() + '/' + this.id)
286
288
  propagateMarks(this, out)
@@ -393,6 +395,17 @@ class FuncBaseVal extends FeatureVal {
393
395
  }
394
396
 
395
397
 
398
+ // A function may hold its resolution for a later pass even with its
399
+ // arguments settled -- it then rides the ordinary args-not-done
400
+ // path, residuating as any unresolved call does. super() defers on a
401
+ // recursion residual argument (SuperFuncVal), which is why the DRIVEN
402
+ // arguments are passed: this.peg still holds the undriven originals
403
+ // at the decision point.
404
+ deferResolve(_ctx: AontuContext, _args?: Val[]): boolean {
405
+ return false
406
+ }
407
+
408
+
396
409
  } /* node:coverage ignore next 6 */
397
410
 
398
411
 
@@ -0,0 +1,264 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // THE GRAPH ATOMS (RELATIONS.0.md §3.3): `acyclic()` and
4
+ // `inverse(name)`, conjoined at the same field as the `rel()` they
5
+ // govern. Their model is the sizing atoms -- a property that cannot be
6
+ // decided while information can still arrive is HELD during
7
+ // unification and DECIDED at generation, where no more can.
8
+ //
9
+ // During unification they are lattice-inert: both properties are
10
+ // global and non-monotone (one more edge can make an acyclic graph
11
+ // cyclic), and the lattice guarantee -- more information never
12
+ // falsifies what has been observed -- forbids a constraint that could
13
+ // answer true and then false. So the atoms only RESIDUATE: they ride
14
+ // the field through meets, dedup additively, appear in canon and reach
15
+ // the `aon1-` hash, and REGISTER the declaration on the context. The
16
+ // predicate they govern is the key they sit on, evaluation-global,
17
+ // additive across declarations.
18
+ //
19
+ // The verdict lands at generation (relationVerdict,
20
+ // ts/src/relation.ts) and is reported identically by the `relations`
21
+ // verb -- one decision, two surfaces.
22
+
23
+ import type {
24
+ Val,
25
+ ValSpec,
26
+ } from '../type'
27
+
28
+ import {
29
+ DONE,
30
+ } from '../type'
31
+
32
+ import {
33
+ AontuContext,
34
+ } from '../ctx'
35
+
36
+ import { makeNilErr } from '../err'
37
+
38
+ import { FuncBaseVal } from './FuncBaseVal'
39
+ import { FeatureVal } from './FeatureVal'
40
+ import { unite } from '../unify'
41
+ import { propagateMarks } from '../utility'
42
+
43
+
44
+ // The declarations one evaluation accumulates: predicate -> what its
45
+ // atoms said. Additive, exactly as two statements of one map are. The
46
+ // map lives on the CONTEXT (created in the constructor, like the
47
+ // _depth box), so every clone shares one registry.
48
+ export type RelDecl = {
49
+ acyclic?: boolean
50
+ inverses: Set<string>
51
+ }
52
+
53
+ export function relDecls(ctx: any): Map<string, RelDecl> {
54
+ return ctx._reldecls
55
+ }
56
+
57
+
58
+ class GraphAtomVal extends FeatureVal {
59
+ isGraphAtom = true
60
+ isGenable = true
61
+ // AFTER rel() (45000): the atoms say nothing about the value.
62
+ cjo = 46000
63
+
64
+ akind: 'acyclic' | 'inverse'
65
+ invname?: string
66
+ // The value the atom rides on -- the sizing-constraint shape: the
67
+ // atom ABSORBS its fold neighbours (the rel, the container, another
68
+ // atom) and carries them, so the fold's pairwise walk still merges
69
+ // the value across it. Absent until the atom meets one.
70
+ held?: Val
71
+
72
+ constructor(spec: ValSpec, ctx?: AontuContext) {
73
+ super(spec, ctx)
74
+ this.akind = (spec as any).akind ?? 'acyclic'
75
+ this.invname = (spec as any).invname
76
+ this.held = (spec as any).held
77
+ // A settled residual, like an unmet rel(): the bare atom is its
78
+ // own value, and a type() body carrying one must settle. Holding
79
+ // an unsettled value, it is exactly as done as the value.
80
+ this.dc = undefined === this.held || true === this.held.done
81
+ ? DONE : 0
82
+ }
83
+
84
+ clone(ctx: AontuContext, spec?: ValSpec): Val {
85
+ const out: any = super.clone(ctx, spec)
86
+ out.akind = this.akind
87
+ out.invname = this.invname
88
+ out.held = this.held
89
+ out.dc = this.dc
90
+ return out
91
+ }
92
+
93
+ // A rebuilt atom around a new held, at this atom's position.
94
+ private carry(ctx: AontuContext, held: Val): Val {
95
+ const out: any = new GraphAtomVal(
96
+ { akind: this.akind, invname: this.invname, held } as any, ctx)
97
+ propagateMarks(this, out)
98
+ out.site = this.site
99
+ out.path = this.path
100
+ return out
101
+ }
102
+
103
+ // The predicate is the key the atom sits on -- and a predicate is a
104
+ // D-1 NAME, by exactly fieldkey's rule: an atom landed anywhere
105
+ // else declares nothing. Registration is idempotent (the
106
+ // declaration set is a set) and happens at every drive, so
107
+ // whichever pass first sees the atom at its landed position records
108
+ // it.
109
+ register(ctx: AontuContext): void {
110
+ const seg = this.path[this.path.length - 1]
111
+ if ('string' !== typeof seg || !GRAPH_ATOM_NAME.test(seg)) {
112
+ return
113
+ }
114
+ const decls = relDecls(ctx)
115
+ let d = decls.get(seg)
116
+ if (undefined === d) {
117
+ d = { inverses: new Set() }
118
+ decls.set(seg, d)
119
+ }
120
+ if ('acyclic' === this.akind) {
121
+ d.acyclic = true
122
+ }
123
+ else if (undefined !== this.invname) {
124
+ d.inverses.add(this.invname)
125
+ }
126
+ }
127
+
128
+ unify(peer: Val, ctx: AontuContext): Val {
129
+ const p: any = peer
130
+ this.register(ctx)
131
+
132
+ // The self-drive: unite's tail calls unify(top) directly on any
133
+ // not-done result, and the held is what still has work to do.
134
+ // (A null/nil peer never arrives -- unite's ladder absorbs both.)
135
+ if (null == peer || true === p.isTop) {
136
+ if (undefined === this.held) {
137
+ return this
138
+ }
139
+ if (true === this.held.done) {
140
+ // Doneness is monotone, so recording it in place is safe --
141
+ // and without it the bag walk keeps asking and generation
142
+ // refuses a finished value.
143
+ this.dc = DONE
144
+ return this
145
+ }
146
+ // The self-drive refines IN PLACE (the MapVal top-peer
147
+ // pattern): a fresh atom per pass changes object identity, so
148
+ // spread apply-once stamps and the entity merge's fast paths
149
+ // stop holding, and the enclosing bags re-open every pass --
150
+ // the service catalog never converged.
151
+ const held = unite(ctx, this.held, undefined, 'atom-drive')
152
+ if (true === held.isNil) {
153
+ return held
154
+ }
155
+ this.held = held
156
+ if (true === held.done) {
157
+ this.dc = DONE
158
+ }
159
+ return this
160
+ }
161
+
162
+ // The SAME declaration twice is one declaration; their helds
163
+ // merge.
164
+ if (true === p.isGraphAtom &&
165
+ p.akind === this.akind && p.invname === this.invname) {
166
+ const held = undefined === this.held ? p.held
167
+ : undefined === p.held ? this.held
168
+ : unite(ctx, this.held, p.held, 'atom-dup')
169
+ if (undefined !== held && true === held.isNil) {
170
+ return held
171
+ }
172
+ return undefined === held ? this : this.carry(ctx, held)
173
+ }
174
+
175
+ // Anything else -- the rel, the container, a different atom -- is
176
+ // ABSORBED: the atom carries the value and the fold's pairwise
177
+ // walk merges across it.
178
+ const held = undefined === this.held ? peer
179
+ : unite(ctx, this.held, peer, 'atom-held')
180
+ return true === held.isNil ? held : this.carry(ctx, held)
181
+ }
182
+
183
+ get canon(): string {
184
+ const own = 'acyclic' === this.akind
185
+ ? 'acyclic()'
186
+ : 'inverse(' + JSON.stringify(this.invname) + ')'
187
+ return undefined === this.held ? own : this.held.canon + '&' + own
188
+ }
189
+
190
+ gen(ctx: AontuContext) {
191
+ // The atom is transparent at generation -- its verdict is global
192
+ // (relationVerdict), never a value at this field -- and a BARE
193
+ // atom is silent, exactly as an unmet rel() is.
194
+ return undefined === this.held ? undefined : this.held.gen(ctx)
195
+ }
196
+ }
197
+
198
+
199
+ // D-1 (docs/design/RELATIONS.0.md): the name grammar shared by entity
200
+ // names, edge predicates and inverse names.
201
+ const GRAPH_ATOM_NAME = /^[_a-zA-Z][-_a-zA-Z0-9]*$/
202
+
203
+
204
+ class AcyclicFuncVal extends FuncBaseVal {
205
+ isAcyclicFunc = true
206
+
207
+ constructor(spec: ValSpec, ctx?: AontuContext) {
208
+ super(spec, ctx)
209
+ }
210
+
211
+ make(_ctx: AontuContext, spec: ValSpec): Val {
212
+ return new AcyclicFuncVal(spec)
213
+ }
214
+
215
+ funcname() {
216
+ return 'acyclic'
217
+ }
218
+
219
+ resolve(ctx: AontuContext, _args: Val[]) {
220
+ const out: any = new GraphAtomVal({ akind: 'acyclic' } as any, ctx)
221
+ out.site = this.site
222
+ out.path = this.path
223
+ return out
224
+ }
225
+ }
226
+
227
+
228
+ class InverseFuncVal extends FuncBaseVal {
229
+ isInverseFunc = true
230
+
231
+ constructor(spec: ValSpec, ctx?: AontuContext) {
232
+ super(spec, ctx)
233
+ }
234
+
235
+ make(_ctx: AontuContext, spec: ValSpec): Val {
236
+ return new InverseFuncVal(spec)
237
+ }
238
+
239
+ funcname() {
240
+ return 'inverse'
241
+ }
242
+
243
+ resolve(ctx: AontuContext, args: Val[]) {
244
+ const a: any = args[0]
245
+ // The mirroring predicate is a NAME -- D-1, spelled bare or
246
+ // quoted. A relation is a vocabulary term, not an address.
247
+ if (true !== a?.isScalar || 'string' !== typeof a.peg
248
+ || !GRAPH_ATOM_NAME.test(a.peg)) {
249
+ return makeNilErr(ctx, 'inverse_name', this, undefined, 'inverse')
250
+ }
251
+ const out: any = new GraphAtomVal(
252
+ { akind: 'inverse', invname: a.peg } as any, ctx)
253
+ out.site = this.site
254
+ out.path = this.path
255
+ return out
256
+ }
257
+ } /* node:coverage ignore next 7 */
258
+
259
+
260
+ export {
261
+ GraphAtomVal,
262
+ AcyclicFuncVal,
263
+ InverseFuncVal,
264
+ }