@prestack/thread 0.1.3 → 0.1.5

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 (270) hide show
  1. package/CHANGELOG.md +563 -0
  2. package/dist/core/actor.d.ts +1 -1
  3. package/dist/core/actor.js +5 -4
  4. package/dist/core/actor.ts +5 -4
  5. package/dist/core/attach.d.ts +0 -7
  6. package/dist/core/attach.js +38 -67
  7. package/dist/core/attach.ts +34 -80
  8. package/dist/core/chain.js +13 -17
  9. package/dist/core/chain.ts +11 -15
  10. package/dist/core/condition.d.ts +4 -4
  11. package/dist/core/condition.js +2 -2
  12. package/dist/core/descend.d.ts +4 -2
  13. package/dist/core/descend.js +91 -23
  14. package/dist/core/descend.ts +90 -21
  15. package/dist/core/errors/catalogue.d.ts +52 -0
  16. package/dist/{operations/errors/errors.js → core/errors/catalogue.js} +1 -1
  17. package/dist/{operations/errors/errors.ts → core/errors/catalogue.ts} +1 -1
  18. package/dist/{operations/errors/catalog.d.ts → core/errors/error.d.ts} +2 -3
  19. package/dist/{operations/errors/catalog.js → core/errors/error.js} +1 -16
  20. package/dist/{operations/errors/catalog.ts → core/errors/error.ts} +3 -19
  21. package/dist/core/errors/namespaces.d.ts +3 -0
  22. package/dist/core/errors/namespaces.js +15 -0
  23. package/dist/core/errors/namespaces.ts +19 -0
  24. package/dist/core/finding.js +2 -2
  25. package/dist/core/finding.ts +1 -2
  26. package/dist/core/init.d.ts +1 -1
  27. package/dist/core/init.js +51 -5
  28. package/dist/core/init.ts +58 -5
  29. package/dist/core/invoked.d.ts +1 -1
  30. package/dist/core/invoked.js +3 -1
  31. package/dist/core/invoked.ts +5 -2
  32. package/dist/core/round.js +36 -22
  33. package/dist/core/round.ts +26 -9
  34. package/dist/core/rows.d.ts +4 -0
  35. package/dist/core/rows.js +31 -2
  36. package/dist/core/rows.ts +33 -1
  37. package/dist/core/scope.d.ts +1 -0
  38. package/dist/core/scope.js +1 -0
  39. package/dist/core/scope.ts +2 -0
  40. package/dist/core/thread.d.ts +3 -3
  41. package/dist/core/tree.d.ts +4 -1
  42. package/dist/core/tree.js +26 -6
  43. package/dist/core/tree.ts +29 -6
  44. package/dist/core/types.d.ts +3 -3
  45. package/dist/core/types.js +3 -1
  46. package/dist/core/types.ts +2 -1
  47. package/dist/derivation/attach.js +3 -2
  48. package/dist/derivation/attach.ts +2 -1
  49. package/dist/derivation/generators/build.js +6 -6
  50. package/dist/derivation/generators/build.ts +1 -1
  51. package/dist/derivation/generators/check.js +1 -1
  52. package/dist/derivation/generators/determinism.js +1 -1
  53. package/dist/derivation/generators/release.js +60 -47
  54. package/dist/derivation/generators/release.ts +66 -43
  55. package/dist/derivation/release.d.ts +7 -3
  56. package/dist/derivation/release.js +14 -1
  57. package/dist/derivation/release.ts +22 -2
  58. package/dist/integrations/attach.d.ts +4 -0
  59. package/dist/integrations/attach.js +37 -56
  60. package/dist/integrations/attach.ts +54 -60
  61. package/dist/integrations/cli.d.ts +37 -0
  62. package/dist/integrations/cli.js +440 -0
  63. package/dist/integrations/cli.ts +543 -0
  64. package/dist/{core/files.d.ts → integrations/disk.d.ts} +16 -24
  65. package/dist/integrations/disk.js +435 -0
  66. package/dist/integrations/disk.ts +465 -0
  67. package/dist/integrations/generators/read.js +6 -6
  68. package/dist/integrations/generators/read.ts +4 -4
  69. package/dist/integrations/generators/vault.js +10 -11
  70. package/dist/integrations/generators/vault.ts +4 -5
  71. package/dist/{operations/http.d.ts → integrations/https.d.ts} +10 -6
  72. package/dist/integrations/https.js +170 -0
  73. package/dist/integrations/https.ts +216 -0
  74. package/dist/integrations/pglite.d.ts +26 -0
  75. package/dist/integrations/pglite.js +143 -0
  76. package/dist/integrations/pglite.ts +206 -0
  77. package/dist/integrations/secret.d.ts +8 -2
  78. package/dist/integrations/secret.js +3 -5
  79. package/dist/integrations/secret.ts +10 -6
  80. package/dist/integrations/target.d.ts +155 -14
  81. package/dist/integrations/target.js +171 -50
  82. package/dist/integrations/target.ts +377 -61
  83. package/dist/integrations/vault.d.ts +1 -0
  84. package/dist/integrations/vault.js +20 -5
  85. package/dist/integrations/vault.ts +17 -5
  86. package/dist/integrity/attach.js +8 -77
  87. package/dist/integrity/attach.ts +7 -76
  88. package/dist/integrity/check-threads.js +5 -5
  89. package/dist/integrity/check-threads.ts +2 -2
  90. package/dist/integrity/constraint.d.ts +0 -1
  91. package/dist/integrity/constraint.js +267 -366
  92. package/dist/integrity/constraint.ts +267 -375
  93. package/dist/integrity/generators/compiler.js +14 -17
  94. package/dist/integrity/generators/compiler.ts +10 -15
  95. package/dist/integrity/generators/gate.js +15 -15
  96. package/dist/integrity/generators/gate.ts +10 -10
  97. package/dist/integrity/generators/issues.js +7 -7
  98. package/dist/integrity/generators/publish.js +39 -57
  99. package/dist/integrity/generators/publish.ts +44 -55
  100. package/dist/integrity/generators/trial.d.ts +3 -3
  101. package/dist/integrity/generators/trial.js +133 -165
  102. package/dist/integrity/generators/trial.ts +145 -169
  103. package/dist/integrity/lint.js +9 -13
  104. package/dist/integrity/lint.ts +7 -10
  105. package/dist/integrity/lsp/bin.js +5 -4
  106. package/dist/integrity/lsp/bin.ts +3 -1
  107. package/dist/integrity/lsp/diagnostics.js +3 -3
  108. package/dist/integrity/lsp/diagnostics.ts +1 -1
  109. package/dist/integrity/lsp/lint.js +19 -30
  110. package/dist/integrity/lsp/lint.ts +16 -32
  111. package/dist/integrity/lsp/server.js +3 -3
  112. package/dist/integrity/slopnator.d.ts +3 -13
  113. package/dist/integrity/slopnator.js +59 -38
  114. package/dist/integrity/slopnator.ts +54 -38
  115. package/dist/integrity/slopnators/sl_broken_lineage.js +2 -2
  116. package/dist/integrity/slopnators/sl_declared_elsewhere.js +1 -1
  117. package/dist/integrity/slopnators/sl_derived_drift.js +4 -4
  118. package/dist/integrity/slopnators/sl_derived_drift.ts +1 -1
  119. package/dist/integrity/slopnators/sl_door_in_a_node.js +220 -0
  120. package/dist/integrity/slopnators/sl_door_in_a_node.ts +250 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +10 -8
  122. package/dist/integrity/slopnators/sl_inert_island.ts +8 -6
  123. package/dist/integrity/slopnators/sl_missing_row.js +6 -51
  124. package/dist/integrity/slopnators/sl_missing_row.ts +4 -50
  125. package/dist/integrity/slopnators/sl_no_barrel.js +1 -1
  126. package/dist/integrity/slopnators/sl_no_child_process.js +36 -18
  127. package/dist/integrity/slopnators/sl_no_child_process.ts +44 -18
  128. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +80 -149
  129. package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +95 -147
  130. package/dist/integrity/slopnators/sl_no_import_cycle.js +15 -4
  131. package/dist/integrity/slopnators/sl_no_import_cycle.ts +20 -4
  132. package/dist/integrity/slopnators/sl_no_such_command.js +2 -2
  133. package/dist/integrity/slopnators/sl_no_such_command.ts +1 -1
  134. package/dist/integrity/slopnators/sl_outside_an_operation.js +20 -0
  135. package/dist/integrity/slopnators/sl_outside_an_operation.ts +25 -0
  136. package/dist/integrity/slopnators/sl_screen_holds_only_declared.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_screen_holds_only_declared.js +61 -0
  138. package/dist/integrity/slopnators/sl_screen_holds_only_declared.ts +70 -0
  139. package/dist/integrity/slopnators/sl_secret_literal.js +1 -33
  140. package/dist/integrity/slopnators/sl_secret_literal.ts +1 -33
  141. package/dist/integrity/slopnators/sl_stale_path.js +5 -22
  142. package/dist/integrity/slopnators/sl_stale_path.ts +4 -20
  143. package/dist/integrity/slopnators/sl_twice_meant.js +1 -1
  144. package/dist/integrity/slopnators/sl_twice_parented.js +29 -5
  145. package/dist/integrity/slopnators/sl_twice_parented.ts +28 -4
  146. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +4 -1
  147. package/dist/integrity/slopnators/sl_undeclared_crossing.js +426 -62
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.ts +454 -70
  149. package/dist/integrity/slopnators/sl_undeclared_destination.js +59 -25
  150. package/dist/integrity/slopnators/sl_undeclared_destination.ts +69 -21
  151. package/dist/integrity/slopnators/sl_undeclared_list.d.ts +2 -0
  152. package/dist/integrity/slopnators/sl_undeclared_list.js +363 -0
  153. package/dist/integrity/slopnators/sl_undeclared_list.ts +358 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.js +1 -11
  155. package/dist/integrity/slopnators/sl_undeclared_node.ts +0 -10
  156. package/dist/integrity/slopnators/sl_undeclared_operation.js +111 -29
  157. package/dist/integrity/slopnators/sl_undeclared_operation.ts +134 -26
  158. package/dist/integrity/slopnators/sl_undeclared_variant.js +2 -2
  159. package/dist/integrity/slopnators/sl_undeclared_webhook.d.ts +2 -0
  160. package/dist/integrity/slopnators/sl_undeclared_webhook.js +90 -0
  161. package/dist/integrity/slopnators/sl_undeclared_webhook.ts +108 -0
  162. package/dist/integrity/slopnators/sl_underived_route.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_underived_route.js +61 -0
  164. package/dist/integrity/slopnators/sl_underived_route.ts +72 -0
  165. package/dist/integrity/slopnators/sl_ungoverned_island.js +3 -3
  166. package/dist/integrity/slopnators/sl_ungoverned_island.ts +1 -1
  167. package/dist/integrity/slopnators/sl_unlisted_program.d.ts +2 -0
  168. package/dist/integrity/slopnators/sl_unlisted_program.js +39 -0
  169. package/dist/integrity/slopnators/sl_unlisted_program.ts +40 -0
  170. package/dist/integrity/slopnators/sl_unnamed_island.js +1 -1
  171. package/dist/integrity/slopnators/sl_unpaired_member.js +3 -3
  172. package/dist/integrity/slopnators/sl_unpaired_member.ts +1 -1
  173. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +4 -4
  174. package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +1 -1
  175. package/dist/integrity/slopnators/sl_unprefixed_member.js +5 -31
  176. package/dist/integrity/slopnators/sl_unprefixed_member.ts +2 -28
  177. package/dist/integrity/slopnators/sl_unthreaded.js +11 -7
  178. package/dist/integrity/slopnators/sl_unthreaded.ts +6 -7
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +2 -25
  180. package/dist/integrity/slopnators/sl_untranslated_key.ts +1 -25
  181. package/dist/integrity/slopnators/sl_unvaulted_secret.d.ts +2 -0
  182. package/dist/integrity/slopnators/sl_unvaulted_secret.js +107 -0
  183. package/dist/integrity/slopnators/sl_unvaulted_secret.ts +132 -0
  184. package/dist/integrity/slopnators/sl_unversioned.js +6 -4
  185. package/dist/integrity/slopnators/sl_unversioned.ts +5 -3
  186. package/dist/integrity/sweep.d.ts +1 -0
  187. package/dist/integrity/sweep.js +105 -42
  188. package/dist/integrity/sweep.ts +115 -42
  189. package/dist/knowledge/attach.js +4 -3
  190. package/dist/knowledge/attach.ts +3 -2
  191. package/dist/knowledge/generators/docsgen.js +8 -6
  192. package/dist/knowledge/generators/docsgen.ts +4 -2
  193. package/dist/knowledge/generators/glossary.js +4 -4
  194. package/dist/knowledge/generators/glossary.ts +1 -1
  195. package/dist/operations/access.d.ts +23 -0
  196. package/dist/operations/access.js +28 -1
  197. package/dist/operations/access.ts +43 -0
  198. package/dist/operations/attach.js +33 -113
  199. package/dist/operations/attach.ts +51 -117
  200. package/dist/operations/capabilities.d.ts +14 -5
  201. package/dist/operations/capabilities.js +13 -4
  202. package/dist/operations/capabilities.ts +27 -8
  203. package/dist/operations/dispatch.d.ts +8 -2
  204. package/dist/operations/dispatch.js +128 -185
  205. package/dist/operations/dispatch.ts +138 -191
  206. package/dist/operations/error-results.d.ts +1 -1
  207. package/dist/operations/error-results.js +2 -2
  208. package/dist/operations/error-results.ts +2 -2
  209. package/dist/operations/generators/capability.d.ts +11 -1
  210. package/dist/operations/generators/capability.js +481 -224
  211. package/dist/operations/generators/capability.ts +562 -233
  212. package/dist/operations/generators/compile.js +66 -39
  213. package/dist/operations/generators/compile.ts +78 -39
  214. package/dist/operations/generators/permits.js +11 -12
  215. package/dist/operations/generators/permits.ts +3 -4
  216. package/dist/operations/generators/seed.d.ts +1 -0
  217. package/dist/operations/generators/seed.js +19 -17
  218. package/dist/operations/generators/seed.ts +22 -14
  219. package/dist/operations/generators/trial.d.ts +6 -8
  220. package/dist/operations/generators/trial.js +200 -70
  221. package/dist/operations/generators/trial.ts +230 -93
  222. package/dist/operations/nodes.d.ts +15 -16
  223. package/dist/operations/nodes.ts +16 -17
  224. package/dist/operations/operations.d.ts +40 -4
  225. package/dist/operations/operations.js +1 -0
  226. package/dist/operations/operations.ts +61 -4
  227. package/dist/operations/result.d.ts +2 -2
  228. package/dist/operations/result.ts +2 -2
  229. package/package.json +1 -4
  230. package/dist/core/answers.d.ts +0 -171
  231. package/dist/core/answers.js +0 -789
  232. package/dist/core/answers.ts +0 -999
  233. package/dist/core/client.d.ts +0 -63
  234. package/dist/core/client.js +0 -249
  235. package/dist/core/client.ts +0 -289
  236. package/dist/core/files.js +0 -171
  237. package/dist/core/files.ts +0 -222
  238. package/dist/integrations/module.d.ts +0 -6
  239. package/dist/integrations/module.js +0 -153
  240. package/dist/integrations/module.ts +0 -191
  241. package/dist/integrity/slopnators/sl_unkept_rows.js +0 -104
  242. package/dist/integrity/slopnators/sl_unkept_rows.ts +0 -111
  243. package/dist/integrity/slopnators/sl_unseamed_reach.js +0 -73
  244. package/dist/integrity/slopnators/sl_unseamed_reach.ts +0 -79
  245. package/dist/operations/cost.d.ts +0 -14
  246. package/dist/operations/cost.js +0 -37
  247. package/dist/operations/cost.ts +0 -58
  248. package/dist/operations/errors/errors.d.ts +0 -52
  249. package/dist/operations/http.js +0 -124
  250. package/dist/operations/http.ts +0 -169
  251. package/dist/operations/memory.d.ts +0 -104
  252. package/dist/operations/memory.js +0 -365
  253. package/dist/operations/memory.ts +0 -470
  254. package/dist/operations/metering.d.ts +0 -55
  255. package/dist/operations/metering.js +0 -247
  256. package/dist/operations/metering.ts +0 -328
  257. package/dist/operations/queue-policy.d.ts +0 -3
  258. package/dist/operations/queue-policy.js +0 -8
  259. package/dist/operations/queue-policy.ts +0 -10
  260. package/dist/operations/seams.d.ts +0 -239
  261. package/dist/operations/seams.js +0 -45
  262. package/dist/operations/seams.ts +0 -364
  263. package/dist/operations/store.d.ts +0 -14
  264. package/dist/operations/store.js +0 -1
  265. package/dist/operations/store.ts +0 -29
  266. /package/dist/{operations → core}/errors/classes.d.ts +0 -0
  267. /package/dist/{operations → core}/errors/classes.js +0 -0
  268. /package/dist/{operations → core}/errors/classes.ts +0 -0
  269. /package/dist/integrity/slopnators/{sl_unkept_rows.d.ts → sl_door_in_a_node.d.ts} +0 -0
  270. /package/dist/integrity/slopnators/{sl_unseamed_reach.d.ts → sl_outside_an_operation.d.ts} +0 -0
@@ -0,0 +1,358 @@
1
+ import { dirname, resolve } from 'node:path'
2
+
3
+ import ts from 'typescript'
4
+
5
+ import { readFileSync } from '../../integrations/disk.ts'
6
+
7
+ import type { Slopnator, SlopnatorContext } from '../slopnator.ts'
8
+
9
+ const LIST = 2
10
+
11
+ interface Held {
12
+ readonly type?: string
13
+ readonly value?: unknown
14
+ readonly name?: string
15
+ readonly operator?: string
16
+ readonly elements?: readonly (Held | null)[]
17
+ readonly properties?: readonly (Held | null)[]
18
+ readonly arguments?: readonly Held[]
19
+ readonly init?: Held | null
20
+ readonly id?: { readonly type?: string; readonly name?: string }
21
+ readonly callee?: Held
22
+ readonly object?: Held
23
+ readonly property?: Held
24
+ readonly expression?: Held
25
+ readonly argument?: Held | null
26
+ readonly test?: Held | null
27
+ readonly consequent?: Held | null
28
+ readonly alternate?: Held | null
29
+ readonly body?: readonly (Held | null)[]
30
+ }
31
+
32
+ const WIDENED = 'TSAsExpression'
33
+
34
+ function bare(held: Held | null | undefined): Held | undefined {
35
+ let one = held ?? undefined
36
+ while (one?.type === WIDENED) one = one.expression
37
+ return one
38
+ }
39
+
40
+ const spoken = (held: Held | null | undefined): boolean => {
41
+ const one = bare(held)
42
+ return one?.type === 'Literal' && typeof one.value === 'string'
43
+ }
44
+
45
+ const strings = (held: Held | null | undefined): boolean => {
46
+ const one = bare(held)
47
+ if (one?.type !== 'ArrayExpression') return false
48
+ const all = one.elements ?? []
49
+ return all.length >= LIST && all.every((each) => spoken(each) || woven(each))
50
+ }
51
+
52
+ const SPREAD = 'SpreadElement'
53
+
54
+ function woven(held: Held | null | undefined): boolean {
55
+ const one = bare(held)
56
+ if (one?.type !== SPREAD) return false
57
+ let words = 0
58
+ let other = false
59
+ const walk = (node: unknown, seen: Set<unknown>): void => {
60
+ if (node === null || typeof node !== 'object' || seen.has(node)) return
61
+ seen.add(node)
62
+ if (Array.isArray(node)) {
63
+ for (const each of node) walk(each, seen)
64
+ return
65
+ }
66
+ const held = node as Held
67
+ if (held.type === 'Literal') {
68
+ if (typeof held.value === 'string') words += 1
69
+ else other = true
70
+ return
71
+ }
72
+ for (const [key, each] of Object.entries(node as Record<string, unknown>)) {
73
+ if (key !== BACK) walk(each, seen)
74
+ }
75
+ }
76
+ walk(one.argument, new Set())
77
+ return words > 0 && !other
78
+ }
79
+
80
+ function valued(held: Held): readonly Held[] {
81
+ const found: Held[] = []
82
+ for (const one of held.properties ?? []) {
83
+ if (one?.type === 'Property') found.push(one.value as Held)
84
+ }
85
+ return found
86
+ }
87
+
88
+ function tabled(held: Held | null | undefined, seen: Set<unknown>): boolean {
89
+ const one = bare(held)
90
+ if (one === undefined || one.type !== 'ObjectExpression' || seen.has(one)) return false
91
+ seen.add(one)
92
+ const all = valued(one)
93
+ if (all.filter((each) => spoken(each)).length >= LIST) return true
94
+ return all.some((each) => strings(each) || tabled(each, seen))
95
+ }
96
+
97
+ function drawn(held: Held): string | undefined {
98
+ if (held.type !== 'NewExpression' || held.callee?.type !== 'Identifier' || held.callee.name !== 'Set') return undefined
99
+ const only = held.arguments ?? []
100
+ if (only.length !== 1) return undefined
101
+ const call = bare(only[0])
102
+ const callee = call?.callee
103
+ if (call?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return undefined
104
+ if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return undefined
105
+ const asked = callee.property?.name
106
+ if (asked !== 'keys' && asked !== 'values') return undefined
107
+ const from = bare((call.arguments ?? [])[0])
108
+ return from?.type === 'Identifier' ? from.name : undefined
109
+ }
110
+
111
+ function adapted(node: ts.Node): Held | undefined {
112
+ if (ts.isAsExpression(node) || ts.isParenthesizedExpression(node)) {
113
+ const inner = adapted(node.expression)
114
+ return inner === undefined ? undefined : { type: WIDENED, expression: inner }
115
+ }
116
+ if (ts.isStringLiteral(node)) return { type: 'Literal', value: node.text }
117
+ if (ts.isSpreadElement(node)) return { type: SPREAD, argument: adapted(node.expression) ?? null }
118
+ if (ts.isCallExpression(node)) {
119
+ return { type: 'CallExpression', arguments: node.arguments.map((one) => adapted(one)).filter((one): one is Held => one !== undefined) }
120
+ }
121
+ if (ts.isArrayLiteralExpression(node)) {
122
+ return { type: 'ArrayExpression', elements: node.elements.map((one) => adapted(one) ?? null) }
123
+ }
124
+ if (ts.isObjectLiteralExpression(node)) {
125
+ const properties: Held[] = []
126
+ for (const one of node.properties) {
127
+ if (!ts.isPropertyAssignment(one)) continue
128
+ const value = adapted(one.initializer)
129
+ if (value !== undefined) properties.push({ type: 'Property', value })
130
+ }
131
+ return { type: 'ObjectExpression', properties }
132
+ }
133
+ return undefined
134
+ }
135
+
136
+ function parsed(at: string, text: string): ts.SourceFile {
137
+ return ts.createSourceFile(at, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
138
+ }
139
+
140
+ function brought(source: ts.SourceFile, name: string): string | undefined {
141
+ for (const statement of source.statements) {
142
+ if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier)) continue
143
+ const bindings = statement.importClause?.namedBindings
144
+ if (bindings === undefined || !ts.isNamedImports(bindings)) continue
145
+ if (!bindings.elements.some((one) => one.name.text === name)) continue
146
+ return statement.moduleSpecifier.text
147
+ }
148
+ return undefined
149
+ }
150
+
151
+ function elsewhere(at: SlopnatorContext, name: string): Held | undefined {
152
+ const specifier = brought(parsed(at.filename, at.text), name)
153
+ if (specifier === undefined || !specifier.startsWith('.')) return undefined
154
+ const path = resolve(dirname(at.filename), specifier)
155
+ let text: string
156
+ try {
157
+ text = readFileSync(path, 'utf8')
158
+ } catch {
159
+ return undefined
160
+ }
161
+ for (const statement of parsed(path, text).statements) {
162
+ if (!ts.isVariableStatement(statement)) continue
163
+ for (const one of statement.declarationList.declarations) {
164
+ if (!ts.isIdentifier(one.name) || one.name.text !== name || one.initializer === undefined) continue
165
+ return adapted(one.initializer)
166
+ }
167
+ }
168
+ return undefined
169
+ }
170
+
171
+ function initOf(held: unknown, name: string, seen: Set<unknown>): Held | undefined {
172
+ if (held === null || typeof held !== 'object' || seen.has(held)) return undefined
173
+ seen.add(held)
174
+ if (Array.isArray(held)) {
175
+ for (const one of held) {
176
+ const found = initOf(one, name, seen)
177
+ if (found !== undefined) return found
178
+ }
179
+ return undefined
180
+ }
181
+ const node = held as Held
182
+ const init = node.init ?? undefined
183
+ if (node.type === 'VariableDeclarator' && node.id?.type === 'Identifier' && node.id.name === name && init !== undefined) return init
184
+ for (const [key, one] of Object.entries(held as Record<string, unknown>)) {
185
+ if (key === BACK) continue
186
+ const found = initOf(one, name, seen)
187
+ if (found !== undefined) return found
188
+ }
189
+ return undefined
190
+ }
191
+
192
+ function listed(init: Held | null | undefined, program: unknown, at: SlopnatorContext, through: ReadonlySet<string>): boolean {
193
+ const one = bare(init)
194
+ if (one === undefined) return false
195
+ if (strings(one)) return true
196
+ if (tabled(one, new Set())) return true
197
+ if (one.type === 'NewExpression' || one.type === 'CallExpression') {
198
+ const only = one.arguments ?? []
199
+ if (only.length === 1 && strings(only[0])) return true
200
+ }
201
+ const from = drawn(one)
202
+ if (from === undefined || through.has(from)) return false
203
+ const followed = new Set(through)
204
+ followed.add(from)
205
+ const here = initOf(program, from, new Set())
206
+ return listed(here ?? elsewhere(at, from), program, at, followed)
207
+ }
208
+
209
+ const asks = (held: Held | undefined, name: string): boolean => {
210
+ const callee = held?.callee
211
+ if (held?.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
212
+ const on = bare(callee.object)
213
+ if (on?.type !== 'Identifier' || on.name !== name) return false
214
+ const said = callee.property?.name
215
+ return said === 'has' || said === 'includes'
216
+ }
217
+
218
+ const BACK = 'parent'
219
+
220
+ function asking(held: unknown, name: string, seen: Set<unknown>): boolean {
221
+ if (held === null || typeof held !== 'object' || seen.has(held)) return false
222
+ seen.add(held)
223
+ if (Array.isArray(held)) return held.some((one) => asking(one, name, seen))
224
+ if (asks(held as Held, name)) return true
225
+ return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && asking(one, name, seen))
226
+ }
227
+
228
+ function consults(node: Held, name: string): boolean {
229
+ if (node.type === 'MemberExpression') {
230
+ const on = bare(node.object)
231
+ if (on?.type === 'Identifier' && on.name === name) return true
232
+ }
233
+ const callee = node.callee
234
+ if (node.type !== 'CallExpression' || callee?.type !== 'MemberExpression') return false
235
+ if (callee.object?.type !== 'Identifier' || callee.object.name !== 'Object') return false
236
+ const only = node.arguments ?? []
237
+ const from = bare(only[0])
238
+ return only.length === 1 && from?.type === 'Identifier' && from.name === name
239
+ }
240
+
241
+ function consulted(held: unknown, name: string, seen: Set<unknown>): boolean {
242
+ if (held === null || typeof held !== 'object' || seen.has(held)) return false
243
+ seen.add(held)
244
+ if (Array.isArray(held)) return held.some((one) => consulted(one, name, seen))
245
+ if (consults(held as Held, name)) return true
246
+ return Object.entries(held as Record<string, unknown>).some(([key, one]) => key !== BACK && consulted(one, name, seen))
247
+ }
248
+
249
+ function continues(held: Held | null | undefined): boolean {
250
+ if (held === null || held === undefined) return false
251
+ if (held.type === 'ContinueStatement') return true
252
+ if (held.type !== 'BlockStatement') return false
253
+ return (held.body ?? []).some((one) => one?.type === 'ContinueStatement')
254
+ }
255
+
256
+ const LEAVES = 'ReturnStatement'
257
+
258
+ const THROWN = 'ThrowStatement'
259
+
260
+ const leaving = (one: Held | null | undefined): boolean => one?.type === LEAVES || one?.type === THROWN
261
+
262
+ function exits(held: Held | null | undefined): boolean {
263
+ if (held === null || held === undefined) return false
264
+ if (leaving(held)) return true
265
+ if (held.type !== 'BlockStatement') return false
266
+ return (held.body ?? []).some((one) => leaving(one))
267
+ }
268
+
269
+ function look(held: unknown, name: string, found: { gates: boolean; skips: boolean }, seen: Set<unknown>): void {
270
+ if (held === null || typeof held !== 'object' || seen.has(held)) return
271
+ seen.add(held)
272
+ if (Array.isArray(held)) {
273
+ for (const one of held) look(one, name, found, seen)
274
+ return
275
+ }
276
+ const node = held as Held
277
+ if (node.type === 'UnaryExpression' && node.operator === '!' && asks(node.argument ?? undefined, name)) {
278
+ found.skips = true
279
+ return
280
+ }
281
+ if (node.type === 'IfStatement' && continues(node.consequent) && asking(node.test, name, new Set())) {
282
+ found.skips = true
283
+ look(node.consequent, name, found, seen)
284
+ look(node.alternate, name, found, seen)
285
+ return
286
+ }
287
+ if (node.type === 'IfStatement' && exits(node.consequent) && asking(node.test, name, new Set())) {
288
+ found.gates = true
289
+ return
290
+ }
291
+ if (asks(node, name)) {
292
+ found.gates = true
293
+ return
294
+ }
295
+ for (const [key, one] of Object.entries(node as Record<string, unknown>)) {
296
+ if (key !== BACK) look(one, name, found, seen)
297
+ }
298
+ }
299
+
300
+ export const slopnator: Slopnator = {
301
+ on: ['VariableDeclarator'],
302
+ holds: (node, at) => {
303
+ const held = node as Held
304
+ const program = at.ancestors().find((one) => (one as Held | undefined)?.type === 'Program')
305
+ if (!listed(held.init, program, at, new Set<string>())) return false
306
+ const named = held.id?.type === 'Identifier' ? held.id.name : undefined
307
+ const table = bare(held.init)?.type === 'ObjectExpression'
308
+ if (table && (named === undefined || !consulted(program, named, new Set()))) return false
309
+ if (named !== undefined) {
310
+ const found = { gates: false, skips: false }
311
+ look(program, named, found, new Set())
312
+ if (!found.gates && found.skips) return false
313
+ }
314
+ return (
315
+ `\`${named ?? 'this'}\` is ${table ? 'a table' : 'a list'} of words written out here and this check refuses what is IN it, so ` +
316
+ 'whatever breaks the rule under a name nobody typed walks straight past. Wrapping them in `as const`, nesting them under a key, ' +
317
+ 'or building a set out of the keys leaves the same list one step further out of sight. A list that decides what a check CATCHES ' +
318
+ 'has to come from the tree: `thread inspect <word>` names the row that coins any word you need, and a check that reads those ' +
319
+ 'rows widens when the tree does. A list a check only SKIPS is left alone, because adding a word to one of those makes it ' +
320
+ 'stricter and it can never go blind.'
321
+ )
322
+ },
323
+ proves: {
324
+ wrong: [
325
+ "const CROSSINGS = ['fetch', 'setTimeout', 'setInterval']",
326
+ "const STARTS = new Set(['node:child_process', 'child_process'])\nexport const opens = (from: string): boolean => {\n if (!STARTS.has(from)) return false\n return true\n}",
327
+ "const GLOBALS = new Set(['Date', 'performance', 'Math'])",
328
+ "const MOVES = new Set(['push', 'replace'])\nexport const moved = (one: string): boolean => MOVES.has(one)",
329
+ "const SURFACES = ['cli', 'api', 'mcp'] as const\nexport const surfaced = (one: string): boolean => (SURFACES as readonly string[]).includes(one)",
330
+ "const MOVING = { globals: ['window', 'globalThis'], histories: ['router', 'history'] }\nexport const moves = (one: string): boolean => MOVING.globals.includes(one)",
331
+ "const HANDED_BY = { postgres: 'store', pglite: 'store', https: 'http' }\nexport const handedBy = (reach: string): string => HANDED_BY[reach] ?? reach",
332
+ "const HANDS = { postgres: 'store', pglite: 'store' }\nconst PINS = new Set(Object.keys(HANDS))\nexport const pinned = (one: string): boolean => PINS.has(one)",
333
+ {
334
+ files: [
335
+ { at: 'threads/held.ts', source: "export const PINNED = { clock: ['Date.now', 'setTimeout'] }\n" },
336
+ { at: 'threads/slopnators/sl_probe_followed.ts', source: "import { PINNED } from '../held.ts'\n\nconst PINS = new Set(Object.keys(PINNED))\nexport const pinned = (one: string): boolean => PINS.has(one)\n" },
337
+ ],
338
+ at: 'threads/slopnators/sl_probe_followed.ts',
339
+ },
340
+ ],
341
+ right: [
342
+ 'const CROSSINGS = declared()',
343
+ "const UNDER = 'operations/'",
344
+ "const COMMON = new Set(['a', 'the'])\nexport const words = (all: readonly string[]): readonly string[] => all.filter((one) => !COMMON.has(one))",
345
+ "const SKIP = new Set(['dist', 'node_modules'])\nexport function walk(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if (SKIP.has(name)) continue\n seen += 1\n }\n return seen\n}",
346
+ "const LEFT = ['dist', 'node_modules'] as const\nexport function walked(names: readonly string[]): number {\n let seen = 0\n for (const name of names) {\n if ((LEFT as readonly string[]).includes(name)) continue\n seen += 1\n }\n return seen\n}",
347
+ "export const SAYS = { cli: 'runs at a terminal', api: 'answers a request' }",
348
+ {
349
+ files: [
350
+ { at: 'threads/made.ts', source: "export const MADE = declared()\n" },
351
+ { at: 'threads/slopnators/sl_probe_derived.ts', source: "import { MADE } from '../made.ts'\n\nconst PICKED = new Set(Object.keys(MADE))\nexport const picked = (one: string): boolean => PICKED.has(one)\n" },
352
+ ],
353
+ at: 'threads/slopnators/sl_probe_derived.ts',
354
+ },
355
+ "export const slopnator = { on: ['CallExpression', 'NewExpression'], holds: () => false, proves: { wrong: ['new Date()'], right: ['const at = 1'] } }",
356
+ ],
357
+ },
358
+ }
@@ -1,15 +1,5 @@
1
1
  import { dirname, join, normalize } from 'node:path';
2
- import { here, index, parses, unreadable } from "./sl_unthreaded.js";
3
- /*
4
- * Every string this file writes, whatever it writes it for.
5
- *
6
- * The first reading matched `node: '<name>'`, which was the shape when an operation named its node
7
- * in a string beside a step of its own; a node builds its own span now, so that matched nothing on
8
- * any real file. The second matched `import … from '<path>'`, which was the one route anybody had
9
- * thought of — and a dynamic `import()`, a `require()` and an `export … from` each walked straight
10
- * through it, measured on a real tree. Enumerating the ways in is how a refusal ends up as a list of
11
- * whatever somebody happened to try. What matters is the path, not the syntax carrying it.
12
- */
2
+ import { here, index, parses, unreadable } from './sl_unthreaded.js';
13
3
  const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g;
14
4
  const RELATIVE = /^\.\.?\//;
15
5
  const UNDER = 'nodes/';
@@ -3,16 +3,6 @@ import { dirname, join, normalize } from 'node:path'
3
3
  import type { Slopnator } from '../slopnator.ts'
4
4
  import { here, index, parses, unreadable } from './sl_unthreaded.ts'
5
5
 
6
- /*
7
- * Every string this file writes, whatever it writes it for.
8
- *
9
- * The first reading matched `node: '<name>'`, which was the shape when an operation named its node
10
- * in a string beside a step of its own; a node builds its own span now, so that matched nothing on
11
- * any real file. The second matched `import … from '<path>'`, which was the one route anybody had
12
- * thought of — and a dynamic `import()`, a `require()` and an `export … from` each walked straight
13
- * through it, measured on a real tree. Enumerating the ways in is how a refusal ends up as a list of
14
- * whatever somebody happened to try. What matters is the path, not the syntax carrying it.
15
- */
16
6
  const SAID = /'((?:[^'\\\n]|\\.)*)'|"((?:[^"\\\n]|\\.)*)"|`((?:[^`\\$]|\\.)*)`/g
17
7
 
18
8
  const RELATIVE = /^\.\.?\//
@@ -1,5 +1,5 @@
1
- import { descend, read, reached } from "../../core/descend.js";
2
- import { rowsIn } from "./sl_unthreaded.js";
1
+ import { descend, read, reached } from '../../core/descend.js';
2
+ import { rowsIn } from './sl_unthreaded.js';
3
3
  const UNDER = 'operations/';
4
4
  const DISPATCH = 'dispatch';
5
5
  function declared() {
@@ -15,16 +15,111 @@ function declared() {
15
15
  continue;
16
16
  if (one.id !== undefined)
17
17
  found.add(one.id);
18
- const named = where.split('/').at(-1)?.replace(/\.[^.]+$/, '');
19
- if (named !== undefined)
20
- found.add(named);
21
18
  }
22
19
  }
23
20
  return found;
24
21
  }
22
+ function bodies() {
23
+ const found = new Set();
24
+ descend();
25
+ for (const at of reached()) {
26
+ const text = read(at);
27
+ if (text === undefined)
28
+ continue;
29
+ for (const one of rowsIn(text)) {
30
+ const where = one.at?.split('\\').join('/');
31
+ if (where === undefined || !where.includes(UNDER))
32
+ continue;
33
+ if (one.id !== undefined)
34
+ found.add(where.replace(/\.[^./]+$/, ''));
35
+ }
36
+ }
37
+ return found;
38
+ }
39
+ function declaring() {
40
+ const found = new Set();
41
+ descend();
42
+ for (const at of reached()) {
43
+ const text = read(at);
44
+ if (text === undefined)
45
+ continue;
46
+ for (const one of rowsIn(text)) {
47
+ const where = one.at?.split('\\').join('/');
48
+ if (where === undefined)
49
+ continue;
50
+ found.add(where.replace(/\.[^./]+$/, ''));
51
+ }
52
+ }
53
+ return found;
54
+ }
55
+ function importedFrom(name, ancestors) {
56
+ const program = ancestors.find((one) => one?.type === 'Program');
57
+ for (const one of program?.body ?? []) {
58
+ if (one.type !== 'ImportDeclaration' || typeof one.source?.value !== 'string')
59
+ continue;
60
+ if (!(one.specifiers ?? []).some((each) => each.local?.name === name))
61
+ continue;
62
+ return one.source.value;
63
+ }
64
+ return undefined;
65
+ }
66
+ function namedIn(said) {
67
+ if (said === undefined)
68
+ return undefined;
69
+ if (typeof said.typeName?.name === 'string')
70
+ return said.typeName.name;
71
+ const inner = namedIn(said.typeAnnotation);
72
+ if (inner !== undefined)
73
+ return inner;
74
+ for (const one of said.types ?? []) {
75
+ const found = namedIn(one);
76
+ if (found !== undefined)
77
+ return found;
78
+ }
79
+ return undefined;
80
+ }
81
+ const typeNamed = (held) => namedIn(held?.typeAnnotation?.typeAnnotation);
82
+ function annotating(name, ancestors) {
83
+ for (const held of ancestors) {
84
+ for (const param of held.params ?? []) {
85
+ if (param.type !== 'Identifier' || param.name !== name)
86
+ continue;
87
+ const said = typeNamed(param);
88
+ if (said !== undefined)
89
+ return said;
90
+ }
91
+ const body = held.body;
92
+ for (const one of Array.isArray(body) ? body : []) {
93
+ if (one.type !== 'VariableDeclaration')
94
+ continue;
95
+ for (const each of one.declarations ?? []) {
96
+ if (each.id?.type !== 'Identifier' || each.id.name !== name)
97
+ continue;
98
+ const said = typeNamed(each.id);
99
+ if (said !== undefined)
100
+ return said;
101
+ }
102
+ }
103
+ }
104
+ return undefined;
105
+ }
106
+ const settled = (from, source) => {
107
+ if (!source.startsWith('.'))
108
+ return source.replace(/\.[^./]+$/, '');
109
+ const parts = from.split('/').slice(0, -1);
110
+ for (const step of source.replace(/\.[^./]+$/, '').split('/')) {
111
+ if (step === '.' || step === '')
112
+ continue;
113
+ if (step === '..')
114
+ parts.pop();
115
+ else
116
+ parts.push(step);
117
+ }
118
+ return parts.join('/');
119
+ };
25
120
  export const slopnator = {
26
121
  on: ['CallExpression'],
27
- holds: (node) => {
122
+ holds: (node, at) => {
28
123
  const call = node;
29
124
  const callee = call?.callee;
30
125
  const named = callee?.type === 'Identifier'
@@ -39,33 +134,20 @@ export const slopnator = {
39
134
  return ('dispatch is called with nothing to dispatch, so no operation is named and none can be found — the name ' +
40
135
  'is the whole of what makes an operation reachable');
41
136
  }
42
- /*
43
- * THE OPERATION ITSELF IS A BETTER ANSWER THAN ITS NAME, AND THIS USED TO REFUSE IT.
44
- *
45
- * This asked for a string literal and refused everything else, which was right when the only
46
- * caller was a UI seam handed a bare name. It has been wrong since dispatch stopped taking one:
47
- * the note above says so in as many words — "`dispatch` in packages/core/operations/src/dispatch.ts
48
- * takes an Operation object rather than a name". What ships now imports the compiled operation
49
- * and hands over the binding, and a binding cannot be assembled at run time: if it named nothing,
50
- * the file would not compile.
51
- *
52
- * Measured 2026-08-31 on a registry install: an operations-only app, everything made by the CLI,
53
- * failed its own sweep on `operations/_client.ts` — a file its own generator had just written,
54
- * naming an operation the same tree declares. The gate refuses the build now, so a law that
55
- * refuses a generated file is an app that cannot ship.
56
- *
57
- * Narrow on purpose. An identifier is accepted only when it IS one of the declared operations,
58
- * which is the shape the generators emit — `import { operation as wf_charge }` and then
59
- * `dispatch(wf_charge, …)`. A variable holding a computed name is still refused, because it does
60
- * not match a declaration, and that is the hole this law was written for.
61
- */
62
137
  const all = declared();
63
138
  if (first.type === 'Identifier' && typeof first.name === 'string') {
64
139
  if (all.has(first.name))
65
140
  return false;
66
- return (`dispatch is handed \`${first.name}\`, and nothing declares an operation by that name. A binding is only ` +
67
- 'checkable when it is the operation — import the compiled operation and hand that over, so what runs and ' +
68
- 'what was declared are the same thing rather than two things that agree today');
141
+ const source = importedFrom(first.name, at.ancestors());
142
+ if (source !== undefined && bodies().has(settled(at.filename, source)))
143
+ return false;
144
+ const named = annotating(first.name, at.ancestors());
145
+ const typed = named === undefined ? undefined : importedFrom(named, at.ancestors());
146
+ if (typed !== undefined && declaring().has(settled(at.filename, typed)))
147
+ return false;
148
+ return (`dispatch is handed \`${first.name}\`, and it is not imported from a file any row declares as an operation. ` +
149
+ 'A binding is only checkable when it is the operation — import the compiled operation and hand that over, so ' +
150
+ 'what runs and what was declared are the same thing rather than two things that agree today');
69
151
  }
70
152
  if (first.type !== 'Literal' || typeof first.value !== 'string') {
71
153
  return ('dispatch is handed a name this file does not write down, so nothing can hold it against what is declared. ' +