aontu 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/dist/aontu.d.ts +3 -2
  2. package/dist/aontu.js +36 -7
  3. package/dist/aontu.js.map +1 -1
  4. package/dist/cli.d.ts +2 -1
  5. package/dist/cli.js +500 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/ctx.d.ts +5 -0
  8. package/dist/ctx.js +1 -0
  9. package/dist/ctx.js.map +1 -1
  10. package/dist/diff.js.map +1 -1
  11. package/dist/err.js +7 -1
  12. package/dist/err.js.map +1 -1
  13. package/dist/graph.d.ts +2 -5
  14. package/dist/graph.js +83 -46
  15. package/dist/graph.js.map +1 -1
  16. package/dist/hcanon.js +9 -10
  17. package/dist/hcanon.js.map +1 -1
  18. package/dist/hints.js +94 -26
  19. package/dist/hints.js.map +1 -1
  20. package/dist/jsonschema.js +34 -0
  21. package/dist/jsonschema.js.map +1 -1
  22. package/dist/lang.js +593 -191
  23. package/dist/lang.js.map +1 -1
  24. package/dist/lsp.d.ts +1 -1
  25. package/dist/lsp.js +86 -6
  26. package/dist/lsp.js.map +1 -1
  27. package/dist/mcp.js +153 -6
  28. package/dist/mcp.js.map +1 -1
  29. package/dist/mod-tool.js +42 -8
  30. package/dist/mod-tool.js.map +1 -1
  31. package/dist/mod.d.ts +4 -0
  32. package/dist/mod.js +97 -2
  33. package/dist/mod.js.map +1 -1
  34. package/dist/patch.d.ts +5 -0
  35. package/dist/patch.js +25 -25
  36. package/dist/patch.js.map +1 -1
  37. package/dist/provenance.d.ts +1 -0
  38. package/dist/provenance.js +2 -1
  39. package/dist/provenance.js.map +1 -1
  40. package/dist/query.js.map +1 -1
  41. package/dist/reach.d.ts +1 -0
  42. package/dist/reach.js +49 -21
  43. package/dist/reach.js.map +1 -1
  44. package/dist/relation.d.ts +4 -0
  45. package/dist/relation.js +125 -200
  46. package/dist/relation.js.map +1 -1
  47. package/dist/sig.d.ts +25 -0
  48. package/dist/sig.js +277 -0
  49. package/dist/sig.js.map +1 -0
  50. package/dist/sigdecl.d.ts +2 -0
  51. package/dist/sigdecl.js +11 -0
  52. package/dist/sigdecl.js.map +1 -0
  53. package/dist/siggate.d.ts +4 -0
  54. package/dist/siggate.js +90 -0
  55. package/dist/siggate.js.map +1 -0
  56. package/dist/std.js +75 -16
  57. package/dist/std.js.map +1 -1
  58. package/dist/subsume.js +57 -10
  59. package/dist/subsume.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/dist/unify.d.ts +2 -2
  62. package/dist/unify.js +93 -114
  63. package/dist/unify.js.map +1 -1
  64. package/dist/utility.d.ts +1 -2
  65. package/dist/utility.js +7 -61
  66. package/dist/utility.js.map +1 -1
  67. package/dist/val/AggFuncVal.d.ts +12 -1
  68. package/dist/val/AggFuncVal.js +165 -3
  69. package/dist/val/AggFuncVal.js.map +1 -1
  70. package/dist/val/BagVal.d.ts +2 -0
  71. package/dist/val/BagVal.js +56 -8
  72. package/dist/val/BagVal.js.map +1 -1
  73. package/dist/val/ConstraintVal.js +33 -5
  74. package/dist/val/ConstraintVal.js.map +1 -1
  75. package/dist/val/ContainerKindVal.d.ts +35 -0
  76. package/dist/val/ContainerKindVal.js +99 -0
  77. package/dist/val/ContainerKindVal.js.map +1 -0
  78. package/dist/val/CopyFuncVal.js +0 -7
  79. package/dist/val/CopyFuncVal.js.map +1 -1
  80. package/dist/val/DisjunctVal.d.ts +1 -2
  81. package/dist/val/DisjunctVal.js +146 -39
  82. package/dist/val/DisjunctVal.js.map +1 -1
  83. package/dist/val/ExpectVal.js +37 -2
  84. package/dist/val/ExpectVal.js.map +1 -1
  85. package/dist/val/FuncBaseVal.d.ts +1 -0
  86. package/dist/val/FuncBaseVal.js +19 -8
  87. package/dist/val/FuncBaseVal.js.map +1 -1
  88. package/dist/val/GraphAtomVal.d.ts +39 -0
  89. package/dist/val/GraphAtomVal.js +184 -0
  90. package/dist/val/GraphAtomVal.js.map +1 -0
  91. package/dist/val/JunctionVal.js +22 -5
  92. package/dist/val/JunctionVal.js.map +1 -1
  93. package/dist/val/ListVal.js +27 -34
  94. package/dist/val/ListVal.js.map +1 -1
  95. package/dist/val/MapVal.d.ts +1 -0
  96. package/dist/val/MapVal.js +82 -35
  97. package/dist/val/MapVal.js.map +1 -1
  98. package/dist/val/PathFuncVal.d.ts +2 -2
  99. package/dist/val/PathFuncVal.js +75 -16
  100. package/dist/val/PathFuncVal.js.map +1 -1
  101. package/dist/val/PathVal.d.ts +25 -0
  102. package/dist/val/PathVal.js +150 -0
  103. package/dist/val/PathVal.js.map +1 -0
  104. package/dist/val/PlusOpVal.d.ts +2 -1
  105. package/dist/val/PlusOpVal.js +50 -35
  106. package/dist/val/PlusOpVal.js.map +1 -1
  107. package/dist/val/PrefVal.d.ts +2 -0
  108. package/dist/val/PrefVal.js +159 -32
  109. package/dist/val/PrefVal.js.map +1 -1
  110. package/dist/val/RecurseVal.d.ts +19 -0
  111. package/dist/val/RecurseVal.js +217 -0
  112. package/dist/val/RecurseVal.js.map +1 -0
  113. package/dist/val/RefVal.d.ts +2 -1
  114. package/dist/val/RefVal.js +105 -72
  115. package/dist/val/RefVal.js.map +1 -1
  116. package/dist/val/ReferFuncVal.d.ts +30 -7
  117. package/dist/val/ReferFuncVal.js +395 -94
  118. package/dist/val/ReferFuncVal.js.map +1 -1
  119. package/dist/val/ScalarKindVal.d.ts +4 -2
  120. package/dist/val/ScalarKindVal.js +12 -1
  121. package/dist/val/ScalarKindVal.js.map +1 -1
  122. package/dist/val/SuperFuncVal.d.ts +4 -2
  123. package/dist/val/SuperFuncVal.js +118 -14
  124. package/dist/val/SuperFuncVal.js.map +1 -1
  125. package/dist/val/TopVal.d.ts +1 -1
  126. package/dist/val/Val.d.ts +2 -3
  127. package/dist/val/Val.js +39 -20
  128. package/dist/val/Val.js.map +1 -1
  129. package/dist/val/arith.js +4 -1
  130. package/dist/val/arith.js.map +1 -1
  131. package/dist/vet.d.ts +1 -0
  132. package/dist/vet.js +32 -1
  133. package/dist/vet.js.map +1 -1
  134. package/dist/view.d.ts +90 -0
  135. package/dist/view.js +2168 -0
  136. package/dist/view.js.map +1 -0
  137. package/grammar/aontu.gbnf +18 -10
  138. package/grammar/aontu.lark +15 -10
  139. package/grammar/aontu.tmLanguage.json +184 -0
  140. package/package.json +10 -3
  141. package/skill/grammar-card.md +1 -2
  142. package/src/aontu.ts +37 -7
  143. package/src/cli.ts +553 -8
  144. package/src/ctx.ts +20 -0
  145. package/src/diff.ts +4 -2
  146. package/src/err.ts +8 -1
  147. package/src/graph.ts +125 -75
  148. package/src/hcanon.ts +9 -11
  149. package/src/hints.ts +112 -29
  150. package/src/jsonschema.ts +41 -0
  151. package/src/lang.ts +642 -203
  152. package/src/lsp.ts +75 -6
  153. package/src/mcp.ts +164 -6
  154. package/src/mod-tool.ts +48 -9
  155. package/src/mod.ts +110 -1
  156. package/src/patch.ts +31 -27
  157. package/src/provenance.ts +8 -1
  158. package/src/query.ts +4 -2
  159. package/src/reach.ts +52 -23
  160. package/src/relation.ts +139 -236
  161. package/src/sig.ts +345 -0
  162. package/src/sigdecl.ts +11 -0
  163. package/src/siggate.ts +144 -0
  164. package/src/std.ts +77 -16
  165. package/src/subsume.ts +59 -10
  166. package/src/unify.ts +102 -125
  167. package/src/utility.ts +7 -67
  168. package/src/val/AggFuncVal.ts +231 -4
  169. package/src/val/BagVal.ts +58 -9
  170. package/src/val/ConstraintVal.ts +34 -5
  171. package/src/val/ContainerKindVal.ts +158 -0
  172. package/src/val/CopyFuncVal.ts +0 -7
  173. package/src/val/DisjunctVal.ts +152 -40
  174. package/src/val/ExpectVal.ts +39 -4
  175. package/src/val/FuncBaseVal.ts +21 -8
  176. package/src/val/GraphAtomVal.ts +264 -0
  177. package/src/val/JunctionVal.ts +23 -6
  178. package/src/val/ListVal.ts +30 -37
  179. package/src/val/MapVal.ts +87 -39
  180. package/src/val/PathFuncVal.ts +107 -19
  181. package/src/val/PathVal.ts +221 -0
  182. package/src/val/PlusOpVal.ts +56 -37
  183. package/src/val/PrefVal.ts +186 -38
  184. package/src/val/RecurseVal.ts +285 -0
  185. package/src/val/RefVal.ts +105 -83
  186. package/src/val/ReferFuncVal.ts +445 -100
  187. package/src/val/ScalarKindVal.ts +12 -0
  188. package/src/val/SuperFuncVal.ts +137 -13
  189. package/src/val/TopVal.ts +1 -1
  190. package/src/val/Val.ts +44 -35
  191. package/src/val/arith.ts +4 -1
  192. package/src/vet.ts +41 -4
  193. package/src/view.ts +2882 -0
  194. package/dist/val/IdFuncVal.d.ts +0 -13
  195. package/dist/val/IdFuncVal.js +0 -54
  196. package/dist/val/IdFuncVal.js.map +0 -1
  197. package/src/val/IdFuncVal.ts +0 -91
package/src/subsume.ts CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  constraintAdmitsScalar,
33
33
  } from './val/ConstraintVal'
34
34
  import { kindSubsumes } from './val/ScalarKindVal'
35
+ import { prefixMeet } from './val/PathVal'
35
36
  import { prefInnerPeg } from './val/PrefVal'
36
37
 
37
38
 
@@ -360,6 +361,22 @@ export function subsumeNode(
360
361
  return 'no'
361
362
  }
362
363
 
364
+ // Container kinds (docs/design/PATHS.0.md): `map()` subsumes every
365
+ // map and itself, `list()` every list. The unit literals (`{}`,
366
+ // `[]`) already subsume through the container rules; only the kind
367
+ // former needs an arm.
368
+ if (true === (g as any)?.isContainerKind) {
369
+ const ga: any = g
370
+ const sa: any = s
371
+ if ((true === ga.isMapKind && (true === sa?.isMap || true === sa?.isMapKind))
372
+ || (true === ga.isListKind && (true === sa?.isList || true === sa?.isListKind))) {
373
+ return 'yes'
374
+ }
375
+ record(state, 'compat_narrowed', path, g, s,
376
+ 'the general container kind admits no such value')
377
+ return 'no'
378
+ }
379
+
363
380
  // Constraint residuals.
364
381
  if (true === g?.isConstraint) {
365
382
  if (true === s?.isConstraint) {
@@ -396,8 +413,14 @@ export function subsumeNode(
396
413
  }
397
414
 
398
415
  // Concrete scalars subsume only themselves (identity compares kind
399
- // as well as value).
416
+ // as well as value) -- except paths, whose meet is the prefix rule
417
+ // (ADR-016): a prefix admits every extension of itself, so it
418
+ // subsumes one, exactly as the meet answers the longer.
400
419
  if (true === g?.isScalar) {
420
+ if (true === g.isPath && true === (s as any)?.isPath &&
421
+ prefixMeet(g.peg, (s as any).peg) === (s as any).peg) {
422
+ return 'yes'
423
+ }
401
424
  if (true === s?.isScalar && true === g.same?.(s)) {
402
425
  return 'yes'
403
426
  }
@@ -431,13 +454,29 @@ export function subsumeNode(
431
454
  (v: any, k: string) => v.peg[Number(k)])
432
455
  }
433
456
 
434
- // The ladder above is total in practice: every evaluated former is a
435
- // scalar, kind, constraint, map, list, disjunct, or top, or is caught
436
- // by admission (pref) or unresolved (ref, var, conjunct, expect,
437
- // func). The arm is kept because "in practice" is evaluation's
438
- // property, not this walk's, and a future value class (or a nil, see
439
- // the top rule) must land on an honest `undecided`, not fall out of
440
- // the walk with no answer.
457
+ // THE LADDER IS NOT TOTAL, and the formers that fall past it are the
458
+ // ones the evaluator MEANS to leave standing: a recursion, a
459
+ // relation and its graph atom, a `refer()` target constraint. None
460
+ // of them describes a set of values a structural walk can compare,
461
+ // so `undecided` is the honest answer for two DIFFERENT ones.
462
+ //
463
+ // For two IDENTICAL ones it is not. REFLEXIVITY IS A LAW -- every
464
+ // value admits itself -- and identity is the HASH FORM, the same
465
+ // rule the unresolved branch above applies and for the same reason:
466
+ // it costs nothing, because it runs only where the answer would
467
+ // otherwise be `undecided`. Without it a document that declares a
468
+ // relation, shares a template by reference or alias, or recurses did
469
+ // not subsume ITSELF, so `breaking` on the idiom the language exists
470
+ // for hard-failed and had to run --allow-undecided, which masks the
471
+ // genuine undecideds it exists to surface (use-cases/BUGS.md 64,
472
+ // and 28 before it).
473
+ //
474
+ // A NIL is the exception, and the reason the law is spelled here
475
+ // rather than in `unresolved`: a nil is not a value, so it admits
476
+ // nothing, itself included.
477
+ if (true !== g?.isNil && true !== s?.isNil && hcanon(g) === hcanon(s)) {
478
+ return 'yes'
479
+ }
441
480
  record(state, 'sub_unresolved', path, g, s,
442
481
  'no subsumption rule covers this pair of value formers')
443
482
  return 'undecided'
@@ -510,11 +549,21 @@ function subsumeBag(
510
549
  }
511
550
 
512
551
  // Spread templates: a path-dependent template's meaning depends on
513
- // where it lands, which no structural comparison can decide.
552
+ // where it lands, which no structural comparison can decide -- UNLESS
553
+ // the two templates are the same template. REFLEXIVITY IS A LAW and
554
+ // identity is the HASH FORM (the same rule the unresolved branch of
555
+ // subsumeNode applies): two byte-identical templates admit the same
556
+ // set wherever they land, so a document with a reference- or
557
+ // alias-valued template subsumes itself, and the comparison either
558
+ // side of the template is decided on its own merits rather than
559
+ // dragged to `undecided` (use-cases/BUGS.md 64). Where they are NOT
560
+ // identical, nothing structural can decide them and the fold stands.
514
561
  const gcj = g.spread?.cj
515
562
  const scj = s.spread?.cj
516
563
  if (null != gcj || null != scj) {
517
- if (true === gcj?.isPathDependent || true === scj?.isPathDependent) {
564
+ const sameTemplate = null != gcj && null != scj && hcanon(gcj) === hcanon(scj)
565
+ if (!sameTemplate &&
566
+ (true === gcj?.isPathDependent || true === scj?.isPathDependent)) {
518
567
  record(state, 'sub_path_dependent_spread', path, gcj ?? g, scj ?? s,
519
568
  'a path-dependent spread template cannot be compared structurally')
520
569
  worse('undecided')
package/src/unify.ts CHANGED
@@ -8,6 +8,7 @@ import { AontuContext } from './ctx'
8
8
  import { DONE } from './type'
9
9
 
10
10
  import { makeNilErr } from './err'
11
+ import { findAt } from './val/ReferFuncVal'
11
12
 
12
13
  import { NilVal } from './val/NilVal'
13
14
  import { hasPlace } from './val/PlaceVal'
@@ -111,13 +112,20 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
111
112
  && !a.isMap && !a.isList
112
113
  && !a.isConjunct && !a.isDisjunct
113
114
  && !a.isRef && !a.isPref && !a.isFunc && !a.isExpect
114
- // NOT two TOPs (G4 phase 1): every top has the same
115
- // (absent) peg, so this path treated any two as the same
116
- // value — true of the unit itself, false of a unit
117
- // CARRYING AN IDENTITY, and `id(x) & id(y)` is two of
118
- // those. The slow path answers the same thing for two
119
- // plain tops, and refuses the pair for two named ones.
120
- && !a.isTop && !b.isTop) {
115
+ // NOT two TOPs: every top has the same (absent) peg, so
116
+ // this path would treat any two as the same value and
117
+ // return one of them whole — dropping a rider the other
118
+ // carries. The slow path answers the same thing for two
119
+ // plain tops, so nothing is lost by declining the shortcut.
120
+ && !a.isTop && !b.isTop
121
+ // NOT two rel residuals (RELATIONS P1) for the same
122
+ // reason: a settled rel is DONE with an absent peg, so
123
+ // any two matched here — dropping one side's type and
124
+ // held constraints. RelVal.unify merges them instead.
125
+ // NOT two graph atoms (P2) either: acyclic() and
126
+ // inverse(x) share a constructor and an absent peg
127
+ // without being the same declaration.
128
+ && !a.isRel && !a.isGraphAtom && !a.isRecurse) {
121
129
  // The deprecation record survives the fast path too (G3):
122
130
  // `deprecate(5) & 5` short-circuits here.
123
131
  if (null == a.deprecation && null != b.deprecation) {
@@ -222,14 +230,24 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
222
230
  // purpose -- every other operator meets its peer the way it
223
231
  // always has, through the conjunct fold that drives it.
224
232
  || (b.isOp && hasPlace(b))
233
+ // A graph atom DRIVES (RELATIONS P2): its peer is the value
234
+ // it rides beside -- a container, a rel, a scalar -- and none
235
+ // of them know the atom; the atom knows to residuate.
236
+ || b.isGraphAtom
237
+ // The recursive residual DRIVES for the same reason: its peer
238
+ // is the concrete structure it expands against.
239
+ || b.isRecurse
225
240
  ) {
226
241
  out = b.unify(a, te ? ctx.clone({ explain: ec(te, 'BW') }) : ctx)
227
242
  unified = true
228
243
  why = 'bv'
229
244
  }
230
245
  // Exactly equal scalars (not caught by early fast-path — e.g.
231
- // because a or b isn't .done yet).
232
- else if (a.constructor === b.constructor && a.peg === b.peg) {
246
+ // because a or b isn't .done yet). Rel residuals are excluded
247
+ // exactly as in the fast path: their pegs are equally absent
248
+ // without the values being the same relation.
249
+ else if (a.constructor === b.constructor && a.peg === b.peg
250
+ && !a.isRel && !a.isGraphAtom && !a.isRecurse) {
233
251
  out = update(a, b)
234
252
  why = 'up'
235
253
  }
@@ -297,24 +315,6 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
297
315
  ctx.prov.record(ctx.path, a, b, out)
298
316
  }
299
317
 
300
- // The IDENTITY survives every meet (G4 phase 1), by the same
301
- // channel and for the same reason as the deprecation record below.
302
- // TWO DIFFERENT NAMES on one node is a contradiction, not a merge:
303
- // one node cannot be two entities, and the error names both sites.
304
- if (null != out && true === (out as any).isVal && !out.isNil) {
305
- const ae = null != a ? a.entity : undefined
306
- const be = null != b ? b.entity : undefined
307
- if (null != ae && null != be && ae !== be) {
308
- out = makeNilErr(ctx, 'id_conflict', a, b)
309
- }
310
- else if (!out.isTop) {
311
- const e = ae ?? be
312
- if (null != e) {
313
- out.entity = e
314
- }
315
- }
316
- }
317
-
318
318
  // The deprecation record survives EVERY meet (G3 phase 4): the
319
319
  // boolean marks have their own sweeps (ConjunctVal, the bag walks),
320
320
  // but a record lost in one meet shape is a use the tooling never
@@ -364,99 +364,69 @@ function residuePaths(v: Val, max: number): string[] {
364
364
  }
365
365
 
366
366
 
367
- // IDENTITY-MERGE (G4 phase 1): every node in one evaluation carrying
368
- // the same id is unified with every other. Declaring two nodes the
369
- // same entity MEANS unifying them, so this is not a lookup table —
370
- // it is a meet, and a contradiction between two declarations is an
371
- // ordinary conflict naming both sites.
367
+ // THE TYPE FLOW, APPLIED (G4 phase 2). `refer(t)` unifies `t` INTO the
368
+ // node it addresses, which is a write at a position the meet is not
369
+ // currently at -- the one non-local effect in the evaluator.
372
370
  //
373
- // Run once per fixpoint pass, after the pass's own unification: a
374
- // position picks up the representative, the representative picks up
375
- // the position, and the two converge across passes exactly as chained
376
- // references do, inside the same `maxcc` bound.
371
+ // It cannot be only a write made during the pass. A pass BUILDS a new
372
+ // tree from the old one, and `ctx.root` during pass N is pass N-1's
373
+ // result; a subtree rebuilt by pass N (which is exactly what happens
374
+ // when the link sits inside its own target, or when two nodes link at
375
+ // each other) drops a write made into the previous one. So each flow is
376
+ // also RECORDED, keyed by the target's path, and re-applied to the
377
+ // pass's own result here.
377
378
  //
378
- // The tree stays a TREE. Every declared position holds the merged
379
- // value and generation emits it at each path — duplication, as
380
- // references generate today. Identity adds addressing, not a new
381
- // shape.
382
- // The ctx DESCENDS with the walk, so the merge's meet happens at the
383
- // position's own path: a contribution `$.b.k` picked up from `$.a.k`
384
- // is recorded against `$.b.k`, which is where a reader asking `why`
385
- // stands. Merging under the root ctx instead filed every contribution
386
- // at the top and left the positions themselves with an empty record —
387
- // and the Go port, whose bag loops derive the base from the value's
388
- // own path, already answered the useful way.
389
- function mergeEntities(ctx: AontuContext, root: Val): Val {
390
- const reg: Map<string, Val> = (ctx as any).entities
391
-
392
- // COLLECT, then APPLY — the same walk twice, not two walks. A single
393
- // pass merges each position into the representative as it meets it,
394
- // which leaves the positions it already passed holding the pre-merge
395
- // value: `a: id(x) & {k:1}` kept `{k:1}` while `b: id(x) & {j:2}`
396
- // became `{j:2,k:1}`, and the two sites disagreed about what the one
397
- // entity is. The representative is therefore settled over the WHOLE
398
- // tree before any position is written.
399
- //
400
- // `write` is which half is running. One function rather than two
401
- // because the two halves differ in three lines and agree in the walk
402
- // — and a walk written twice is a walk that drifts.
403
- const walk = (node: any, seen: Set<any>, nctx: AontuContext,
404
- write: boolean): any => {
405
- if (null == node || true !== node.isVal) {
406
- return node
407
- }
408
-
409
- const name = (node as any).entity
410
- if (null != name) {
411
- if (write) {
412
- // The SUBSTITUTION happens before the seen-guard, not after.
413
- // Two positions of one entity hold the SAME object once a pass
414
- // has merged them, so a guard that ran first would visit the
415
- // first position, replace it with a newer representative, and
416
- // then skip the second as already-seen — leaving it on the
417
- // older value. That is exactly what a `refer(t)` flow
418
- // produces: it writes a new representative mid-pass, and every
419
- // position must take it.
420
- const rep: any = reg.get(name)
421
- if (null != rep && rep !== node) {
422
- node = rep
423
- }
424
- }
425
- else {
426
- const rep = reg.get(name)
427
- reg.set(name, null == rep || rep === node ? node :
428
- unite(nctx, node, rep, 'entity'))
429
- }
430
- }
431
-
432
- // The guard bounds the DESCENT, which is all it was ever for: a
433
- // unified tree is a graph, and a subtree is worth walking once.
434
- if (seen.has(node)) {
435
- return node
436
- }
437
- seen.add(node)
438
-
439
- if ((true === node.isMap || true === node.isList) && null != node.peg) {
440
- for (const k of Object.keys(node.peg)) {
441
- const out = walk(node.peg[k], seen, nctx.descend(k), write)
442
- if (write) {
443
- node.peg[k] = out
444
- }
445
- }
446
- }
447
- return node
448
- }
449
-
450
- walk(root, new Set(), ctx, false)
451
-
452
- // NOTHING TO APPLY. The collect half is also the "does this document
453
- // use identity at all?" answer, so a document that never says `id()`
454
- // pays for one walk per pass rather than two — and the writing half
455
- // never runs over a tree it cannot change.
456
- if (0 === reg.size) {
379
+ // Keyed by PATH, so there is no registry of names to collide in
380
+ // (ADR-014) -- the key is the position the address resolved to, and
381
+ // re-uniting the same type at the same position is idempotent, which is
382
+ // what makes replaying every recorded flow every pass correct rather
383
+ // than merely cheap.
384
+ //
385
+ // The pass loop is its only caller; it is EXPORTED for the test that
386
+ // pins the unresolved-path guard below (see there).
387
+ function applyFlows(ctx: AontuContext, root: Val): Val {
388
+ const flows: Map<string, Val> | undefined = (ctx as any).referflows
389
+ // NOTHING TO APPLY is the common case -- a document with no links
390
+ // pays one property load per pass, and the walk never runs.
391
+ if (null == flows || 0 === flows.size) {
457
392
  return root
458
393
  }
459
- return walk(root, new Set(), ctx, true)
394
+ // Sorted, so two flows landing at overlapping positions arrive in the
395
+ // same order in both ports.
396
+ for (const key of [...flows.keys()].sort()) {
397
+ const path = key.split('\x00')
398
+ // The SHARED resolver, the one `refer` itself uses: a second
399
+ // descent written here would be a second answer to "what does this
400
+ // path name", and the two would drift (the Go twin calls the same
401
+ // findAt, arm for arm -- ADR-001).
402
+ //
403
+ // A RECORDED PATH THAT NO LONGER RESOLVES is skipped rather than
404
+ // refused: the record outliving its position is a question about
405
+ // the tree, and the link that named it answers it
406
+ // (test/spec/refer.tsv, `flow-target-moved-away`).
407
+ //
408
+ // NO DOCUMENT REACHES IT: a record is written only for a path that
409
+ // HAD resolved, and unification never takes a node back out of the
410
+ // tree — `move` copies and hides its source rather than removing
411
+ // it, which is the one rearrangement that looked like it would
412
+ // (probed in both ports: the flow still resolves on every pass,
413
+ // `flow-lands-then-its-parent-moves`). The guard is the contract
414
+ // for a rearrangement that does, and it is pinned by a direct call
415
+ // (`apply-flows-skips-a-record-that-stops-resolving` in
416
+ // ts/test/coverage3.test.ts) rather than by an ignore marker: node's
417
+ // `coverage ignore` drops LINES, and the gate reads BRANCH records,
418
+ // which survive it. The Go twin in go/unify.go can use the marker
419
+ // because that gate counts statements.
420
+ const found = findAt(root, path)
421
+ if (undefined === found) {
422
+ continue
423
+ }
424
+ const { parent, key: pkey, val: node } = found
425
+ const merged = unite(ctx.descend(pkey as string), node,
426
+ flows.get(key) as Val, 'refer-flow')
427
+ parent.peg[pkey as string] = merged
428
+ }
429
+ return root
460
430
  }
461
431
 
462
432
 
@@ -514,11 +484,17 @@ class Unify {
514
484
  // keyed by ref canon + source site, shared across all passes.
515
485
  ; (uctx as any).snapmap = new Map()
516
486
 
517
- // The identity registry (G4 phase 1): id -> the representative
518
- // value every position with that id has been merged into. Same
519
- // lifetime and placement as the ref-spread snapshot map above —
520
- // one evaluation, one set of entities.
521
- ; (uctx as any).entities = new Map()
487
+ // The RECORDED TYPE FLOWS (G4 phase 2): target path -> the type
488
+ // `refer(t)` unified into it, replayed onto each pass's result by
489
+ // applyFlows. Seeded HERE, on the unify root, for the reason the
490
+ // snapshot map above is: a descended context is Object.create'd
491
+ // from its parent, so a `??=` in the descendant would make its
492
+ // OWN map and the root would never see what was recorded.
493
+ ; (uctx as any).referflows = new Map()
494
+
495
+ // The re-entrancy guard for those flows: the set of target paths
496
+ // a flow is currently inside. Same placement, same reason.
497
+ ; (uctx as any)._referflow = new Set()
522
498
 
523
499
  const explain = null == ctx?.explain ? undefined : ctx?.explain
524
500
  const te = explain && explainOpen(uctx, explain, 'root', res)
@@ -582,9 +558,10 @@ class Unify {
582
558
  // conjuncts) are pinned as vet.tsv's multi-* rows in both
583
559
  // ports.
584
560
 
585
- // The identity merge, after the pass's own unification: the
586
- // positions this pass produced are what there is to merge.
587
- res = mergeEntities(uctx, res)
561
+ // The recorded type flows, re-applied to the tree THIS pass
562
+ // built: a pass rebuilds subtrees, and a flow written into the
563
+ // previous pass's tree does not survive that.
564
+ res = applyFlows(uctx, res)
588
565
 
589
566
  // The staging signal for the NEXT pass, rendered here rather
590
567
  // than at the top of the loop so a model that is FINISHED is
@@ -635,5 +612,5 @@ export {
635
612
  Unify,
636
613
  unite,
637
614
  withDepth,
638
- mergeEntities,
615
+ applyFlows,
639
616
  }
package/src/utility.ts CHANGED
@@ -75,78 +75,19 @@ function deprecationMessage(d: Record<string, string>): string {
75
75
  }
76
76
 
77
77
 
78
- // SPREAD TEMPLATES MAY NOT STAMP ONE ID ONTO EVERY CHILD (G4 phase
79
- // 1, clearing rule 3). `&: id(svc/thing) & {…}` says that every child
80
- // of the bag IS the entity `svc/thing`, and the identity merge then
81
- // unifies all of them into one another: an author who wrote a
82
- // per-child template would get a single merged blob, and any two
83
- // children that disagreed about a field would fail at a site that
84
- // explains nothing.
78
+ // The canonical form of a value, wrapped in the RIDER it carries —
79
+ // the deprecation record (G3 phase 4) — reparseably, so
80
+ // `deprecate(x, m)` survives canon. Bags render their children through
81
+ // this (MapVal/ListVal canon), which is where a marked FIELD — the
82
+ // realistic case — lives.
85
83
  //
86
- // A PATH-DEPENDENT argument is allowed, and is how the author says
87
- // what they meant: `&: id(key()) & {…}` names each child distinctly,
88
- // resolved per destination by the existing spreadClone machinery.
89
- // Duck-typed on the `isIdFunc` flag rather than imported: this file
90
- // is below the Val classes, and the identity function sits above
91
- // them.
92
- function constantIdFunc(v: any, seen?: Set<any>): any {
93
- if (null == v || true !== v.isVal) {
94
- return undefined
95
- }
96
- const s = seen ?? new Set()
97
- if (s.has(v)) {
98
- return undefined
99
- }
100
- s.add(v)
101
-
102
- if (true === v.isIdFunc && true !== v.isPathDependent) {
103
- return v
104
- }
105
-
106
- const peg = v.peg
107
- if (null != peg && 'object' === typeof peg) {
108
- for (const k of Object.keys(peg)) {
109
- const found = constantIdFunc(peg[k], s)
110
- if (undefined !== found) {
111
- return found
112
- }
113
- }
114
- }
115
- return constantIdFunc(v.spread?.cj, s)
116
- }
117
-
118
-
119
- // The IDENTITY wrapper (G4 phase 1): `id("svc/auth")&{…}`, written
120
- // as the conjunct an author writes, so canon reparses to the same
121
- // entity. This deliberately differs from the type/hide MARKS, which
122
- // canon drops (test/spec/marks.tsv, row `type-canon`): identity is
123
- // semantic content, and G6's canon-hash must see it — two documents
124
- // that disagree about which entity a node IS do not mean the same
125
- // thing and must not hash alike.
126
- //
127
- // The name is JSON-quoted whatever it spells: `-` is not a bare-text
128
- // character (test/spec/op-chars.tsv pins `a:6-2` as a parse error), so
129
- // an unquoted `id(team-pay)` would not reparse.
130
- function canonEntity(v: Val): string {
131
- const c = v.canon
132
- const e = v.entity
133
- return null == e ? c : 'id(' + JSON.stringify(e) + ')&' + c
134
- }
135
-
136
-
137
- // The canonical form of a value, wrapped in the RIDERS it carries —
138
- // the identity (G4 phase 1) and the deprecation record (G3 phase 4) —
139
- // reparseably, so `id(name) & x` and `deprecate(x, m)` survive canon.
140
- // Bags render their children through this (MapVal/ListVal canon),
141
- // which is where a marked FIELD — the realistic case — lives.
142
- //
143
- // The riders render HERE and not in the value's own `canon` for the
84
+ // The rider renders HERE and not in the value's own `canon` for the
144
85
  // same reason the guard at the MapVal call site tests the isVal flag:
145
86
  // a bag's canon recursion visits each child once, and a child that
146
87
  // wrapped itself as well would render its subtree twice per level —
147
88
  // 2^depth on a nested document.
148
89
  function canonRiders(v: Val): string {
149
- const c = canonEntity(v)
90
+ const c = v.canon
150
91
  const d = v.deprecation
151
92
  if (null == d) {
152
93
  return c
@@ -327,7 +268,6 @@ function items(o: any) {
327
268
  export {
328
269
  items,
329
270
  propagateMarks,
330
- constantIdFunc,
331
271
  canonRiders,
332
272
  collectDeprecations,
333
273
  walkBagVals,