aontu 0.66.0 → 0.67.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.
package/src/cli.ts CHANGED
@@ -166,22 +166,30 @@ query between a document and its own earlier versions.
166
166
 
167
167
  Options:
168
168
  -c, --canon Print the canonical form instead of generated JSON
169
- --format <f> text (default) or json. The json form wraps the
170
- answer as {aontu, findings, ok, out}, so a failure
171
- here reads like every other verb's
169
+ (the bare command's, as --jsonl is; model get has
170
+ its own)
171
+ --format <f> text (default) or json, on every verb that answers a
172
+ report. The json form is one object opening with an
173
+ aontu block; the bare command's carries findings, ok
174
+ and out
172
175
  -h, --help Show this help and exit (the verbs and their flags);
173
176
  aontu help is the LANGUAGE, and lists its own topics
174
177
  --jsonl REPL: answer every command as one JSON line
175
178
  -v, --version Print the version and exit
176
- --trust <t> Include capability: system (default), none, or
177
- root[:dir] to confine @"..." below a directory.
178
- Every verb takes it too, and a bare root means the
179
- document's own directory
179
+ --trust <t> Include capability: system, none, or root[:dir] to
180
+ confine @"..." below a directory. A bare root means
181
+ the entry root: the document's own directory, or the
182
+ project's for the package verbs. Every verb takes it
183
+ but help, explain, init and lsp, which read no
184
+ document, and mcp, which is the npm build's server
185
+ and confines with --root. Unset, a run behaves as
186
+ system, and the bare command warns once for an
187
+ include that leaves the entry root
180
188
  --include-root <dir> Shorthand for --trust root:<dir>
181
- --text-ext <e> Read these extensions as text too, comma-separated
182
- and without dots (md,sql). .txt needs no flag; a
189
+ --text-ext <e> Read these extensions as text too, comma-separated,
190
+ with or without dots (md,sql). .txt needs no flag; a
183
191
  named format keeps its meaning, and .js stays
184
- refused. Every verb takes it
192
+ refused. The verbs that take --trust take it too
185
193
 
186
194
  Package verbs (a module is imported; a package is published):
187
195
  sync Make the project correct: resolve by minimum version
@@ -708,7 +716,7 @@ function takeTrust(argv: string[], io: Io = PROCESS_IO):
708
716
  }
709
717
  else if ('--include-root' === arg) {
710
718
  const dir = argv[++i]
711
- if (null == dir) {
719
+ if (null == dir || '' === dir) {
712
720
  io.err('aontu: --include-root needs a directory\n')
713
721
  return undefined
714
722
  }
@@ -2704,20 +2712,42 @@ function pkgNetText(verb: string, report: any): string {
2704
2712
  const MODEL_HELP = 'aontu model get|why|set ... (try --help)'
2705
2713
 
2706
2714
  // One document, interrogated or edited (ADR-039 part 5).
2715
+ // Where the subcommand is, past any global flag that precedes it.
2716
+ // `takeTrust` strips those anywhere in a tail, so the subcommand has
2717
+ // to be found past a flag AND past its value: in `--text-ext get` the
2718
+ // `get` is the extension list, not the subcommand.
2719
+ const MODEL_SUBS = ['get', 'why', 'set']
2720
+ const GLOBAL_VALUED = ['--trust', '--include-root', '--text-ext']
2721
+
2722
+
2723
+ function modelSubAt(argv: string[]): number {
2724
+ for (let i = 0; i < argv.length; i++) {
2725
+ if (GLOBAL_VALUED.includes(argv[i])) {
2726
+ i++
2727
+ continue
2728
+ }
2729
+ return MODEL_SUBS.includes(argv[i]) ? i : -1
2730
+ }
2731
+ return -1
2732
+ }
2733
+
2734
+
2707
2735
  function runModel(argv: string[]): number {
2708
2736
  const sub = argv[0]
2709
2737
  if ('-h' === sub || '--help' === sub) {
2710
2738
  process.stdout.write(HELP)
2711
2739
  return 0
2712
2740
  }
2713
- if ('get' === sub) {
2714
- return runGet(argv.slice(1))
2715
- }
2716
- if ('why' === sub) {
2717
- return runWhy(argv.slice(1))
2718
- }
2719
- if ('set' === sub) {
2720
- return runSet(argv.slice(1))
2741
+ const at = modelSubAt(argv)
2742
+ if (0 <= at) {
2743
+ const tail = argv.slice(0, at).concat(argv.slice(at + 1))
2744
+ if ('get' === argv[at]) {
2745
+ return runGet(tail)
2746
+ }
2747
+ if ('why' === argv[at]) {
2748
+ return runWhy(tail)
2749
+ }
2750
+ return runSet(tail)
2721
2751
  }
2722
2752
  process.stderr.write(`aontu: model needs get, why or set\n${MODEL_HELP}\n`)
2723
2753
  return 2
@@ -2931,6 +2961,70 @@ function isDirectory(path: string): boolean {
2931
2961
  }
2932
2962
 
2933
2963
 
2964
+ // A `File` the write path skips, RENAMED rather than removed: dropping
2965
+ // the node would take its children's claims with it and hide drift the
2966
+ // write path does make. The skip is the File's own save alone, so what
2967
+ // separates the runs is its output path, composed by the runtime rather
2968
+ // than here.
2969
+ const EXCLUDED_NAME = '.aontu-check-excluded-'
2970
+
2971
+
2972
+ // `exclude` as the runtime reads it: `true`, or a string or list member
2973
+ // equal to the node's COMPONENT path, the chain of `name` props above
2974
+ // it, which a Project's `folder` is not part of. The Go runtime honours
2975
+ // the boolean alone, so the ports' `--check` answers differ for the
2976
+ // path forms (test/spec/divergent.tsv).
2977
+ function excludedFile(exclude: any, at: string[]): boolean {
2978
+ if (true === exclude) {
2979
+ return true
2980
+ }
2981
+ const path = at.join('/')
2982
+ if ('string' === typeof exclude) {
2983
+ return exclude === path
2984
+ }
2985
+ return Array.isArray(exclude) && exclude.includes(path)
2986
+ }
2987
+
2988
+
2989
+ function renameExcluded(node: any, at: string[], cut: number[]): any {
2990
+ if (Array.isArray(node)) {
2991
+ return node.map((child) => renameExcluded(child, at, cut))
2992
+ }
2993
+ // A hand-written tree carries nodes with no `props` and nodes with
2994
+ // no `children`, and neither needs an arm of its own.
2995
+ const props: any = node.props ?? {}
2996
+ const below = 'string' === typeof props.name ? at.concat(props.name) : at
2997
+ if ('File' === node.cmp && excludedFile(props.exclude, below)) {
2998
+ cut.push(1)
2999
+ return { ...node, props: { ...props, name: EXCLUDED_NAME + cut.length } }
3000
+ }
3001
+ if (!Array.isArray(node.children)) {
3002
+ return node
3003
+ }
3004
+ return { ...node, children: renameExcluded(node.children, below, cut) }
3005
+ }
3006
+
3007
+
3008
+ // A file the runtime declined to touch, its copy on disk carrying the
3009
+ // protect marker, is in none of the lists it answers with: they hold
3010
+ // what was DONE to a file, and nothing was. The run's own record names
3011
+ // it. `since` drops what earlier runs left.
3012
+ function renderSkipped(folder: string, since: number): string[] {
3013
+ const at = join(folder, '.jostraca', 'jostraca.meta.log')
3014
+ let meta: any
3015
+ try {
3016
+ meta = JSON.parse(readFileSync(at, 'utf8'))
3017
+ }
3018
+ catch (err: any) {
3019
+ return []
3020
+ }
3021
+ return Object.entries(meta?.files ?? {})
3022
+ .filter(([_, f]: [string, any]) => 'skip' === f?.action && since <= f?.when)
3023
+ .map(([path]) => path)
3024
+ .sort(cmpCodePoint)
3025
+ }
3026
+
3027
+
2934
3028
  async function runRender(argv: string[]): Promise<number> {
2935
3029
  const trusted = takeTrust(argv)
2936
3030
  if (null == trusted) {
@@ -3081,7 +3175,22 @@ async function runRender(argv: string[]): Promise<number> {
3081
3175
  try {
3082
3176
  if (check) {
3083
3177
  const res = await runtime.check({ folder }, root)
3084
- const drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
3178
+ let drift = res.drift.map((d: any) => ({ kind: d.kind, path: d.path }))
3179
+ // `--check` answers "would `render` change anything", so a file
3180
+ // the write path leaves alone is not held to the generator's
3181
+ // bytes. The skip is gated on the target BEING there -- `render`
3182
+ // writes an absent one -- so drift at a path with nothing at it
3183
+ // survives, which is the `missing` a deleted file reports.
3184
+ const cut: number[] = []
3185
+ const renamed = renameExcluded(tree, [], cut)
3186
+ if (0 < cut.length) {
3187
+ const kept = new Set<string>((await Jostraca().check(
3188
+ { folder }, cmpTree(renamed, { raw: true }))).checked)
3189
+ const skipped = new Set<string>(
3190
+ res.checked.filter((p: string) => !kept.has(p)))
3191
+ drift = drift.filter((d: any) => !skipped.has(d.path) ||
3192
+ !existsSync(join(folder, d.path)))
3193
+ }
3085
3194
  if ('json' === format) {
3086
3195
  process.stdout.write(exactJSON({
3087
3196
  aontu: { version: version(), verb: 'render' },
@@ -3098,14 +3207,21 @@ async function runRender(argv: string[]): Promise<number> {
3098
3207
  return 0 === drift.length ? 0 : 1
3099
3208
  }
3100
3209
 
3210
+ const since = Date.now()
3101
3211
  const res = await runtime.generate({ folder }, root)
3212
+ const skipped = renderSkipped(folder, since)
3102
3213
  if ('json' === format) {
3103
3214
  process.stdout.write(exactJSON({
3104
3215
  aontu: { version: version(), verb: 'render' },
3105
3216
  verdict: 'ok',
3106
- files: res.files,
3217
+ files: { ...res.files, skipped },
3107
3218
  }, 2) + '\n')
3108
3219
  }
3220
+ else {
3221
+ for (const path of skipped) {
3222
+ process.stdout.write(`skipped: ${path}\n`)
3223
+ }
3224
+ }
3109
3225
  return 0
3110
3226
  }
3111
3227
  catch (err: any) {
@@ -3932,7 +4048,7 @@ function runHash(argv: string[]): number {
3932
4048
  if (0 < ctx.err.length || true === v?.isNil) {
3933
4049
  process.stderr.write(
3934
4050
  `aontu: ${files[0]} does not evaluate on its own; nothing to hash\n` +
3935
- renderFinding(evalFailure(ctx)) + '\n')
4051
+ renderFinding(evalFailure(ctx, v)) + '\n')
3936
4052
  return 4
3937
4053
  }
3938
4054
 
@@ -5322,7 +5438,7 @@ function main(argv: string[], servers: Servers = SERVERS): void {
5322
5438
  }
5323
5439
  else if ('--include-root' === arg) {
5324
5440
  const dir = args[++i]
5325
- if (null == dir) {
5441
+ if (null == dir || '' === dir) {
5326
5442
  process.stderr.write('aontu: --include-root needs a directory\n')
5327
5443
  return finish(2)
5328
5444
  }
@@ -5385,7 +5501,7 @@ export {
5385
5501
  runJsonSchema,
5386
5502
  runTemplate,
5387
5503
  runTrace,
5388
- runRender,
5504
+ runRender, renderSkipped,
5389
5505
  runPkg, runModel, runPackageVerb, pkgToolOptions, serveUntilInterrupted,
5390
5506
  runHash, runGet, runHelp, runExplain, runInit, nearestVerb,
5391
5507
  looksLikeVerb,
package/src/hints.ts CHANGED
@@ -181,7 +181,7 @@ const hints: Record<string, string> = {
181
181
 
182
182
  merge_conflict: 'A version-control conflict marker was found in the source. The\nfile still holds an unresolved merge: resolve it and remove the\n`<<<<<<<`, `=======` and `>>>>>>>` lines before unifying.\n \nExamples:\n <<<<<<< HEAD -> nil # A conflict marker, not a `<` operation;\n ======= -> nil # ... nor a chain of `=` characters;\n >>>>>>> other -> nil # ... nor a `>` operation.',
183
183
 
184
- include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon" -> {..} # Inside the confinement root: allowed;\n a:@"../secret.aon" -> nil # ... but escaping the root is denied;\n a:@"/etc/hostname" -> nil # ... and so is an absolute path outside it.',
184
+ include_denied: 'An @"..." include was refused by the active trust profile\n(docs/trust.md). The document asked to read a source the evaluation\'s\ninclude capability does not allow: widen the capability if the read is\nintended, or remove the include if it is not.\n \nExamples:\n a:@"./in-root.aon" -> {..} # Inside the confinement root: allowed;\n a:@"../secret.aon" -> nil # ... but escaping the root is denied;\n a:@"/etc/hostname" -> nil # ... and so is an absolute path outside it.',
185
185
  include_extension: 'An @\"...\" include named a file the engine does not read. THE\nEXTENSION DECIDES which of two things a file is: `.aon` and\n`.aontu` are aontu source, with the whole language in them, and\n`.json`, `.jsonld`, `.jsonc`, `.json5`, `.jsonic`, `.jsc`, `.toml`,\n`.yaml`, `.yml` and `.ini` are configuration DATA, read by that\nformat\'s own parser. Anything else -- and a name with no extension\n-- is refused rather than guessed at: a guess produces a document\nthat looks right and is not.\n \nExamples:\n a:@\"model.aon\" -> {..} # aontu source;\n a:@\"server.toml\" -> {..} # ... config data, read as TOML;\n a:@\"notes.txt\" -> nil # ... but text is not a document;\n a:@\"data\" -> nil # ... and neither is an unnamed kind.',
186
186
 
187
187
  func_arity: 'This function was called with the wrong number of arguments:\n{func} takes {want}, but was given {got}.\n \nExamples:\n upper(\"a\") -> \"A\" # One argument, which is what upper takes;\n upper(\"a\",\"b\") -> nil # ... so two is a mistake in the source;\n key() -> \"\" # key takes none, or one level count;\n neq(1,2,3) -> neq # ... and neq takes one or more exclusions.',
@@ -780,6 +780,7 @@ const codeClasses: Record<string, string> = {
780
780
 
781
781
  // internal -- the engine reached a state it should not reach
782
782
  internal: 'internal',
783
+ unify_failed: 'internal',
783
784
  unify_no_res: 'internal',
784
785
  unknown_op: 'internal',
785
786
  }
package/src/lang.ts CHANGED
@@ -1544,9 +1544,13 @@ function makeModelResolver(options: any) {
1544
1544
 
1545
1545
  const memCapability =
1546
1546
  'object' === typeof capability && null != (capability as any).mem
1547
+ // An EMPTY root denies: resolving it yields the process directory.
1548
+ const emptyRoot =
1549
+ 'object' === typeof capability && '' === (capability as any).root
1547
1550
  const rootDir: string | undefined =
1548
1551
  'object' === typeof capability &&
1549
- 'string' === typeof (capability as any).root
1552
+ 'string' === typeof (capability as any).root &&
1553
+ '' !== (capability as any).root
1550
1554
  ? pathResolve((capability as any).root) : undefined
1551
1555
 
1552
1556
  let memResolver = makeMemResolver(memCapability
@@ -1584,7 +1588,7 @@ function makeModelResolver(options: any) {
1584
1588
  const deny = (path: string): never => {
1585
1589
  // Only 'none' and 'root' can deny: the mem capability's misses are
1586
1590
  // not-found (its set is the whole world), so there is no third arm.
1587
- const capname = 'none' === capability ? 'none' : 'root:' + rootDir
1591
+ const capname = null == rootDir ? 'none' : 'root:' + rootDir
1588
1592
  const err: any = new Error(
1589
1593
  'include denied: ' + path + ' (capability: ' + capname + ')')
1590
1594
  err.code = 'include_denied'
@@ -1679,7 +1683,7 @@ function makeModelResolver(options: any) {
1679
1683
  return { found: false, path: '' + (path ?? ''), search: [] }
1680
1684
  }
1681
1685
 
1682
- if ('none' === capability) {
1686
+ if ('none' === capability || emptyRoot) {
1683
1687
  deny(path)
1684
1688
  }
1685
1689
 
package/src/mcp-server.ts CHANGED
@@ -33,7 +33,7 @@ export function parseArgs(argv: string[]): ServerArgs {
33
33
  }
34
34
  if ('--root' === arg) {
35
35
  const dir = argv[++i]
36
- if (null == dir) {
36
+ if (null == dir || '' === dir) {
37
37
  return { err: 'aontu-mcp: --root needs a directory' }
38
38
  }
39
39
  root = dir
package/src/mcp.ts CHANGED
@@ -653,7 +653,7 @@ function hashOf(
653
653
  const v: any = aontu.unify(
654
654
  src, null == path ? undefined : { path }, ctx)
655
655
  if (0 < ctx.err.length || null == v || true === v.isNil) {
656
- return { ok: false, hash: '', findings: [evalFailure(ctx)] }
656
+ return { ok: false, hash: '', findings: [evalFailure(ctx, v)] }
657
657
  }
658
658
  return {
659
659
  ok: true,
package/src/query.ts CHANGED
@@ -5,7 +5,7 @@ import { includeOpts } from './utility'
5
5
  import { Aontu } from './aontu'
6
6
  import type { TrustOptions } from './type'
7
7
  import { exactJSON } from './exactjson'
8
- import { anchorAt } from './vet'
8
+ import { anchorAt, engineFinding } from './vet'
9
9
  import type { VetFinding } from './vet'
10
10
  import { cmpCodePoint } from './keyorder'
11
11
  import { Provenance } from './provenance'
@@ -87,9 +87,6 @@ export function pathParts(path: string): string[] {
87
87
  }
88
88
 
89
89
 
90
- // The queried path, normalised the way anchorAt reads it — so a
91
- // finding names `$.a.b` whether the caller wrote that, `a.b` or
92
- // `$.a.b.` — and `$` for the root.
93
90
  function pathText(path: string): string {
94
91
  const parts = pathParts(path)
95
92
  return '$' + (0 < parts.length ? '.' + parts.join('.') : '')
@@ -170,9 +167,10 @@ function finding(
170
167
  }
171
168
 
172
169
 
173
- export function evalFailure(ctx: any): VetFinding {
174
- const err: any = ctx.err[0]
175
- return finding(err.why, '$', err.msg)
170
+ // A document that does not stand up, reported at the root: a literal
171
+ // nil adds nothing to `ctx.err`, so `failed` carries the only code.
172
+ export function evalFailure(ctx: any, failed?: any): VetFinding {
173
+ return engineFinding(ctx.err[0] ?? failed, ctx, '$')
176
174
  }
177
175
 
178
176
 
@@ -216,7 +214,7 @@ export function get(
216
214
  const root: any = aontu.unify(src, parseOpts, ctx)
217
215
 
218
216
  if (0 < ctx.err.length || null == root || true === root.isNil) {
219
- return { ok: false, out: '', findings: [evalFailure(ctx)] }
217
+ return { ok: false, out: '', findings: [evalFailure(ctx, root)] }
220
218
  }
221
219
 
222
220
  const node: any = anchorAt(root, path)
@@ -232,10 +230,7 @@ export function get(
232
230
  return {
233
231
  ok: false,
234
232
  out: '',
235
- findings: [finding(
236
- err?.why ?? 'no_gen',
237
- pathText(path),
238
- err?.msg ?? 'The value at this path is not concrete.')],
233
+ findings: [engineFinding(err, ctx, pathText(path))],
239
234
  }
240
235
  }
241
236
  return { ok: true, out: exactJSON(gen, 2), findings: [] }
@@ -274,7 +269,7 @@ export function why(
274
269
 
275
270
  const root: any = aontu.unify(parsed, parseOpts, ctx)
276
271
  if (0 < ctx.err.length || null == root || true === root.isNil) {
277
- return { ok: false, findings: [evalFailure(ctx)] }
272
+ return { ok: false, findings: [evalFailure(ctx, root)] }
278
273
  }
279
274
 
280
275
  const node: any = anchorAt(root, path)
package/src/tsconfig.json CHANGED
@@ -18,6 +18,7 @@
18
18
  "types": [
19
19
  "node"
20
20
  ],
21
- "composite": true
21
+ "composite": true,
22
+ "tsBuildInfoFile": "../.tsbuild/src.tsbuildinfo"
22
23
  }
23
24
  }
package/src/vet.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  } from 'node:path'
11
11
 
12
12
  import { descErr, getHint } from './err'
13
+ import { codeClass } from './hints'
13
14
  import { ConjunctVal } from './val/ConjunctVal'
14
15
  import { walkVals, collectNils } from './walk'
15
16
  import { sizingResidue } from './val/BagVal'
@@ -204,6 +205,9 @@ function materialise(nil: any, ctx: any): void {
204
205
  }
205
206
 
206
207
 
208
+ const UNSHAPED = 'unify_failed'
209
+
210
+
207
211
  // The terminal colour escapes the parser puts in its message text. A
208
212
  // RegExp built from a string, not a literal: the escape is a control
209
213
  // character, and spelling it `\u001b` keeps the source readable.
@@ -214,16 +218,10 @@ function stripAnsi(s: string): string {
214
218
  }
215
219
 
216
220
 
217
- function findingOf(nil: any, prov: Prov): VetFinding {
221
+ // The registry's hint and a code's own `details` fields: the same set
222
+ // on every finding reporting an ENGINE code.
223
+ function fromRegistry(finding: VetFinding, nil: any): VetFinding {
218
224
  const details = nil.details ?? {}
219
- const finding: VetFinding = {
220
- code: nil.why,
221
- class: nil.class,
222
- severity: 'error',
223
- path: pathText(nil.path),
224
- message: stripAnsi(nil.msg.split('\n')[0]),
225
- sites: sitesOf(nil, prov),
226
- }
227
225
 
228
226
  const hint = getHint(nil.why, nil.details)
229
227
  if (null != hint && '' !== hint) {
@@ -244,6 +242,18 @@ function findingOf(nil: any, prov: Prov): VetFinding {
244
242
  }
245
243
 
246
244
 
245
+ function findingOf(nil: any, prov: Prov): VetFinding {
246
+ return fromRegistry({
247
+ code: nil.why,
248
+ class: nil.class,
249
+ severity: 'error',
250
+ path: pathText(nil.path),
251
+ message: stripAnsi(nil.msg.split('\n')[0]),
252
+ sites: sitesOf(nil, prov),
253
+ }, nil)
254
+ }
255
+
256
+
247
257
  const ORDER_PAD = 9
248
258
 
249
259
 
@@ -265,6 +275,34 @@ function orderKey(f: VetFinding, index: number): string {
265
275
  }
266
276
 
267
277
 
278
+ // An ENGINE code at a path the caller names: class from the registry
279
+ // through NilVal.class, message materialised first (a `gen` nil has none).
280
+ export function engineFinding(
281
+ nil: any, ctx: any, path: string): VetFinding {
282
+ if (null == nil) {
283
+ return {
284
+ code: UNSHAPED,
285
+ class: codeClass(UNSHAPED),
286
+ severity: 'error',
287
+ path,
288
+ message: 'The document does not evaluate.',
289
+ sites: [],
290
+ }
291
+ }
292
+
293
+ materialise(nil, ctx)
294
+
295
+ return fromRegistry({
296
+ code: nil.why,
297
+ class: nil.class,
298
+ severity: 'error',
299
+ path,
300
+ message: stripAnsi(nil.msg.split('\n')[0]),
301
+ sites: [],
302
+ }, nil)
303
+ }
304
+
305
+
268
306
  export function failureFinding(
269
307
  ctx: any, url?: string, failed?: any): VetFinding {
270
308
  const nil: any = ctx.err[0] ?? failed
package/src/view.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  import { basename, dirname, isAbsolute, relative, resolve } from 'node:path'
5
5
 
6
6
  import { Aontu } from './aontu'
7
- import { failureFinding, anchorAt, throughResidue } from './vet'
7
+ import { failureFinding, engineFinding, anchorAt, throughResidue } from './vet'
8
8
  import type { VetFinding } from './vet'
9
9
  import type { TrustOptions } from './type'
10
10
  import { graphOf } from './graph'
@@ -2544,10 +2544,8 @@ function drawLoaded(
2544
2544
  const before = ctx.err.length
2545
2545
  value = root.gen(ctx)
2546
2546
  if (before < ctx.err.length) {
2547
- const err: any = ctx.err[before]
2548
2547
  return {
2549
- errors: [finding(err?.why ?? 'unify_failed', 'reference', '$',
2550
- err?.msg ?? 'The document does not generate.')],
2548
+ errors: [engineFinding(ctx.err[before], ctx, '$')],
2551
2549
  }
2552
2550
  }
2553
2551
  }
@@ -2749,11 +2747,9 @@ export function viewSet(
2749
2747
  const before = ctx.err.length
2750
2748
  const value = root.gen(ctx)
2751
2749
  if (before < ctx.err.length) {
2752
- const err: any = ctx.err[before]
2753
2750
  return {
2754
2751
  verdict: 'error', views: [],
2755
- errors: [finding(err?.why ?? 'unify_failed', 'reference', '$',
2756
- err?.msg ?? 'The document does not generate.')],
2752
+ errors: [engineFinding(ctx.err[before], ctx, '$')],
2757
2753
  }
2758
2754
  }
2759
2755
  const declared = genAt(value, at)